使用redis调用lua脚本实现原子性操作
如果对redis提供的指令集不满足 可以通过lua脚本执行自定义操作来保证执行的原子性,比如实现将匹配 key 和删除 key 合并在一起执行等。
Redis 服务器会单线程原子性执行 lua 脚本,保证 lua 脚本在处理的过程中不会被任意其它请求打断。
public class RedisLock {
private JedisTemplate jedisTemplate = SpringContextHolder.getBean(JedisTemplate.class);
private static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
private final static String REDIS_PREFIX = "redis_prefix_";
private final static String NX = "NX";
private final static String PX = "PX";
private final static String OK = "OK";
/**
* 超时时间
* 默认1秒
*/
private Integer timeout = 1_000;
private String lockName;
/**
* 记录获取锁的线程标识
* 防止删除其他线程的key
*/
private ThreadLocal<String> threadId = new ThreadLocal<>();
public RedisLock(String lockName) {
this.lockName = REDIS_PREFIX + lockName;
}
public RedisLock(String lockName, Integer lockTime) {
this.lockName = REDIS_PREFIX + lockName;
this.timeout = lockTime;
}
public boolean tryLock() {
threadId.set(UUID.randomUUID().toString());
String result = jedisTemplate.set(lockName, threadId.get(),NX,PX,timeout);
logger.info("锁的key:"+ lockName +", 设置锁的结果:" + result);
return OK.equals(result);
}
/**
* 避免删除非自己获取得到的锁
* @return
*/
public boolean releaseLock() {
String threadIdStr = threadId.get();
jedisTemplate.compareAndDel(lockName, threadIdStr);
threadId.remove();
return true;
}
}
/**
*
* @param key
* @param value
* @param nxxx NX
* @param expx EX-秒 PX-毫秒
* @param time
* @return
*/
public String set(final String key, final String value, final String nxxx, final String expx,
final int time) {
return execute(new JedisAction<String>() {
@Override
public String action(Jedis jedis) {
return jedis.set(key, value,nxxx,expx,time);
}
});
}
/**
* 比较删除
* 保证原子性
* @param key
* @param value
* @return
*/
public boolean compareAndDel(final String key, final String value) {
return execute(new JedisAction<Boolean>() {
String lua = " local val = redis.call('get', KEYS[1])\n" +
" if val == ARGV[1] then\n" +
" redis.call('del', KEYS[1])\n" +
" return true\n" +
" end";
/**
lua 脚本保证一系列操作的原子性
local val = redis.call('get', KEYS[1])
if val == ARGV[1] then
redis.call('del', KEYS[1])
return true
end
*/
@Override
public Boolean action(Jedis jedis) {
jedis.evalsha(jedis.scriptLoad(lua), Lists.newArrayList(key), Lists.newArrayList(value));
return true;
}
});
}
具体调用方法:
RedisLock redisLock = new RedisLock(key, 5000);
try {
if (!redisLock.tryLock()) {
throw new BusinessException(ReturnCode.ACTION_FREQ);
}
} catch (Exception e) {
logger.error("error", e);
return buildResponse(ReturnCode.UNDIFINED_ERROR, null);
} finally {
//不要等他过期
redisLock.releaseLock();
}
来源:CSDN
作者:Nicolos_Z
链接:https://blog.csdn.net/USTC_Zn/article/details/103786061