spring boot 整合 redis 实现分布式锁

spring boot 整合 redis 实现分布式锁

利用redis setnx 命令的特性实现分布式锁

  • SETNX key value

    只在键 key 不存在的情况下, 将键 key 的值设置为 value

    若键 key 已经存在, 则 SETNX 命令不做任何动作。

    SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

  • 返回值

    命令在设置成功时返回 1 , 设置失败时返回 0

  • 设置key 过期时间,防止死锁

具体实现代码

  • lock接口

    public interface Lock {
    
        /**
         获取锁,直至成功
         */
         String getLock(String key, Long ttl);
        /**
         * 尝试获取锁,成功获取锁之后,会返回一个非空的字符串,解锁时需要传
         * @param key
         * @param ttl 占有锁的时间,单位秒
         * @return
         */
        String tryLock(String key, Long ttl);
    
        /**
         * 释放锁
         * @param key 获取锁时对应的key
         * @param value 获取锁时返回的非空字符串
         * @return
         */
        Boolean unLock(String key, String value);
    
  • redis实现

    @Component
    @Slf4j
    public class RedisDistributedLock implements Lock {
    
        private static final  String commandSet="set";
        private static final  String commandNX="NX";
        private static final  String commandEX="EX";
        private static  final  String script="if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "       return 0 end";
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        /**
         *
         * @param key
         * @param ttl 占有锁的时间,单位秒
         * @return
         */
        @Override
        public String tryLock(String key, Long ttl) {
            String value = UUID.fastUUID().toString();
            Boolean isSuccess = (Boolean) redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
                RedisSerializer valueSerializer = redisTemplate.getValueSerializer();
                RedisSerializer keySerializer = redisTemplate.getKeySerializer();
                Object result = redisConnection.execute(commandSet, keySerializer.serialize(key),
                        valueSerializer.serialize(value),
                        commandNX.getBytes(StandardCharsets.UTF_8),
                        commandEX.getBytes(StandardCharsets.UTF_8),
                        String.valueOf(ttl).getBytes(StandardCharsets.UTF_8));
                return !ObjectUtils.isEmpty(result);
            });
    
            return ObjectUtils.nullSafeEquals(isSuccess, Boolean.TRUE) ? value : null;
        }
    
        /**
         * 使用lua脚本,确保原子性操作
         * @param key   获取锁时对应的key
         * @param value 获取锁时返回的非空字符串
         * @return
         */
        @Override
        public Boolean unLock(String key, String value) {
            DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<>();
            defaultRedisScript.setResultType(Boolean.class);
            defaultRedisScript.setScriptText(script);
    
            return (Boolean) redisTemplate.execute(defaultRedisScript, Arrays.asList(key), value);
        }
    
        //获取锁 直至成功
        @Override
        public String getLock(String key, Long ttl) {
            String lockUuid;
            while (StrUtil.isBlank(lockUuid = this.tryLock(key, ttl))) {
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    log.error("RedisDistributedLock--getLock --异常");
                }
            }
            return lockUuid;
        }
    
    
  • 测试

    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class AppApiApplicationTest {
    
    
        @Inject
        private Lock lock;
    
        @Test
        public void  testLock(){
            String lockKey="member:15814841700";
            String tryLock = lock.tryLock(lockKey, 60L);
            if (StrUtil.isEmpty(tryLock)){
                throw new BusinessException("重复请求");
            }
            try {
                System.out.println("执行任务---");
            }catch (Exception e){
    
            }finally {
                lock.unLock(lockKey,tryLock);
            }
    
        }
    

其他实现方案

  • redisson

    也是基于redis实现

  • zookeeper

    基于zk节点特性(持久顺序节点)+ watch机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值