Redis 实现分布式锁
2021-06-23 本文已影响0人
小玉1991
工作中需要分布式锁。现在总结一下实现过程。
1、添加配置,引入依赖
1、添加配置
#redis相关配置
spring.redis.host=10.9.198.84
spring.redis.port=6380
spring.redis.database=0
spring.redis.jedis.pool.max-idle=200
spring.redis.jedis.pool.max-wait=5000
2、添加pom依赖
<properties>
<jedis.version>3.1.0</jedis.version>
<spring-data-redis.version>1.8.0.RELEASE</spring-data-redis.version>
</properties>
<!-- redis begin -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>${spring-data-redis.version}</version>
</dependency>
<!-- redis end -->
2、创建lua脚本,并构建相关工具类
release_distributed_lock.lua
local lockKey = KEYS[1]
local value = KEYS[2]
if redis.call('get', lockKey) == value then
return redis.call('del', KEYS[1])
else
return 0
end
try_get_distributed_lock.lua
local lockKey = KEYS[1]
local value = KEYS[2]
local expireTime = KEYS[3]
return redis.call("set", lockKey, value, "px", expireTime, 'NX');
request_rate_limiter.lua
local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]
--redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)
local rate = tonumber(KEYS[3])
local capacity = tonumber(KEYS[4])
local now = tonumber(KEYS[5])
local requested = tonumber(KEYS[6])
local fill_time = capacity/rate
local ttl = math.floor(fill_time*2)
--redis.log(redis.LOG_WARNING, "rate " .. ARGV[1])
--redis.log(redis.LOG_WARNING, "capacity " .. ARGV[2])
--redis.log(redis.LOG_WARNING, "now " .. ARGV[3])
--redis.log(redis.LOG_WARNING, "requested " .. ARGV[4])
--redis.log(redis.LOG_WARNING, "filltime " .. fill_time)
--redis.log(redis.LOG_WARNING, "ttl " .. ttl)
local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
last_tokens = capacity
end
--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)
local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
last_refreshed = 0
end
--redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed)
local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
local allowed_num = 0
if allowed then
new_tokens = filled_tokens - requested
allowed_num = 1
end
--redis.log(redis.LOG_WARNING, "delta " .. delta)
--redis.log(redis.LOG_WARNING, "filled_tokens " .. filled_tokens)
--redis.log(redis.LOG_WARNING, "allowed_num " .. allowed_num)
--redis.log(redis.LOG_WARNING, "new_tokens " .. new_tokens)
redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)
return { allowed_num, new_tokens }
工具类:
LuaHelper:
@Slf4j
public class LuaHelper {
public final static String DISTRIBUTE_LOCK_GET = "get distributed lock";
public final static String DISTRIBUTE_LOCK_RELEASE = "release distributed lock";
public final static String REQUEST_RATE_LIMITER = "request rate limiter";
public class LuaScript {
public String script;
public String sha1;
}
Map<String, LuaScript> map;
private static LuaHelper instance = new LuaHelper();
public static LuaHelper getInstance() {
return instance;
}
private LuaHelper() {
map = new HashMap<>();
init();
}
void init() {
log.info("LuaHelper init");
// 分布式锁
addScriptFromFile(DISTRIBUTE_LOCK_GET, "lua/try_get_distributed_lock.lua");
addScriptFromFile(DISTRIBUTE_LOCK_RELEASE, "lua/release_distributed_lock.lua");
addScriptFromFile(REQUEST_RATE_LIMITER, "lua/request_rate_limiter.lua");
}
void addScript(String key, String script) {
LuaScript script1 = new LuaScript();
script1.script = script;
script1.sha1 = DigestUtils.sha1Hex(script);
map.put(key, script1);
}
void addScriptFromFile(String key, String file_path) {
ClassPathResource res = new ClassPathResource(file_path);
try {
byte[] bytes = FileCopyUtils.copyToByteArray(res.getInputStream());
String data = new String(bytes, StandardCharsets.UTF_8);
addScript(key, data);
} catch (IOException e) {
e.printStackTrace();
log.error(" load script error " + file_path);
}
}
public LuaScript getScript(String key) {
if (!map.containsKey(key)) {
return null;
}
return map.get(key);
}
public static Object runScript(RedisTemplate<String, String> redisTemplate, final String scriptKey, final String... params){
long start = System.currentTimeMillis();
Object object = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
LuaScript script = LuaHelper.getInstance().getScript(scriptKey);
assert script != null;
Jedis jedis = (Jedis) redisConnection.getNativeConnection();
try {
return jedis.evalsha(script.sha1, params.length, params);
} catch (Exception ex) {
log.info("params:" + params.length);
for(int i = 0; i < params.length; i++){
log.info(i + " " + params[i]);
}
return jedis.eval(script.script, params.length, params);
}
}
});
// log.info( "lua script: " + scriptKey + " "+ (System.currentTimeMillis() - start));
return object;
}
}
RedisService:
@Service
public class RedisService {
@Autowired
RedisTemplate<String, String> redisTemplate;
/**
* 尝试获取分布式锁
*
* @param lockKey
* @param value
* @param expireTime 过期时间 毫秒
* @return
*/
public boolean getDistributedLock(String lockKey, String value, Integer expireTime) {
String key = getLockKey(lockKey);
Object obj = LuaHelper.runScript(redisTemplate, LuaHelper.DISTRIBUTE_LOCK_GET, key, value, expireTime + "");
return RedisCons.LOCK_SUCCESS.equals(obj);
}
private String getLockKey(String lockKey) {
return RedisCons.KEY_PREFIX + RedisCons.LOCK_KEY_PREFIX + lockKey;
}
private String getCommonKey(String lockKey) {
return RedisCons.KEY_PREFIX + lockKey;
}
/**
* 尝试释放分布式锁
*
* @param lockKey
* @param value
* @return
*/
public boolean releaseDistributedLock(String lockKey, String value) {
String key = getLockKey(lockKey);
Object obj = LuaHelper.runScript(redisTemplate, LuaHelper.DISTRIBUTE_LOCK_RELEASE, key, value);
return RedisCons.RELEASE_SUCCESS.equals(obj);
}
public Long incrMachineId() {
String key = RedisCons.KEY_PREFIX + RedisCons.KEY_INCR_MACHINE_ID;
Long increment = redisTemplate.opsForValue().increment(key, 1);
if (increment >= Integer.MAX_VALUE) {
redisTemplate.opsForValue().set(key, "0");
}
return increment;
}
}
配置多个redis源
#lock redis setting
spring.redis.lock.host=10.9.188.145
spring.redis.lock.port=6380
spring.redis.lock.database=0
spring.redis.lock.jedis.pool.max-total=500
spring.redis.lock.jedis.pool.max-idle=200
spring.redis.lock.jedis.pool.max-wait=5000
======================================================================
@Configuration
public class RedisLockConfig {
@Value("${spring.redis.lock.host}")
private String host;
@Value("${spring.redis.lock.port}")
private Integer port;
// @Value("${spring.redis.lock.pwd}")
private String password;
@Value("${spring.redis.lock.jedis.pool.max-idle}")
private Integer maxIdle;
@Value("${spring.redis.lock.jedis.pool.max-total}")
private Integer maxTotal;
@Value("${spring.redis.lock.jedis.pool.max-wait}")
private Integer maxWaitMillis;
@Value("${spring.redis.lock.database}")
private Integer dataBase;
//配置工厂
public RedisConnectionFactory connectionFactory(String host, int port, String password, int maxIdle,
int maxTotal, long maxWaitMillis, int index) {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.setHostName(host);
jedisConnectionFactory.setPort(port);
if (!StringUtils.isEmpty(password)) {
jedisConnectionFactory.setPassword(password);
}
if (index != 0) {
jedisConnectionFactory.setDatabase(index);
}
jedisConnectionFactory.setPoolConfig(poolConfig(maxIdle, maxTotal, maxWaitMillis, false));
jedisConnectionFactory.afterPropertiesSet();
return jedisConnectionFactory;
}
//连接池配置
public JedisPoolConfig poolConfig(int maxIdle, int maxTotal, long maxWaitMillis, boolean testOnBorrow) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMaxTotal(maxTotal);
poolConfig.setMaxWaitMillis(maxWaitMillis);
poolConfig.setTestOnBorrow(testOnBorrow);
return poolConfig;
}
@Bean(name = "lockRedisTemplate")
public StringRedisTemplate lockRedisTemplate() {
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(
connectionFactory(host, port, password, maxIdle, maxTotal, maxWaitMillis, dataBase));
//设置序列化Key的实例化对象
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置序列化Value的实例化对象
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
其他代码,打包上传。