Redis应用

  一、分布式锁主流方案

      1、基于数据库

      2、基于缓存Redis(性能最高)

      3、基于Zookeeper(可靠性最高)

  二、redis方案

      1、使用setnx 上锁,通过del释放锁

      2、锁一直没有释放,设置过期时间,自动释放

      3、上锁之后突然出现异常,无法设置过期时间(解决:上锁同时添加过期时间)

set [key] [value] nx ex [second]
或 setex [key] [second] [value]

      4、代码案例

    @GetMapping("testLock")
    public void testLock() {

        //获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",3,TimeUnit.SECONDS);
        //获取锁成功,查询num值
        if (lock) {
            try{
                Object value = redisTemplate.opsForValue().get("num");
                if (ObjectUtils.isEmpty(value)) {
                    return;
                }
                //将有值的num转化为int
                int num = Integer.parseInt(value + "");
                //将num+1
                redisTemplate.opsForValue().set("num", String.valueOf(++num));
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                //释放锁
                redisTemplate.delete("lock");
            }
        }else{
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

   ab测试

 ab -n 1000 -c 100   http://192.168.xxx.1:9000/redisTest/testLock

   这里需要注意的是要在配置文件中添加redis集群配置

#集群节点
spring.redis.cluster.nodes=192.168.xxx.165:6379,192.168.xxx.165:6380,192.168.xxx.165:6381,192.168.xxx.165:6389,192.168.xxx.165:6390,192.168.xxx.165:6391

   并且在使用ab测试的时候出现了redis反序列化问题,在redisTemplete配置中添加如下代码

template.setValueSerializer(new StringRedisSerializer());
三、UUID防止锁误删

      (解决a线程释放b线程的锁)

      原因:假如有两个线程a和b,a先执行,在a执行过程中,因为某种原因a卡顿了,然后到了锁的自动释放时间锁自动释放了,此时b线程可以上锁,然后执行业务逻辑,此时a线程恢复正常,往下执行,然后释放锁,此时释放的是b线程上的锁。

      解决:用uuid表示不同的线程

                释放的时候,首先判断当前uuid和要释放的uuid是否一样

      代码:

@GetMapping("testLock")
    public void testLock() {
        //生成uuid
        String uuid = UUID.randomUUID().toString();
        //获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3,TimeUnit.SECONDS);
        //获取锁成功,查询num值
        if (lock) {
            try{
                Object value = redisTemplate.opsForValue().get("num");
                if (ObjectUtils.isEmpty(value)) {
                    return;
                }
                //将有值的num转化为int
                int num = Integer.parseInt(value + "");
                //将num+1
                redisTemplate.opsForValue().set("num", String.valueOf(++num));
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                String lockUuid = (String)redisTemplate.opsForValue().get("lock");
                if(uuid.equals(lockUuid)){
                    //释放锁
                    redisTemplate.delete("lock");
                }
            }
        }else{
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
  四、LUA保证删除原子性

         问题:释放锁的时候,比较uuid一样,删除锁的时候,正要删除,还没有删除,锁到了过期时间,自动释放,此时其他线程可以进行上锁操作,然后当前线程执行删除时,会删除其他线程上的锁

         根本原因:删除操作不具备原子性

         解决:使用lua脚本

         代码:

@GetMapping("testLockLua")
    public void testLockLua(){
        //生成
        String uuid = UUID.randomUUID().toString();
        System.out.println(uuid);
        String locKey = "lock";
        //定义lua脚本
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] \n\r"
                + "then return redis.call('del',KEYS[1]) \n\r"
                + "else return 0 \n\r"
                + "end ";
        //获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid,3,TimeUnit.SECONDS);
        //获取锁成功,查询num值
        if (lock) {
            try{
                Object value = redisTemplate.opsForValue().get("num");
                if (ObjectUtils.isEmpty(value)) {
                    return;
                }
                //将有值的num转化为int
                int num = Integer.parseInt(value + "");
                //将num+1
                redisTemplate.opsForValue().set("num", String.valueOf(++num));
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(script);
                redisScript.setResultType(Long.class);
                redisTemplate.execute(redisScript, Arrays.asList(locKey),uuid);
            }
        }else{
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

  五,分布式锁满足的四个条件

      1、互斥性:在任意时刻,只有一个客户端能持有锁

      2、不会发生死锁:即使有一个客户端在持有锁的期间崩溃而没有主动释放锁,也能保证后续其他客户端能加锁。

      3、加锁和解锁必须是同一个客户端,客户端不能把别人加的锁给解了

      4、加锁和解锁必须具有原子性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值