
- 1. RedisLockDto
- 2. RedisLockService
- 3. RedisLockServiceImpl
- 3.1 获取锁的key
- 3.2 获取锁的值lockValue
- 3.3 分布式锁异常RedisLockException
- 3.4 获取锁
- 3.5 锁续期
- 3.6 释放锁
- 4. 定义注解@RedisLock
- 5. 定义注解@BusinessId
- 6. 读取目标方法上加的注解并加锁
- 7. Spring boot redis分布式锁自动化配置
- 8. 测试
- 9. 测试使用
RedisLockDto定义与锁相关的信息
@Data
@Builder
public class RedisLockDto {
@Tolerate
public RedisLockDto() {
}
/**
* redis lock的前缀
*/
private String prefix;
/**
* 过期时间(单位:毫秒)
*/
private Long expiredTime;
/**
* 业务ID
*/
private String businessId;
/**
* 是否等待重试
*/
private Boolean retry = true;
/**
* 锁的值
*/
private String lockValue;
/**
* 最大重试次数
*/
private Long maxRetryTimes;
/**
* 等待时间
*/
private Long delayTime;
}
2. RedisLockService
public interface RedisLockService {
/**
* 获取锁
*
* @param lock 锁信息
* @throws RedisLockException 获取锁失败时抛出异常
*/
void lock(@NonNull RedisLockDto lock) throws RedisLockException;
/**
* 释放锁
*
* @param lock 锁信息
*/
void unlock(@NonNull RedisLockDto lock);
/**
* 锁续命
*
* @param lock 锁信息
* @return 续命是否成功
*/
boolean extend(@NonNull RedisLockDto lock);
}
3. RedisLockServiceImpl
@AllArgsConstructor
@Slf4j
public class RedisLockServiceImpl implements RedisLockService {
/**
* redis template
*/
private final RedisTemplate<String, String> redisTemplate;
/**
* 最大重试次数
*/
private final int maxRetryTimes;
/**
* 等待时间
*/
private final long delayTime;
@Override
public void lock(@NonNull RedisLockDto lock) throws RedisLockException {
if (lock.getRetry()) {
lockWithRetry(lock);
} else {
lockWithoutRetry(lock);
}
}
/**
* 不带重试的获取锁
*
* @param lock 锁
* @throws RedisLockException 当获取不到锁时抛出异常
*/
private void lockWithoutRetry(@NonNull RedisLockDto lock) throws RedisLockException {
if (!tryLock(lock)) {
log.warn("failed get lock , key: {}", this.getLockKey(lock));
throw new RedisLockException(lock.getPrefix(), lock.getBusinessId(), lock, getLockValue(lock));
}
log.info("successfully get lock , key: {}", this.getLockKey(lock));
}
/**
* 带重试的获取锁
*
* @param lock 锁
* @throws RedisLockException 如果尝试获取锁超过指定次数时,抛出异常
*/
private void lockWithRetry(RedisLockDto lock) throws RedisLockException {
int retryTimes = 0;
while (!tryLock(lock)) {
++retryTimes;
long realMaxRetryTimes;
if (lock.getMaxRetryTimes() != null) {
realMaxRetryTimes = lock.getMaxRetryTimes();
} else {
realMaxRetryTimes = this.maxRetryTimes;
}
if (realMaxRetryTimes >= 0 && retryTimes >= realMaxRetryTimes) {
log.warn("failed get lock ,key: {}", this.getLockKey(lock));
throw new RedisLockException(lock.getPrefix(), lock.getBusinessId(), lock, getLockValue(lock));
}
long realDelayTime;
if (lock.getDelayTime() != null) {
realDelayTime = lock.getDelayTime();
} else {
realDelayTime = this.delayTime;
}
try {
Thread.sleep(realDelayTime);
} catch (InterruptedException | IllegalArgumentException e) {
// 什么也不做
}
}
log.info("successfully get lock ,key: {}", this.getLockKey(lock));
}
@Override
public void unlock(@NonNull RedisLockDto lock) {
if (lock.getLockValue().equals(redisTemplate.opsForValue().get(this.getLockKey(lock)))) {
redisTemplate.delete(this.getLockKey(lock));
log.info("successfully delete lock , key:{}", this.getLockKey(lock));
}
}
/**
* 尝试获取锁
*
* @param lock 锁
* @return 获取结果
*/
private boolean tryLock(@NonNull RedisLockDto lock) {
return Boolean.TRUE.equals(
redisTemplate.opsForValue()
.setIfAbsent(this.getLockKey(lock), lock.getLockValue(), lock.getExpiredTime(), TimeUnit.MILLISECONDS)
);
}
/**
* 获取锁当前的值
*
* @param lock 锁
* @return 获取结果
*/
private String getLockValue(@NonNull RedisLockDto lock) {
return redisTemplate.opsForValue().get(this.getLockKey(lock));
}
/**
* 锁续命
*
* @return 续命是否成功
*/
@Override
public boolean extend(@NonNull RedisLockDto lock) {
return Boolean.TRUE.equals(
redisTemplate.expire(this.getLockKey(lock), lock.getExpiredTime(), TimeUnit.MILLISECONDS)
);
}
/**
* 获取锁的key
*
* @param lock 锁
* @return 锁的key
*/
private String getLockKey(RedisLockDto lock) {
return lock.getPrefix() + "_" + lock.getBusinessId();
}
}
3.1 获取锁的key
/**
* 获取锁的key
*
* @param lock 锁
* @return 锁的key
*/
private String getLockKey(RedisLockDto lock) {
return lock.getPrefix() + "_" + lock.getBusinessId();
}
3.2 获取锁的值lockValue
/**
* 获取锁当前的值
*
* @param lock 锁
* @return 获取结果
*/
private String getLockValue(@NonNull RedisLockDto lock) {
return redisTemplate.opsForValue().get(this.getLockKey(lock));
}
3.3 分布式锁异常RedisLockException
public class RedisLockException extends Exception {
/**
* redis锁前缀
*/
@Getter
private String prefix;
/**
* 业务id
*/
@Getter
private String businessId;
/**
* 锁配置对象
*/
@Getter
private RedisLockDto lock;
/**
* 锁的值
*/
@Getter
private String lockValue;
public RedisLockException(String prefix, String businessId, RedisLockDto lock, String lockValue) {
super("failed to acquire redis lock,prefix is " + prefix + ",business id is " + businessId + ",lock value is " + lockValue);
this.prefix = prefix;
this.businessId = businessId;
this.lock = lock;
this.lockValue = lockValue;
}
}
3.4 获取锁
@Override
public void lock(@NonNull RedisLockDto lock) throws RedisLockException {
if (lock.getRetry()) {
lockWithRetry(lock);
} else {
lockWithoutRetry(lock);
}
}
不带重试的获取锁:
/**
* 不带重试的获取锁
*
* @param lock 锁
* @throws RedisLockException 当获取不到锁时抛出异常
*/
private void lockWithoutRetry(@NonNull RedisLockDto lock) throws RedisLockException {
if (!tryLock(lock)) {
log.warn("failed get lock , key: {}", this.getLockKey(lock));
throw new RedisLockException(lock.getPrefix(), lock.getBusinessId(), lock, getLockValue(lock));
}
log.info("successfully get lock , key: {}", this.getLockKey(lock));
}
尝试获取锁:
/**
* 尝试获取锁
*
* @param lock 锁
* @return 获取结果
*/
private boolean tryLock(@NonNull RedisLockDto lock) {
return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(
this.getLockKey(lock),
lock.getLockValue(),
lock.getExpiredTime(),
TimeUnit.MILLISECONDS));
}
带重试的尝试获取锁:
/**
* 带重试的获取锁
*
* @param lock 锁
* @throws RedisLockException 如果尝试获取锁超过指定次数时,抛出异常
*/
private void lockWithRetry(RedisLockDto lock) throws RedisLockException {
// 重试次数
int retryTimes = 0;
// 如果获取锁失败,则重试获取锁
while (!tryLock(lock)) {
// 重试次数+1
++retryTimes;
// 最大重试次数
long realMaxRetryTimes;
if (lock.getMaxRetryTimes() != null) {
realMaxRetryTimes = lock.getMaxRetryTimes();
} else {
realMaxRetryTimes = this.maxRetryTimes;
}
// 如果重试次数大于最大重试次数,则获取锁失败
if (realMaxRetryTimes >= 0 && retryTimes >= realMaxRetryTimes) {
log.warn("failed get lock ,key: {}", this.getLockKey(lock));
throw new RedisLockException(lock.getPrefix(), lock.getBusinessId(), lock, getLockValue(lock));
}
// 线程等待时间realDelayTime
long realDelayTime;
if (lock.getDelayTime() != null) {
realDelayTime = lock.getDelayTime();
} else {
realDelayTime = this.delayTime;
}
// 让线程等待realDelayTime后再获取锁
try {
Thread.sleep(realDelayTime);
} catch (InterruptedException | IllegalArgumentException e) {
// 什么也不做
}
}
log.info("successfully get lock ,key: {}", this.getLockKey(lock));
}
3.5 锁续期
担心 joinPoint.proceed()切点执行的方法太耗时,导致Redis中的key由于超时提前释放了。
例如,线程A先获取锁,proceed() 方法耗时,超过了锁超时时间,到期释放了锁,这时另一个线程B成功获取Redis锁,两个线程同时对同一批数据进行 *** 作,导致数据不准确。
/**
* 锁续命
*
* @return 续命是否成功
*/
@Override
public boolean extend(@NonNull RedisLockDto lock) {
return Boolean.TRUE.equals(redisTemplate.expire(
this.getLockKey(lock),
lock.getExpiredTime(),
TimeUnit.MILLISECONDS)
);
}
3.6 释放锁
@Override
public void unlock(@NonNull RedisLockDto lock) {
if (lock.getLockValue().equals(redisTemplate.opsForValue().get(this.getLockKey(lock)))) {
redisTemplate.delete(this.getLockKey(lock));
log.info("successfully delete lock , key:{}", this.getLockKey(lock));
}
}
4. 定义注解@RedisLock
定义注解@RedisLock用于读取redis分布式锁相关的信息,和spring aop搭配使用,注解用于方法上,可以为方法添加分布式锁。
@Retention(RetentionPolicy.RUNTIME) // 可以通过反射读取注解
@Target(ElementType.METHOD) // 该注解只能加在方法上
public @interface RedisLock {
/**
* redis分布式锁的前缀,必填
*
* @return redis分布式锁的前缀
*/
String prefix();
/**
* redis分布式锁的过期时间(毫秒)
*
* @return redis分布式锁的前缀
*/
long expiredTime() default 1000L;
/**
* 是否等待锁释放,默认不等待直接报错
*
* @return 是否等待锁释放
*/
boolean retry() default false;
}
5. 定义注解@BusinessId
和@RedisLock搭配使用,用于标注入参中的业务id。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface BusinessId {
}
6. 读取目标方法上加的注解并加锁
@Aspect
@Component
@EnableAspectJAutoProxy
public class RedisLockAspect {
/**
* 声明切点
*/
@Pointcut("@annotation(com.example.redislock.test.RedisLock)")
private void redisLockPointCut() {
}
@Autowired
private RedisLockService redisLockService;
/**
* 在目标方法执行之前获取redis锁
* 在目标方法执行之后释放锁
*/
@Around("redisLockPointCut()")
public Object redisLockAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 1.从注解获取redis lock的配置参数
RedisLockDto lock = this.getRedisLockFromAnnotation(joinPoint);
// 2.尝试获取锁
try {
redisLockService.lock(lock);
// 放行,目标方法的执行
return joinPoint.proceed();
} finally {
// 3.释放锁
redisLockService.unlock(lock);
}
}
/**
* 从注解获取分布式锁相关信息
*
* @param joinPoint 切点
* @return redis锁对象
*/
private RedisLockDto getRedisLockFromAnnotation(ProceedingJoinPoint joinPoint) {
// 获取目标方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 获取方法上指定类型的注解
RedisLock redisLock = method.getAnnotation(RedisLock.class);
// 从注解对象中获取元素的值
String prefix = redisLock.prefix();
if (StringUtils.isEmpty(redisLock.prefix())) {
// 如果redis分布式锁的key的前缀为空,则使用目标方法的名称作为前缀
prefix = method.getName();
}
// 获取方法中每个参数前加的注解的值,一个方法可能有多个参数,一个参数前可能加多个注解,所以为二维数组
Annotation[][] annotations = method.getParameterAnnotations();
StringBuilder businessIdBuilder = new StringBuilder();
for (int i = 0; i < annotations.length; i++) {
// 获取目标方法的第i个参数
Object param = joinPoint.getArgs()[i];
// 获取目标方法的第i个参数前所加的所有注解
Annotation[] paramAnnotations = annotations[i];
if (param == null || paramAnnotations.length == 0) {
continue;
}
// 遍历目标方法第i个参数前的所有注解
for (Annotation annotation : paramAnnotations) {
// 判断注解类型是否BusinessId
if (annotation.annotationType().equals(BusinessId.class)) {
businessIdBuilder.append(param);
}
}
}
return RedisLockDto.builder()
.prefix(prefix)
.expiredTime(redisLock.expiredTime())
.retry(redisLock.retry())
.lockValue(UUID.randomUUID().toString())
.businessId(businessIdBuilder.toString())
.build();
}
}
7. Spring boot redis分布式锁自动化配置
application.yml文件:
ngsoc:
redis-lock:
maxRetryTimes: 300
delayTime: 3000
扫描bean并注入:
@Configuration
public class RedisLockAutoConfiguration {
/**
* 最大重试次数
*/
@Value("${ngsoc.redis-lock.maxRetryTimes:10}")
@Getter
private int maxRetryTimes;
/**
* 等待时间
*/
@Value("${ngsoc.redis-lock.delayTime:100}")
@Getter
private long delayTime;
/**
* 配置RedisLockAspect
*/
@Bean
public RedisLockAspect redisLockAspect() {
return new RedisLockAspect();
}
/**
* 配置 RedisTemplate 及其序列化方式
*/
@Bean(value = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
Object.class
);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY
);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
StringRedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
redisTemplate.setHashKeySerializer(redisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 配置 RedisLockService
*/
@Bean
public RedisLockService redisLockService(RedisTemplate<String, String> redisTemplate) {
return new RedisLockServiceImpl(redisTemplate, maxRetryTimes, delayTime);
}
}
新增一个spring.factories文件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.redislock.test.RedisLockAutoConfiguration
通过spring.factories,将RedisLockAutoConfiguration配置进去,可以实现自动加载的功能。
8. 测试@ActiveProfiles("test")
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = RedisLockTestApp.class)
@Slf4j
@Disabled
public class RedisLockTest {
private static final int THREAD_NUM = 16;
private static final int CALL_NUM = 200;
@Autowired
private RedisLockService redisLockService;
private volatile int sum = 0;
@Test
public void testSimpleLock() throws InterruptedException, ExecutionException {
sum = 0;
RedisLockDto lock = new RedisLockDto();
lock.setPrefix("RedisLockTest");
lock.setRetry(true);
lock.setMaxRetryTimes(-1L);
lock.setBusinessId(UUID.randomUUID().toString());
lock.setLockValue(UUID.randomUUID().toString());
lock.setExpiredTime(30_000L);
// 利用线程池创建16个线程
ExecutorService executor = Executors.newFixedThreadPool(THREAD_NUM);
// 创建200个任务
ArrayList<Callable<Integer>> callables = new ArrayList<>(CALL_NUM);
for (int i = 0; i < CALL_NUM; i++) {
callables.add(() -> {
redisLockService.lock(lock);
int tmpSum = sum + 1;
sum = tmpSum;
redisLockService.unlock(lock);
Thread.sleep((int) (Math.random() * 100));
return tmpSum;
}
);
}
// 线程池执行任务
List<Future<Integer>> futureIntegers = executor.invokeAll(callables);
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < futureIntegers.size(); i++) {
Future<Integer> futureInteger = futureIntegers.get(i);
Integer integer = futureInteger.get();
Integer currentNum = map.get(integer);
if (currentNum == null) {
currentNum = 0;
}
currentNum++;
map.put(integer, currentNum);
System.out.println("done : " + i);
}
assertEquals(CALL_NUM, sum);
for (int i = 1; i <= CALL_NUM; i++) {
assertEquals(1, map.get(i));
}
}
@Test
public void testOneTryLock() {
sum = 0;
RedisLockDto lock = new RedisLockDto();
lock.setRetry(false);
lock.setPrefix("RedisLockTest");
lock.setBusinessId(UUID.randomUUID().toString());
lock.setLockValue(UUID.randomUUID().toString());
lock.setExpiredTime(30_000L);
ExecutorService executor = Executors.newFixedThreadPool(THREAD_NUM);
assertThrows(
RedisLockException.class,
() -> {
redisLockService.lock(lock);
redisLockService.lock(lock);
}
);
}
}
@EnableScheduling
@EnableAsync
@SpringBootApplication
@ContextConfiguration(classes = { RedisLockAutoConfiguration.class })
public class RedisLockTestApp {
public static void main(String[] args) {
SpringApplication.run(RedisLockTestApp.class, args);
}
}
9. 测试使用
@RedisLock(prefix = "test_lock", expiredTime = 300000L)
public void completeTask(
int riskOrderId,
TaskCompleteQo taskCompleteQo,
String currentUserName,
@BusinessId String taskId)
}
实现方法参考文章:https://www.cnblogs.com/xiaowan7/p/14277925.html
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)