本文共 6016 字,大约阅读时间需要 20 分钟。
本地缓存的问题:每个微服务都得要有自己的缓存服务,数据更新时只能更新本服务的缓存 造成数据不一致。分布式项目时,本地锁只能锁住当前服务。
redis命令中setnx表示同一时刻只能设置成功一个。
抽取访问数据库的代码
private Map> getDataFromDB() { //线程进入之前,再次查缓存 String catelogJson = stringRedisTemplate.opsForValue().get("catelogJson"); if (StringUtils.isNotEmpty(catelogJson)) { //如果缓存中有数据 //解析成json返回 return JSON.parseObject(catelogJson, new TypeReference
在redisTemplate中使用setIfAbsent进行setnx操作。
//进行分布式锁的改造public Map> getCatelogJsonFromWithDistributeLock() { //设置分布式锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111"); if(lock){ //加锁成功.执行业务 Map > dataFromDB = getDataFromDB(); //业务执行成功,需要解锁 stringRedisTemplate.delete("lock"); return dataFromDB; }else { //加锁失败 //进行重试,自旋 //休眠 return getCatelogJsonFromWithDistributeLock(); }}
public Map> getCatelogJsonFromWithDistributeLock() { //设置分布式锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111"); if(lock){ //加锁成功.执行业务 //设置过期时间 stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS); Map > dataFromDB = getDataFromDB(); //业务执行成功,需要解锁 stringRedisTemplate.delete("lock"); return dataFromDB; }else { //加锁失败 //进行重试,自旋 //休眠 return getCatelogJsonFromWithDistributeLock(); }}
使用setnx ex
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111",300,TimeUnit.SECONDS);
当设置过期时间小于业务执行代码时间时,业务代码未执行完,锁被释放,其他线程应用此时再次抢到lock锁,之前线程执行代码完成后,需要删除锁,此时删除的就不再是自己的锁了,是其他线程的锁。导致问题出现。
//进行分布式锁的改造public Map> getCatelogJsonFromWithDistributeLock() { //指定值为uuid,确保删除的是当前锁 String redisValue = UUID.randomUUID().toString(); //设置分布式锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", redisValue,300,TimeUnit.SECONDS); if(lock){ //加锁成功.执行业务 Map > dataFromDB = getDataFromDB(); //业务执行成功,需要解锁 //判断是否是当前锁 if(redisValue.equals(stringRedisTemplate.opsForValue().get("lock"))){ //如果匹配,就执行删除 stringRedisTemplate.delete("lock"); } return dataFromDB; }else { //加锁失败 //进行重试,自旋 //休眠 return getCatelogJsonFromWithDistributeLock(); }}
如果判断为true之后,redis中lock时间到期,此时删除的依然不是自己的key
使用redis+lua脚本保证原子性
if redis.call("get",KEYS[1]) == ARGV[1]then return redis.call("del",KEYS[1])else return 0end
String luaScript="if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
//进行分布式锁的改造public Map> getCatelogJsonFromWithDistributeLock() { //指定值为uuid,确保删除的是当前锁 String redisValue = UUID.randomUUID().toString(); //设置分布式锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", redisValue,300,TimeUnit.SECONDS); if(lock){ //加锁成功.执行业务 Map > dataFromDB = getDataFromDB(); //业务执行成功,需要解锁 //判断是否是当前锁 String luaScript="if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";// if(redisValue.equals(stringRedisTemplate.opsForValue().get("lock"))){ // //如果匹配,就执行删除// stringRedisTemplate.delete("lock");// } Integer lock1 = stringRedisTemplate.execute(new DefaultRedisScript (luaScript, Integer.class), Arrays.asList("lock"), redisValue); return dataFromDB; }else { //加锁失败 //进行重试,自旋 //休眠 return getCatelogJsonFromWithDistributeLock(); }}
最后在finally执行删锁命令
//进行分布式锁的改造public Map> getCatelogJsonFromWithDistributeLock() { //指定值为uuid,确保删除的是当前锁 String redisValue = UUID.randomUUID().toString(); //设置分布式锁 Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", redisValue,300,TimeUnit.SECONDS); Map > dataFromDB = null; if(lock){ try { dataFromDB = getDataFromDB(); }catch (Exception e){ String luaScript="if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end"; Integer lock1 = stringRedisTemplate.execute(new DefaultRedisScript (luaScript, Integer.class), Arrays.asList("lock"), redisValue); } return dataFromDB; }else { return getCatelogJsonFromWithDistributeLock(); }}
不论是加锁还是删锁,都得保证原子性。
转载地址:http://pexni.baihongyu.com/