背景
基于 redis 实现。
代码
package xxx.trade.util;
import xxx.core.exception.BizException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import redis.clients.jedis.JedisCluster;
import java.util.Collections;
public class JedisUtil {
private static final Logger LOGGER = LoggerFactory
.getLogger(JedisUtil.class);
private static JedisCluster jedisCluster;
private static final String PREFIX="xxx-callback:";
public JedisUtil() {// do nothing}
public static boolean lock(String key , String value , String nxx, Long lockExpireTimeOut) {if (StringUtils.isBlank(key)) {throw new BizException("key must not null!");
} else {LOGGER.info("JedisTemplate:get cache key={},value={}", key, value);
String result = jedisCluster.set(PREFIX+key ,value,nxx,"EX",lockExpireTimeOut);
if ("OK".equals(result)) {return true;}
return false;
}
}
public static boolean unlock(String key, String value) {
try {String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedisCluster.eval(luaScript, Collections.singletonList(PREFIX + key),
Collections.singletonList(value));
if (!"0".equals(result.toString())) {return true;}
} catch (Exception ex) {LOGGER.error("unlock error");
}
return false;
}
static {ApplicationContext context = new ClassPathXmlApplicationContext(new String[]
{"conf/springconf/redis/redis-spring-context.xml"});
jedisCluster = (JedisCluster)context.getBean("jedisClusterConfigA");
}
}
应用
次要是两步
1. 获取锁
2. 开释锁
代码
try{
获取锁;
解决业务;
} finally{开释锁;}
外围
是基于 redis 的 set 命令。
超时
为什么要设置超时?因为怕断电这种状况,导致获取锁之后,始终没有开释,导致其余的服务都获取不了锁。
断电之后,开释锁的代码,就没执行。
lua
为什么要用 Lua,因为是 lua 脚本原子操作。
而删除的时候,蕴含两步
1. 先读 // 先测验 redis 是否有数据
2. 后写 // 如果没有,就删除数据
所以,开释锁的代码应用了 lua 脚本来确保两步操作的原子性。