Redlock算法和底层源码分析
Redlock算法和底层源码分析
当前代码用v8.0的
Redis分布式锁-Redlock红锁算法Distributed locks with Redis
产生原因----- 单点故障:
redis之父提出了Redlock算法解决这个问题
Redis也提供了Redlock算法,用来实现基于多个实例的分布式锁。
锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。Redlock算法是实现高可靠分布式锁的一种有效解决方案,可以在实际开发中使用。
设计理念
该方案为了解决数据不一致的问题,直接舍弃了异步复制只使用master节点,同时由于舍弃了slave,为了保证可用性,引入了N个节点,官方建议是5。
演示用3台实例来做说明。
客户端只有在满足下面的这两个条件时,才能认为是加锁成功。
条件1:客户端从超过半数(大于等于N/2+1)的Redis实例上成功获取到了锁;
条件2:客户端获取锁的总耗时没有超过锁的有效时间。
redisson实现
pom
<!--redisson--> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.13.4</version> </dependency>
RedisConfig
package com.zzrg.redislock.config; import org.redisson.Redisson; import org.redisson.config.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; /** * @Author ZzRG * @Date 2023/6/14 0:39 * @Version 1.0 */ @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.126.130:6379").setDatabase(0).setPassword(""); return (Redisson) Redisson.create(config); } }
InventoryServiceImpl
package com.zzrg.redislock.service.Impl; import cn.hutool.core.util.IdUtil; import com.zzrg.redislock.config.DistributedLockFactory; import com.zzrg.redislock.myLock.RedisDistributedLock; import com.zzrg.redislock.service.InventoryService; import lombok.extern.slf4j.Slf4j; import org.redisson.Redisson; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Service; import java.sql.Time; import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @Author ZzRG * @Date 2023/6/14 1:09 * @Version 1.0 */ @Service @Slf4j public class InventoryServiceImpl implements InventoryService { @Autowired private StringRedisTemplate stringRedisTemplate; @Value("${service.port}") private String port; @Autowired private Redisson redisson; @Override public String saleByRedisson() { String retMessage = ""; RLock redisRedissonLock=redisson.getLock("redisRedissonLock"); redisRedissonLock.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 { redisRedissonLock.unlock(); } return retMessage + "\t" + "服务端口号:" + port; } }
InventoryService
package com.zzrg.redislock.service; /** * @Author ZzRG * @Date 2023/6/14 1:09 * @Version 1.0 */ public interface InventoryService { String sale(); String saleByRedisson(); }
InventoryController
package com.zzrg.redislock.controller; import com.zzrg.redislock.service.InventoryService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @Author ZzRG * @Date 2023/6/14 1:14 * @Version 1.0 */ @RestController @Api(tags = "redis分布式锁测试") public class InventoryController { @Autowired private InventoryService inventoryService; @ApiOperation("扣减库存sale,一次卖一个") @GetMapping(value = "/inventory/sale") public String sale() { return inventoryService.sale(); } @ApiOperation("扣减库存saleByRedisson,一次卖一个") @GetMapping(value = "/inventory/saleByRedisson") public String saleByRedisson() { return inventoryService.saleByRedisson(); } }
但是还是会出现BUG
修改
package com.zzrg.redislock.service.Impl; import cn.hutool.core.util.IdUtil; import com.zzrg.redislock.config.DistributedLockFactory; import com.zzrg.redislock.myLock.RedisDistributedLock; import com.zzrg.redislock.service.InventoryService; import lombok.extern.slf4j.Slf4j; import org.redisson.Redisson; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Service; import java.sql.Time; import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @Author ZzRG * @Date 2023/6/14 1:09 * @Version 1.0 */ @Service @Slf4j public class InventoryServiceImpl implements InventoryService { @Autowired private StringRedisTemplate stringRedisTemplate; @Value("${service.port}") private String port; @Autowired private Redisson redisson; @Override public String saleByRedisson() { String retMessage = ""; RLock redisRedissonLock=redisson.getLock("redisRedissonLock"); redisRedissonLock.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 (redisRedissonLock.isLocked() && redisRedissonLock.isHeldByCurrentThread()) { redisRedissonLock.unlock(); } } return retMessage + "\t" + "服务端口号:" + port; } }
Redisson源码解析
Redis分布式锁过期了,但是业务逻辑还没处理完怎么办
缓存续命
守护线程"续命"
额外起一个线程,定期检查线程是否还持有锁,如果有则延长过期时间。
Redisson里面就实现了这个方案,使用“看门狗”定期检查(每1/3的锁时间检查1次),如果线程还持有锁,则刷新过期时间;在获取锁成功后,给锁加一个watchdog, watchdo 会起一个定时任务,在锁没有被释放且快要过期的时候会续期
源码分析1
通过redisson新建出来的锁key,默认是30秒钟
lock 还是juc里面的
源码分析2
lock() --- tryACquire() ---tryAcquireAsync()
源码分析3
流程解释
- 通过exists判断,如果锁不存在,则设置值和过期时间,加锁成功
- 通过hexists判断,如果锁已存在,并且锁的是当前线程,则证明是重入锁,加锁成功
- 如果锁已存在,但锁的不是当前线程,则证明有其他线程持有锁。返回当前锁的过期时间(代表了锁key的剩余生存时间),加锁失败
源码分析4
解锁