引论:Redis可以非常好地为各个微服务引用提供一个公共的数据交换空间,但是多个客户端(微服务应用)同时访问一个公共数据时,难免会相互竞争导致混乱。
为了避免这一种情况发生,程序在访问数据之前先获取一个全局锁,以确保该数据在这一段时间内只允许有一个应用在操作,当操作完成后在释放锁.
Redis的setnx命令天生适合用来实现锁的功能,这个命令只有在键不存在的情况下才能为键设置。获取锁之后,其他程序在设置值就会失败,即获取不到锁。获取锁失败,只需要不断地尝试获取锁,直到成功获取锁,或者到设置的超时时间为止。另外为了防止死锁,即某个程序获取锁之后,程序出错而没有释放,其他程序无法获取锁,从而导致整个分布式系统无法获取锁以至于引起一系列问题,甚至导致系统无法正常运行。这时需要给锁设置一超时时间,即setex命令,锁超时后,其他程序就可以获取锁了.
全局锁代码如下:
package org.jy.data.yh.bigdata.platform.service; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * redis实现全局锁 */ @Component public class RedisDistributedLockHandler { public final static long LOCK_EXPIRE = 30 * 1000L; public final static long LOCK_TRY_INTERVAL = 30L; public final static long LOCK_TRY_TIMEOUT= 20 *1000L; @Autowired private RedisTemplate<String,Object> redisTemplate; /** * 处理锁的逻辑,首先根据锁的Key值判断是否存在,如果不存在则返回true, * 并设置一个带有失效性的锁;当检测到锁存在时表示当前时间段内已经加锁,无法继续操作 * 等待一端时间后再次判断key是否存在,在规定的时间内检测到锁一直存在,则直接返回false, * 表示当时资源已被锁住. * @param key * @param value * @return */ public boolean getLock(String key,String value){ try{ if(StringUtils.isEmpty(key) || StringUtils.isEmpty(value)){ return false; } long startTime = System.currentTimeMillis(); do { if (!redisTemplate.hasKey(key)) { redisTemplate.opsForValue().set(key, value, LOCK_EXPIRE, TimeUnit.MICROSECONDS); return true; } if (System.currentTimeMillis() - startTime > LOCK_TRY_TIMEOUT) { return false; } Thread.sleep(LOCK_TRY_INTERVAL); }while(redisTemplate.hasKey(key)); } catch (InterruptedException e) { e.printStackTrace(); return false; } return false; } /** * 显示释放掉指定的锁资源 * @param key */ public void releaseLock(String key){ if (!StringUtils.isEmpty(key)){ redisTemplate.delete(key); } } }
测试代码如下:
package org.jy.data.yh.bigdata.platform.controller; import org.jy.data.yh.bigdata.platform.service.RedisDistributedLockHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class RedisDistributedLockController { @Autowired RedisDistributedLockHandler redisDistributedLockHandler; @RequestMapping("/getRedisLock") public String testLockRelease() throws InterruptedException { StringBuilder sb = new StringBuilder(); // 初次尝试并未锁住,将获得锁 String result01 = redisDistributedLockHandler.getLock("key","value") ? "获得到锁" : "被锁住"; System.out.println(sb.append(result01).toString()); sb.append("-->"); // 再次尝试发现已经得到锁 String result02 = redisDistributedLockHandler.getLock("key","value") ? "获得到锁":"被锁住"; System.out.println(sb.append(result02).toString()); sb.append("-->"); // 模拟锁超时 Thread.sleep(RedisDistributedLockHandler.LOCK_EXPIRE); // 再次请求锁已超时,将再次获得锁 String result03 = redisDistributedLockHandler.getLock("key","value") ? "获得到锁" : "被锁住"; System.out.println(sb.append(result03).toString()); // 手动是否锁 redisDistributedLockHandler.releaseLock("key"); return sb.toString(); } }
http://localhost:8811/getRedisLock 查看锁经历的过程
查看锁经历的阶段:
获得到锁-->被锁住-->获得到锁