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();
template
.setKeySerializer(stringRedisSerializer
);
template
.setHashKeySerializer(stringRedisSerializer
);
template
.setValueSerializer(jackson2JsonRedisSerializer
);
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();
}
public boolean expire(String key
, long time
) {
if (checkKey(key
)) {
return redisTemplate
.expire(key
, time
, TimeUnit
.SECONDS
);
}
return false;
}
public long getExpire(String key
) {
if (checkKey(key
)) {
return redisTemplate
.getExpire(key
, TimeUnit
.SECONDS
);
}
return 0;
}
public boolean hasKey(String key
) {
if (checkKey(key
)) {
return redisTemplate
.hasKey(key
);
}
return false;
}
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
));
}
}
}
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
));
}
}
}
public<T> T
get(String key
) {
Object value
= null
;
if (checkKey(key
)) {
value
= redisTemplate
.opsForValue().get(key
);
}
return (T
)value
;
}
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;
}
public boolean set(String key
, Object value
, long time
) {
if (checkKey(key
)) {
redisTemplate
.opsForValue().set(key
, value
, time
, TimeUnit
.SECONDS
);
return true;
}
return false;
}
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;
}
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;
}
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
;
}
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;
}
public<k, v> Map
<k, v>hmget(String key
) {
if (checkKey(key
)) {
return (Map
<k, v>)redisTemplate
.opsForHash().entries(key
);
}
return null
;
}
public boolean hmset(String key
, Map
<?, ?> value
) {
if (checkKey(key
)) {
redisTemplate
.opsForHash().putAll(key
, value
);
return true;
}
return false;
}
public boolean hdel(String key
, Object
...item
) {
if (checkKey(key
)) {
if (null
!= item
) {
redisTemplate
.opsForHash().delete(key
, item
);
return true;
}
}
return false;
}
public boolean hHasKey(String key
, Object item
) {
if (checkKey(key
)) {
if (null
!= item
) {
return redisTemplate
.opsForHash().hasKey(key
, item
);
}
}
return false;
}
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;
}
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
);
}