优雅的使用Java分布式锁

homeant | 2020-04-04 12:34 浏览99

为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。 那么如何在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();
        }
    }

}

今天就写到这里,下次见,码字不易,有问题请留言交流。