//伪代码:下订单
1、查库存:getStock()
2、判断库存:stock>0下单
3、下单:addOrder()
4、减库存
public class RedisUtils {
private static RedisTemplate redisTemplate=ApplicationContextUtils.applicationContext.getBean("redisTemplate",RedisTemplate.class);
private static StringRedisTemplate stringRedisTemplate=ApplicationContextUtils.applicationContext.getBean(StringRedisTemplate.class);
/**
* setNx
* @param key
* @param value
* @return
*/
public static Boolean setNx(String key,String value){
return stringRedisTemplate.opsForValue().setIfAbsent(key,value);
}
/**
* setNx
* @param key
* @param value
* @param seconds 过期时间,单位秒
* @return
*/
public static Boolean setNx(String key, String value, Long seconds){
return stringRedisTemplate.opsForValue().setIfAbsent(key,value,seconds, TimeUnit.SECONDS);
}
/**
* 删除key
* @param key
* @return
*/
public static Boolean deleteKey(String key){
return redisTemplate.delete(key);
}
/**
* 获取NX设置的值
* @param key
* @return
*/
public static String getNX(String key){
return stringRedisTemplate.opsForValue().get(key);
}
/**
* 设置key的过期时间
* @param key
* @param seconds
* @return
*/
public static Boolean expireKey(String key,Long seconds){
return stringRedisTemplate.expire(key,seconds,TimeUnit.SECONDS);
}
}
public class RedisLock {
/**
* 存储的KEY,每个业务应该不同
*/
private String key;
/**
* 过期时间,单位秒
*/
private Long expireSeconds;
/**
* 异步任务,用于分开一个线程延长过期时间,也可以使用定时器
*/
private TaskAsync taskAsync;
public RedisLock(String key,Long expireSeconds){
this.key=key;
this.expireSeconds=expireSeconds;
taskAsync= ApplicationContextUtils.applicationContext.getBean(TaskAsync.class);
}
/**
* 上锁
* @param uuid setNx的值,唯一标识当前线程
*/
public void lock(String uuid) throws InterruptedException {
//获取value
String nx = RedisUtils.getNX(key);
/**
* 如果此时的uuid和redis中的一致,那么就是可重入的
*/
if (StringUtils.equals(nx,uuid)){
return;
}
//如果不一致,需要设置,为了避免死锁,需要设置一个过期时间
Boolean b = RedisUtils.setNx(key, uuid,expireSeconds);
//加锁失败,每隔两秒重试一次
while(!b){
b = RedisUtils.setNx(key, uuid,expireSeconds);
Thread.sleep(2000);
}
//开启异步线程延长过期时间
taskAsync.delayExpireTime(uuid,key,expireSeconds);
}
/**
* 解锁
* @param uuid 为了避免误删,这里的uuid是唯一标识当前方法执行的,如果和当前方法的相同才能删除
*/
public void unlock(String uuid) {
String value= RedisUtils.getNX(key);
//说明当前线程执行的方法所获得的锁已经被释放了
if (!StringUtils.equals(value,uuid)){
return;
}
Boolean b = RedisUtils.deleteKey(key);
while(!b){
b=RedisUtils.deleteKey(key);
}
}
}
@Component
public class TaskAsync {
/**
* 异步开一个线程延长执行的任务,
* @param uuid 当前线程持有锁的唯一标识
* @param key key
* @param expireSeconds 过期时间
*/
@Async
public void delayExpireTime(String uuid,String key,Long expireSeconds) throws InterruptedException {
//无限循环
while(true){
String nx = RedisUtils.getNX(key);
//key对应的值不存在,或者不等于当前方法的唯一id,直接跳出,不需要延长时间了
if (nx==null||!StringUtils.equals(uuid,nx))
break;
//延长时间
RedisUtils.expireKey(key,expireSeconds);
Thread.sleep(3000L);
}
}
}
RedissonClient
通过getxxLock(name)
获取不同锁的对象,RLock对应的是可重入锁的接口。与SpringBoot整合之后,配置的方式创建RedissonClient,并且注入了一个处理订单业务的锁:@Configuration
@EnableConfigurationProperties(value = {RedissonProperties.class})
public class RedissonConfig {
/**
* 注入RedissonClient对象
*/
@Bean
public RedissonClient redissonClient(RedissonProperties redissonProperties){
Config config = new Config();
config.useSingleServer().setAddress(redissonProperties.getAddress()).setPassword(redissonProperties.getPassword()).setDatabase(redissonProperties.getDatabase());
return Redisson.create(config);
}
/**
* 注入可重入锁,用于订单业务
*/
@Bean
public RLock orderLock(@Qualifier(value = "redissonClient") RedissonClient redissonClient,RedissonProperties redissonProperties){
return redissonClient.getLock(redissonProperties.getOrderLock());
}
}
void lock(long leaseTime, TimeUnit unit)
:获得锁,leaseTime设置的过期时间,unit是时间单位,如果设置了-1,redisson会设置默认的时间30秒,这个时间可以在config配置中修改,具体看文档。锁的值是UUID:线程Id
(作为唯一标识)void unlock()
:解锁public void add(String goodsId) throws Exception {
try {
//获取锁
rLock.lock(10, TimeUnit.SECONDS);
//检查库存,存储在redis中
Integer stock = Integer.valueOf(stringRedisTemplate.opsForValue().get(goodsId));
if (stock<0)
return;
//减库存
Long increment = stringRedisTemplate.opsForValue().increment(goodsId, -1);
if (increment<0)
return;
//减库存成功,下单
Order order = Order.builder().build();
orderMapper.add(order);
}finally {
//解锁
rLock.unlock();
}
}