springboot+redis+redisson 分布式锁 续期 秒杀场景
pom.xml配置文件
org.springframework.boot
spring-boot-starter-data-redis
io.lettuce
lettuce-core
redis.clients
jedis
org.redisson
redisson
3.8.2
yaml配置文件
spring:
#############redis配置
redis:
database: 1
host: 10.1.1.12
port: 6039
jedis:
pool:
max-idle: 8
max-active: 9000
max-wait: 6000ms
min-idle: 5
timeout: 6000ms
配置文件:
package com.hongkun.config;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* redisson配置
*/
@Slf4j
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.database:0}")
private int database;
@Bean
public RedissonClient getRedisson() throws Exception{
RedissonClient redisson = null;
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setDatabase(database);
// log.info("redisson配置,addressUrl:"+addressUrl);
redisson = Redisson.create(config);
System.out.println(redisson.getConfig().toJSON().toString());
return redisson;
}
}
RedissonLocker
import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;
public interface RedissonLocker {
RLock lock(String lockKey);
RLock lock(String lockKey, long timeout);
RLock lock(String lockKey, TimeUnit unit, long timeout);
boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime);
void unlock(String lockKey);
void unlock(RLock lock);
}
RedissonLockerImpl:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 总结
* 1.要使 watchLog机制生效 ,lock时 不要设置 过期时间
* 2.watchlog的延时时间 可以由 lockWatchdogTimeout指定默认延时时间,但是不要设置太小。如100
* 3.watchdog 会每 lockWatchdogTimeout/3时间,去延时。
* 4.watchdog 通过 类似netty的 Future功能来实现异步延时
* 5.watchdog 最终还是通过 lua脚本来进行延时
* @auther: TF12778
* @date: 2020/7/29 16:23
* @description:
*/
@Component
public class RedissonLockerImpl implements RedissonLocker {
@Autowired
private RedissonClient redissonClient; // RedissonClient已经由配置类生成,这里自动装配即可
// lock(), 拿不到lock就不罢休,不然线程就一直block
@Override
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
// leaseTime为加锁时间,单位为秒
@Override
public RLock lock(String lockKey, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return null;
}
// timeout为加锁时间,时间单位由unit确定
@Override
public RLock lock(String lockKey, TimeUnit unit, long timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
/**
* 设置了leaseTime之后,如果waitTime大于leaseTime,那么锁自动失效后,其他线程会等待waitTime之后,才会获取锁
* 而不是立即获取锁。
*/
/**
* 尝试获取锁
* @param lockKey
* @param unit 时间单位
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
@Override
public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
@Override
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
@Override
public void unlock(RLock lock) {
lock.unlock();
}
}
业务实现
RedissionFacadeServiceImpl
import com.alibaba.fastjson.JSONObject;
import com.hongkun.common.RedisKey;
import com.hongkun.mapper.CouponInfoMapper;
import com.hongkun.untils.DateUtil;
import com.hongkun.untils.RedisUtil;
import com.hongkun.untils.SnowflakeUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* @auther: TF12778
* @date: 2020/7/29 11:09
* @description:
*/
@Slf4j
@Service("redissionFacadeService")
public class RedissionFacadeServiceImpl implements RedissionFacadeService {
private static final String LOCK_KEY = "lockswww";
@Resource
private RedisUtil redisUtil;
@Resource
private CouponInfoMapper couponInfoMapper;
@Resource
private RedissonLocker redissonLocker;
/**
* 下单步骤:校验库存,扣库存,创建订单,支付
*
*/
// @Transactional 此处不需要加事物,否则订单数量超表
@Override
public void saveOrder(Integer userId,Integer couponId) {
/**
* 秒杀流程
* 1、通过redis判断用户是否重复秒杀
* 2、redis预减库存;抢购,减少库存
* 3、秒杀成功,存入用户id 到商品去重表以及消息队列进行商品订单处理
*/
//1、通过redis判断重复秒杀
Boolean userExist= redisUtil.sIsMember(String.format(RedisKey.COUPON_INFO_USER, couponId), String.valueOf(userId));
//用存在直接返回
if(userExist){
log.info("用户:"+userId+",已经存在商品:"+couponId);
}else{
//2.创建锁实例
RLock lock = redissonLocker.lock(LOCK_KEY);// 释放
try {
//3.尝试获取分布式锁,第一个参数为等待时间
boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);
log.info("用户抢购加锁userId:"+userId+",尝试加锁isLock:"+isLock);
if(isLock){
Thread.sleep(20000);
//获取剩余数量
String couponCount = redisUtil.get(String.format(RedisKey.COUPON_COUNT, couponId));
log.info("用户抢购加锁成功,userId:"+userId+"商品:"+couponId+",剩余优惠卷数量couponCount:"+couponCount);
if(Integer.parseInt(couponCount) > 0) {
//2、 redis预减库存;抢购,减少库存
Long count = redisUtil.decrBy(String.format(RedisKey.COUPON_COUNT, couponId), 1);
log.info("用户抢购加锁成功,userId:"+userId+"商品:"+couponId+",剩余优惠卷数量count:"+count);
//如果剩余数量大于等于0,认为抢购成功。
if(count >=0){
log.info("用户抢购成功userId:"+userId+"商品:"+couponId+",剩余优惠卷数量:"+count);
//3、秒杀成功,存入用户id 到商品去重
redisUtil.sAdd(String.format(RedisKey.COUPON_INFO_USER, couponId),String.valueOf(userId));
JSONObject jsonObject = new JSONObject();
jsonObject.put("orderId", SnowflakeUtil.nextId());//订单id,雪花算法
jsonObject.put("userId",userId);
jsonObject.put("couponId",couponId);
jsonObject.put("rushBuyingTime", DateUtil.getCurrent());//抢购时间
//用户
redisUtil.set(String.format(RedisKey.USER_COUPON_INFO,couponId,userId),jsonObject.toString());
}else{
log.info("用户抢购失败userId:"+userId+"商品:"+couponId+",剩余优惠卷数量:"+count);
}
}
redissonLocker.unlock(LOCK_KEY); // 释放锁
}else {
log.info("用户抢购加锁失败userId:"+userId+"商品:"+couponId);
//this.saveOrder( userId, couponId);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
redissonLocker.unlock(LOCK_KEY); // 释放锁
}
}
}