Redis分布式锁
_灯火阑珊处 人气:0- 分布式锁需要具备的条件和刚需
- 独占性:OnlyOne,任何时刻只能有且仅有一个线程持有
- 高可用:若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况
- 防死锁:杜绝死锁,必须有超时控制机制或者撤销操作,有个兜底终止跳出方案
- 不乱抢:防止张冠李戴,不能私下unlock别人的锁,只能自己加锁自己释放
- 重入性:同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁
- Redisson使用
引入 redisson 依赖:
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.16.2</version> </dependency>
添加 redisson 配置:
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; import java.io.Serializable; @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { RedisTemplate<String, Serializable> 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.10.233:6379").setDatabase(0); return (Redisson) Redisson.create(config); } }
接口测试:
import org.redisson.Redisson; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private Redisson redisson; private static final String KEY = "spike"; @GetMapping("/buy") public String bugGood() { RLock redissonLock = redisson.getLock(KEY); redissonLock.lock(); try { String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodNumber = result == null ? 0 : Integer.parseInt(result); if (goodNumber > 0) { int realNum = goodNumber - 1; stringRedisTemplate.opsForValue().set("goods:001", realNum + ""); return "秒杀成功,剩余库存:" + realNum; } return "商品已售罄!"; } finally { redissonLock.unlock(); } } }
多节点的情况下,代码相同,要保证 lock 的 key 一样
- SpringBoot集成Redisson
引入 redis 和 redisson starter:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.redisson/redisson --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> </dependency>
redis 配置:
spring: redis: # 地址 host: 192.168.20.26 # 端口,默认为6379 port: 6379 password: 123456 # 连接超时时间 timeout: 30s database: 0 lettuce: pool: # 连接池中的最小空闲连接 min-idle: 0 # 连接池中的最大空闲连接 max-idle: 8 # 连接池的最大数据库连接数 max-active: 8 # #连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1ms
redisson配置:
import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.config.SingleServerConfig; import org.redisson.config.TransportMode; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * redisson配置 */ @Configuration public class RedissonConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private String port; @Value("${spring.redis.password}") private String password; @Bean public RedissonClient redissonClient() { Config config = new Config(); config.setTransportMode(TransportMode.NIO); // 单体配置,如果是SSL连接,使用 rediss:// SingleServerConfig singleServerConfig = config.useSingleServer(); singleServerConfig.setAddress("redis://" + host + ":" + port); singleServerConfig.setPassword(password); return Redisson.create(config); } }
使用:
import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; public class TestController { @Resource private RedissonClient redissonClient; public void doAction1() { String lockKey = "lockKey"; RLock lock = redissonClient.getLock(lockKey); // 加锁,无参数 lock.lock(); // 锁有效时间,时间单位 // lock.lock(5, TimeUnit.SECONDS); try { // do something } finally { // 解锁 lock.unlock(); } } public void doAction2() throws InterruptedException { String lockKey = "lockKey"; RLock lock = redissonClient.getLock(lockKey); // 1. 无参数,直接上锁 // lock.tryLock(); // 2. 等待时间,时间单位 // lock.tryLock(3, TimeUnit.SECONDS); // 3. 等待时间,锁有效时间,时间单位 // lock.tryLock(3, 5, TimeUnit.SECONDS); try { // 常规写法 if (lock.tryLock(3, 5, TimeUnit.SECONDS)) { // do something } else { // 没有获取到锁 } } finally { // 解锁 lock.unlock(); } } }
加载全部内容