date
Jul 2, 2022
type
Page
status
Invisible
slug
distributed-lock-redisson
summary
概要:基于Redisson实现的分布式锁方案
tags
微服务
spring-cloud-alibaba
商城
category
商城实战
password
Property
Sep 27, 2022 02:57 AM
icon
Redisson分布式锁
1. Redisson的整合
添加对应的依赖
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.16.1</version> </dependency>
添加对应的配置类
@Configuration public class MyRedisConfig { @Bean public RedissonClient redissonClient(){ Config config = new Config(); // 配置连接的信息 config.useSingleServer() .setAddress("redis://192.168.56.100:6379"); RedissonClient redissonClient = Redisson.create(config); return redissonClient; } }
2. 可重入锁
/** * 1.锁会自动续期,如果业务时间超长,运行期间Redisson会自动给锁重新添加30s,不用担心业务时间,锁自动过去而造成的数据安全问题 * 2.加锁的业务只要执行完成, 那么就不会给当前的锁续期,即使我们不去主动的释放锁,锁在默认30s之后也会自动的删除 * @return */ @ResponseBody @GetMapping("/hello") public String hello(){ RLock myLock = redissonClient.getLock("myLock"); // 加锁 myLock.lock(); try { System.out.println("加锁成功...业务处理....." + Thread.currentThread().getName()); Thread.sleep(30000); }catch (Exception e){ }finally { System.out.println("释放锁成功..." + Thread.currentThread().getName()); // 释放锁 myLock.unlock(); } return "hello"; }
3. 读写锁
根据业务操作我们可以分为读写操作,读操作其实不会影响数据,那么如果还对读操作做串行处理,效率会很低,这时我们可以通过读写锁来解决这个问题
@GetMapping("/writer") @ResponseBody public String writerValue(){ RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock"); // 加写锁 RLock rLock = readWriteLock.writeLock(); String s = null; rLock.lock(); // 加写锁 try { s = UUID.randomUUID().toString(); stringRedisTemplate.opsForValue().set("msg",s); Thread.sleep(30000); } catch (InterruptedException e) { e.printStackTrace(); } finally { rLock.unlock(); } return s; } @GetMapping("/reader") @ResponseBody public String readValue(){ RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock"); // 加读锁 RLock rLock = readWriteLock.readLock(); rLock.lock(); String s = null; try { s = stringRedisTemplate.opsForValue().get("msg"); }finally { rLock.unlock(); } return s; }
在读写锁中,只有读读的行为是共享锁,相互之间不影响,只要有写的行为存在,那么就是一个互斥锁(排他锁)
4 闭锁
基于Redisson的Redisson分布式闭锁(CountDownLatch)Java对象RCountDownLatch
采用了与java.util.concurrent.CountDownLatch
相似的接口和用法。
@GetMapping("/lockDoor") @ResponseBody public String lockDoor(){ RCountDownLatch door = redissonClient.getCountDownLatch("door"); door.trySetCount(5); try { door.await(); // 等待数量降低到0 } catch (InterruptedException e) { e.printStackTrace(); } return "关门熄灯..."; } @GetMapping("/goHome/{id}") @ResponseBody public String goHome(@PathVariable Long id){ RCountDownLatch door = redissonClient.getCountDownLatch("door"); door.countDown(); // 递减的操作 return id + "下班走人"; }
5. 信号量(Semaphore)
基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore
采用了与java.util.concurrent.Semaphore
相似的接口和用法。同时还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。
@GetMapping("/park") @ResponseBody public String park(){ RSemaphore park = redissonClient.getSemaphore("park"); boolean b = true; try { // park.acquire(); // 获取信号 阻塞到获取成功 b = park.tryAcquire();// 返回获取成功还是失败 } catch (Exception e) { e.printStackTrace(); } return "停车是否成功:" + b; } @GetMapping("/release") @ResponseBody public String release(){ RSemaphore park = redissonClient.getSemaphore("park"); park.release(); return "释放了一个车位"; }
6. 缓存数据一致性问题




针对于上的两种解决方案我们怎么选择?
- 缓存的所有数据我们都加上过期时间,数据过期之后主动触发更新操作
- 使用读写锁来处理,读读的操作是不相互影响的
无论是双写模式还是失效模式,都会导致缓存的不一致问题。即多个实例同时更新会出事。怎么办?
- 如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加 上过期时间,每隔一段时间触发读的主动更新即可
- 如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
- 缓存数据+过期时间也足够解决大部分业务对于缓存的要求。
- 通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心 脏数据,允许临时脏数据可忽略)
总结:
- 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。
- 我们不应该过度设计,增加系统的复杂性
- 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。