redis分布式锁

文章目录

  • 一、概述
  • 二、Redis的应用场景
  • 三、实战
    • [3.1 Redis为什么这么快](#3.1 Redis为什么这么快)
    • [3.2 缓存穿透](#3.2 缓存穿透)
    • [3.2 缓存雪崩](#3.2 缓存雪崩)
    • [3.3 ReadTimeout](#3.3 ReadTimeout)
    • [3.4 Redis与DB数据一致性](#3.4 Redis与DB数据一致性)
      • [3.4.1 缓存与数据库双写数据一致性](#3.4.1 缓存与数据库双写数据一致性)
      • [3.4.2 少卖](#3.4.2 少卖)
    • [3.5 Redis实现分布式锁](#3.5 Redis实现分布式锁)
    • [3.6 RedLock](#3.6 RedLock)
    • [3.7 Redssion](#3.7 Redssion)
      • [3.7.1 快速开启](#3.7.1 快速开启)
      • [3.7.2 加锁原理](#3.7.2 加锁原理)
      • [3.7.3 解锁](#3.7.3 解锁)
      • [3.7.4 semaphore限流](#3.7.4 semaphore限流)
      • [3.7. 5 CountDownLatch](#3.7. 5 CountDownLatch)
      • [3.7.6 实现RedLock](#3.7.6 实现RedLock)
      • [3.7.7 使用注意事项](#3.7.7 使用注意事项)
    • [3.8 匹配key](#3.8 匹配key)
    • [3.9 MySQL实现分布式锁](#3.9 MySQL实现分布式锁)

一、概述

Redis

  • Redis是非关系型(NoSQL)的键值对数据库
  • 数据是存在内存中的,每秒可以处理超过 10万次读写操作,故广泛应用于缓存

数据类型

Redis主要有5种数据类型:String,List,Set,Zset,Hash

String

应用场景

1)缓存

java 复制代码
set(k,v)
get(k)

2)计数、分布式ID

java 复制代码
incr(k)
get(k)

3)分布式锁

java 复制代码
setNx
del

Hash

应用场景

1)对象

java 复制代码
Map<Integer, String> skuIdAndNameMap = new HashMap<>();  
skuIdAndNameMap.put(1, "苹果");  
skuIdAndNameMap.put(2, "香蕉");  
skuIdAndNameMap.put(3, "橘子");  
     
skuIdAndNameMap.forEach((field, value) -> jedis.hset("323-20240909", field, value));  
  
Map<Integer, String> map = jedis.hgetAll("323-20240909");  
  
String name = jedis.hget("323-20240909", "3");// 橘子 

List

粉丝列 表、评论列表;分布式队列:可以通过 lpush 和 rpop 写入和读取消息、或者将库存存入,然后扣减

Set

交集、并 集、差集 的操作,两个人的共同好友

Zset

去重、排序, 获取排名前几名的用户

二、Redis的应用场景

1、计数器

2、自增ID

2、缓存

3、分布式锁

  • SETNX
  • RedLock

三、实战

3.1 Redis为什么这么快

  1. 内存
  2. 高效的数据结构和算法(跳表、)
  3. 采用单线程(无切换、无锁)-文件事件分派器队列的消费是单线程
  4. 非阻塞 IO,文件事件处理器使用 I/O 多路复用

3.2 缓存穿透

缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案:布隆过滤器(Bloom Filter),先用布隆过滤器判断下,如果不存在,直接不用查了

3.2 缓存雪崩

并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

过期时间打散

java 复制代码
set("mjp", String.valueOf(18), 
                30 + ThreadLocalRandom.current().nextInt(10), TimeUnit.SECONDS);

3.3 ReadTimeout

在setNx时,出现异常,异常

java 复制代码
e.getMessage().contains("ReadTimeout") {
	// redis服务端,已经加锁成功了,只不过在返回给客户端的时候,超时了,这种场景我们认为是加锁成功了
}

3.4 Redis与DB数据一致性

3.4.1 缓存与数据库双写数据一致性

1、思路

读的时候,先读缓存,缓存没有的话,就读db,然后取出数据后放入缓存,同时返回响应

更新的时候,先更新db,然后再删除缓存

2、问题

如果删除缓存失败了,那么会导致db是新数据,缓存中是旧数据,数据就出现了不一致

3、解决

归根原因是,CAP中的C一致性

如果业务可以接受:最终一致性,则

  • 方案一:延时双删
java 复制代码
putToDB(key,value);
deleteFromRedis(key);

// 数秒后重新执行删除操作,时间 = 读业务逻辑数据的耗时 + 几百毫秒
deleteFromRedis(key,5);
  • 方案二:消息队列

把删除失败的key放到消息队列,重试删除

分布式事务,参考:分布式事务-队列实现最终一致性


3.4.2 少卖

1、背景

锁资源粒度,将100个库存,分别放在redis的10个list中

2、下单,扣减库存(先更新db,再更新缓存)

  • 如果redis中有库存,则允许下单,先扣减redis库存,再扣减db库存
java 复制代码
        ListOperations<String, String> list = redisTemplate.opsForList();
        String val = list.leftPop("key");
        if (StringUtils.isEmpty(val)) {
            // 已卖完
        } else {
            // 扣减db
        }

3、少买问题

Redis扣减了100个,但是db90个扣减成功,10个扣减失败。少买了10个品

4、解决

本质是数据一致性问题,即分布式事务问题

可以catch住db异常,然后将redis的库存补回

3.5 Redis实现分布式锁

3.5.1 需要场景1-超卖

java 复制代码
//获取库存数目
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
//库存数目大于零则减库存
if(stock > 0){
     int finalStock = stock - 1;
     //更新库存
     redisTemplate.opsForValue().set("stock",Integer.toString(finalStock));
 }

1、背景

  • 假如此时库存,只有1个了
  • t1和t2并行进入,get拿到库存后,发现都是1 > 0
  • 二者都进行了扣减库存操作,导致库存出现 负数问题,即超卖

2、解决:

分布式锁

3.5.2 需要场景2-缓存击穿

1、背景

  • 热点key过期了
  • 此时高并发查询此key,1wQPS
  • 请求直接打到db,打崩

2、解决

分布式锁,在缓存和db之间加分布式锁

java 复制代码
		// 读缓存
        String key = "key";
        String val = redisTemplate.opsForValue().get(key);
		// 缓存中无数据,则可能击穿
        if (StringUtils.isEmpty(val)) {
            // 1.加分布式锁
            Boolean success = redisTemplate.opsForValue().setIfAbsent(key, "v");
            // 2、获取锁成功
            if (success) {
                // 3.再读一次缓存
                val = redisTemplate.opsForValue().get(key);
                if (StringUtils.isNoneEmpty(val)) {
                    return val;
                } else {
                    // 读db
                    // 回写缓存
                    return "查询结果";
                }
            }
        } else {
            return val;
        }

3.5.3 Redis分布式锁问题&解决

死锁

1、背景

  • t1在finally中有释放锁代码,但未设置锁过期,此时客户端server1服务器宕机
  • t2,尝试在server2加锁,获取不到锁
  • "死锁了"

2、解决

只要是加锁,就必须设置锁过期时间,否则一旦服务器宕机,未释放锁,其它服务器,都没有机会再获取到此锁,再处理了


锁过期-导致锁失效

1、背景

java 复制代码
        // 1.加锁-setNx
        
        // 2.执行业务
        
        // 3.释放锁-delete

t1,setNx(k1,v1),并设置锁超时时间为3s

  • t1执行业务,耗费了5s
  • 虽然t1尚未执行释放锁,但是锁已经因为超时失效了

此时t2,进来

  • t2,能够获取锁,正常执行

此时t1,执行完业务,然后释放锁。此时t1释放的是t2的锁了

导致锁失效

这里有两个问题

  • 问题1:t1线程未执行完,锁就过期了,对于t1而言,本质相当于没加锁,对于共享数据,相当于没加锁

  • 问题2:发生了问题1的情况下,t1释放了t2的锁,是另一个问题,解铃必须系铃人!

    这里如果没有问题1,就不会有问题2

2、解决

在加锁,返回true之前:

Timer定时器 + lua脚本为锁续期

  • 使用juc包下的Timer定时器, 启一个子线程,每隔10(1/3的过期时间)s,执行一次lua脚本
  • lua脚本
    • 查看自己的锁是否存在
    • 如果存在,则将其过期时间,重置为30s
java 复制代码
private boolean lock(String key, String val, Long expire) {
        stringRedisTemplate.opsForValue().set(key, val, expire, TimeUnit.SECONDS);
        renewExpire(key,val,expire, 10, 0);
        return true;
}

private void renewExpire(String key, String val, Long expire, int maxRetries, int retryCount) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // KEYS[1]即key、ARGV[1]即val(一般是我们设置的uuid-解铃还须系铃人)、expire锁过期时间
                String lua = "if redis.call('exists', KEYS[1], ARGV[1]) == 1 then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
                DefaultRedisScript<Boolean> script = new DefaultRedisScript<>(lua, Boolean.class);
                Boolean executed = stringRedisTemplate.execute(script, Collections.singletonList(key), val, String.valueOf(expire));
                if (executed) {
                    // 如果锁存在,lua重置锁过期时间成功了,则延时1/3过期时间后,再执行一次
                    // 加锁时,设置了过期时间为30s,返回true加锁成功之前,执行此方法。
                    // 此方法,延时1/3时间执行run任务,即10s后执行任务将锁的过期时间重置为30s
                    // 下一次再延时10s,执行run任务
                    // 直到业务方手动释放了锁,lua脚本执行返回false,就不会再执行renewExpire了
                    if (retryCount <= maxRetries) {
                        renewExpire(key, val, expire, maxRetries, retryCount+1);
                    }
                }
            }
        }, expire * 1000 / 3); // 延时(1/3的过期时间)执行一次
    }

释放别人加的锁

1、背景

1)场景1

上述锁过期问题,会发生释放别人的锁

2)执行释放锁-恶意解锁

3)防御式编程

某些场景下加锁失败,只剩下释放锁逻辑,就会释放别人的锁

java 复制代码
代码问题导致
// 场景1下加锁成功
// 场景2下加锁失败了

finally中释放锁

t1,场景2下加锁失败了,t2进来,加锁成功,t1执行释放锁。t2加的锁被别人释放了

2、解决

只能释放自己加的锁

java 复制代码
    public void t(String key){
        ThreadLocalRandom random = ThreadLocalRandom.current();
        UUID uuid = new UUID(random.nextLong(), random.nextLong());
        String val = String.valueOf(uuid);
        try {
            Boolean lock = redisTemplate.opsForValue().setIfAbsent(key, val, 3, TimeUnit.SECONDS);
            if (lock) {
                // 业务
            }
        } finally {
            val = redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotEmpty(val) && val.contains(uuid.toString())) {
                redisTemplate.delete(key);
            }
        }
    }
  • 生成uuid

  • setNx时,val加个前缀uuid

  • 释放时,先判断k-对应的val前缀为此uuid,再删除

3、存在问题

  • 判断是自己的,刚准备删除时,锁已经过期释放了

  • 然后再执行删除锁,此时锁刚好被别人获取了,相当于还是删除了别人的锁

  • 根本原因:判断 + 删除锁 ,不是原子操作

4、解决

将判断 + 删除,使用lua脚本打包。lua脚本将二者一次性发送给redis,对于redis而言这个lua脚本就是一个指令

java 复制代码
finally{
	// 使用Lua脚本释放锁
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);

    // 执行脚本
    Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), val);

    // 根据结果判断锁是否释放成功
    if (result == 0L) {
        // 锁不属于当前客户端,释放失败
    } else {
        // 锁释放成功
    }
}

此时完整的加锁 、续约锁时间、 释放锁流程

java 复制代码
public void t() throws Exception {
        String key = "mjp";
        ThreadLocalRandom random = ThreadLocalRandom.current();
        UUID uuid = new UUID(random.nextLong(), random.nextLong());
        String val = String.valueOf(uuid);
        Long expire = 30L;
        try {
           lock(key, val, expire);
            TimeUnit.SECONDS.sleep(20);
        } finally {
            // 使用Lua脚本释放锁
            String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            RedisScript<Long> script = new DefaultRedisScript<>(lua, Long.class);

            // 执行脚本
            Long result = stringRedisTemplate.execute(script, Collections.singletonList(key), val);

            // 根据结果判断锁是否释放成功
            if (result == 0L) {
                // 锁不属于当前客户端,释放失败
            } else {
                // 锁释放成功
                System.out.println("/success");
            }
        }
}

// 加锁 和 重置超时
private boolean lock(String key, String val, Long expire) {
        stringRedisTemplate.opsForValue().set(key, val, expire, TimeUnit.SECONDS);
        renewExpire(key,val,expire, 10, 0);
        return true;
}

    private void renewExpire(String key, String val, Long expire, int maxRetries, int retryCount) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // KEYS[1]即key、ARGV[1]即val(一般是我们设置的uuid-解铃还须系铃人)、expire锁过期时间
                String lua = "if redis.call('exists', KEYS[1], ARGV[1]) == 1 then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
                DefaultRedisScript<Boolean> script = new DefaultRedisScript<>(lua, Boolean.class);
                Boolean executed = stringRedisTemplate.execute(script, Collections.singletonList(key), val, String.valueOf(expire));
                if (executed) {
                    // 如果锁存在,lua重置锁过期时间成功了,则延时1/3过期时间后,再执行一次
                    // 加锁时,设置了过期时间为30s,返回true加锁成功之前,执行此方法。
                    // 此方法,延时1/3时间执行run任务,即10s后执行任务将锁的过期时间重置为30s
                    // 下一次再延时10s,执行run任务
                    // 直到业务方手动释放了锁,lua脚本执行返回false,就不会再执行renewExpire了
                    if (retryCount <= maxRetries) {
                        renewExpire(key, val, expire, maxRetries, retryCount+1);
                    }
                }
            }
        }, expire * 1000 / 3); // 延时(1/3的过期时间)执行一次
    }

集群单节点宕机-锁失效

1、问题描述

t1

  • 获取锁成功,写入master-server1
  • master同步slaver过程中,master宕机了

server2-选举为新master

t2

  • 尝试获取锁,从master-server2中,能够正常获取锁
  • 此时对于t1而言,加的锁失效了,因为t2也能进来

2、解决:

RedLock红锁

3.6 RedLock

1、背景

redis中red即红锁,专门用于解决集群单节点故障,导致的锁失效问题

2、加锁过程

redis集群,节点之间是独立的,无master、slaver

1)应用程序,系统当前时间,curTime

2)使用setNx并设置超时时间,依次从每个redis server中尝试获取锁

  • 超时时间:避免死等某个宕机了的redis server

3)判断获取锁是否成功

依次从server1-server5执行setNx后,应用程序,系统当前时间 = newCurTime

diff = newCurTime - curTime

  • 条件1:diff < 锁设置的过期时间(消耗时间已经 > 锁过期时间了,即使获取到锁也没意义了)
  • 条件2:有一半的服务器获取锁成功

当条件1和条件2都满足时,才认为获取锁成功

  • 如果成功了,则计算剩余的锁过期时间(真正给应用程序用的) = 原设置的锁失效时间 - 累加t消耗时间
  • 如果失败了,则对所有节点执行释放锁操作

3、特点

实际中不易实现、且非主从架构

3.7 Redssion

官网中文文档官网离线

3.7.1 快速开启

1、pom

code栏下,快速开启指南

java 复制代码
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.17.1</version>
</dependency>  

2、程序方式配置

Wiki栏下,配置

1)单节点-自己主机

2)单节点-其它主机

3)主从集群

RedissonClient

java 复制代码
@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        // 单节点-主机
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        return Redisson.create(config);
    }
}

3、使用分布式锁锁

Wiki栏下,分布式锁和同步器

java 复制代码
    @Resource
    private RedissonClient redissonClient;

    @Test
    public void t() throws Exception {
        String key = "mjp";
        long expire = 30L;
        RLock lock = redissonClient.getLock(key);
        lock.lock(expire, TimeUnit.SECONDS);
    }

3.7.2 加锁原理

lock

lock ->RedissonLock#lock -> tryAcquire -> tryAcquireAsync -> tryLockInnerAsync

底层就是lua脚本

lua 复制代码
    <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
                "if (redis.call('exists', KEYS[1]) == 0) then " +
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                        "end; " +
                        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                        "end; " +
                        "return redis.call('pttl', KEYS[1]);",
                Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
    }
  • 如果key-锁不存在,
    • 则直接获取锁
    • 并且设置锁的过期时间
    • return nil
  • 如果锁存在
    • 如果是自己的锁
    • 则对锁的重入次数 + 1
    • 然后重置过期时间
    • 返回nil

续时原理

lock ->RedissonLock#lock -> tryAcquire -> tryAcquireAsync -> scheduleExpirationRenewal -> renewExpiration

底层使用的netty的HashedWheelTimer,而非juc的Timer

正常的lua脚本执行renewExpirationAsync

java 复制代码
    protected CompletionStage<Boolean> renewExpirationAsync(long threadId) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return 1; " +
                        "end; " +
                        "return 0;",
                Collections.singletonList(getRawName()),
                internalLockLeaseTime, getLockName(threadId));
    }
  • 判断是否是自己的锁
    • 是,则重置过期时间
java 复制代码
if (res) {
    // reschedule itself
	renewExpiration();
} else {
    cancelExpirationRenewal(null);
}

重置成功,则后续会再次重置,否则取消


3.7.3 解锁

unlock -> RedissonLock#unlock -> unlockAsync -> unlockInnerAsync -> RedissonLock#unlockInnerAsync

lua脚本

lua 复制代码
    protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                        "return nil;" +
                        "end; " +
                        "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
                        "if (counter > 0) then " +
                        "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                        "return 0; " +
                        "else " +
                        "redis.call('del', KEYS[1]); " +
                        "redis.call('publish', KEYS[2], ARGV[1]); " +
                        "return 1; " +
                        "end; " +
                        "return nil;",
                Arrays.asList(getRawName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
    }
  • 如果不是自己的锁,则end结束
    • 如果-1后的counter >0,则返回0并重置过期时间
    • 否则,del锁

3.7.4 semaphore限流

java 复制代码
        // 分布式
        RSemaphore semaphore = redissonClient.getSemaphore("semaphore");
        //限流的量,允许的并发量
        semaphore.tryAcquire(10);
        // 尝试获取资源(10个资源之一)
        semaphore.acquire();
        // 业务
        // 释放资源
        semaphore.release();

3.7. 5 CountDownLatch

作用等效juc中的CountDownLatch,这里是分布式

java 复制代码
// main线程
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
// 英雄联盟,一局游戏10个玩家
latch.trySetCount(10);
latch.await();//必须10个玩家都加载到100%,才能进入游戏画面

// 在其他线程或其他JVM里
// 其他每个玩家(10分之一),完成加载100%,则执行countDown
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();
  • 可以阻塞等待整体完成,才能进入下一步
java 复制代码
	@Resource
    private RedissonClient redissonClient;

	@GetMapping("/test/latch")
    public String testLatch(){
        RCountDownLatch cdl = redissonClient.getCountDownLatch("cdl");
        cdl.trySetCount(5);
        try {
            cdl.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "5个人玩家都ready了";
    }

    @GetMapping("/test/countdown")
    public String testCountdown(){
        RCountDownLatch cdl = redissonClient.getCountDownLatch("cdl");
        try {
            TimeUnit.SECONDS.sleep(1);
            cdl.countDown();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "一个玩家准备好了";
    }

执行5次testCountdown方法,testLatch方法才会返回结果

  • 也可以一步一步阻塞等待上一步完成,才能进入下一步

3.7.6 实现RedLock

java 复制代码
public class RedissonRedLockExample {  
    public static void main(String[] args) {  
        // 配置多个 Redis 服务器  
        Config config1 = new Config();  
        config1.useSingleServer().setAddress("redis://128.10.10.101:6379");  
  
        Config config2 = new Config();  
        config2.useSingleServer().setAddress("redis://127.0.0.1:6379");  
  
        // 创建多个 RedissonClient  
        RedissonClient redisson1 = Redisson.create(config1);  
        RedissonClient redisson2 = Redisson.create(config2);  
  
        // 创建多个 RLock 实例  
        RLock lock1 = redisson1.getLock("anyLock");  
        RLock lock2 = redisson2.getLock("anyLock");  
  
        // 使用 RedLock  
        RLock redLock = new RedissonRedLock(Arrays.asList(lock1, lock2));  
  
        try {  
            // 尝试获取锁,最多等待 100 秒,上锁以后 10 秒自动解锁  
            boolean res = redLock.tryLock(100, 10, TimeUnit.SECONDS);  
            if (res) {  
                try {  
                    // 执行业务逻辑  
                } finally {  
                    // 释放锁  
                    redLock.unlock();  
                }  
            }  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
  
        // 关闭 RedissonClient  
        redisson1.shutdown();  
        redisson2.shutdown();  
    }  
}

3.7.7 使用注意事项

1、问题

在事务内部使用锁,锁在事务提交前释放了

2、解决

不要在事务内部使用锁

java 复制代码
public void func() {
    RLock lock = redissonClient.getLock(paymentOrder.getBizNo());
    lock.lock();
    try {
       createPaymentOrderNoLock(paymentOrder);
    } finally {
        //释放锁
        lock.unlock();
    }
}
 
@Transactional
public void createPaymentOrderNoLock(PaymentOrder paymentOrder) {
    // 双本地写
}

3.8 匹配key

假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

SCAN命令时,你可以指定一个模式(pattern)来匹配key

3.9 MySQL实现分布式锁

1、独占排他(必须)

使用唯一键:lock_name

insert语句,insert失败支持重试

2、超时时间(必须有,否则客户端宕机,则死锁)

使用lock_time

  • 定时轮询,将过期的数据del
  • 或者在insert失败后,看下锁是否过期,过期则删除然后inser(事务)

3、可重入

funcA() {
	// 加锁key成功
 	// 业务
    
    funcB();
    
    // 业务
}

funcB(){
	// 加锁key也应该能成功,锁要满足可重入
}

使用tl字段,使用ThreadLocal存tl,保证一个线程可重入(线程id、重入次数)

4、释放锁(必须)

deleteById(insert成功后,会将主键id回写到DO中)

5、续时

总结

1、简易:mysql

2、性能:redis

3、可靠性:zk

相关推荐
岁月变迁呀13 分钟前
Redis梳理
数据库·redis·缓存
Code apprenticeship2 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站2 小时前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle
装不满的克莱因瓶2 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
黄名富6 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
G_whang7 小时前
centos7下docker 容器实现redis主从同步
redis·docker·容器
.生产的驴7 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
我叫啥都行10 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
阿乾之铭11 小时前
Redis四种模式在Spring Boot框架下的配置
redis
on the way 12313 小时前
Redisson锁简单使用
redis