由于工作项目采用微服务架构, 每个应用分布式部署, 所以需要开发一个分布式锁, 由于所有的业务系统共用一个库, 所以之前已经写好一个基于数据库的分布式锁实现,但是效率上有欠缺, 所以需要开发一个基于redis的分布式锁,对于基于数据库的分布式锁和基于redis的分布式锁和基于zookeeper的分布式锁的优缺点,可自行在网上查阅, 这里只想讲一下redis与springboot封装的redistemplate集成的具体实现。
1. 引入相关依赖 , Maven 方式
org.springframework.session spring-session-data-redis true --这个可选, 由于我这个jar包属于被其他系统引用的底层jar包, 是否使用redis可由具体的业务系统决定redis.clients jedis 2.9.0 true com.alibaba fastjson 1.2.47 true
2. 加锁代码段
/**
* redis 加锁
* LOCK_SUCCESS = "OK";
* SET_IF_NOT_EXIST = "NX";
* SET_WITH_EXPIRE_TIME = "PX";
*
* String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); 其实如果使用jedis原生代码的话, 是执行这段语句
*
* @param lockName
* @param uniqueId
*/
public void lock(String lockName, String uniqueId) {
String lockKey = LOCK_PREFIX+lockName;
String successMsg = StrUtil.format("{}-{}加锁成功!",lockName,uniqueId);
String failMsg = StrUtil.format("{}-{}加锁失败[Redis]!",lockName,uniqueId);
int expireTime = PropertyUtil.getInt("lock.expire.time");
Boolean result = redisTemplate.execute(new RedisCallback() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.set(lockKey.getBytes(),uniqueId.getBytes(), Expiration.from(expireTime, TimeUnit.SECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT);
}
});
printResult(result, successMsg, failMsg);
}
3. 解锁代码段
/**
* redis 解锁:eval函数在redis集群环境中不支持, 具体查看spring源码
* @See JedisClusterScriptingCommands
*
* Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
* @param lockName
* @param uniqueId
*/
public void unlock(String lockName, String uniqueId) {
String lockKey = LOCK_PREFIX+lockName;
String successMsg = StrUtil.format("{}-{}解锁成功[Redis]!",lockName,uniqueId);
String failMsg = StrUtil.format("{}-{}解锁失败[Redis]!",lockName,uniqueId);
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript redisScript = new DefaultRedisScript(script,Boolean.class);
Boolean result = redisTemplate.execute(redisScript, new StringRedisSerializer(), new FastJsonRedisSerializer(Boolean.class), Collections.singletonList(lockKey),uniqueId);
printResult(result, successMsg, failMsg);
}
4. 总结
之所以使用redisTemplate, 是因为不需要关心redis是集群的还是单体的。如果是集群环境, 需要调用JedisCluster来对redis进行操作, 单体的则是使用Jedis来对Redis进行操作。spring 定义了RedisConnection接口来封装了,源码如下: