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

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM