在分布式系统或者前端与后端分离的情况下,如何通过锁来保持数据的排他性访问。比如在前后端分离的系统中,客户端需要领取优惠券,后端通过任务可以领取优惠券,在这种情况下,如何保证领取优惠券的数据正确。一种办法是把领取优惠券的功能抽取一个模块,所有的领取都从这个接口访问;第二种方法基于分布式锁实现。本文简要介绍基于redis实现的分布式锁的方案。
一、基于redis实现
1、引入依赖包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
核心方法基于jedis的
/**
* Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
* GB).
* @param key
* @param value
* @param nxxx NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the key
* if it already exist.
* @param expx EX|PX, expire time units: EX = seconds; PX = milliseconds
* @param time expire time in the units of <code>expx</code>
* @return Status code reply
*/
public String set(final String key, final String value, final String nxxx, final String expx,
final long time)
2、获取锁代码
public boolean getLock(String lockKey, String value, int expireTime){
boolean ret = false;
try{
do{
ret = setnxex(lockKey, value, expireTime);
Thread.sleep(500);
}while(!ret);
}catch(Exception e){
}
return ret;
}
/**
* Description: 当前key值不存在则缓存key值 否则不缓存
*
* @param key
* @param value
* @param ex 失效时间
* @param nx 当前key值是否存在
* @return
*/
public boolean setnxex(String key, String value, long expireTime) {
Jedis jedis = this.getConnect();
try {
String statusCodeReply = jedis.set(key, value, "nx", "ex", expireTime);
if (statusCodeReply.equals("OK")) {
return true;
}
} catch (Exception e) {
log.error(SET_CACHE_ERROR_INFO + e.getMessage());
log.error("set#Key:" + key + ",Value:" + value + ",expire:" + expireTime);
return false;
} finally {
jedis.close();
}
return false;
}
通过key缓存数据成功,表示成功获取锁。并设置过期时间,防止异常或忘了释放锁。
3、释放锁
释放锁即是删除缓存key。
public boolean releaseLock(String lockKey, String value){
Long RELEASE_SUCCESS = 1L;
Jedis jedis = this.getConnect();
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(value));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
script 是lua脚本,意思是当value与缓存key的值相等时删除key,防止错删。
二、基于redisson实现
1、引入依赖包
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.3.2</version>
</dependency>
2、redisson初始化
public void init(){
Config config = new Config();
config.useSingleServer().setAddress("127.0.0.1:6379");
config.useSingleServer().setPassword("redis_pass");
redisson = Redisson.create(config);
}
3、获取锁
public boolean getLock(String name){
boolean ret = false;
RLock lock = redisson.getLock(name);
try {
ret = lock.tryLock(12000, 5000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ret;
}
4、释放锁
public void releaseLock(String name){
RLock lock = redisson.getLock(name);
lock.unlock();
}