用锁保护共享资源,例如 生成唯一的序列号、电商系统下单前确保库存足够等。
使用N个完全独立、没有主从关系的Redis master节点以保证他们大多数情况下都不会同时宕机,N一般为奇数。一个客户端需要做如下操作来获取锁:
简单来说,就是利用多个的主节点,在超过半数以上的主节点获取锁成功,才算成功;否则算失败,回滚–删除之前在所有节点上获取的锁。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.redissongroupId>
<artifactId>redisson-spring-boot-starterartifactId>
<version>3.15.5version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.12version>
dependency>
server:
port: 8091
spring:
redisson:
address: 127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002 # Redis集群地址
try-time: 0 #尝试时间
lock-time: 4 #锁的时间
password: # redis密码
scanInterval: 1000 # 扫描间隔
retryAttempts: 5 # 命令失败重试次数
timeout: 3000 # 超时时间
@Configuration
public class RedissonConfig {
@Value(value = "${spring.redisson.address}")
private String redissonAddress;
@Value(value = "${spring.redisson.password}")
private String redissonPassword;
@Value(value = "${spring.redisson.scanInterval}")
private int redissonScanInterval;
@Value(value = "${spring.redisson.retryAttempts}")
private int redissonRetryAttempts;
@Value(value = "${spring.redisson.timeout}")
private int redissonTimeout;
@Bean
public RedissonClient redissonClient() {
String[] nodes = redissonAddress.split(",");
for (int i = 0; i < nodes.length; i++) {
nodes[i] = "redis://" + nodes[i];
}
Config config = new Config();
config.useClusterServers() //这是用的集群server
.setScanInterval(redissonScanInterval) //设置集群状态扫描时间
.addNodeAddress(nodes).setRetryAttempts(redissonRetryAttempts).setTimeout(redissonTimeout);
if (StringUtils.isNotEmpty(redissonPassword)) {
config.useClusterServers().setPassword(redissonPassword);
}
return Redisson.create(config);
}
}
@RestController
@RequestMapping("api/redisson" )
@Slf4j
public class RedissonLockController {
/**
* 锁测试共享变量
*/
private Integer lockCount = 10;
/**
* 无锁测试共享变量
*/
private Integer count = 10;
/**
* 模拟线程数
*/
private static int threadNum = 1000;
@Autowired
private RedissonClient redissonClient;
/**
* 模拟并发测试加锁和不加锁
* @return
*/
@GetMapping("/test")
public void lock(){
// 计数器
final CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < threadNum; i ++) {
MyRunnable myRunnable = new MyRunnable(countDownLatch);
Thread myThread = new Thread(myRunnable);
myThread.start();
}
// 释放所有线程
countDownLatch.countDown();
}
/**
* 加锁测试
*/
private void testLockCount() {
RLock lock = redissonClient.getLock("myLock");
try {
// 加锁,设置超时时间2秒 上锁以后10秒自动解锁
boolean getLock = lock.tryLock(2000,10000, TimeUnit.MILLISECONDS);
if (!getLock) {
log.info("当前线程:[{}]没有获得锁", Thread.currentThread().getName());
return ;
}
lockCount--;
log.info("lockCount值:"+lockCount);
}catch (Exception e){
log.error(e.getMessage(),e);
}finally {
// 释放锁
if(null != lock && lock.isLocked() && lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
/**
* 无锁测试
*/
private void testCount() {
count--;
log.info("count值:"+count);
}
public class MyRunnable implements Runnable {
/**
* 计数器
*/
final CountDownLatch countDownLatch;
public MyRunnable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
// 阻塞当前线程,直到计时器的值为0
countDownLatch.await();
} catch (InterruptedException e) {
log.error(e.getMessage(),e);
}
// 无锁操作
//testCount();
// 加锁操作
testLockCount();
}
}
}
@SpringBootApplication
public class RedisLockApplication {
public static void main(String[] args) {
SpringApplication.run(RedisLockApplication.class, args);
}
}
访问:http://localhost:8091/api/redisson/test,控制台输出同步的序列号如下:
在极端环境下,RedLock 存在锁超时不安全的问题,参考。
https://redis.io/topics/distlock
https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers
https://github.com/redisson/redisson/blob/master/redisson-spring-boot-starter/README.md
源码