单机环境redis+lua实现分布式锁没有问题
生产环境是redis集群的,报错:EvalSha is not supported in cluster environment
解决:
package com.zuma.coupon.util; import com.zuma.common.utils.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisCluster; import redis.clients.jedis.JedisCommands; import java.util.ArrayList; import java.util.List; import java.util.UUID; /** * @author wangjiangtao */ @Component public class RedisDistributedLock { @Autowired(required = false) private StringRedisTemplate redisTemplate; public static final String UNLOCK_LUA; static { StringBuilder sb = new StringBuilder(); sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] "); sb.append("then "); sb.append(" return redis.call(\"del\",KEYS[1]) "); sb.append("else "); sb.append(" return 0 "); sb.append("end "); UNLOCK_LUA = sb.toString(); } private final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class); public boolean lock(String key, Long expire) { try { RedisCallback<String> callback = (connection) -> { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); String uuid = UUID.randomUUID().toString(); logger.info(uuid); return commands.set(key, uuid, "NX", "PX", expire); }; String result = redisTemplate.execute(callback); return !StringUtils.isEmpty(result); } catch (Exception e) { logger.error("set redis occured an exception", e); } return false; } public String get(String key) { try { RedisCallback<String> callback = (connection) -> { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); return commands.get(key); }; String result = redisTemplate.execute(callback); return result; } catch (Exception e) { logger.error("get redis occured an exception", e); } return ""; } public boolean releaseLock(String key, String requestId) { try { List<String> keys = new ArrayList<>(); keys.add(key); List<String> args = new ArrayList<>(); args.add(requestId); // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁 // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本 RedisCallback<Long> callback = (connection) -> { Object nativeConnection = connection.getNativeConnection(); // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行 // 集群模式 if (nativeConnection instanceof JedisCluster) { return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args); } // 单机模式 else if (nativeConnection instanceof Jedis) { return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args); } // 设置过期时间 //connection.pexpireAt(key.getBytes(),100L); return 0L; }; Long result = redisTemplate.execute(callback); return result != null && result > 0; } catch (Exception e) { logger.error("release lock occured an exception", e); } finally { } return false; } }
调用:
@GetMapping(value = "/testLock") public void testLock() { if (redisDistributedLock.lock(CouponConstant.REDIS_COUPON_LOCK + "100", 3000L)) { try { System.out.println("111111"); } finally { String requestId = redisDistributedLock.get(CouponConstant.REDIS_COUPON_LOCK + "100"); logger.info("requestId" + requestId); boolean unlock = redisDistributedLock.releaseLock(CouponConstant.REDIS_COUPON_LOCK + "100", requestId); if (unlock) { logger.info("-------解锁成功-----"); } } } }