Redis学习记录:分布式锁

常用分布式锁的情况:

例如一个简单的用户操作,一个线程去修改用户的状态,首先从数据库中读取用户状态,然后在内存中修改,修改完成后,在存入数据库。单线程环境下没有问题,但多线程环境下,可能会出现数据紊乱等问题。

对于这种问题,使用分布式锁可以限制程序的并发执行。
分布式锁实现思路: 一个线程先占位,当别的线程进入时,发现已经有线程占位,就会放弃,或者稍后再来。
利用Redis中的setnx指令(创建一个key\value,如果没有该key,就会创建新的key,如果已经有了该key,就不会创建也不会修改value),线程操作完成后,在调用del指令释放位置。

根据思路初步构建代码:

    public static void main(String[] args) {
        Redis redis = new Redis();
        redis.excute(jedis -> {
            long setnx = jedis.setnx("k1", "v1");
            if (setnx ==1){
                //添加过期时间,防止业务抛出异常,导致锁不释放
                jedis.expire("k1",5);
                //没有占位,执行业务模块
                jedis.set("name","neko");
                System.out.println(jedis.get("name"));
                //释放锁
                jedis.del("k1");
            }else{
                //有占位,停止/暂缓操作
            }
        });
    }

由于expire和setnx不是一起执行,上述代码还是存在死锁可能。通过Redis的 set 方法可以改进:

    public static void main(String[] args) {
        Redis redis = new Redis();
        redis.excute(jedis -> {
            String set = jedis.set("k1", "v1", new SetParams().nx().ex(5));
            if (set !=null && "OK".equals(set)){
                //添加过期时间,防止业务抛出异常,导致锁不释放
                jedis.expire("k1",5);
                //没有占位,执行业务模块
                jedis.set("name","neko");
                System.out.println(jedis.get("name"));
                //释放锁
                jedis.del("k1");
            }else{
                //有占位,停止/暂缓操作
            }
        });
    }
}

解决超时问题

为了防止锁不释放,给锁添加了超时时间,超时后会自动释放锁。但如果业务耗时,在超时之前未执行完成,锁就被释放,导致第二个线程占用锁。此时第一个线程执行完毕,释放了第二个线程的锁。锁被释放,第三个线程又占用锁。

两个角度解决问题:

  • 尽量避免获取锁之后进行耗时操作
  • 优化锁的结构:将锁的value值设置为随机字符串,每次释放锁的时候,先去比较字符串是否一致,确认锁的归属,一致再去释放锁,否则不释放。
    第二种方法:分三个步骤,1. 获取锁的value值 2. 比较value的值 3. 释放锁。三步不具备原子性,通过引入Lua脚本解决。

Lua脚本:

  • Redis中内置了对Lua的支持
  • Lua脚本可以在Redis服务端执行多个Redis命令,并且具备原子性。
  • 网络会影响Redis性能,使用Lua脚本让多个命令一次执行,减少网络通信次数,提高Redis性能的影响

Redis中使用Lua脚本大致分为两种:

  1. 提前在Redis服务端上写好脚本,然后在Java客户端去调用脚本(推荐)
  2. 在Java客户端写Lua脚本,需要执行时,发送到Redis上执行

创建Lua脚本
if redis.call("get",KEYS[1])==ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
计算脚本的SHA1校验和,并且缓存到redis中

cat h:\getidthengetentity.lua | redis-cli script load --pipe

测试代码

public class LuaTest {
    public static void main(String[] args) {
        Redis redis = new Redis();
        redis.excute(jedis -> {
            String string = UUID.randomUUID().toString();
            String k1 = jedis.set("k1", string, new SetParams().nx().ex(5));
            if (k1 != null && "OK".equals(k1)){
                jedis.set("name","neko");
                System.out.println(jedis.get("name"));
                //执行lua脚本,判断是否是自己的锁
                jedis.evalsha("adkjagfjhalgjaldad", Arrays.asList(k1),Arrays.asList(string));
            }else {
                System.out.println("没获取到锁");
            }
        });
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值