为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。 那么如何在Java 1.8中写出优雅的分布式锁,此篇文章为你讲解。
加锁
redis提供 setnx 命令命令。key 是锁的唯一标识,可以按业务来决定命名。当一个线程第一次执行时,返回1标识加锁成功,此时第二个线程开始执行,返回0标识加锁识别,已经有程序在处理。
setnx('key','value'); # set if not exits
释放锁
那么,锁加上了,怎么释放锁呢,代码也非常的简单,使用del就可以释放锁。
del('key'); # 删除Redis中的缓存
锁超时
在系统中总会存在一种情况,在加锁的途中,处理业务场景失败,没来得及释放锁,那么下一次业务将无法正常处理,这时咱们就需要使用第三个函数来自动释放锁。
expire('key',millisecond); # 为缓存设置过期时间
存在问题
了解redis的大佬们都知道,redis命令是一个命令一次连接,如果在加锁的过程中,加锁成功,紧接着redis节点宕机了,程序处理又没来得起其实释放锁,那么还是出现了上述死锁的问题,虽然这种情况很少发生,但也存在隐患。
set('key','value',millisecond,'NX'); # NX SET IF NOT EXIST
JAVA实现
了解几个常用命令,那么我们来在Java代码中实现
@Slf4j
@Component
public class BusinessLockComponent{
@Autowired
private RedisTemplate redisTemplate;
private final static String JSON_STRING = "{\"key\":\"%s\",\"result\":%b,\"time\":%d}";
/**
* 为每个业务加锁
*
* @param lockKey
* @param time
* @param callback
*/
public void lock(String lockKey, Long time, LockCallback callback) {
Boolean flag = lock(lockKey, time);
log.info("BUSINESS_LOCK|{}", String.format(JSON_STRING, lockKey, flag, time));
try {
if (flag) {
callback.execute();
}
} finally {
deleteLook(lockKey);
}
}
private Boolean lock(String lockKey, Long time) {
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
long expireAt = System.currentTimeMillis() + time + 1;
Boolean acquire = connection.setNX(lockKey.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
return true;
} else {
byte[] value = connection.get(lockKey.getBytes());
if (Objects.nonNull(value) && value.length > 0) {
long expireTime = Long.parseLong(new String(value));
// 如果锁已经过期
if (expireTime < System.currentTimeMillis()) {
// 重新加锁,防止死锁
byte[] oldValue = connection.getSet(lockKey.getBytes(), String.valueOf(System.currentTimeMillis() + time + 1).getBytes());
return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
}
}
}
return false;
});
}
private void deleteLook(String lockKey) {
redisTemplate.delete(lockKey);
}
}
加锁成功执行函数
/**
* 锁回调方法
*
*/
public interface LockCallback {
/**
* 执行方法
*/
@NonNull
void execute();
}
使用
public class Test{
@Autowired
private BusinessLockComponent lockComponent;
public void methodX(){
lockComponent.lock("20200404",System.currentTimeMillis()+5000,()->{
System.out.println("武汉加油");
});
}
}
使用spring integeration 实现分布式锁
其实Spring早期就实现了,存在于Spring Cloud Cluster项目中,后期发展,迁移到了Spring Integration中,实现方式如下:
加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
添加配置
在application.yml中添加
spring:
redis:
port: 6379
host: localhost
配置bean
@Configuration
public class RedisLockConfiguration {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
return new RedisLockRegistry(redisConnectionFactory, "spring-cloud");
}
}
使用
@RestController
@RequestMapping("redis")
public class IndexController {
@Autowired
private RedisLockRegistry redisLockRegistry;
@GetMapping("/test")
public void test(){
Lock lock = redisLockRegistry.obtain("lock");
boolean isLock = lock.tryLock(1, TimeUnit.SECONDS);
if(isLock){
System.out.println(s + "取锁锁了");
lock.unlock();
}
}
}
今天就写到这里,下次见,码字不易,有问题请留言交流。