• springboot中加分布式redis锁


    分布式redis锁,spring-boot-starter-data-redis,RedisTemplate

    公司聊天的聊天系统,近期出现多个客服并发接待同一个客户的记录,经排查,是由于代码加的同步锁在集群环境下不适用,

    我们的客服系统是2条服务器,redis中缓存session,故考虑通过加redis分布式锁来解决该问题.

    根据实际情况,只针对单台redis实例,代码逻辑先获取锁再执行操作.

    代码体系用的是spring-boot-starter-data-redis,所以访问redis的客户端就是 RedisTemplate<String, String> redisTemplate

    具体的加锁和解锁方法,用到了lua脚本来操作,尽可能的保证操作的原子性.

    脚本封装入 DefaultRedisScript对象中,再使用RedisTemplate来执行,官方介绍 

    6.11. Redis Scripting

     https://docs.spring.io/spring-data/redis/docs/1.6.2.RELEASE/reference/html/

    lua脚本操作redis相关命令理解,请参考  http://redisdoc.com/string/index.html

    1,获取锁lua脚本 

        redis.call('set', KEYS[1], ARGV[1],ARGV[2],ARGV[3],ARGV[4]) if redis.call('get',KEYS[1]) == ARGV[1] then return true else return false end

        参数对应 set( key,reqId,NX,PX,时间毫秒)

        原子操作 set NX参数,不存在key则设置key-value, PX 并同时设置key超时毫秒数,这里为了返回参数判断,加入get 获取key的值是否等于我设置的值,是则获取锁成功.

        注意 expireTime 是 int类型的,RedisTemplate 底层要转字符串的,所以入参是记得加 String.valueOf(expireTime),否则会出现转换异常,当然只是我在处理得时候遇到了,特别说下.

        当然这是我的参考实现,要有更好建议,还请提出.

    2,释放锁lua脚本

        if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('del', KEYS[1]) return true else return false end     

        参数对应 get( key) == reqId 是true则直接删除key释放锁

    这个实现参考了另一篇博客,那个是用Jedis做客户端而我在RedisTemplate底层调用中没找到相似功能的方法,所以才改变思路都用lua脚本操作,后面附博客链接。

    3,贴上代码如下

    package com.xxxxx.kefu.utils;

    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;

    import java.util.Collections;

    public class RedisLockTool {

    /**不存在则设置k - v 值*/
    private static final String SET_IF_NOT_EXIST = "NX";
    /**设置毫秒超时*/
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
    * 尝试获取分布式锁
    */
    public static boolean getLock(RedisTemplate<String, String> redisTemplate, String lockKey, String requestId, int expireTime) {
        String script = "redis.call('set', KEYS[1], ARGV[1],ARGV[2],ARGV[3],ARGV[4]) if redis.call('get',KEYS[1]) == ARGV[1] then return true else return false end";
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<Boolean>(script,Boolean.class);
        Boolean result = redisTemplate.execute(redisScript,Collections.singletonList(lockKey), requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,String.valueOf(expireTime));
        if (result != null && result) {
             return true;
        }
        return false;
    }

    /**
    * 释放分布式锁
    */
    public static boolean unLock(RedisTemplate<String, String> redisTemplate, String lockKey, String requestId) {
             String script = "if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('del', KEYS[1]) return true else return false end"; 
             DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<Boolean>(script,Boolean.class);
             Boolean ok = redisTemplate.execute(redisScript,Collections.singletonList(lockKey),requestId);
             if(ok != null && ok){
                   return true;
             }else{
              return false;
            }
       }
    }

    4,参考博客

          https://yq.aliyun.com/articles/307547

          里面指出了,其他实现方法的一些问题,在某些情况下不能确保操作原子性,看了理解之后,我发现同事之前用的redis锁在删除锁时没有判断客户端id,也就是里面指出的错误示例,极端情况下会出现。

         参考改造出了这个实现,个人觉得是没有什么大问题的,如有发现不妥之处,还请指教.

    5,查阅的其他链接

         https://docs.spring.io/spring-data/redis/docs/1.6.2.RELEASE/reference/html/   RedisTemplate调用lua脚本示例

         http://redisdoc.com/string/index.html   lua脚本操作redis 示例

         http://ifeve.com/redis-lock/  官方redis文档,提到单实例和多实例的分布式解决方案

  • 相关阅读:
    python中F/f表达式优于format()表达式
    java8新特性-foreach&lambda
    Java实现多线程的四种方式
    Java中的字符串常量池,栈和堆的概念
    java对象只有值传递,为什么?
    面向对象和面向过程程序设计理解及区别
    String对象为什么不可变
    mybatis_plus插件——生成器
    基于grpc的流式方式实现双向通讯(python)
    Django使用DataTables插件总结
  • 原文地址:https://www.cnblogs.com/wangrq/p/11107023.html
Copyright © 2020-2023  润新知