redis分布式锁

文章目录

  • 锁的种类
  • 一个靠谱分布式锁需要具备的条件和刚需
    • 独占性
    • 高可用
    • 防死锁
    • 不乱抢
    • 重入性
  • 如何一步一步实现一个完备的分布式锁
    • 单机版加锁
    • Redis分布式锁setnx
      • 宕机与过期 + 防死锁
      • 防止误删key的问题
      • lua脚本保证原子性
    • hsetnx 可重入锁+简单工厂模式
      • RedisDistributeLock
      • DistributedLockFactory
    • 自动续期
      • CAP
    • 小结 *
  • RedLock
    • 为什么需要它
    • Redlock设计理念
    • 如何使用
        • RedisConfig
        • saleByRedisson
    • 源码简单分析

锁的种类

单机版同一个JVM虚拟机内:synchronized或者Lock接口。
分布式多个不同JVM虚拟机,单机的线程锁机制不再起作用,资源类在不同的服务器之间共享了。

一个靠谱分布式锁需要具备的条件和刚需

独占性

任何时候只能有一个线程持有

高可用

若redis集群环境下,不能因为某个节点挂了而出现获取锁和释放锁失败的情况
高并发请求下,依旧性能足够

防死锁

杜绝死锁,必须要有超时控制机制或者撤销操作,有个兜底终止跳出方案

不乱抢

防止张冠李戴,不能私下unlock别人的锁,只能自己加锁自己释放

重入性

同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁

如何一步一步实现一个完备的分布式锁

分布式锁用于分布式环境下并发控制的一种机制,用于控制某个资源在同一时刻只能被一个应用所使用
使用场景:多个服务间保证同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击),类似超卖。
在这里插入图片描述
在这里插入图片描述

单机版加锁

  • 为什么单机版加了锁还没有控制住?
    在单机环境下,可以使用synchronized或Lock来实现。
    但是在分布式系统中,因为竞争的线程可能不在同一个节点上(同一个jvm中),所以需要一个让所有进程都能访问到的锁来实现(比如redis或者zookeeper来构建),不同进程jvm层面的锁就不管用了,那么可以利用第三方的一个组件,来获取锁,未获取到锁,则阻塞当前想要运行的线程
  • 分布式锁能干嘛
    可以跨进程,跨服务;解决超卖;防止缓存击穿;
    单机版加锁配合Nginx和Jmeter压测后,不满足高并发分布式锁的性能要求,出现超卖
  private Lock lock = new ReentrantLock();
    public String sale()
    {
        String retMessage = "";

        lock.lock();
        try
        {
            //1 查询库存信息
            String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存,每次减少一个
            if(inventoryNumber > 0)
            {
                stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余:"+inventoryNumber;
                System.out.println(retMessage+"\t"+"服务端口号"+port);
            }else{
                retMessage = "商品卖完了,o(╥﹏╥)o";
            }
        }finally {
            lock.unlock();
        }
        return retMessage+"\t"+"服务端口号"+port;
    }

Redis分布式锁setnx

Redis具有极高的性能,且它的命令对分布式锁支持友好,借助SET命令即可实现加锁处理。
下面代码出现的问题:
递归重试,容易导致stackoverflowerror,所以不太推荐;另外,高并发唤醒后推荐用while判断而不是if

  public String sale()
    {
        String retMessage = "";
        String key = "zzyyRedisLock";
        String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();

        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);
        //flag=false,抢不到的线程要继续重试。。。。。。
        if(!flag)
        {
            //暂停20毫秒,进行递归重试.....
            try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
            sale();
        }else{
            //抢锁成功的请求线程,进行正常的业务逻辑操作,扣减库存
            try
            {
                //1 查询库存信息
                String result = stringRedisTemplate.opsForValue().get("inventory001");
                //2 判断库存是否足够
                Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
                //3 扣减库存,每次减少一个
                if(inventoryNumber > 0)
                {
                    stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
                    retMessage = "成功卖出一个商品,库存剩余:"+inventoryNumber;
                    System.out.println(retMessage+"\t"+"服务端口号"+port);
                }else{
                    retMessage = "商品卖完了,o(╥﹏╥)o";
                }
            }finally {
                stringRedisTemplate.delete(key);
            }
        }
        return retMessage+"\t"+"服务端口号"+port;
      }

下面代码采用自旋锁代替递归重试:
下面代码问题:
部署了微服务的Java程序机器挂了,代码层面根本没有走到finally这块,没办法保证解锁(无过期时间该key一直存在),这个key没有被删除,需要加入一个过期时间限定key。

在这里插入图片描述

public String sale()
    {
        String retMessage = "";

        String key = "zzyyRedisLock";
        String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();

        //不用递归了,高并发下容易出错,我们用自旋替代递归方法重试调用;也不用if了,用while来替代
        while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue))
        {
            //暂停20毫秒,进行重试.....
            try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        }

        //抢锁成功的请求线程,进行正常的业务逻辑操作,扣减库存
        try
        {
            //1 查询库存信息
            String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存,每次减少一个
            if(inventoryNumber > 0)
            {
                stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余:"+inventoryNumber;
                System.out.println(retMessage+"\t"+"服务端口号"+port);
            }else{
                retMessage = "商品卖完了,o(╥﹏╥)o";
            }
        }finally {
            stringRedisTemplate.delete(key);
        }
        return retMessage+"\t"+"服务端口号"+port;
    }

宕机与过期 + 防死锁

下面代码设置了过期时间,并且合并成了一行,具备原子性
下面代码存在问题:
stringRedisTemplate.delete(key);只能自己删除自己的锁,不可以删除别人的,需要添加判断是否是自己的锁来进行操作

  public String sale()
    {
        String retMessage = "";

        String key = "zzyyRedisLock";
        String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();

        //改进点:加锁和过期时间设置必须同一行,保证原子性
        while(!stringRedisTemplate.opsForValue().setIfAbsent(key,uuidValue,30L,TimeUnit.SECONDS))
        {
            //暂停20毫秒,进行重试.....
            try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        }

        //stringRedisTemplate.expire(key,30L,TimeUnit.SECONDS);

        //抢锁成功的请求线程,进行正常的业务逻辑操作,扣减库存
        try
        {
            //1 查询库存信息
            String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存,每次减少一个
            if(inventoryNumber > 0)
            {
                stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余:"+inventoryNumber;
                System.out.println(retMessage+"\t"+"服务端口号"+port);
            }else{
                retMessage = "商品卖完了,o(╥﹏╥)o";
            }
        }finally {
            stringRedisTemplate.delete(key);
        }
        return retMessage+"\t"+"服务端口号"+port;
    }

防止误删key的问题

存在问题:
最后的判断+del不是一行原子命令操作,需要用lua脚本进行修改

public String sale()
    {
        String retMessage = "";

        String key = "zzyyRedisLock";
        String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();

        while(!stringRedisTemplate.opsForValue().setIfAbsent(key,uuidValue,30L,TimeUnit.SECONDS))
        {
            //暂停20毫秒,进行递归重试.....
            try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        }

        //抢锁成功的请求线程,进行正常的业务逻辑操作,扣减库存
        try
        {
            //1 查询库存信息
            String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存,每次减少一个
            if(inventoryNumber > 0)
            {
                stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余:"+inventoryNumber;
                System.out.println(retMessage+"\t"+"服务端口号"+port);
            }else{
                retMessage = "商品卖完了,o(╥﹏╥)o";
            }
        }finally {
            //改进点,只能删除属于自己的key,不能删除别人的
            // v5.0判断加锁与解锁是不是同一个客户端,同一个才行,自己只能删除自己的锁,不误删他人的
            if(stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue))
            {
                stringRedisTemplate.delete(key);
            }
        }
        return retMessage+"\t"+"服务端口号"+port;
    }

lua脚本保证原子性

什么是lua脚本?
设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Redis调用Lua脚本通过eval命令保证执行的原子性,直接用return返回脚本执行后的结果值。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
LUA脚本条件判断语法:
在这里插入图片描述
在这里插入图片描述

存在问题:
不满足可重入性,需要重新修改

public String sale()
    {
        String retMessage = "";

        String key = "zzyyRedisLock";
        String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();

        while(!stringRedisTemplate.opsForValue().setIfAbsent(key,uuidValue,30L,TimeUnit.SECONDS))
        {
            //暂停20毫秒,进行递归重试.....
            try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        }
        //redislock();
        //抢锁成功的请求线程,进行正常的业务逻辑操作,扣减库存
        try
        {
            //1 查询库存信息
            String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存,每次减少一个
            if(inventoryNumber > 0)
            {
                stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余:"+inventoryNumber;
                System.out.println(retMessage+"\t"+"服务端口号"+port);
                testReEnter();
            }else{
                retMessage = "商品卖完了,o(╥﹏╥)o";
            }
        }finally {
            //unredislock();
            //改进点,修改为Lua脚本的redis分布式锁调用,必须保证原子性,参考官网脚本案例
            String luaScript =
                    "if redis.call('get',KEYS[1]) == ARGV[1] then " +
                        "return redis.call('del',KEYS[1]) " +
                    "else " +
                        "return 0 " +
                    "end";
            stringRedisTemplate.execute(new DefaultRedisScript(luaScript,Boolean.class), Arrays.asList(key),uuidValue);
        }
        return retMessage+"\t"+"服务端口号"+port;
    }

    private void testReEnter()
    {
       *//* String key = "zzyyRedisLock";
        String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();

        while(!stringRedisTemplate.opsForValue().setIfAbsent(key,uuidValue,30L,TimeUnit.SECONDS))
        {
            //暂停20毫秒,进行递归重试.....
            try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
        }
        redislock();
        //biz......
        unredislock();
        //改进点,修改为Lua脚本的redis分布式锁调用,必须保证原子性,参考官网脚本案例
        String luaScript =
                "if redis.call('get',KEYS[1]) == ARGV[1] then " +
                        "return redis.call('del',KEYS[1]) " +
                        "else " +
                        "return 0 " +
                        "end";
        stringRedisTemplate.execute(new DefaultRedisScript(luaScript,Boolean.class), Arrays.asList(key),uuidValue);*//*
    }

hsetnx 可重入锁+简单工厂模式

以上方式达到了分布式锁要求的独占性,高可用,防死锁,不乱抢,但是没有重入性。
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁
总结一句话就是一个线程的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。(自己可以获取自己的内部锁)
问题:

setnx只能解决锁的有无问题,够用但不完美。
hset不但解决有无,还解决可重入问题。
将lock和unlock方法进行改进,把它们全部变成lua脚本。
在这里插入图片描述
加锁的lua脚本:
在这里插入图片描述
在这里插入图片描述
解锁的lua脚本:
在这里插入图片描述

RedisDistributeLock

package com.atguigu.redislock.mylock;

import cn.hutool.core.util.IdUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @auther zzyy
 * @create 2023-01-09 16:41
 * 我们自研的redis分布式锁,实现了Lock接口
 */
//@Component 引入DistributedLockFactory工厂模式,从工厂获得即可
public class RedisDistributedLock implements Lock
{
    private StringRedisTemplate stringRedisTemplate;

    private String lockName;//KEYS[1]
    private String uuidValue;//ARGV[1]
    private long   expireTime;//ARGV[2]

    /*public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName)
    {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();
        this.expireTime = 25L;
    }*/

    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName, String uuid)
    {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = uuid+":"+Thread.currentThread().getId();
        this.expireTime = 30L;
    }

    @Override
    public void lock()
    {
        tryLock();
    }
    @Override
    public boolean tryLock()
    {
        try {tryLock(-1L,TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
    {
        if(time == -1L)
        {
            String script =
                    "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then    " +
                            "redis.call('hincrby',KEYS[1],ARGV[1],1)    " +
                            "redis.call('expire',KEYS[1],ARGV[2])    " +
                            "return 1  " +
                    "else   " +
                            "return 0 " +
                    "end";
            System.out.println("lockName:"+lockName+"\t"+"uuidValue:"+uuidValue);

            while(!stringRedisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName), uuidValue,String.valueOf(expireTime)))
            {
                //暂停60毫秒
                try { TimeUnit.MILLISECONDS.sleep(60); } catch (InterruptedException e) { e.printStackTrace(); }
            }
            //新建一个后台扫描程序,来坚持key目前的ttl,是否到我们规定的1/2 1/3来实现续期
            renewExpire();
            return true;
        }
        return false;
    }


    @Override
    public void unlock()
    {
        System.out.println("unlock(): lockName:"+lockName+"\t"+"uuidValue:"+uuidValue);
        String script =
                "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then    " +
                        "return nil  " +
                "elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then    " +
                        "return redis.call('del',KEYS[1])  " +
                "else    " +
                        "return 0 " +
                "end";
        // nil = false 1 = true 0 = false
        Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));

        if(null == flag)
        {
            throw new RuntimeException("this lock doesn't exists,o(╥﹏╥)o");
        }
    }
//自动续期:确保redisLock过期时间大于业务执行时间的问题
    private void renewExpire()
    {
        String script =
                "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then     " +
                        "return redis.call('expire',KEYS[1],ARGV[2]) " +
                "else     " +
                        "return 0 " +
                "end";

        new Timer().schedule(new TimerTask()
        {
            @Override
            public void run()
            {
                if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime)))
                {
                    renewExpire();
                }
            }
        },(this.expireTime * 1000)/3);
    }



    //====下面两个暂时用不到,不再重写
    //====下面两个暂时用不到,不再重写
    //====下面两个暂时用不到,不再重写
    @Override
    public void lockInterruptibly() throws InterruptedException
    {

    }
    @Override
    public Condition newCondition()
    {
        return null;
    }
}

DistributedLockFactory

package com.atguigu.redislock.mylock;

import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.locks.Lock;

/**
 * @auther zzyy
 * @create 2023-01-09 17:28
 */
@Component
public class DistributedLockFactory
{
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private String lockName;
    private String uuid;

    public DistributedLockFactory()
    {
        this.uuid = IdUtil.simpleUUID();
    }

    public Lock getDistributedLock(String lockType)
    {
        if(lockType == null) return null;

        if(lockType.equalsIgnoreCase("REDIS")){
            this.lockName = "zzyyRedisLock";
            return new RedisDistributedLock(stringRedisTemplate,lockName,uuid);
        }else if(lockType.equalsIgnoreCase("ZOOKEEPER")){
            this.lockName = "zzyyZookeeperLockNode";
            //TODO zookeeper版本的分布式锁
            return null;
        }else if(lockType.equalsIgnoreCase("MYSQL")){
            //TODO MYSQL版本的分布式锁
            return null;
        }

        return null;
    }
}

@Autowired
private DistributedLockFactory distributedLockFactory;
public String sale()
    {
        String retMessage = "";

        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try
        {
            //1 查询库存信息
            String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存,每次减少一个
            if(inventoryNumber > 0)
            {
                stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余:"+inventoryNumber;
                System.out.println(retMessage+"\t"+"服务端口号"+port);
                testReEntry();
            }else{
                retMessage = "商品卖完了,o(╥﹏╥)o";
            }
        }finally {
            redisLock.unlock();
        }
        return retMessage+"\t"+"服务端口号"+port;
    }

    private void testReEntry()//用在V7.0版本程序作为测试可重入性
    {
        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try
        {
            System.out.println("===========测试可重入锁========");
        }finally {
            redisLock.unlock();
        }
    }

自动续期

为了确保redisLock过期时间大于业务执行时间,需要给锁续期。

CAP

AP:Redis集群
redis异步复制造成的锁丢失,主节点没来的及把刚刚set进来这条数据给从节点,主机挂了,从机上位但无该数据
CP: Zookeeper集群
具体不了解了

小结 *

  • 第一代:synchronized 单机版OK,上分布式就混乱了,无法使用nginx分布式微服务
  • 第二代:取消单机锁,上redis分布式锁setnx:
    只加了锁,没有释放锁,出现异常的话,可能就无法释放锁,必须代码层面在finally处释放锁;
    宕机了,部署了微服务代码层面,根本没有走到finally这块,无法保证解锁,这个key没有被删除,需要有lockKey的过期时间设定;
    为redis的分布式锁key增加过期时间,并且需要setnx+过期时间必须同一行,必须规定只能自己删除自己的锁,防止张冠李戴把别人的锁删除了,unlock变为LUA脚本保证原子性,锁重入方面,使用hset代替setnx+lock变为Lua脚本保证,最后再增加一个key的自动续期。

RedLock

为什么需要它

线程 1 首先获取锁成功,将键值对写入 redis 的 master 节点,在 redis 将该键值对同步到 slave 节点之前,master 发生了故障;redis 触发故障转移,其中一个 slave 升级为新的 master,此时新上位的master并不包含线程1写入的键值对,因此线程 2 尝试获取锁也可以成功拿到锁,此时相当于有两个线程获取到了锁,可能会导致各种预期之外的情况发生,例如最常见的脏数据。
我们加的是排它独占锁,同一时间只能有一个建redis锁成功并持有锁,严禁出现2个以上的请求线程拿到锁。危险的
在这里插入图片描述

Redlock设计理念

Redis也提供了Redlock算法,用来实现基于多个实例的分布式锁。
锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。Redlock算法是实现高可靠分布式锁的一种有效解决方案,可以在实际开发中使用。
为了解决数据不一致的问题,直接舍弃了异步复制只使用 master 节点,同时由于舍弃了 slave,为了保证可用性,引入了 N 个节点,官方建议是 5。
客户端只有在满足下面的这两个条件时,才能认为是加锁成功。
条件1:客户端从超过半数(大于等于N/2+1)的Redis实例上成功获取到了锁;
条件2:客户端获取锁的总耗时没有超过锁的有效时间。
使用容错公式解决:
在这里插入图片描述

如何使用

RedisConfig

package com.atguigu.redislock.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @auther zzyy
 * @create 2023-01-04 15:58
 */
@Configuration
public class RedisConfig
{
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory)
    {
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置value的序列化方式json
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

    @Bean
    public Redisson redisson()
    {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.111.185:6379").setDatabase(0).setPassword("111111");

        return (Redisson) Redisson.create(config);
    }
}

saleByRedisson

 @Autowired
    private Redisson redisson;
    public String saleByRedisson()
    {
        String retMessage = "";

        RLock redissonLock = redisson.getLock("zzyyRedisLock");
        redissonLock.lock();

        try
        {
            //1 查询库存信息
            String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存,每次减少一个
            if(inventoryNumber > 0)
            {
                stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余:"+inventoryNumber;
                System.out.println(retMessage+"\t"+"服务端口号"+port);
            }else{
                retMessage = "商品卖完了,o(╥﹏╥)o";
            }
        }finally {
            //改进点,只能删除属于自己的key,不能删除别人的
            if(redissonLock.isLocked() && redissonLock.isHeldByCurrentThread())
            {
                redissonLock.unlock();
            }
        }
        return retMessage+"\t"+"服务端口号"+port;
    }

源码简单分析

如果Redis分布式锁过期了,但是业务逻辑没处理完怎么办?
额外起一个线程,定期检查线程是否还持有锁,如果有则延长过期时间。
Redisson 里面就实现了这个方案,使用“看门狗”定期检查(每1/3的锁时间检查1次),如果线程还持有锁,则刷新过期时间;
在获取锁成功后,给锁加一个watchdog,watchdog会起一个定时任务,在锁没有被释放且快要过期的时候会续期。

通过Redission新建出来的锁key,默认是30秒。
在这里插入图片描述
在这里插入图片描述
加锁:

在这里插入图片描述
在这里插入图片描述
看门狗:
在这里插入图片描述

这里面初始化了一个定时器,dely 的时间是 internalLockLeaseTime/3。
在 Redisson 中,internalLockLeaseTime 是 30s,也就是每隔 10s 续期一次,每次 30s。
客户端A加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端A还持有锁key,那么就会不断的延长锁key的生存时间,默认每次续命又从30秒新开始。
在这里插入图片描述
在这里插入图片描述
解锁:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/17889.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

阿里云服务器购买教程(新手入门指南)

阿里云服务器ECS选购指南&#xff0c;阿里云百科分享2023阿里云服务器新手选择流程&#xff0c;选购云服务器有两个入口&#xff0c;一个是选择活动机&#xff0c;只需要选择云服务器地域、系统、带宽即可&#xff1b;另一个是在云服务器页面&#xff0c;自定义选择云服务器配置…

mac下用git客户端生成ssh秘钥并配置到souretree进行使用

一、使用git 生成 ssh 密钥 1、Mac 安装 git 客户端 打开终端&#xff0c;执行命令&#xff1a; $ brew install git2、执行命令 $ git config --global user.name "xxx" 你自己的名字 $ git config --global user.email "xxxxxx.com&q…

深度学习的环境搭建(window+pytorch)

1.检查是否安装CUDA CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由 NVIDIA 推出的一种并行计算平台和编程模型&#xff0c;用于利用 NVIDIA GPU&#xff08;Graphics Processing Unit&#xff09;的强大计算能力进行高性能计算任务。CUDA 的主要特点是…

App违反Google数据安全政策,解决方案

前言 google隐私政策阶段性会进行更新&#xff0c;时长关注隐私政策变化&#xff0c;避免app被强制下架&#xff0c;影响后续运营工作。 邮件内容 摘录邮件详情 我们在以下区域发现了问题&#xff1a; SPLIT_BUNDLE 2:政策声明&#xff0c;数据安全部分&#xff1a;“https:…

大数据企业应用合作解决方案案例

打造产教融合的就业育人的综合服务平台&#xff0c;给予十余年的数据智能产业实践经验&#xff0c;专注于大数据和人工智能方向。 目前合作的企业案例包括&#xff1a;信访大数据平台解决方案、工业废水处理解决方案、找齐远程监控解决方案、道路运输安全、广电用户服务大数据解…

清洁赛道新势力,米博凭“减法”突围?

在五四青年节这个特殊的日子&#xff0c;方太旗下的高端智能清洁品牌“米博”发布了新一代无滚布洗地机7系列。 5月4日晚&#xff0c;米博以“减法生活&#xff0c;净请7代”为主题&#xff0c;举办了新品发布会。在发布会上&#xff0c;从小红书翻红的董洁作为方太集团米博产…

Windows命令提示行使用指南一

命令提示行使用指南 前言一、起源和发展二、和DOS的关系三、常用命令 前言 cmd 是 Windows 操作系统中的命令行界面&#xff08;CLI&#xff09;&#xff0c;也称为命令提示符&#xff08;CMD&#xff09;或批处理文件。它是 Windows 命令行界面的主要组成部分&#xff0c;用于…

ASK,FSK和PSK

一、ASK&#xff0c;FSK和PSK 数字信号只有有限个离散值&#xff0c;使用数字信号对载波进行调制的方式称为键控(Keying),分为幅度键控&#xff08;ASK)、频移键控&#xff08;FSK)和相移键控&#xff08;PSK)。 幅度键控可以通过乘法器和开关电路来实现&#xff0c;在数字信…

八、(了解即可)MyBatis懒加载(或者叫延迟加载)

文章目录 八、懒加载(了解即可)8.1 为啥需要懒加载?8.2 懒加载是什么&#xff1f;8.3 开启方式8.4 既然fetchType可以控制懒加载那么我仅仅配置fetchType不配置全局的可以吗&#xff1f;8.5 aggressiveLazyLoading是做什么么的&#xff1f;8.6 注意点8.7 案例验证懒加载准备工…

自学Python必须知道的优秀社区

国内学习Python网站&#xff1a; 知乎学习平台&#xff1a;Python - 基础入门 - 知学堂黑马程序员视频库&#xff1a;大数据学习路线2023版-黑马程序员大数据学习路线图菜鸟教程&#xff1a;菜鸟教程 - 学的不仅是技术&#xff0c;更是梦想&#xff01;极客学院&#xff1a;极…

进阶自动化测试,这3点你一定要知道的...

自动化测试指软件测试的自动化&#xff0c;在预设状态下运行应用程序或系统&#xff0c;预设条件包括正常和异常&#xff0c;最后评估运行结果。将人为驱动的测试行为转化为机器执行的过程。 自动化测试框架一般可以分为两个层次&#xff0c;上层是管理整个自动化测试的开发&a…

玩转服务器之Java Web篇:手把手教你搭建Java Web环境

前言 Java Web项目&#xff0c;简单来说就是我们在浏览器上可以看到的内容。就简单的Web来说&#xff0c;服务器上也同样需要有计算机上所需要的运行环境&#xff0c;如&#xff1a;java,tomcat,mysql 。Java Web环境可以用来开发和部署各种Web应用程序&#xff0c;例如网站、…

Redis基础

Redis基础 课程内容 Redis入门Redis数据类型Redis常用命令在Java中操作Redis 1. 前言 1.1 什么是Redis Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件&#xff0c;它是「Remote Dictionary Service」的首字母缩写&#xff0…

HTTP的特点

灵活可扩展 HTTP 协议最初诞生的时候就比较简单&#xff0c;本着开放的精神只规定了报文的基本格式&#xff0c;比如用空格分隔单词&#xff0c;用换行分隔字段&#xff0c;“headerbody”等&#xff0c;报文里的各个组成部分都没有做严格的语法语义限制&#xff0c;可以由开发…

优先级队列(大根堆与小根堆)

优先级队列&#xff08;大根堆与小根堆&#xff09; 文章目录 优先级队列&#xff08;大根堆与小根堆&#xff09;堆的介绍模拟堆以数组模型为例&#xff0c;创建堆向下调整&#xff08;shiftDown&#xff09;入队&#xff08;push&#xff09;及向上调整&#xff08;shiftUp&a…

SLAM中将地图映射到谷歌地图上的方法——完美运行

文章目录 前言一、rviz_satellite二、mapviz 前言 老是看到论文中有将地图映射到谷歌地图上的图&#xff0c;实在是泰裤辣&#xff01;&#xff01;&#xff08;武汉大学&#xff09; 搜索了很久&#xff0c;发现有两种可视化软件&#xff0c;分别为rviz_satellite和mapviz。…

第4章-动态规划

第4章-动态规划 总分&#xff1a;100分 得分&#xff1a;100.0分 10.0 分 1 . 多选题 中等 10分 有关0-1背包问题,用c[i][j]描述子问题:1...i共i个物品,背包容量为j的最优值(装入背包的最大价值),则其子问题为:1...i-1共i-1个物品,背包容量为j-w ix i,以下说法正确的是( AB…

如何利用分钟级降水预报 API 优化城市水利管理?

引言 降水预报对于城市水利管理部门来说至关重要&#xff0c;它可以帮助管理者及时了解当地的降雨情况&#xff0c;以便更好地管理城市水利设施&#xff0c;保障公共安全。然而&#xff0c;传统的降水预报数据一般只提供每小时或每3小时的粗略预报数据&#xff0c;无法满足城市…

ICV: 全球QRNG产业规模在2030年有望突破200亿美元

近日&#xff0c;专注于前沿科技领域的国际咨询机构ICV发布了《全球量子随机数发生器的产业研究报告》&#xff0c;从多个角度对QRNG的市场进行预测。 QRNG 是解决与随机数相关的问题&#xff08;例如密码解决方案&#xff09;的重要硬件来源。 QRNG 是随着量子物理技术的发展…

2023年6月DAMA-CDGA/CDGP数据治理认证报名请尽早啦!

6月18日DAMA-CDGA/CDGP数据治理认证考试开放报名中&#xff01; 考试开放地区&#xff1a;北京、上海、广州、深圳、长沙、呼和浩特、杭州、南京、济南、成都、西安。其他地区凑人数中… DAMA-CDGA/CDGP数据治理认证开班时间&#xff1a;5月7日 DAMA认证为数据管理专业人士提供…
最新文章