redis-stringRedisTemplate分布式锁
1.redis的分布式锁
加锁:
stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value);//setnx
释放锁:
stringRedisTemplate.delete(REDIS_LOCK_KEY);//释放锁
2.出异常无法释放锁,则强制完成删锁,防止阻塞
也就是把释放锁加入finally{}代码块中
3.加过期时间,处理服务器突发宕机
stringRedisTemplate.expire(REDIS_LOCK_KEY,10L, TimeUnit.SECONDS);
4.设置key+过期时间分开了,必须要合并成一行具备原子性:
加锁和设置过期时间合并为同一语句保证原子性:
Boolean lockFlag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK_KEY, value,10L, TimeUnit.SECONDS);
5.★张冠李戴,删了其他线程的锁,解决:
先判断再删除
finally { if (value.**equalsIgnoreCase**(stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY))){ stringRedisTemplate.delete(REDIS_LOCK_KEY);//释放锁 }
6.finally块的判断+del删除操作不是原子性的
不用lua脚本,如何实现(实际工作中100%用lua!)???
解决:redis事务版:

finally { while (true) { stringRedisTemplate.watch(REDIS_LOCK_KEY); //加事务,乐观锁 if (value.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(REDIS_LOCK_KEY))){ stringRedisTemplate.setEnableTransactionSupport(true); stringRedisTemplate.multi();//开始事务 stringRedisTemplate.delete(REDIS_LOCK_KEY); List<Object> list = stringRedisTemplate.exec(); if (list == null) { //如果等于null,就是没有删掉,删除失败,再回去while循环那再重新执行删除 continue; } } //如果删除成功,释放监控器,并且breank跳出当前循环 stringRedisTemplate.unwatch(); break; } }
lua脚本版:
Redis可以通过eval命令保证代码执行的原子性
jedis工具配置类:
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class RedisUtils { private static JedisPool jedisPool; static { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(20); jedisPoolConfig.setMaxIdle(10); jedisPool = new JedisPool(jedisPoolConfig,"ip",6379,100000); } public static Jedis getJedis() throws Exception{ if (null!=jedisPool){ return jedisPool.getResource(); } throw new Exception("Jedispool is not ok"); } }
lua:
finally { Jedis jedis = RedisUtils.getJedis(); String script = "if redis.call('get', KEYS[1]) == ARGV[1]"+"then " +"return redis.call('del', KEYS[1])"+"else "+ " return 0 " + "end"; try{ Object result = jedis.eval(script, Collections.singletonList(REDIS_LOCK_KEY), Collections.singletonList(value)); if ("1".equals(result.toString())){ System.out.println("------del REDIS_LOCK_KEY success"); }else { System.out.println("------del REDIS_LOCK_KEY error"); } }finally { if (null != jedis){ jedis.close(); } } }
7.解决redisLock过期时间小于业务执行时间的问题:Redis分布式锁如何续期?
8.redis异步复制造成的锁丢失
比如:主节点没来的及把刚刚set进来这条数据给从节点,就挂了,从机上位主节点后没有原主节点的存根。
redi :AP 。
zookeeper:CP,没有redis集群异步丢失的问题,保证主从数据一致性。当时牺牲了高可用性能
★★终极解决方案:redisson+超高并发解锁判断是否为本线程的锁
redis集群环境下,我们自己写的也不OK, 直接上RedLock之Redisson落地实现:
redisson配置类:
@Configuration public class RedisConfig { @Value("${spring.redis.host}") private String redisHost; @Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){ RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setConnectionFactory(connectionFactory); return redisTemplate; } @Bean public Redisson redisson(){ Config config = new Config(); config.useSingleServer().setAddress("redis://"+redisHost+":6379").setDatabase(0); return (Redisson) Redisson.create(config); } }
★★★真正的方案:
避免出现"试图释放的锁不属于当前线程"的问题
@RestController public class GoodController { public static final String REDIS_LOCK_KEY = "lockhhf"; @Autowired private StringRedisTemplate stringRedisTemplate; @Value("${server.port}") private String serverPort; @Autowired private Redisson redisson; @GetMapping("/buy_goods") public String buy_Goods(){ String value = UUID.randomUUID().toString()+Thread.currentThread().getName(); RLock redissonLock = redisson.getLock(REDIS_LOCK_KEY); ★★★: redissonLock.lock(); try{ String result = stringRedisTemplate.opsForValue().get("goods:001"); int goodsNumber = result == null ? 0 : Integer.parseInt(result); if (goodsNumber > 0){ int realNumber = goodsNumber - 1; stringRedisTemplate.opsForValue().set("goods:001",realNumber + ""); System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort); return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort; }else { System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort); } return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort; }finally { ★★★:还在持有锁的状态,并且是当前线程持有的锁再解锁 if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){ ★★★: redissonLock.unlock(); } } } }
浙公网安备 33010602011771号