所有的商品服务都去占同一把锁。
利用redis,的set nx , 当redis中不存在时才放入redis
set nx 是一个原子性的 *** 作,只要set失败的线程就认为占锁失败。
进入虚拟机
输入
docker exec -it redis redis-cli
进入redis,模拟多个服务set nx
set lock haha NX
只有一个人会返回OK,其他都是nil
这样是否存在问题?
public Map> getCatalogJsonFromDbWithRedisLock() { //占分布式锁 Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111"); if (lock) { //加锁成功,执行业务 return getStringListMap(); }else { //加锁失败 } }
synchronized `自旋`:同步锁会在这里一直监听,别人一释放他就会拿到。一直重试
public Map> getCatalogJsonFromDbWithRedisLock() { //占分布式锁 Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111"); if (lock) { //加锁成功,执行业务 Map > listMap = getStringListMap(); redisTemplate.delete("lock");//删除锁 return listMap; }else { //加锁失败 //可以休眠100ms重试,调用自己 return getCatalogJsonFromDbWithRedisLock(); //自旋 } }
如果出现异常,没有删锁,就会导致死锁。
我们可以给锁设置过期时间
if (lock) { //设置过期时间 redisTemplate.expire("lock",30,TimeUnit.SECONDS); //加锁成功,执行业务 Map> listMap = getStringListMap(); redisTemplate.delete("lock");//删除锁 return listMap; }else {
那如果在if下面,设置过期时间上面断电怎么办?
我们必须保证加锁和删除锁是一个原子 *** 作
所以要写成这样
public Map> getCatalogJsonFromDbWithRedisLock() { //占分布式锁 Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",300,TimeUnit.SECONDS); if (lock) { //设置过期时间 //redisTemplate.expire("lock",30,TimeUnit.SECONDS); //加锁成功,执行业务 Map > listMap = getStringListMap(); redisTemplate.delete("lock");//删除锁 return listMap; }else { //加锁失败 //可以休眠100ms重试,调用自己 return getCatalogJsonFromDbWithRedisLock(); //自旋 } }
再一次改进
String uuid = UUID.randomUUID().toString(); Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",300,TimeUnit.SECONDS); if (lock) { //设置过期时间 //redisTemplate.expire("lock",30,TimeUnit.SECONDS); //加锁成功,执行业务 Map> listMap = getStringListMap(); //获取值-对比-对比成功再删除 == 原子 *** 作 String lock1 = redisTemplate.opsForValue().get("lock"); if (uuid.equals(lock1)) { redisTemplate.delete("lock");//删除锁 } return listMap; }else {
但是还是会出现删除别人的锁的情况。
需要用到lua脚本
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
1.2 采用Redission框架
@Controller public class IndexController { @Autowired RedissonClient redisson; @GetMapping("/hello") @ResponseBody public String hello(){ //获取锁 RLock lock = redisson.getLock("my-lock"); //加锁 lock.lock(); //阻塞式等待,直到拿到锁才运行下面方法 try { System.out.println("加锁成功,执行业务" + Thread.currentThread().getId()); //模拟业务 Thread.sleep(30000); } catch (Exception e) { e.printStackTrace(); } finally { //解锁 System.out.println("释放锁:"+Thread.currentThread().getId()); lock.unlock(); } return "hello"; } }
redission的强大之处:
1.锁的自动续期,如果业务超长,会在运行期间会自动给锁续上新的TTL,不用担心业务时间长,锁自动过期被删掉。
2.加锁的业务只要运行完成,不会给当前锁续期,即使不手动解锁,锁默认也会在30S后自动删除。
读写锁,保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。读锁是一个共享锁。
写锁没有释放,读就必须等待。
写+读:等待写锁释放
写+写:阻塞方式
读+写:
@GetMapping("/write") @ResponseBody public String writevalue(){ //读写锁 RReadWriteLock rwLock = redisson.getReadWriteLock("rw-lock"); String s = ""; //写锁 RLock writeLock = rwLock.writeLock(); try { //改数据加写锁 writeLock.lock(); s = UUID.randomUUID().toString(); Thread.sleep(3000); stringRedisTemplate.opsForValue().set("writevalue",s); } catch (InterruptedException e) { e.printStackTrace(); } finally { writeLock.unlock(); } return s; } @GetMapping("/read") @ResponseBody public String readValue(){ RReadWriteLock rwLock = redisson.getReadWriteLock("rw-lock"); //读锁 RLock readLock = rwLock.readLock(); String s = ""; readLock.lock(); try { s = stringRedisTemplate.opsForValue().get("writevalue"); } catch (Exception e) { e.printStackTrace(); } finally { readLock.unlock(); } return s; }1.3 缓存里面的数据如何和数据库保存一致? 1.双写模式
即更新数据库的数据后,随即更新缓存里的数据
存在问题:
2.失效模式即更新数据库的数据后删除缓存中的数据
存在问题:
3.总结无论是双写模式还是失效模式,都会有缓存的不一致的问题,即并发情况下同时更新就会有不一致的问题
解决方式:
如果是用户维度数据(比如订单数据,用户数据),这种并发几率小的数据,可以不考虑这方面的问题。可以使用过期时间这一方式,让数据每一段时间触发数据的读的主动更新即可如果是菜单,商品介绍等基础数据,可以使用Canal(阿里开源中间件)订阅binlog的方式通常情况下,使用缓存数据 + 过期时间 就可以解决大部分业务的需求
通过加锁保证并发读写,写写的时候按顺序排队(读写锁)
总而言之:
缓存中的数据不应该是实时性,一致性要求较高的数据大部分业务使用缓存数据 + 过期时间,保证每天能拿到最新数据即可实时性,一致性较高的数据应该存储在数据库
一般缓存数据一致性解决方案:
- 缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新缓存
- 读写数据的时候,加上分布式读写锁
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)