• redis 加锁与解锁的详细总结,解决线程并发导致脏数据


    1.前言

    对每个controller来说都是全新且单独的,原因是多线程,如果多个请求操作共有的数据,这样的并发操作会导致脏数据

    怎么解决?

    mysql可以使用积极锁解决,

    这里讲解的是redis的解决办法,虽然有几种解决办法,但我这里只记录最好的:setnx指令算法加锁,思路与mysql的消极锁相似

    2.redis锁需要满足几个要求:

    (1)只能让一个客户端加锁,当锁存在时其他客户端不可以加锁

    (2)只能让加锁的客户端解锁,不允许其他客户端解锁

    (3)当锁存在时,加锁失败的客户端需要等待解锁后自己加锁,只有自己加锁成功后才可以操作共有数据,即阻塞操作

    (4)不能产生死锁,如果加锁的客户端还没有解锁前就因为某些原因就崩溃了,锁自动解锁,不影响其他客户端操作

    3.原理

    (1)加锁 则是 使用setnx指令,新建一个键值对,如果成功会返回 OK ,即视为加锁成功,如果已经存在,则返回空 ,视为加锁失败

    (2)为了确保不产生死锁,应该对这个数据设置存活时间,如果崩溃后,到时间会自动删除

    (3)解锁 则是 使用Lua脚本语句对键值对判断这个锁是不是自己加的,如果时别人的或者不存在,则不操作,如果是自己的则做删除键值对操作

    (4)无论时加锁还是解锁操作,为了确保逻辑的原子性,都应该是个多参数操作指令,有些低版本的redis不支持,才会将指令拆分成多条顺序执行

       但是容易导致逻辑问题,形成脏数据,

    4.封装工具

    我做的一个工具类,输入参数即可完成加锁 解锁操作 ,这是单机的,如果是分布式则改将jedis对象改成分布式对象ShardedJedisPool即可,思路一样

    package cn.cen2guo.clinic.redis;
    
    import redis.clients.jedis.Jedis;
    
    import java.util.Collections;
    
    public class JedisLock {
        //加锁检查
        //加锁标识,返回结果是这个则说明加锁成功
        private static final String LOCK_SUCCESS = "OK";
        //定义set方法的使用方式,如果不存在则以 lockKey 为 key ,requestId 为value 新建string类型键值 ,
        private static final String SET_IF_NOT_EXIST = "NX";
        //表示开启存活时间,PX是毫秒数 ,如果想以秒为单位则设为EX
        private static final String SET_WITH_EXPIRE_TIME = "PX";
        /**
         * 尝试获取锁[加锁]
         *
         * @param jedis      Redis客户端
         * @param lockKey    锁
         * @param requestId  请求标识
         * @param expireTime 超期时间
         * @return 是否获取成功
         */
        public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
            //语句意思是,判断lockKey 这个key是否存在,不存在则以 lockKey 为 key ,requestId 为value 新建string类型键值 ,
            //并开启存活时间,单位毫秒,
            //新建成功返回结果 OK ,说明加锁成功
            // 失败则为空,说明加锁失败
            String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            if (LOCK_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        }
    
        //
        //
        //
        //
        //
        //
        //
        //
        //
    
        //解锁标识,返回结果是这个则说明解锁成功
        private static final Long RELEASE_SUCCESS = 1L;
        /**
         * 释放锁
         * * @param jedis Redis客户端
         * * @param lockKey 锁
         * * @param requestId 请求标识
         * * @return 是否释放成功
         */
        public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
    //        使用Lua脚本,执行判断语句,
            //删除成功则返回结果1L,失败则为0
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
            System.out.println("解锁结果是=="+result);
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        }
    }
    JedisLock

    5.使用

    加锁

        /**
                 * 给等待池加锁
                 */
                //锁的key名,自定义
                String lockKey = "lock" ;
                //唯一识别是哪个用户端的锁id
    //            标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)。
                //时间戳+随机数
                String requestId = System.currentTimeMillis() + UUID.randomUUID().toString();
                //存活时间,10秒,防止崩溃造成死锁
                int expireTime = 10000;
                //获取jedis对象
                Jedis jedis = jedisMyGetJedis.myGetJedis();
                //加锁操作
                boolean f = JedisLock.tryGetDistributedLock(jedis, lockKey, requestId, expireTime);
                if (!f) {
    //                失败,锁已经存在
                    //休眠0.5秒后再次加锁操作
                    System.out.println("加锁失败,锁已经存在" + new Date());
                    int k = 0;
                    while (k == 0) {
                        System.out.println("睡眠一次" + new Date());
                        //当前线程休眠0.5秒
                        Thread.sleep(500);
                        boolean f2 = JedisLock.tryGetDistributedLock(jedis, lockKey, requestId, expireTime);
                        if (f2) {
                            //加锁成功
                            // 退出循环
                            k = 1;
                        }
                    }
                }
                //加锁成功
                System.out.println("加锁成功" + new Date());
                //下面的操作只会有一个人操作,因此不需要担心有并发操作
    View Code

    解锁

      //解锁
                    boolean r = JedisLock.releaseDistributedLock(jedis, lockKey, requestId);
                    if (r) {
                        System.out.println("解锁成功");
                    } else {
                        System.out.println("解锁失败");
                    }
                    //关闭jedis对象
                    jedis.close();
    View Code
  • 相关阅读:
    lnmp vhost 虚拟目录配置
    vi 编辑器常用命令(转)
    centos7 nginx 加入开机启动
    centos7 编译安装mysql
    IE8以下支持css3 border-radius渲染方法
    html5 web 摇一摇切换歌曲
    L0、L1与L2范数
    c++多线程编程:常见面试题
    核函数以及SVM相关知识(重点)
    梯度下降法的三种形式BGD、SGD以及MBGD
  • 原文地址:https://www.cnblogs.com/c2g5201314/p/12666402.html
Copyright © 2020-2023  润新知