使用分布式锁的原因

解决缓存雪崩—加锁

1.synchronized 本地锁存在的问题——分布式情况下无法锁住所有的线程
解决缓存雪崩—加锁
1.synchronized 本地锁存在的问题——分布式情况下无法锁住所有的线程
.分布式锁—-效率问题

  1. redisTemplate.opsForValue().setIfAbsent("lock", "lock");

如果没有拿到锁则重复调用自己这个方法—-自旋

问题1:如果业务代码异常(可以把解锁放入finally中),或者程序宕机,没有执行删除锁的逻辑?

解决:
设置自动过期时间,即使出现问题也会到时间自动删除锁

  1. redisTemplate.expire("lock", 30, TimeUnit.SECONDS);

问题2:如果在设置过期时间之前程序宕机呢?

解决:加锁的同时设置过期时间—-加锁和设置过期时间整合为一个原子操作

  1. Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "lock", 300, TimeUnit.SECONDS);

问题3:如果业务超时了,自己线程的锁已经过期了,当业务执行完之后删除锁把别人的锁删除了怎么办?

解决:占锁的时候加入UUID,用UUID的唯一字符串作为value

  1. Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuidLock, 300, TimeUnit.SECONDS);

解锁的时候判断lock对应的value和uuid得到的是否相同再进行删除

问题4:向redis中查询lock对应的value需要花费时间,如果在查询到结果返回的时候锁过期,被更换了怎么办?

解决:获取值对比和删除指定值也需要是一个原子操作—-需要LUA脚本

问题5:在业务执行中不能让锁过期

解决:自动续费ttl时间

分布式锁框架—-Redisson

pom

  1. <!--Redisson作为分布式锁框架-->
  2. <dependency>
  3. <groupId>org.redisson</groupId>
  4. <artifactId>redisson</artifactId>
  5. <version>3.12.0</version>
  6. </dependency>

配置(单节点)

  1. @Configuration
  2. public class myRedissonCConfig {
  3. @Bean(destroyMethod = "shutdown")
  4. RedissonClient redisson() {
  5. //创建配置
  6. Config config = new Config();
  7. config.useSingleServer().setAddress("redis://192.168.111.129:6379");
  8. //根据配置创建实例
  9. return Redisson.create(config);
  10. }
  11. }

优点

1.锁的自动续期,如果业务实际超长会在运行时间自动续时30s,不用担心因为业务时间过长导致业务还没执行完就将锁删除的错误行为
2.加锁的业务只要运行完成,就不会给当前锁续期,即使发生异常没有删除锁,锁也会在默认30s后自动删除
如果传递了锁的超时时间,就会发送给redis执行lua脚本,此时超时时间是我们设置的超时时间
如果未使用时间就使用看门狗的默认超时时间—30s—-通过定时任务来设置新的过期时间,默认10秒续期


存在的问题:

读写锁:

1.读+读:相当于无锁,并发读,只会在redis中记录所有的读锁,它们会同时加锁成功

2.写+读:等待写锁释放

3.写+写:阻塞方式

4.读+写:有读锁,写也需要等待

信号量:相当于停车位问题

闭锁: 等待其他全部完成后才能进行

缓存和数据库的数据一致性问题

满足最终一致性:最后肯定可以看到数据库中一致的数据

1.双写模式

修改完数据库中的数据就对缓存中的数据进行修改

  1. **问题**:高并发下同时修改一条数据,如A先修改了数据还未即使放入缓存,此时B修改了数据并放入缓存,然后A再放入缓存,造成了数据的不一致<br /> 根据是否能够接收数据不一致问题(设置了过期时间到时间也会删除了)<br />2.失效模式<br />修改完数据库中的数据然后将缓存删除<br /> **问题:**当三个线程进行操作,A先改了数据库,线程失去CPU执行权,还未删除缓存,B也修改了数据库并且删除了缓存,此时C进行查询,发现没有缓存然后去读数据库并放入缓存,此时A又将缓存删除

加读写锁可以解决问题,但是性能会下降,需要根据不同场景权衡

1.png

也可以用Cannel订阅mysql的binlog日志
2.png