1 什么是分布式锁
概念
锁可以看成是多线程情况下访问共享资源的一种线程同步机制。这是对于单进程应用而言的,即所有线程都在同一个JVM进程里的时候,使用Java语言提供的锁机制可以起到对共享资源进行同步的作用。如果分布式环境下多个不同线程需要对共享资源进行同步,那么用Java的锁机制就无法实现了,这个时候就必须借助分布式锁来解决分布式环境下共享资源的同步问题。分布式锁有很多种解决方案,今天我们要讲的是怎么使用缓存数据库Redis来实现分布式锁。
场景
重复支付问题,支付服务集群部署,分别在A,B服务部署,然后针对同一个订单,有两个请求过来,分别落在A,B两个服务上,这时候传统的事务控制就失效了,很有可能发生订单重复支付问题,这时候可以采用redis的分布式锁解决,单节点部署的时候,首先A去redis上获取锁,发现没有,添加并获取,采用的key是订单的编号,它获取到锁以后,正准备执行代码,这时候B也去redis上获取锁,利用相同的key(订单编号),发现锁已经被人获取了,然后它的这次请求就取消了,然后说A,A拿到锁以后,执行完代码就释放锁。
2 引入依赖
<properties>
<spring-boot-version>2.0.2.RELEASE</spring-boot-version>
<java.version>1.8</java.version>
<jackson.version>2.9.9</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
3 Redis配置
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
4 Redis的工具类
@Component
public class RedisService {
@Autowired
private RedisTemplate<String, Object> 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();
}
/**
* 设置缓存有效期
* @param key
* @param time
* @return
*/
public boolean expire(String key, long time) {
if (checkKey(key)) {
return redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return false;
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
if (checkKey(key)) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
return 0;
}
/**
* 判断key的存在性
* @param key
* @return
*/
public boolean hasKey(String key) {
if (checkKey(key)) {
return redisTemplate.hasKey(key);
}
return false;
}
/**
* 删除缓存
* @param key
*/
public void delele(String ...key) {
if (null != key && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
}
else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 删除缓存
* @param key
*/
public void delete(String ...key) {
if (null != key && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
}
else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 获取缓存
* @param key
* @return
*/
public<T> T get(String key) {
Object value = null;
if (checkKey(key)) {
value = redisTemplate.opsForValue().get(key);
}
return (T)value;
}
/**
* 设置缓存
* @param key
* @param value
* @return
*/
public boolean set(String key, Object value) {
if (checkKey(key)) {
redisTemplate.opsForValue().set(key, value);
return true;
}
return false;
}
public boolean expireAt(String key, Date date) {
if (checkKey(key)) {
return redisTemplate.expireAt(key, date);
}
return false;
}
/**
* 设置缓存同时设置有效期
* @param key
* @param value
* @param time
* @return
*/
public boolean set(String key, Object value, long time) {
if (checkKey(key)) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
return true;
}
return false;
}
/**
* 递增
* @param key
* @param delta 递增步长
* @return
*/
public long incr(String key, long delta) {
if (checkKey(key)) {
if (delta <= 0) {
throw new RedisRuntimeException("delta must be greater than zero");
}
return redisTemplate.opsForValue().increment(key, delta);
}
return 0;
}
/**
* 递减
* @param key
* @param delta 递减步长
* @return
*/
public long decr(String key, long delta) {
if (checkKey(key)) {
if (delta <= 0) {
throw new RedisRuntimeException("delta must be greater than zero");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
return 0;
}
/**
* 获取map中的某个item数据
* @param key
* @param item
* @return
*/
public<T> T hget(String key, String item) {
if (checkKey(key)) {
if (null == item || item.length() == 0) {
throw new RedisRuntimeException("param item is empty");
}
return (T)redisTemplate.opsForHash().get(key, item);
}
return null;
}
/**
* 设置map中的某个item数据
* @param key
* @param item
* @param value
* @return
*/
public boolean hset(String key, String item, Object value) {
if (checkKey(key)) {
if (null == item || item.length() == 0) {
throw new RedisRuntimeException("param item is empty");
}
redisTemplate.opsForHash().put(key, item, value);
return true;
}
return false;
}
/**
* 获取map
* @param key
* @return
*/
public<k, v> Map<k, v>hmget(String key) {
if (checkKey(key)) {
return (Map<k, v>)redisTemplate.opsForHash().entries(key);
}
return null;
}
/**
* 设置map
* @param key
* @param value
* @return
*/
public boolean hmset(String key, Map<?, ?> value) {
if (checkKey(key)) {
redisTemplate.opsForHash().putAll(key, value);
return true;
}
return false;
}
/**
* 删除hash表中的值
* @param key
* @param item
* @return
*/
public boolean hdel(String key, Object ...item) {
if (checkKey(key)) {
if (null != item) {
redisTemplate.opsForHash().delete(key, item);
return true;
}
}
return false;
}
/**
* 判断hash表中item是否存在
* @param key
* @param item
* @return
*/
public boolean hHasKey(String key, Object item) {
if (checkKey(key)) {
if (null != item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
}
return false;
}
/**
* 获取分布式锁
* @param key
* @param value
* @param time 单位秒
* @return
*/
public boolean setLock(String key, String value, long time) {
if (checkKey(key)) {
RedisCallback<Boolean> callback = (connection) -> connection.set(key.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8")), Expiration.seconds(time), RedisStringCommands.SetOption.SET_IF_ABSENT);
Boolean res = redisTemplate.execute(callback);
return res == null? false:res;
}
return false;
}
/**
* 释放分布式锁
* @param key
* @param value
* @return
*/
public boolean releaseLock(String key,String value) {
RedisCallback<Boolean> callback = (connection) -> connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN, 1, key.getBytes(Charset.forName("UTF-8")), value.getBytes(Charset.forName("UTF-8")));
Boolean res = redisTemplate.execute(callback);
return res == null? false:res;
}
public void publish(String channel, Object msg) {
redisTemplate.convertAndSend(channel, msg);
}
private boolean checkKey(String key) {
if (null == key || key.length() == 0) {
throw new RedisRuntimeException("key is empty");
}
return true;
}
}
5 redis分布式锁的使用
try {
boolean hasLock = redisService.setLock(lockKey, lockKey, 120);
if (hasLock) {
//执行逻辑代码
.....
}
} catch (
BusinessException e) {
throw new BusinessException(e.getErrCode(), e.getErrMsg());
} catch (
Exception e) {
throw new BusinessException("", e);
} finally {
redisService.releaseLock(lockKey, lockKey);
}