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

相关推荐
Dlwyz1 小时前
问题: redis-高并发场景下如何保证缓存数据与数据库的最终一致性
数据库·redis·缓存
飞升不如收破烂~2 小时前
redis的List底层数据结构 分别什么时候使用双向链表(Doubly Linked List)和压缩列表(ZipList)
redis
吴半杯3 小时前
Redis-monitor安装与配置
数据库·redis·缓存
J不A秃V头A4 小时前
Redisson 中开启看门狗(watchdog)机制
java·分布式锁·看门狗
会code的厨子5 小时前
Redis缓存高可用集群
redis·缓存
尽兴-6 小时前
Redis模拟延时队列 实现日程提醒
java·redis·java-rocketmq·mq
Karoku06610 小时前
【企业级分布式系统】ELK-企业级日志分析系统
运维·数据库·redis·mysql·elk·缓存
是店小二呀11 小时前
【C++】右值引用与移动语义详解:如何利用万能引用实现完美转发
c++·redis
一直要努力哦1 天前
Redis的高可用性
数据库·redis·缓存
吃着火锅x唱着歌1 天前
Redis设计与实现 学习笔记 第十八章 发布与订阅
redis·笔记·学习