一、使用场景
调用第三方接口需要获取token,每月获取token上限50次,所以使用:分布式锁+缓存
二、业务代码
1、依赖
<!--Spring Data Redis 的启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring</artifactId>
<version>3.11.3</version>
</dependency>
2、RedissonClient配置类
package com.aisino.uwcloud.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author:
* @Date:
*/
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedissonConfig {
/**
* redisson 配置
*
* @param properties
* @return
*/
@Bean
public RedissonClient getRedisson(RedisProperties properties) {
Config config = new Config();
if (properties.getCluster() != null && properties.getCluster().getNodes() != null) {
String[] nodes = properties.getCluster().getNodes().toArray(new String[0]);
String[] result = new String[nodes.length];
for (int i = 0; i < nodes.length; i++) {
String nodePart = "redis://" + nodes[i];
result[i] = nodePart;
}
//集群配置
config.useClusterServers()
.setScanInterval(10000)
.addNodeAddress(result)
.setPassword(properties.getPassword());
} else {
//单机配置
config.useSingleServer().setAddress("redis://" + properties.getHost() + ":" + properties.getPort()).setPassword(properties.getPassword());
}
return Redisson.create(config);
}
}
3、分布式锁代码
@Resource
private RedissonClient redissonClient;
private final static String LOCK_KEY = "RESOURCE_KEY";
public String getToken(){
//获取锁
RLock lock = redissonClient.getLock(LOCK_KEY);
try {
//尝试加锁,最大等待时间300毫秒,上锁30毫秒自动解锁(根据接口执行时间设置,最大等待时间可以设置短点,以防线程堆积,自动解锁时间要设置超过接口执行时间)
if (lock.tryLock(30, 300, TimeUnit.MILLISECONDS)) {
log.info("线程:" + Thread.currentThread().getName() + "获得了锁");
log.info("剩余数量:{}", --n);
}
} catch (Exception e) {
log.error("程序执行异常:{}", e);
} finally {
if (lock.isLocked()&&lock.isHeldByCurrentThread()){
log.info("线程:" + Thread.currentThread().getName() + "准备释放锁");
//释放锁
lock.unlock();
}
}
}
return "token";
}
4、用apipost进行压测
分布式锁的trylock的最大等待时间和自动解锁时间,应该根据具体的业务场景和需求来设置。
一般来说,最大等待时间应该根据实际情况来设定。如果等待时间过长,可能会导致线程长时间等待,影响系统性能;如果等待时间过短,可能会导致线程频繁地尝试获取锁,也会影响系统性能。因此,需要根据实际情况来权衡。
对于自动解锁时间,一般建议设置为一个比较合理的时间段。如果自动解锁时间过长,可能会导致线程长时间占用锁,影响其他线程获取锁;如果自动解锁时间过短,可能会导致线程频繁地释放锁,也会影响系统性能。因此,需要根据实际情况来选择一个比较合理的时间段。
另外,需要注意的是,分布式锁的trylock方法一般都会有一个超时时间参数,表示最大等待时间。如果在这个时间内没有获取到锁,线程会自动放弃。因此,在设置最大等待时间时,需要考虑到这个因素,避免出现线程无限等待的情况。
总之,分布式锁的trylock的最大等待时间和自动解锁时间应该根据具体的业务场景和需求来设置,需要考虑系统的性能和稳定性等因素。
5、踩坑总结
(1)
刚开始使用的线程池测试业务代码,发现注入redissonClient是null,调用redissonClient.getLock(LOCK_KEY),显示空指针,但是控制台不打印报错异常日志,使用apipost测试接口就正常了;
(2)
释放锁的时候没加 lock.isLocked()&&lock.isHeldByCurrentThread()的状态判断,会报错attempt to unlock lock, not locked by current thread by node