Redisson 的应用
简介
在当初的我的项目中,常常会有并发问题,解决并发问题的一个思路就是应用分布式锁。在以前的我的项目中,常常会应用 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 去刷新这个工夫实现续命。主线程运行完结,守护线程就会本人死亡,不须要咱们操作。(然而个别线程都是线程池,线程不会死亡,守护线程也就不能主动死亡,所以有危险)。
当然你能够手动实现一个线程专门保护这个续命的工夫,然而实现起来就会很麻烦。所以咱们举荐间接应用 redisson。redisson 自身曾经实现了这里的续命。
Redisson 的具体应用
Redisson 中的 Rlock 等曾经实现了续命,保障咱们的业务执行完结。
引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.4</version>
</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;
}
}
<img src=”https://gitee.com/jsnucrh/blog-sharding_1/raw/master/img/20201213230304.png” alt=”image-20201213230304098″ style=”zoom:150%;” />
参考
参考