文章目录
- 一、Redisson分布式锁的介绍
- 二、SpringBoot集成Redisson
- 2.1、基础配置
- 2.2、基础代码
- 2.3、测试代码
- 2.4、测试结果
- 2.4.1、单线程情况
- 2.4.2、并发情况(验证可重入锁)
- 2.4.3、突然业务中断情况(验证锁是否会释放)
- 2.4.4、设置了锁超时时间的情况(验证锁是否会到期释放)
一、Redisson分布式锁的介绍
二、SpringBoot集成Redisson
2.1、基础配置
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>SpringBoot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 定义公共资源版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 上边引入 parent,因此 下边无需指定版本 -->
<!-- 包含 mvc,aop 等jar资源 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.35</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
<!-- spring boot 单元测试. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 没有该配置,devtools 不生效 -->
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.yml
spring:
profiles:
active: dev
application-dev.yml
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.12.200:3306/wzl?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: Root@1234
redis:
host: 192.168.12.105
port: 6379
password: 123456
redisson:
address: redis://192.168.12.105:6379
password: 123456
mybatis:
mapper-locations: classpath:mapping/*Mapper.xml
#showSql
logging:
level:
com:
demo:
mapper: debug
2.2、基础代码
DistributedLocker.java
package com.demo.configuration;
import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁接口定义
*/
public interface DistributedLocker {
RLock lock(String lockKey);
RLock lock(String lockKey, int timeout);
RLock lock(String lockKey, TimeUnit unit, int timeout);
boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);
void unlock(String lockKey);
void unlock(RLock lock);
}
RedissonDistributedLocker.java
package com.demo.configuration;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
/**
* 基于Redisson的分布式锁实现
*/
public class RedissonDistributedLocker implements DistributedLocker {
private RedissonClient redissonClient;
public RedissonDistributedLocker(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
@Override
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
@Override
public RLock lock(String lockKey, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return lock;
}
@Override
public RLock lock(String lockKey, TimeUnit unit, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
@Override
public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
@Override
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
@Override
public void unlock(RLock lock) {
lock.unlock();
}
}
RedissonProperties.java
package com.demo.configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.io.Serializable;
/**
* Redisson的相关配置参数对象
*/
@Configuration
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties implements Serializable {
private static final long serialVersionUID = 2529825739617287077L;
private String address;
private String password;
private int database = 0;
private int timeout = 3000;
private int connectionPoolSize = 64;
private int connectionMinimumIdleSize = 10;
private int slaveConnectionPoolSize = 250;
private int masterConnectionPoolSize = 250;
private String[] sentinelAddresses;
private String masterName;
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getDatabase() {
return database;
}
public void setDatabase(int database) {
this.database = database;
}
public int getConnectionPoolSize() {
return connectionPoolSize;
}
public void setConnectionPoolSize(int connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
}
public int getConnectionMinimumIdleSize() {
return connectionMinimumIdleSize;
}
public void setConnectionMinimumIdleSize(int connectionMinimumIdleSize) {
this.connectionMinimumIdleSize = connectionMinimumIdleSize;
}
public int getSlaveConnectionPoolSize() {
return slaveConnectionPoolSize;
}
public void setSlaveConnectionPoolSize(int slaveConnectionPoolSize) {
this.slaveConnectionPoolSize = slaveConnectionPoolSize;
}
public int getMasterConnectionPoolSize() {
return masterConnectionPoolSize;
}
public void setMasterConnectionPoolSize(int masterConnectionPoolSize) {
this.masterConnectionPoolSize = masterConnectionPoolSize;
}
public String[] getSentinelAddresses() {
return sentinelAddresses;
}
public void setSentinelAddresses(String[] sentinelAddresses) {
this.sentinelAddresses = sentinelAddresses;
}
public String getMasterName() {
return masterName;
}
public void setMasterName(String masterName) {
this.masterName = masterName;
}
}
RedissonConfiguration.java
package com.demo.configuration;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfiguration {
@Autowired
private RedissonProperties redissonProperties;
/**
* 单机模式自动装配
*
* @return
*/
@Bean(name = "redissonClient")
RedissonClient redissonSingle() {
Config config = new Config();
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(redissonProperties.getAddress())
.setTimeout(redissonProperties.getTimeout())
.setConnectionPoolSize(redissonProperties.getConnectionPoolSize())
.setConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumIdleSize());
if (null != redissonProperties.getPassword() && redissonProperties.getPassword().length() > 0) {
serverConfig.setPassword(redissonProperties.getPassword());
}
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
/**
* 装配locker类,并将实例注入到RedissLockUtil中
*
* @return
*/
@Bean
DistributedLocker distributedLocker(RedissonClient redissonClient) {
DistributedLocker distributedLocker = new RedissonDistributedLocker(redissonClient);
RedissLockUtil.setDistributedLocker(distributedLocker);
return distributedLocker;
}
}
RedissLockUtil.java
package com.demo.configuration;
import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;
/**
* redis之Redisson分布式锁工具类
*/
public class RedissLockUtil {
private static DistributedLocker redissLock;
public static void setDistributedLocker(DistributedLocker locker) {
redissLock = locker;
}
/**
* 加锁
*
* @param lockKey
* @return
*/
public static RLock lock(String lockKey) {
return redissLock.lock(lockKey);
}
/**
* 释放锁
*
* @param lockKey
*/
public static void unlock(String lockKey) {
redissLock.unlock(lockKey);
}
/**
* 释放锁
*
* @param lock
*/
public static void unlock(RLock lock) {
redissLock.unlock(lock);
}
/**
* 带超时的锁
*
* @param lockKey
* @param timeout 超时时间 单位:秒
*/
public static RLock lock(String lockKey, int timeout) {
return redissLock.lock(lockKey, timeout);
}
/**
* 带超时的锁
*
* @param lockKey
* @param unit 时间单位
* @param timeout 超时时间
*/
public static RLock lock(String lockKey, TimeUnit unit, int timeout) {
return redissLock.lock(lockKey, unit, timeout);
}
/**
* 尝试获取锁
*
* @param lockKey
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
return redissLock.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
}
/**
* 尝试获取锁
*
* @param lockKey
* @param unit 时间单位
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
return redissLock.tryLock(lockKey, unit, waitTime, leaseTime);
}
}
2.3、测试代码
RedisController.java
package com.demo.controller;
import com.demo.configuration.RedissLockUtil;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/redis")
public class RedisController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private RedissonClient redissonClient;
@RequestMapping(value = "/redisTest/add/{key}/{value}")
public String redisAdd(@PathVariable("key") String key, @PathVariable("value") String value) {
try {
redisTemplate.opsForValue().set(key, value);
} catch (Exception e) {
e.printStackTrace();
}
return "成功";
}
@RequestMapping(value = "/redisTest/del/{key}")
public String redisDel(@PathVariable("key") String key) {
Boolean flag = false;
try {
flag = redisTemplate.delete(key);
} catch (Exception e) {
e.printStackTrace();
}
return "移除结果:" + flag;
}
@RequestMapping(value = "/redisTest/get/{key}")
public String redisGet(@PathVariable("key") String key) {
String s = "";
try {
s = redisTemplate.opsForValue().get(key);
} catch (Exception e) {
e.printStackTrace();
}
return "成功:" + s;
}
@RequestMapping(value = "/redisTest/getHash/{key}")
@ResponseBody
public String redisGetHash(@PathVariable("key") String key) {
List<Object> values = null;
try {
values = redisTemplate.opsForHash().values(key);
} catch (Exception e) {
e.printStackTrace();
}
return "成功:" + values;
}
@RequestMapping(value = "/redissonTest/lock/{key}/{timeout}")
public String redissonLock(@PathVariable("key") String lockKey, @PathVariable("timeout") long timeout) {
RLock lock = null;
try {
//获取Lock锁,设置锁的名称
lock = RedissLockUtil.lock(lockKey);
//模拟业务处理耗时
Thread.sleep(1000 * timeout);
System.out.println("业务处理完毕,耗时(秒):" + timeout);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != lock) {
//解锁
lock.unlock();
}
}
return "锁" + lockKey + "内部耗时(秒):" + timeout;
}
@RequestMapping(value = "/redissonTest/lock2/{key}")
public String redissonLock2(@PathVariable("key") String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock();
Thread.sleep(10000);
System.out.println("业务处理完毕!");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return "已解锁";
}
}
2.4、测试结果
2.4.1、单线程情况
只发起一个请求代码会先在redis中先加锁,类型为hash,key为线程信息,值为锁的次数1业务执行5秒后,redis中锁不存在了
2.4.2、并发情况(验证可重入锁)
先后发起2个相同业务请求,第一个请求要求内部耗时200秒,第二个请求要求内部耗时5秒http://127.0.0.1:8080/redis/redissonTest/lock/a12/5
redisson可重入锁机制: 不断刷新redis锁的信息,发现TTL 每次减少到 20 就再次变为 30,直到业务处理完成,然后锁被删除。(自动续期)。 期间,如果再有相同一笔业务请求的时候,请求会被阻塞,需要等待前一个请求结束后释放锁,当前请求才会进入。
2.4.3、突然业务中断情况(验证锁是否会释放)
如果在业务处理过程中程序突然终止,锁没有得到释放,是否会一直阻塞下去?
http://127.0.0.1:8080/redis/redissonTest/lock/a12/200
代码在执行200秒的sleep期间,强行杀死应用
刷新 redis锁的信息,发现TTL 减少到 20 不再变为 30,而是继续减少,直到为0的时候,提示KEY不存在(看门狗机制因为线程消亡而无法发挥作用)
即便是业务突然中断,锁仍然会按TTL消亡。
2.4.4、设置了锁超时时间的情况(验证锁是否会到期释放)
改写代码,设置了锁超时时间为10秒
第1个请求仍然要求内部耗时200秒
第2个请求仍然要求内部耗时5秒
@RequestMapping(value = "/redissonTest/lock/{key}/{timeout}")
public String redissonLock(@PathVariable("key") String lockKey, @PathVariable("timeout") long timeout) {
RLock lock = null;
try {
//获取Lock锁,设置锁的名称,锁超时时间为10秒
lock = RedissLockUtil.lock(lockKey, 10);
//模拟业务处理耗时
Thread.sleep(1000 * timeout);
System.out.println("业务处理完毕,耗时(秒):" + timeout);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != lock) {
//解锁
lock.unlock();
}
}
return "锁" + lockKey + "内部耗时(秒):" + timeout;
}
第1个请求,10秒后,锁超时自动解锁,业务仍然在运行,前台页面未得到相应
此时第2个请求获得锁,执行了5秒,然后释放了锁,前台页面得到了响应
此时,虽然第1个线程仍然在运行,观察redis中已经没有锁信息了
设置了超时时间的锁,底层即超时时间不是-1的锁,即便线程还在运行,也不会续期