lua:
local voucherId=ARGV[1]
local userId=ARGV[2]
local orderId=ARGV[3]
local stockKey='seckill:stock:'..voucherId
local orderKey='seckill:order:'..voucherId
if(tonumber(redis.call('get',stockKey))<=0) then
return 1
end
if(redis.call('sismember',orderKey,userId)==1) then
return 2
end
redis.call('incrby',stockKey,-1)
redis.call('sadd',orderKey,userId)
redis.call('xadd','stream.orders','*','userId',userId,'voucherId',voucherId,'id',orderId)
return 0
VoucherOrderServiceImpl:
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService seckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker;
@Resource
private StringRedisTemplate stringRedisTemplate;
IVoucherOrderService proxy_plus;
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 static final ExecutorService SECKILL_ORDER_EXECUTOR=Executors.newSingleThreadExecutor();
@PostConstruct
private void init(){
SECKILL_ORDER_EXECUTOR.submit(new VoucherHandler());
}
private class VoucherHandler implements Runnable{
@Override
public void run(){
while(true){
try {
//从stream队列里取出一个消息: xreadgroup group g1 c1 count 1 block 2000 streams stream.orders >
List<MapRecord<String, Object, Object>> readlist = stringRedisTemplate.opsForStream().read(
Consumer.from("g1", "c1"),
StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
StreamOffset.create("stream.orders", ReadOffset.lastConsumed())
);
//判断消息是否为空
if(readlist==null || readlist.isEmpty()){
continue;
}
//不为空的话用此消息生成预备订单
MapRecord<String, Object, Object> record = readlist.get(0);
Map<Object, Object> values = record.getValue();
VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);
//处理预备订单
proxy_plus.createVoucherOrder_plus(voucherOrder);
//确认消息:sack stream.orders g1 id
stringRedisTemplate.opsForStream().acknowledge("stream.orders","g1",record.getId());
} catch (Exception e) {
log.error("处理订单异常...");
handlePendingList();
}
}
}
private void handlePendingList() {
while (true) {
try {
//从pendinglist中里取出一个消息:xreadgroup group g1 c1 count 1 block 2000 streams stream.orders 0
List<MapRecord<String, Object, Object>> readlist = stringRedisTemplate.opsForStream().read(
Consumer.from("g1", "c1"),
StreamReadOptions.empty().count(1),
StreamOffset.create("stream.orders", ReadOffset.from("0"))
);
//判断消息是否为空,为空说明没有异常消息(即消息都读完确认了,已经移出了pendinglist)
if (readlist == null || readlist.isEmpty()) {
break;
}
//不为空的话用此消息生成预备订单
MapRecord<String, Object, Object> record = readlist.get(0);
Map<Object, Object> values = record.getValue();
VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(values, new VoucherOrder(), true);
//处理预备订单
proxy_plus.createVoucherOrder_plus(voucherOrder);
//确认消息:sack stream.orders g1 id
stringRedisTemplate.opsForStream().acknowledge("stream.orders", "g1", record.getId());
} catch (Exception e) {
log.error("处理订单异常...");
}
}
}
}
@Transactional
public void createVoucherOrder_plus(VoucherOrder voucherOrder){
seckillVoucherService.update()
.setSql("stock=stock-1")
.eq("voucher_id", voucherOrder.getVoucherId()).gt("stock",0)
.update();
save(voucherOrder);
}
@Override
public Result seckillVoucher(Long voucherId) {
long orderId = redisIdWorker.nextId("order");
//执行lua
Long userId = UserHolder.getUser().getId();
Long result = stringRedisTemplate.execute(
SECKILL_Script,
Collections.emptyList(),
voucherId.toString(), userId.toString(), String.valueOf(orderId)
);
int r=result.intValue();
//不能购买
if(r==1){
return Result.fail("库存不足!");
}
if(r==2){
return Result.fail("不能重复购买!");
}
//能购买
proxy_plus=(IVoucherOrderService) AopContext.currentProxy();
return Result.ok(orderId);
}
}