項(xiàng)目介紹:為公司項(xiàng)目進(jìn)行封裝common包,需要分布式鎖功能的實(shí)現(xiàn)华临,決定寫一個(gè)比較簡(jiǎn)易拿來(lái)就用的包芯杀。
分布式鎖實(shí)現(xiàn)方式通常分為3種:
- 數(shù)據(jù)庫(kù)原生實(shí)現(xiàn)
- redis中setNX
-
zookeeper通過(guò)有序臨時(shí)節(jié)點(diǎn)
以上這3種方式網(wǎng)絡(luò)上的理論知識(shí)有很多,這里只講項(xiàng)目實(shí)現(xiàn)雅潭。
項(xiàng)目結(jié)構(gòu)圖:
1.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--spring boot 版本-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
</parent>
<groupId>com.ict.common</groupId>
<artifactId>Redis-Distribute-Lock</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--Springboot與redis起步依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--AOP切面依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--redisTemplate中jackson序列化依賴-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!--jedis客戶端-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<type>jar</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<phase>none</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.RedisConfig配置類
package com.ict.common.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.*;
import redis.clients.jedis.JedisPoolConfig;
import java.time.Duration;
/**
* @author: DevWenjiang
* Description: Redis配置類
* @date : 2020-06-10 17:34
*/
@Configuration
@EnableAutoConfiguration
public class RedisConfig {
@Value("${spring.redis.defaultExpiration:3600}")
private Long defaultExpiration;
/**
* 創(chuàng)建JedisPoolConfig對(duì)象
*/
@Bean
@ConfigurationProperties(prefix = "spring.redis")
public JedisPoolConfig jedisPoolConfig(){
return new JedisPoolConfig();
}
/**
* 創(chuàng)建JedisConnectionFactory對(duì)象
*/
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.redis")
public JedisConnectionFactory jedisConnectionFactory(){
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
JedisPoolConfig config = jedisPoolConfig();
jedisConnectionFactory.setPoolConfig(config);
return jedisConnectionFactory;
}
@Bean
public RedisTemplate<String,Object> jdkRedisTemplate(){
RedisTemplate<String,Object> jdkRedisTemplate = new RedisTemplate<>();
jdkRedisTemplate.setConnectionFactory(jedisConnectionFactory());
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
jdkRedisTemplate.setHashKeySerializer(stringRedisSerializer);
jdkRedisTemplate.setKeySerializer(stringRedisSerializer);
jdkRedisTemplate.afterPropertiesSet();
return jdkRedisTemplate;
}
/**
* 創(chuàng)建以jackson序列化方式redisTemplate
* @return
*/
@Bean
public RedisTemplate<String,Object> redisTemplate(){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
//設(shè)置對(duì)象mapper
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//設(shè)置key與value序列化方式
redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 創(chuàng)建stringRedisTemplate
*/
@Bean
public StringRedisTemplate stringRedisTemplate(){
return new StringRedisTemplate(jedisConnectionFactory());
}
/**
* 緩存管理器
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//初始化一個(gè)RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//設(shè)置CacheManager的值序列化方式為json序列化
RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair
.fromSerializer(jsonSerializer);
RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(pair);
//設(shè)置默認(rèn)超過(guò)期時(shí)間是30秒
defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
//初始化RedisCacheManager
return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
}
}
3.LockStrategy所類型枚舉
package com.ict.common.enums;
/**
* @author: DevWenjiang
* Description:
* @date : 2020-06-10 17:29
*/
public enum LockStrategy {
//失敗重試
WAIT_RETRY,
//忽略
IGNORE,
//拋出異常
THROWABLE
}
4.RedisDistributeLockAnnotion分布式鎖注解
package com.ict.common.annotion;
import com.ict.common.enums.LockStrategy;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author: DevWenjiang
* Description: redis分布式鎖注解(需要加鎖的方法加入注解)
* @date : 2020-06-10 17:23
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisDistributeLockAnnotion {
@AliasFor("value")
String key() default "";
@AliasFor("key")
String value() default "";
//鎖策略:失敗忽略|重試|拋出異常
LockStrategy LOCK_STRATEGY() default LockStrategy.IGNORE;
/**
* 獲鎖最長(zhǎng)時(shí)間
*/
long maxLockTime() default 30000l;
/**
* 等待重試時(shí)間
*/
long retryTime() default 500l;
/**
* 最大重試次數(shù)
*/
int maxRetryTimes() default 10;
}
5.RedisClient封裝的Redis客戶端操作
package com.ict.common.lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author : DevWenjiang
* Description: Redis客戶端類揭厚,與redis服務(wù)器進(jìn)行數(shù)據(jù)交互類
* @date : 2020/6/10-20:13
*/
@Component
public class RedisClient {
@Autowired()
@Qualifier("redisTemplate")
private RedisTemplate<String, Object> redisTemplate;
@Autowired()
@Qualifier("stringRedisTemplate")
private StringRedisTemplate stringRedisTemplate;
/**
* string字符串set方法
*
* @param key
* @param value
*/
public void set(String key, String value) {
stringRedisTemplate.opsForValue().set(key, value);
}
/**
* string字符串set方法,帶有過(guò)期時(shí)間
*
* @param key
* @param value
* @param expireTime
* @param timeUnit
*/
public void set(String key, String value, Long expireTime, TimeUnit timeUnit) {
stringRedisTemplate.opsForValue().set(key, value, expireTime, timeUnit);
}
/**
* Object對(duì)象set方法
*
* @param key
* @param value
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* Object對(duì)象set方法,帶有過(guò)期時(shí)間
*
* @param key
* @param value
* @param expireTime
* @param timeUnit
*/
public void set(String key, Object value, Long expireTime, TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, expireTime, timeUnit);
}
/**
* setNX方法
*
* @param key
* @param value
* @return
*/
public Boolean setNX(String key, Object value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
/**
* setNX,帶有過(guò)期時(shí)間
* @param key
* @param value
* @param expireTime
* @param timeUnit
* @return
*/
public Boolean setNX(String key, Object value, Long expireTime, TimeUnit timeUnit) {
return redisTemplate.execute(new SessionCallback<Boolean>() {
@Override
public Boolean execute(RedisOperations redisOperations) throws DataAccessException {
//開啟事務(wù)
redisOperations.multi();
redisOperations.opsForValue().setIfAbsent(key, value);
redisOperations.expire(key, expireTime, timeUnit);
List exec = redisOperations.exec();
if (exec == null || exec.isEmpty()) {
return false;
}
return (Boolean) exec.get(0);
}
});
}
/**
* 自增
* @param key
* @param i
* @return
*/
public Long increment(String key, int i) {
return stringRedisTemplate.boundValueOps(key).increment(i);
}
/**
* 獲取key對(duì)應(yīng)值
* @param key
* @return
*/
public String get(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
/**
* Object的
* @param key
* @return
*/
public Object getObj(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 模糊查詢滿足要求的key
* @param pattern
* @return
*/
public Set<String> keys(String pattern){
return redisTemplate.keys(pattern);
}
/**
* 獲得過(guò)期時(shí)間(以秒為單位)
* @param key
* @return
*/
public Long getExpire(String key) {
return stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 設(shè)置過(guò)期時(shí)間
* @param key
* @param timeout
* @param unit
* @return
*/
public Boolean expire(String key,Long timeout,TimeUnit unit){
return redisTemplate.expire(key, timeout, unit);
}
/**
* 刪除key
* @param key
*/
public void delete(String key) {
redisTemplate.delete(key);
}
/**
* 刪除多個(gè)key
* @param keys
*/
public void delete(Set<String> keys){
redisTemplate.delete(keys);
}
/**
* 對(duì)key進(jìn)行hash
* @param key
* @return
*/
public boolean haskey(String key) {
return stringRedisTemplate.hasKey(key);
}
/**
* 右進(jìn)
* @param key
* @param value
* @return
*/
public Long rightPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}
/**
* 左出
* @param key
* @return
*/
public Object leftPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
/**
* 獲取list集合
* @param key
* @return
*/
public List<Object> getList(String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
}
6.RedisDistributeLock分布式具體實(shí)現(xiàn)類
package com.ict.common.lock;
import org.springframework.util.Assert;
import java.util.concurrent.TimeUnit;
/**
* @author : DevWenjiang
* Description: redis分布式鎖實(shí)現(xiàn)類
* @date : 2020/6/10-20:38
*/
public class RedisDistributeLock {
//默認(rèn)最大線程獲取鎖時(shí)間
private static final Long DEFAULT_MAX_LOCK_TIME = 300000l;//30s
//redis客戶端
private RedisClient redisClient;
//key
private String key;
//最大鎖時(shí)間
private Long maxLockTime;
//持有鎖狀態(tài)
private Boolean isLock;
private RedisDistributeLock(RedisClient redisClient, String key, Long maxLockTime) {
Assert.notNull(redisClient, "redisClient不能為空");
Assert.hasText(key, "key不能為空");
this.redisClient = redisClient;
this.key = key;
this.maxLockTime = maxLockTime;
}
/**
* 建造者模式
* @param redisClient
* @param key
* @param maxLockTime
* @return
*/
public static RedisDistributeLock build(RedisClient redisClient,String key,Long maxLockTime){
return new RedisDistributeLock(redisClient,key,maxLockTime);
}
public static RedisDistributeLock build(RedisClient redisClient,String key){
return new RedisDistributeLock(redisClient,key,DEFAULT_MAX_LOCK_TIME);
}
/**
* 獲取鎖
* @return
*/
public Boolean lock(){
Boolean aBoolean = redisClient.setNX(key, "1", maxLockTime, TimeUnit.MILLISECONDS);
if (aBoolean){
isLock = true;
return true;
}
isLock = false;
return false;
}
/**
* 釋放鎖
*/
public void unlock(){
if (isLock){
redisClient.delete(key);
isLock = false;
}
}
}
7.RedisDistributeLockAspect分布式鎖切面增強(qiáng)類
package com.ict.common.aop;
import com.ict.common.annotion.RedisDistributeLockAnnotion;
import com.ict.common.exception.RedisDistributeLockException;
import com.ict.common.lock.RedisClient;
import com.ict.common.lock.RedisDistributeLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
/**
* @author: DevWenjiang
* Description: Redis分布式鎖切面類,加入注解開啟分布式鎖
* @date : 2020-06-11 09:36
*/
@Component
@Aspect
public class RedisDistributeLockAspect {
//日志對(duì)象
private static final Logger log = LoggerFactory.getLogger(RedisDistributeLockAspect.class);
//注入redisClient
@Autowired
private RedisClient redisClient;
//存放重試次數(shù)的ThreadLocal
private static ThreadLocal<Integer> retryTimes = new ThreadLocal<>();
//注解切面寻馏,只要方法上加該注解就會(huì)進(jìn)行切面方法增強(qiáng)
@Pointcut("@annotation(com.ict.common.annotion.RedisDistributeLockAnnotion)")
public void pointCut() {
}
@Around("pointCut()")
public Object aroundMethod(ProceedingJoinPoint pj) throws RedisDistributeLockException {
//獲取方法參數(shù)
Object[] args = pj.getArgs();
//獲取方法簽名,方法名稱
MethodSignature signature = (MethodSignature) pj.getSignature();
//獲取參數(shù)名
String[] parameterNames = signature.getParameterNames();
//存放參數(shù)名:參數(shù)值
HashMap<String, Object> params = new HashMap<>();
if (null != parameterNames && parameterNames.length > 0 && null != args && args.length > 0) {
for (int i = 0; i < parameterNames.length; i++) {
params.put(parameterNames[i], args[i]);
}
}
//MethodSignature signature = (MethodSignature) pj.getSignature();
//獲取方法注解
RedisDistributeLockAnnotion redisDistributeLockAnnotion = signature.getMethod().getAnnotation(RedisDistributeLockAnnotion.class);
//獲取key
String key = redisDistributeLockAnnotion.key();
if (null == key || key.trim().equals("")) {
key = redisDistributeLockAnnotion.value();
}
if (null == key || key.trim().equals("")) {
log.error("key值不能為空");
throw new RedisDistributeLockException("key值不能為空");
}
//創(chuàng)建redis分布式鎖對(duì)象
RedisDistributeLock redisDistributeLock = RedisDistributeLock.build(redisClient, key, redisDistributeLockAnnotion.maxLockTime());
try {
Boolean lock = redisDistributeLock.lock();
if (lock){
try {
//獲取鎖成功,執(zhí)行方法
System.out.println(Thread.currentThread().getName()+"獲取到鎖"+key);
Object proceed = pj.proceed(args);
//移除ThreadLocal中的重試次數(shù)
retryTimes.remove();
//返回執(zhí)行結(jié)果
return proceed;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+"沒有獲取到鎖"+key);
}
switch (redisDistributeLockAnnotion.LOCK_STRATEGY()){
case IGNORE:
break;
case THROWABLE:
throw new RedisDistributeLockException("該"+key+"已經(jīng)被獲取");
case WAIT_RETRY:
//獲取
Integer retryTime = retryTimes.get();
if (retryTime != null &&retryTime >= redisDistributeLockAnnotion.maxRetryTimes()){
retryTimes.remove();
throw new RedisDistributeLockException(Thread.currentThread().getId()+"嘗試獲取鎖失敗超過(guò)了最大重試次數(shù),key="+key);
}
if (retryTime == null){
retryTime = 1;
}else {
retryTime++;
}
retryTimes.set(retryTime);
try{
Thread.sleep(redisDistributeLockAnnotion.retryTime());
}catch (Exception e){
log.error(e.getMessage(),e);
}
aroundMethod(pj);
break;
default:break;
}
return null;
}finally {
redisDistributeLock.unlock();
}
}
}
8.RedisDistributeLockException自定義異常類
package com.ict.common.exception;
/**
* @author : DevWenjiang
* Description: redis分布式鎖異常類
* @date : 2020/6/10-20:10
*/
public class RedisDistributeLockException extends Exception {
public RedisDistributeLockException() {
}
public RedisDistributeLockException(String message) {
super(message);
}
public RedisDistributeLockException(String message, Throwable cause) {
super(message, cause);
}
public RedisDistributeLockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
9.application.yml配置文件(此項(xiàng)目無(wú)需定義棋弥,只需要調(diào)用此服務(wù)的非服務(wù)上有即可)
server:
port: 8800
tomcat:
uri-encoding: UTF-8
servlet:
context-path: /
spring:
redis:
database: 2
hostName: 119.3.182.194
port: 6386
password: ict2020.
pool:
maxActive: 50
maxWait: 3000
maxIdle: 30
minIdle: 10
timeout: 1000
defaultExpiration: 3600
10.多線程運(yùn)行結(jié)果圖,測(cè)試時(shí)依據(jù)商品庫(kù)存所做诚欠,庫(kù)存不會(huì)出現(xiàn)超買現(xiàn)象
項(xiàng)目中用到的類就這些顽染,可以進(jìn)行一些擴(kuò)展等等漾岳;也在嘗試中。
項(xiàng)目github地址:https://github.com/yanwenjiang01/Redis_Distribute_Lock.git