秒杀
全局唯一id
@Component
public class RedisIdWorker {
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 开始时间戳
* public static void main(String[] args) {
* LocalDateTime beginTime = LocalDateTime.of(2024, 1, 1, 0, 0);
* long second = beginTime.toEpochSecond(ZoneOffset.UTC);
* System.out.println(second);
* }
*/
private static final long BEGIN_TIMESTAMP = 1704067200;
/**
* 序列号位数
*/
private static final long BITS_COUNT = 32;
/**
* 生成全局唯一id
* @param keyPrefix
* @return
*/
public long nextId(String keyPrefix){
// 1.生成时间戳
LocalDateTime nowTime = LocalDateTime.now();
long nowSecond = nowTime.toEpochSecond(ZoneOffset.UTC);
long timestamp = nowSecond - BEGIN_TIMESTAMP;
// 2.生成序列号
// 2.1.获取当前日期,精确到秒 (:是为了方便Redis使用)
String date = nowTime.format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"));
// 2.2.自增长
Long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
// 3.拼接并返回
return (timestamp << BITS_COUNT) | count;
}
}
下单(未优化)
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Autowired
private ISeckillVoucherService seckillVoucherService;
@Autowired
private RedisIdWorker redisIdWorker;
/**
* 秒杀
* @param voucherId
* @return
*/
@Override
@Transactional
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否已经开始
LocalDateTime now = LocalDateTime.now();
if(voucher.getBeginTime().isAfter(now)){
return Result.fail("秒杀尚未开始!");
}
// 3.判断秒杀是否已经结束
if(voucher.getEndTime().isBefore(now)){
return Result.fail("秒杀已经结束");
}
// 4.判断库存是否充足
if(voucher.getStock() <= 0){
return Result.fail("库存不足!");
}
// 5.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).update();
if(!success){
return Result.fail("库存不足!");
}
// 6.生成订单
VoucherOrder order = new VoucherOrder();
// 6.1.生成订单id(全局唯一id)
long orderId = redisIdWorker.nextId("order");
order.setId(orderId);
// 6.2.生成用户id
Long userId = UserHolder.getUser().getId();
order.setUserId(userId);
// 6.3.生成优惠券id
order.setVoucherId(voucherId);
// 6.4.保存订单
save(order);
// 7.返回订单id(获取支付链接,用户支付)
return Result.ok(orderId);
}
}
解决超卖(乐观锁)
// 5.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).eq("stock", voucher.getStock())
.update();
if(!success){
return Result.fail("库存不足!");
}
// 6.生成订单
VoucherOrder order = new VoucherOrder();
解决少卖
// 5.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).gt("stock", 0)
.update();
if(!success){
return Result.fail("库存不足!");
}
// 6.生成订单
VoucherOrder order = new VoucherOrder();
实现一人一单
基于synchronized
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Autowired
private ISeckillVoucherService seckillVoucherService;
@Autowired
private RedisIdWorker redisIdWorker;
/**
* 秒杀
* @param voucherId
* @return
*/
@Override
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否已经开始
LocalDateTime now = LocalDateTime.now();
if(voucher.getBeginTime().isAfter(now)){
return Result.fail("秒杀尚未开始!");
}
// 3.判断秒杀是否已经结束
if(voucher.getEndTime().isBefore(now)){
return Result.fail("秒杀已经结束");
}
// 4.判断库存是否充足
if(voucher.getStock() <= 0){
return Result.fail("库存不足!");
}
return createVoucherOrder(voucherId);
}
@Transactional
public Result createVoucherOrder(Long voucherId) {
// 5.一人一单
Long userId = UserHolder.getUser().getId();
// 锁粒度降低,性能更好,intern()保证只锁相同的用户id值
synchronized (userId.toString().intern()) {
// 5.1.查询订单
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
// 5.2.判断该用户是否下过单
if(count > 0) {
return Result.fail("一人一购!");
}
// 6.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).gt("stock", 0)
.update();
if(!success){
return Result.fail("库存不足!");
}
// 7.生成订单
VoucherOrder order = new VoucherOrder();
// 7.1.生成订单id(全局唯一id)
long orderId = redisIdWorker.nextId("order");
order.setId(orderId);
// 7.2.生成用户id
order.setUserId(userId);
// 7.3.生成优惠券id
order.setVoucherId(voucherId);
// 7.4.保存订单
save(order);
// 8.返回订单id(获取支付链接,用户支付)
return Result.ok(orderId);
}
}
}
因为@Transactional是方法结束后才提交事务,而锁释放后,其他线程将会得到锁,继续执行,此时未提交事务,数据库可能未更新,还是会出现线程安全的,因此需要将调用的方法上锁
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Autowired
private ISeckillVoucherService seckillVoucherService;
@Autowired
private RedisIdWorker redisIdWorker;
/**
* 秒杀
* @param voucherId
* @return
*/
@Override
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否已经开始
LocalDateTime now = LocalDateTime.now();
if(voucher.getBeginTime().isAfter(now)){
return Result.fail("秒杀尚未开始!");
}
// 3.判断秒杀是否已经结束
if(voucher.getEndTime().isBefore(now)){
return Result.fail("秒杀已经结束");
}
// 4.判断库存是否充足
if(voucher.getStock() <= 0){
return Result.fail("库存不足!");
}
Long userId = UserHolder.getUser().getId();
// 锁粒度降低,性能更好,intern()保证只锁相同的用户id值
synchronized (userId.toString().intern()) {
return createVoucherOrder(voucherId);
}
}
@Transactional
public Result createVoucherOrder(Long voucherId) {
// 5.一人一单
// 5.1.查询订单
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
// 5.2.判断该用户是否下过单
if(count > 0) {
return Result.fail("一人一购!");
}
// 6.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).gt("stock", 0)
.update();
if(!success){
return Result.fail("库存不足!");
}
// 7.生成订单
VoucherOrder order = new VoucherOrder();
// 7.1.生成订单id(全局唯一id)
long orderId = redisIdWorker.nextId("order");
order.setId(orderId);
// 7.2.生成用户id
order.setUserId(userId);
// 7.3.生成优惠券id
order.setVoucherId(voucherId);
// 7.4.保存订单
save(order);
// 8.返回订单id(获取支付链接,用户支付)
return Result.ok(orderId);
}
}
Spring AOP通过动态代理来管理事务,只有被Spring管理的bean才会被代理。如果事务方法在同一个类中被调用,而不是通过代理类调用,则事务不会生效。
synchronized (userId.toString().intern()) {
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
}
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
@EnableAspectJAutoProxy(exposeProxy = true)
仍存在线程安全问题(跨JVM)
基于Redis分布式锁:
分布式锁:满足分布式系统或集群模式下多线程可见且互斥的锁
- 初级版本
public interface ILock {
/**
* 尝试获取锁
* @param timeoutSecond
* @return
*/
boolean tryLock(long timeoutSecond);
/**
* 释放锁
*/
void unlock();
}
public class SimpleRedisLock implements ILock{
private StringRedisTemplate stringRedisTemplate;
private String lockName;
private static final String KEY_PREFIX = "lock:";
public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String lockName) {
this.stringRedisTemplate = stringRedisTemplate;
this.lockName = lockName;
}
/**
* 尝试获取锁
* @param timeoutSecond
* @return
*/
@Override
public boolean tryLock(long timeoutSecond) {
long threadId = Thread.currentThread().getId();
Boolean success = stringRedisTemplate.opsForValue().
setIfAbsent(KEY_PREFIX + lockName, threadId + "", timeoutSecond, TimeUnit.SECONDS);
// 防止自动拆箱空指针的安全问题
return BooleanUtil.isTrue(success);
}
/**
* 释放锁
*/
@Override
public void unlock() {
stringRedisTemplate.delete(KEY_PREFIX + lockName);
}
}
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Autowired
private ISeckillVoucherService seckillVoucherService;
@Autowired
private RedisIdWorker redisIdWorker;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 秒杀
* @param voucherId
* @return
*/
@Override
public Result seckillVoucher(Long voucherId) {
// 1.查询优惠券
SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
// 2.判断秒杀是否已经开始
LocalDateTime now = LocalDateTime.now();
if(voucher.getBeginTime().isAfter(now)){
return Result.fail("秒杀尚未开始!");
}
// 3.判断秒杀是否已经结束
if(voucher.getEndTime().isBefore(now)){
return Result.fail("秒杀已经结束");
}
// 4.判断库存是否充足
if(voucher.getStock() <= 0){
return Result.fail("库存不足!");
}
Long userId = UserHolder.getUser().getId();
// 创建锁对象,注意锁的粒度,每个用户一把锁,所以拼接userId
SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "order:" + userId);
// 尝试获取锁
boolean isLock = lock.tryLock(1200);
// 判断是否获取锁成功
if(!isLock){
// 获取锁失败,返回错误或重试
return Result.fail("一人一单!");
}
/*// 锁粒度降低,性能更好,intern()保证只锁相同的用户id值
synchronized (userId.toString().intern()) {*/
try {
// 获取代理对象(事务)
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
} finally {
// 释放锁
lock.unlock();
}
// }
}
@Transactional
public Result createVoucherOrder(Long voucherId) {
// 5.一人一单
Long userId = UserHolder.getUser().getId();
// 5.1.查询订单
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
// 5.2.判断该用户是否下过单
if(count > 0) {
return Result.fail("一人一购!");
}
// 6.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).gt("stock", 0)
.update();
if(!success){
return Result.fail("库存不足!");
}
// 7.生成订单
VoucherOrder order = new VoucherOrder();
// 7.1.生成订单id(全局唯一id)
long orderId = redisIdWorker.nextId("order");
order.setId(orderId);
// 7.2.生成用户id
order.setUserId(userId);
// 7.3.生成优惠券id
order.setVoucherId(voucherId);
// 7.4.保存订单
save(order);
// 8.返回订单id(获取支付链接,用户支付)
return Result.ok(orderId);
}
}
存在的问题:误删除
- 改进
/**
* 尝试获取锁
* @param timeoutSecond
* @return
*/
@Override
public boolean tryLock(long timeoutSecond) {
// 获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 获取锁
Boolean success = stringRedisTemplate.opsForValue().
setIfAbsent(KEY_PREFIX + lockName, threadId, timeoutSecond, TimeUnit.SECONDS);
// 防止自动拆箱空指针的安全问题
return BooleanUtil.isTrue(success);
}
/**
* 释放锁
*/
@Override
public void unlock() {
// 获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 获取锁中标识
String lockId = stringRedisTemplate.opsForValue().get(KEY_PREFIX + lockName);
// 判断标识是否一致
if(threadId.equals(lockId)){
// 释放锁
stringRedisTemplate.delete(KEY_PREFIX + lockName);
}
}
仍存在问题:
- 基于Lua脚本实现秒杀
-- 比较线程标识与锁中的线程标识是否一致
if(redis.call('get', KEYS[1]) == ARGV[1]) then
-- 释放锁
return redis.call('del', KEYS[1])
end
return 0
// 提前读取,提高性能
private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
static {
UNLOCK_SCRIPT = new DefaultRedisScript<>();
UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
UNLOCK_SCRIPT.setResultType(Long.class);
}
/**
* 释放锁
*/
@Override
public void unlock() {
// 调用Lua脚本
stringRedisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + lockName),
ID_PREFIX + Thread.currentThread().getId());
}
基于Redis的分布式锁存在的问题
☆基于Redisson的分布式锁
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.35.0</version>
</dependency>
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
// 配置类
Config config = new Config();
// 添加Redis地址,单点,集群:config.useClusterServers()
config.useSingleServer().setAddress("192.168.174.129:6379").setPassword("123456");
return Redisson.create(config);
}
}
// Redisson
RLock lock = redissonClient.getLock("lock:order:" + userId);
// 尝试获取锁
boolean isLock = lock.tryLock();
// 判断是否获取锁成功
if(!isLock){
// 获取锁失败,返回错误或重试
return Result.fail("一人一单!");
}
try {
// 获取代理对象(事务)
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
} finally {
// 释放锁
lock.unlock();
}
秒杀优化
基于JVM(BlockingQueue)优化:
存在的问题:OOM,消息一旦出故障
@Override
@Transactional
public void addSeckillVoucher(Voucher voucher) {
// 保存优惠券
save(voucher);
// 保存秒杀信息
SeckillVoucher seckillVoucher = new SeckillVoucher();
seckillVoucher.setVoucherId(voucher.getId());
seckillVoucher.setStock(voucher.getStock());
seckillVoucher.setBeginTime(voucher.getBeginTime());
seckillVoucher.setEndTime(voucher.getEndTime());
seckillVoucherService.save(seckillVoucher);
// 保存秒杀库存到Redis中
stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY + voucher.getId(), voucher.getStock().toString());
}
@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Autowired
private ISeckillVoucherService seckillVoucherService;
@Autowired
private RedisIdWorker redisIdWorker;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Resource
private RedissonClient redissonClient;
// lua脚本:判断是否具有秒杀资格
private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
static {
SECKILL_SCRIPT = new DefaultRedisScript<>();
SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
SECKILL_SCRIPT.setResultType(Long.class);
}
// 代理对象
private IVoucherOrderService proxy;
// 阻塞队列
private BlockingQueue<VoucherOrder> ORDER_TASKS = new ArrayBlockingQueue<>(1024 * 1024);
// 完成异步下单的线程
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
// 异步下单在初始化完就执行
@PostConstruct
private void init(){
SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}
// 下单任务内部类
private class VoucherOrderHandler implements Runnable{
// 完成异步下单
@Override
public void run() {
while (true){
try {
// 1.获取队列中的订单信息
VoucherOrder voucherOrder = ORDER_TASKS.take();
// 2.创建订单
handleVoucherOrder(voucherOrder);
} catch (InterruptedException e) {
log.error("处理订单异常", e);
}
}
}
}
private void handleVoucherOrder(VoucherOrder voucherOrder) {
// 创建锁对象
RLock lock = redissonClient.getLock("lock:order:" + voucherOrder.getUserId());
// 获取锁
boolean isLock = lock.tryLock();
// 判断是否获取锁成功
if(!isLock){
// 获取锁失败,重试或返回错误细信息
log.error("一人一单!");
return;
}
try {
// 保存订单
proxy.createVoucherOrder(voucherOrder);
} finally {
// 释放锁
lock.unlock();
}
}
/**
* 秒杀
* @param voucherId
* @return
*/
@Override
public Result seckillVoucher(Long voucherId) {
// 获取用户
Long userId = UserHolder.getUser().getId();
// 1.执行lua脚本
Long longResult = stringRedisTemplate.execute(
SECKILL_SCRIPT,
Collections.emptyList(),
voucherId.toString(), userId.toString());
// 2.判断结果是否为0
int result = longResult.intValue();
// 2.1.结果不为0,无购买资格,返回错误信息
if(result != 0){
return Result.fail(result == 1 ? "库存不足!" : "一人一单!");
}
// 2.2.结果为0,有购买资格,把下单信息保存到阻塞队列
// 2.3.生成订单
VoucherOrder voucherOrder = new VoucherOrder();
// 2.4.生成订单id(全局唯一id)
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
// 2.5.生成用户id
voucherOrder.setUserId(userId);
// 2.6.生成优惠券id
voucherOrder.setVoucherId(voucherId);
// 2.7.放入阻塞队列
ORDER_TASKS.add(voucherOrder);
// 3.获取代理对象
proxy = (IVoucherOrderService) AopContext.currentProxy();
// 4.返回订单id
return Result.ok(orderId);
}
@Transactional
public void createVoucherOrder(VoucherOrder voucherOrder) {
// 5.一人一单
Long userId = voucherOrder.getUserId();
Long voucherId = voucherOrder.getVoucherId();
// 5.1.查询订单
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
// 5.2.判断该用户是否下过单
if(count > 0) {
log.error("一人一单!");
}
// 6.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).gt("stock", 0)
.update();
if(!success){
log.error("库存不足!");
}
// 7.4.保存订单
save(voucherOrder);
}
}
基于Redis-Stream优化
-- 1.参数列表
-- 1.1.优惠券id
local voucherId = ARGV[1]
-- 1.2.用户id
local userId = ARGV[2]
-- 1.3.订单id
local orderId = ARGV[3]
-- 2.数据key
-- 2.1.库存key
local stockKey = 'seckill:stock:' .. voucherId
-- 2.2.订单key
local orderKey = 'seckill:order:' .. voucherId
-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if tonumber(redis.call('get', stockKey)) <= 0 then
-- 3.1.1.库存不足,返回1
return 1
end
-- 3.2.判断用户是否下单
if redis.call('sismember', orderKey, userId) == 1 then
-- 3.3.存在,重复下单,返回2
return 2
end
-- 3.4.扣库存
redis.call('incrby', stockKey, -1)
-- 3.5.下单(保存用户)
redis.call('sadd', orderKey, userId)
-- 3.6.向stream.orders中添加消息
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
--redis.call('xgroup', 'create', 'stream.orders', 'g1', '0', 'mkstream')
return 0
@Slf4j
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private RedissonClient redissonClient;
// lua脚本:判断是否具有秒杀资格
private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
static {
SECKILL_SCRIPT = new DefaultRedisScript<>();
SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
SECKILL_SCRIPT.setResultType(Long.class);
}
// 代理对象
private IVoucherOrderService proxy;
// 完成异步下单的线程
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
// 异步下单在初始化完就执行
@PostConstruct
private void init(){
SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
}
private class VoucherOrderHandler implements Runnable{
private String QUEUE_NAME = "stream.orders";
// 完成异步下单
@Override
public void run() {
// 检查并创建消费者组,避免流不存在的问题
try {
stringRedisTemplate.opsForStream().createGroup(QUEUE_NAME, ReadOffset.latest(), "g1");
} catch (RedisSystemException e) {
// 如果组已存在,忽略该错误
if (e.getMessage().contains("BUSYGROUP")) {
log.info("Consumer group 'g1' already exists.");
} else {
throw e; // 其他异常继续抛出
}
}
while (true){
try {
// 1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS stream.orders >
List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
Consumer.from("g1", "c1"),
StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
StreamOffset.create(QUEUE_NAME, ReadOffset.lastConsumed())
);
// 2.判断消息获取是否成功
if (list == null || list.isEmpty()){
// 2.1.获取失败,继续下一次循环
continue;
}
// 3.解析消息中的订单信息
MapRecord<String, Object, Object> record = list.get(0);
Map<Object, Object> values = record.getValue();
VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);
// 4.创建订单
handleVoucherOrder(voucherOrder);
// 5.ACK确认 SACK stream.orders g1 id
stringRedisTemplate.opsForStream().acknowledge(QUEUE_NAME, "g1", record.getId());
}
catch (Exception e) {
log.error("处理订单异常", e);
handlePendingList();
}
}
}
// 处理PendingList中消息
private void handlePendingList() {
// 检查并创建消费者组,避免流不存在的问题
try {
stringRedisTemplate.opsForStream().createGroup(QUEUE_NAME, ReadOffset.latest(), "g1");
} catch (RedisSystemException e) {
// 如果组已存在,忽略该错误
if (e.getMessage().contains("BUSYGROUP")) {
log.info("Consumer group 'g1' already exists.");
} else {
throw e; // 其他异常继续抛出
}
}
while (true){
try {
// 1.获取PendingList中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 STREAMS streams.order 0
List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
Consumer.from("g1", "c1"),
StreamReadOptions.empty().count(1),
StreamOffset.create(QUEUE_NAME, ReadOffset.from("0"))
);
// 2.判断消息获取是否成功
if (list == null || list.isEmpty()){
// 2.1.获取失败,PendingList没有异常消息,结束循环
break;
}
// 3.解析消息中的订单信息
MapRecord<String, Object, Object> record = list.get(0);
Map<Object, Object> values = record.getValue();
VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);
// 4.创建订单
handleVoucherOrder(voucherOrder);
// 5.ACK确认 SACK stream.orders g1 id
stringRedisTemplate.opsForStream().acknowledge("s1", "g1", record.getId());
} catch (Exception e) {
log.error("处理PendingList订单异常", e);
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
}
private void handleVoucherOrder(VoucherOrder voucherOrder) {
// 创建锁对象
RLock lock = redissonClient.getLock("lock:order:" + voucherOrder.getUserId());
// 获取锁
boolean isLock = lock.tryLock();
// 判断是否获取锁成功
if(!isLock){
// 获取锁失败,重试或返回错误细信息
log.error("一人一单!");
return;
}
try {
// 保存订单
proxy.createVoucherOrder(voucherOrder);
} finally {
// 释放锁
lock.unlock();
}
}
/**
* 秒杀
* @param voucherId
* @return
*/
// Redis中的Stream实现消息队列
@Override
public Result seckillVoucher(Long voucherId) {
// 获取用户
Long userId = UserHolder.getUser().getId();
// 生成订单id(全局唯一id)
long orderId = redisIdWorker.nextId("order");
// 1.执行lua脚本
Long longResult = stringRedisTemplate.execute(
SECKILL_SCRIPT,
Collections.emptyList(),
voucherId.toString(), userId.toString(), String.valueOf(orderId)
);
// 2.判断结果是否为0
int result = longResult.intValue();
// 2.1.结果不为0,无购买资格,返回错误信息
if(result != 0){
return Result.fail(result == 1 ? "库存不足!" : "一人一单!");
}
// 3.获取代理对象
proxy = (IVoucherOrderService) AopContext.currentProxy();
// SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
// 4.返回订单id
return Result.ok(orderId);
}
@Transactional
public void createVoucherOrder(VoucherOrder voucherOrder) {
// 5.一人一单
Long userId = voucherOrder.getUserId();
Long voucherId = voucherOrder.getVoucherId();
// 5.1.查询订单
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
// 5.2.判断该用户是否下过单
if(count > 0) {
log.error("一人一单!");
}
// 6.扣减库存
boolean success = seckillVoucherService.update()
.setSql("stock = stock - 1")
.eq("voucher_id", voucherId).gt("stock", 0)
.update();
if(!success){
log.error("库存不足!");
}
// 7.保存订单
save(voucherOrder);
}
}
标签:return,两万,最全,private,public,voucherId,秒杀,voucher,id
From: https://blog.csdn.net/qq_63275583/article/details/141576292