redis实现分布式锁

x33g5p2x  于2021-10-09 转载在 Redis  
字(3.1k)|赞(0)|评价(0)|浏览(425)

1.什么是分布式锁?

1.1 情景

假设,对一个商品表的简单操作,一个线程去修改这个商品的信息,首先得从数据库中查出这条数据,然后加载在内存中,在内存修改完成之后,再存到数据库里面,对于单线程而言这一个完整的操作是没问题的,但是在多线程中,由于读取,修改,保存到数据库不是原子操作(原子特性:不可分割,要么全都成功,要么全都失败),所以这种操作出现在多线程中就会有很大的问题。

1.2 用redis实现分布式锁的思路?

分布式锁的思路不难,其实就是先进来的线程先占位置,当其他线程进来时,发现位置被人占了,就会放弃争夺或者稍后再来争夺。

在redis中,用setnx命令和del命令,可以简单的实现分布式锁,setnx命令返回1就代表线程抢到锁了,setnx命令返回0代表线程没抢到锁,del命令可以释放锁

2. 简单实现

2.1 jedisutils

package com.yl;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class JedisUtils {
    private static JedisPool jedisPool = null;

    public static Jedis getJedisObject() {
        if (jedisPool == null) {
            GenericObjectPoolConfig config = new GenericObjectPoolConfig();
            //最大空闲数
            config.setMaxIdle(400);
            //最大连接数
            config.setMaxTotal(2000);
            //连接最大等待时间,-1代表没有限制
            config.setMaxWaitMillis(300000);
            /** * 配置连接池的地址,端口号,超时时间,密码 */
            jedisPool = new JedisPool(config,"192.168.244.129",6379,30000,"root123");
        }
        try {
            //通过连接池获取jedis对象
            Jedis jedis = jedisPool.getResource();
            jedis.auth("root123");
            return jedis;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

2.1 测试

package com.yl;

import redis.clients.jedis.Jedis;

public class LockTest {
    public static void main(String[] args) {
        Jedis jedis = JedisUtils.getJedisObject();
        Long setnx = jedis.setnx("k1", "v1");
        if (setnx == 1) {
            //没人占位
            //有可能没有执行到del那一步,所以给锁添加过期时间
            jedis.expire("k1",5);
            jedis.set("name", "yl");
            String name = jedis.get("name");
            System.out.println(name);
            //释放资源
            jedis.del("k1");
        } else {
            //有人占位,停止操作
            System.out.println("没有获取到锁!");
        }
    }
}

3. (2.0简单实现分布锁)存在的问题以及解决方案

3.1 存在的问题

从2.0可以看到,获取到锁后,要设置过期时间(设置过期时间是预防业务代码出错,锁一直没释放掉!),那么这里会出现一种情况,假设这个过期时间设得比较小,然后,线程一进来了,执行它自己的业务代码,很复杂,执行得很慢,花费的时间远大于锁的超时时间,这个时候,线程一还在执行自己的业务代码,线程二抢到了锁进来了,然后也执行自己的业务代码,执行到一半,线程一的业务代码执行完了,线程二的还没执行完,线程一按道理还是会去释放锁的,这个时候线程二是锁的拥有者,线程一释放掉线程二的锁,这种情况合理?显然不合理,2.0中锁的value都固定为v1,所以它们公用同一个锁,且它们的value都是一样的!

3.2 解决方案:锁的value用随机字符串来替代再结合lua脚本判断传入的key去redis中查到的value和传入的value是否一致,如果一致就del掉key,否则直接返回

3.2.1 进入到redis文件,创建脚本文件

mkdir lua
cd lua/
vi release.lua

3.2.2 release.lua脚本文件内容

if redis.call("get",KEYS[1])==ARGV[1]then
   return redis.call("del",KEYS[1])
else
   return 0
end

3.2.3 求出SHA1和,作用:将lua脚本文件加载到redis缓存中,并且返回一个参数,在调用lua脚本时要传这个参数

cat lua/release.lua | redis-cli -a root123 script load --pipe

3.2.4 测试

package com.yl;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.util.Arrays;
import java.util.UUID;

public class LockTest3 {
    public static void main(String[] args) {
        Jedis jedis = JedisUtils.getJedisObject();
        //1.先随机获取一个字符串
        String str= UUID.randomUUID().toString();
        //2.获取锁,并且设置过期时间为5秒
        String lock = jedis.set("k1", str, new SetParams().nx().ex(5));
        //3.是否拿到锁
        if (lock != null && "OK".equals(lock)) {
            //成功拿到锁
            //4.具体业务写这里
            jedis.set("name","yl");
            jedis.set("age","23");
            System.out.println(jedis.get("name"));
            System.out.println(jedis.get("age"));

            //5.调用lua脚本,释放自己的锁
            jedis.evalsha("b8059ba43af6ffe8bed3db65bac35d452f8115d8", Arrays.asList("k1"),Arrays.asList(str));

        } else {
            //没获取到锁
            System.out.println("没有获取到锁!");
        }
    }
}

相关文章