在现在的项目中,经常会有并发问题,解决并发问题的一个思路就是使用分布式锁。在以前的项目中,经常会使用Redis的setnx特性来实现分布式锁,但是有可能会带来死锁的问题,那么就可以使用Redisson来实现Redis的分布式锁。这里我使用的场景是短信验证码服务,同一时刻只能有一个线程给同一个手机号发送短信。
在不使用redisson时,我们一般这样去使用Redis实现分布式锁。
@Component
public class RedissonLockImpl implements RedissonLock {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public boolean tryLock(String key, long timeout, TimeUnit timeUnit) {
if (threadLocal.get() == null){
String lockName = "redis:test:name";
threadLocal.set(lockName);
return redisTemplate.opsForValue().setIfAbsent(key, lockName, timeout, timeUnit);
}else if (threadLocal.get().equals(redisTemplate.opsForValue().get(key))){
return true;
}
return false;
}
}
然后在代码中这样使用:
@RestController
public class RedissonController {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonLock redissonLock;
@RequestMapping("/submitOrder")
public String submitOrder(){
String key = "test";
// 这里可能存在在你设置的时间内当前线程还没有结束
boolean lock = redissonLock.tryLock(key, 4, TimeUnit.SECONDS);
if (!lock){
return "error";
}
try {
// 具体的业务逻辑
int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
if (stock>0){
// 下单
stock = stock - 1;
redisTemplate.opsForValue().set("stock", stock+"");
return "success";
} else {
System.out.println("库存不足");
return "false";
}
} finally {
// 这里可以释放锁
}
}
}
这样可能就会导致在你设置的锁的时间结束后,该线程还没有执行结束,但是锁已经释放,就会导致后面获取锁,释放锁的顺序变乱,导致业务出问题。
对于这种某个线程超时的问题,可以给这个线程new一个守护线程,守护线程每10s去刷新这个时间完成续命。主线程运行结束,守护线程就会自己死亡,不需要我们操作。(但是一般线程都是线程池,线程不会死亡,守护线程也就不能自动死亡,所以有风险)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ga1fG25Q-1608391391166)(https://gitee.com/jsnucrh/blog-sharding_1/raw/master/img/20201213224259.png)]
当然你可以手动实现一个线程专门维护这个续命的时间,但是实现起来就会很麻烦。所以我们推荐直接使用redisson。redisson本身已经实现了这里的续命。
Redisson中的Rlock等已经实现了续命,保证我们的业务执行结束。
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.11.4version>
dependency>
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
//添加主从配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
return Redisson.create(config);
}
}
这样我们可以自动注入RedissonClient
进行使用。但是一般我们可以在上面再包装一层service,来包装我们一般的异常等场景。
/**
* redisson操作类
* @author 朱友德
*/
@Service("redissonService")
public class RedissonService {
@Autowired
private RedissonClient redissonClient;
public void getRedissonClient() throws IOException {
Config config = redissonClient.getConfig();
System.out.println(config.toJSON().toString());
}
/**`
* 获取字符串对象
*
* @param objectName
* @return
*/
public <T> RBucket<T> getRBucket(String objectName) {
RBucket<T> bucket = redissonClient.getBucket(objectName);
return bucket;
}
/**
* 获取Map对象
*
* @param objectName
* @return
*/
public <K, V> RMap<K, V> getRMap(String objectName) {
RMap<K, V> map = redissonClient.getMap(objectName);
return map;
}
/**
* 获取有序集合
*
* @param objectName
* @return
*/
public <V> RSortedSet<V> getRSortedSet(String objectName) {
RSortedSet<V> sortedSet = redissonClient.getSortedSet(objectName);
return sortedSet;
}
/**
* 获取集合
*
* @param objectName
* @return
*/
public <V> RSet<V> getRSet(String objectName) {
RSet<V> rSet = redissonClient.getSet(objectName);
return rSet;
}
/**
* 获取列表
*
* @param objectName
* @return
*/
public <V> RList<V> getRList(String objectName) {
RList<V> rList = redissonClient.getList(objectName);
return rList;
}
/**
* 获取队列
*
* @param objectName
* @return
*/
public <V> RQueue<V> getRQueue(String objectName) {
RQueue<V> rQueue = redissonClient.getQueue(objectName);
return rQueue;
}
/**
* 获取双端队列
*
* @param objectName
* @return
*/
public <V> RDeque<V> getRDeque(String objectName) {
RDeque<V> rDeque = redissonClient.getDeque(objectName);
return rDeque;
}
/**
* 获取锁
* @param objectName
* @return
*/
public RLock getRLock(String objectName) {
RLock rLock = redissonClient.getLock(objectName);
return rLock;
}
public Boolean tryLock(String key, long leaseTime, TimeUnit unit) {
RLock rLock = redissonClient.getLock(key);
boolean tryLock = false;
try {
tryLock = rLock.tryLock(0, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
return tryLock;
}
public Boolean verifyTryLock(RLock rLock, long leaseTime, TimeUnit unit) {
boolean tryLock = false;
try {
tryLock = rLock.tryLock(0, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
return tryLock;
}
/**
* 获取读取锁
*
* @param objectName
* @return
*/
public RReadWriteLock getRWLock(String objectName) {
RReadWriteLock rwlock = redissonClient.getReadWriteLock(objectName);
return rwlock;
}
/**
* 获取原子数
*
* @param objectName
* @return
*/
public RAtomicLong getRAtomicLong(String objectName) {
RAtomicLong rAtomicLong = redissonClient.getAtomicLong(objectName);
return rAtomicLong;
}
/**
* 获取记数锁
*
* @param objectName
* @return
*/
public RCountDownLatch getRCountDownLatch(String objectName) {
RCountDownLatch rCountDownLatch = redissonClient.getCountDownLatch(objectName);
return rCountDownLatch;
}
@Service
public class MyService {
@Autowired
private RedissonService redissonService;
@Autowired
private StringRedisTemplate redisTemplate;
public String sendCodeByTemplate(final String phone){
// 过期时间
String ttlKey = "redis:ttl:phone:"+phone;
String key = "redis:phone:"+phone;
// 对这个手机号发送消息时会对发送手机号加锁
if (!redissonService.tryLock(key, 10, TimeUnit.SECONDS)){
//获取过期时间
Long ttl = redisTemplate.getExpire(ttlKey);
//发送验证码频繁
return "发送频繁";
}
// 发送验证码
sendCode(phone);
return "true";
}
private boolean sendCode(String phone){
// 写具体发送逻辑
return true;
}
}
参考
return “true”;
}
private boolean sendCode(String phone){
// 写具体发送逻辑
return true;
}
}
### 参考
[参考](https://blog.51cto.com/xvjunjie/2428610)