引论: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();
}
}
查看锁经历的阶段:
获得到锁-->被锁住-->获得到锁