用redis的 set nx 命令。
阶段一 ,代码改进:
@Override public Map<String, List<Catelog2Vo>> getCatelogJson() { //加入缓存逻辑 ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); String json = ops.get("CatalogJSON"); if (StringUtils.isEmpty(json)) { //缓存没有,从数据库中查询 // Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbWithLocalLock(); Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbWithRedisLock(); return catalogJsonFromDb; } //视频中是这样转然后返回的 // Map<String, List<Catelog2Vo>> object // = JSON.parseObject(json, new TypeReference<Map<String, List<Catelog2Vo>>>() {}); return (Map<String, List<Catelog2Vo>>) JSON.parse(json); } public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() { //占分布式锁,去redis占坑 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "11111"); if(lock){ //加锁成功,执行业务 Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb(); //释放锁 stringRedisTemplate.delete("lock"); return dataFromDb; }else{ //加锁失败 重试 ----自旋的方式 return getCatalogJsonFromDbWithRedisLock(); } }
如果执行业务的时候宕机了,锁就得不到释放。
=================================
改进,设置过期时间:
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() { //占分布式锁,去redis占坑 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "11111"); if(lock){ //设置过期时间 stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS); //加锁成功,执行业务 Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb(); //释放锁 stringRedisTemplate.delete("lock"); return dataFromDb; }else{ //加锁失败 重试 ----自旋的方式 return getCatalogJsonFromDbWithRedisLock(); } }
但是如果,刚进 if ,过期时间还没设置,就宕机了,同样也会死锁。
加锁和设置过期时间,如果是原子操作,就能解决。
再次改进代码(阶段2):
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() { //占分布式锁,去redis占坑 +设置过期时间 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "11111",30,TimeUnit.SECONDS); if(lock){ //设置过期时间,没保证和加锁一起成为原子操作 // stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS); //加锁成功,执行业务 Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb(); //释放锁 stringRedisTemplate.delete("lock"); return dataFromDb; }else{ //加锁失败 重试 ----自旋的方式 return getCatalogJsonFromDbWithRedisLock(); } }
====================================
删除锁的时候,如果业务执行时间长,锁已经过期了,这时候再删除就是别人的锁了。解决:+UUID保证删的是自己的锁
阶段3,代码再次改进:
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() { String uuid = UUID.randomUUID().toString(); //占分布式锁,去redis占坑 +设置过期时间 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS); if(lock){ //设置过期时间,没保证和加锁一起成为原子操作 // stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS); //加锁成功,执行业务 Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb(); //释放锁 if(stringRedisTemplate.opsForValue().get("lock").equals(uuid)){ stringRedisTemplate.delete("lock"); } return dataFromDb; }else{ //加锁失败 重试 ----自旋的方式 return getCatalogJsonFromDbWithRedisLock(); } }
==========================
删锁的问题。。解决方法就是用lua脚本完成。
阶段四再次改进后:
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() { String uuid = UUID.randomUUID().toString(); //占分布式锁,去redis占坑 +设置过期时间 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS); if(lock){ //设置过期时间,没保证和加锁一起成为原子操作 // stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS); //加锁成功,执行业务 Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb(); //释放锁 仍存在问题,应该用lua脚本删锁 // if(stringRedisTemplate.opsForValue().get("lock").equals(uuid)){ // stringRedisTemplate.delete("lock"); // } //lua脚本解锁 String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; //删除锁 返回值就是执行redis命令的返回值 Integer execute = stringRedisTemplate.execute( new DefaultRedisScript<Integer>(script, Integer.class) , Arrays.asList("lock") , uuid); return dataFromDb; }else{ //加锁失败 重试 ----自旋的方式 return getCatalogJsonFromDbWithRedisLock(); } }
=================================
==============================
如果业务时间超级长,锁过期了,就要对锁进行续期。
最简单的解决方案,就是把锁的时间弄长一些。
上面代码还存在问题,业务异常呢?执行业务的时候,出现异常,锁没释放?
再次改进,finally 中释放锁,永远执行。:
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() { String uuid = UUID.randomUUID().toString(); //占分布式锁,去redis占坑 +设置过期时间 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS); if (lock) { Map<String, List<Catelog2Vo>> dataFromDb; try { //设置过期时间,没保证和加锁一起成为原子操作 // stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS); //加锁成功,执行业务 dataFromDb = getDataFromDb(); } finally { //释放锁 仍存在问题,应该用lua脚本删锁 // if(stringRedisTemplate.opsForValue().get("lock").equals(uuid)){ // stringRedisTemplate.delete("lock"); // } //lua脚本解锁 String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; //删除锁 返回值就是执行redis命令的返回值 Integer execute = stringRedisTemplate.execute( new DefaultRedisScript<Integer>(script, Integer.class) , Arrays.asList("lock") , uuid); } return dataFromDb; } else { //加锁失败 重试 ----自旋的方式 return getCatalogJsonFromDbWithRedisLock(); } }
我没运行,视频 中运行出现错误,解锁那里,integer不能接受应该换long