Redis是一个开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。它支持数据结构,例如字符串,哈希,列表,集合,带范围查询的排序集合,位图,超级日志,带有半径查询和流的地理空间索引。Redis具有内置的复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供了高可用性。
分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。
首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
互斥性。在任意时刻,只有一个客户端能持有锁。不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。如需解决乱码问题需要在启动类上加入,全局配置
@Autowired private RedisTemplate redisTemplate; @Bean public RedisTemplate<String, Object> stringSerializerRedisTemplate() { RedisSerializer<String> stringSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringSerializer); redisTemplate.setValueSerializer(stringSerializer); redisTemplate.setHashKeySerializer(stringSerializer); redisTemplate.setHashValueSerializer(stringSerializer); return redisTemplate; }要实现加锁可以根据redis中的setnx命令实现,该命令可以确保只有一个key。
@Autowired private RedisTemplate redisTemplate; //线程唯一id String uuid = UUID.randomUUID().toString(); /** * 加锁,setIfAbsent=setnx命令 * 如果redis已经有该key后返回false,加锁失败 */ Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(String.valueOf(orderRequest.getSchedulerId()), uuid); /** *如果加锁失败后进入循环等待 */ while(!aBoolean){ TimeUnit.SECONDS.sleep(1); }uuid : 这里会随机生成一个id,为确保加锁和解锁的一致性,一个线程加锁后必须有他解锁,解锁时利用id做判断。解决误删锁。
锁超时:如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,该资源将会被永远占用,其他线程将无法访问
可以给锁添加失效时间(expire方法) redisTemplate.expire(String.valueOf(scheduleId).concat(":").concat(seatArrays[i]),20, TimeUnit.SECONDS);加锁setnx和锁超时expire两个命令未非原子性操作,当执行加锁setnx后,若因网络或客户端问题锁超时expire命令未成功执行时,锁将无法被释放
因此需要调用setIfAbsent()可以同时设置的失效时间,从而解决程序出错二导致的无法释放锁的死锁问题。
redisTemplate.opsForValue().setIfAbsent(String.valueOf(orderRequest.getSchedulerId()),uuid,10,TimeUnit.SECONDS);解锁使用del命令,删除掉锁。
/** *判断uuid和锁的值是否一致, */ if(uuid.equals(redisTemplate.opsForValue().get(String.valueOf(orderRequest.getSchedulerId())))){ //释放锁 redisTemplate.delete(String.valueOf(orderRequest.getSchedulerId())); }