常规秒杀
来源于:https://gitee.com/52itstyle/spring-boot-seckill
Aop锁
使用ReentrantLock重入锁,事物提交后再释放锁。
- aop实现
@Component
@Scope
@Aspect
@Order(1)
//order越小越是最先执行,但更重要的是最先执行的最后结束。order默认值是2147483647
public class LockAspect {
/**
* 思考:为什么不用synchronized
* service 默认是单例的,并发下lock只有一个实例
*/
private static Lock lock = new ReentrantLock(true);//互斥锁 参数默认false,不公平锁
//Service层切点 用于记录错误日志
@Pointcut("@annotation(com.itstyle.seckill.common.aop.Servicelock)")
public void lockAspect() {
}
@Around("lockAspect()")
public Object around(ProceedingJoinPoint joinPoint) {
lock.lock();
Object obj = null;
try {
obj = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException();
} finally{
lock.unlock();
}
return obj;
}
}
- 应用部分
@Servicelock
@Transactional(rollbackFor = Exception.class)
public Result startSeckilAopLock(long seckillId, long userId) {
//来自码云码友<马丁的早晨>的建议 使用AOP + 锁实现
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
Long number = ((Number) object).longValue();
if(number>0){
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=?";
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
SuccessKilled killed = new SuccessKilled();
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState(Short.parseShort(number+""));
killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
dynamicQuery.save(killed);
}else{
return Result.error(SeckillStatEnum.END);
}
return Result.ok(SeckillStatEnum.SUCCESS);
}
数据库悲观锁
- 基于数据库悲观锁实现,更新加锁并判断剩余数量。
/**
* SHOW STATUS LIKE 'innodb_row_lock%';
* 如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result startSeckilDBPCC_TWO(long seckillId, long userId) {
/**
* 单用户抢购一件商品没有问题、但是抢购多件商品不建议这种写法 UPDATE锁表
*/
String nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=? AND number>0";
int count = dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
if(count>0){
SuccessKilled killed = new SuccessKilled();
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState((short)0);
killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
dynamicQuery.save(killed);
return Result.ok(SeckillStatEnum.SUCCESS);
}else{
return Result.error(SeckillStatEnum.END);
}
}
数据库乐观锁
基于数据库乐观锁实现,先查询商品版本号,然后根据版本号更新,判断更新数量。少量用户抢购的时候会出现 少买 的情况。
@Transactional(rollbackFor = Exception.class)
public Result startSeckilDBOCC(long seckillId, long userId, long number) {
Seckill kill = seckillRepository.findOne(seckillId);
/**
* 剩余的数量应该要大于等于秒杀的数量
*/
if(kill.getNumber()>=number){
//乐观锁
String nativeSql = "UPDATE seckill SET number=number-?,version=version+1 WHERE seckill_id=? AND version = ?";
int count = dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{number,seckillId,kill.getVersion()});
if(count>0){
SuccessKilled killed = new SuccessKilled();
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState((short)0);
killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
dynamicQuery.save(killed);
return Result.ok(SeckillStatEnum.SUCCESS);
}else{
return Result.error(SeckillStatEnum.END);
}
}else{
return Result.error(SeckillStatEnum.END);
}
}
进程内队列
基于进程内队列 LinkedBlockingQueue 实现,同步消费
- 队列结构
/** 用于多线程间下单的队列 */
static BlockingQueue<SuccessKilled> blockingQueue = new LinkedBlockingQueue<SuccessKilled>(QUEUE_MAX_SIZE);
/**
* 生产入队
* @param kill
* @throws InterruptedException
* add(e) 队列未满时,返回true;队列满则抛出IllegalStateException(“Queue full”)异常——AbstractQueue
* put(e) 队列未满时,直接插入没有返回值;队列满时会阻塞等待,一直等到队列未满时再插入。
* offer(e) 队列未满时,返回true;队列满时返回false。非阻塞立即返回。
* offer(e, time, unit) 设定等待的时间,如果在指定时间内还不能往队列中插入数据则返回false,插入成功返回true。
*/
public Boolean produce(SuccessKilled kill) {
return blockingQueue.offer(kill);
}
/**
* 消费出队
* poll() 获取并移除队首元素,在指定的时间内去轮询队列看有没有首元素有则返回,否者超时后返回null
* take() 与带超时时间的poll类似不同在于take时候如果当前队列空了它会一直等待其他线程调用notEmpty.signal()才会被唤醒
*/
public SuccessKilled consume() throws InterruptedException {
return blockingQueue.take();
}
- 线程消费
@Override
public void run(ApplicationArguments var){
new Thread(() -> {
LOGGER.info("提醒队列启动成功");
while(true){
try {
//进程内队列
SuccessKilled kill = SeckillQueue.getSkillQueue().consume();
if(kill!=null){
Result result =
seckillService.startSeckilAopLock(kill.getSeckillId(), kill.getUserId());
if(result.equals(Result.ok(SeckillStatEnum.SUCCESS))){
LOGGER.info("用户:{}{}",kill.getUserId(),"秒杀成功");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
Disruptor队列
基于高性能队列 Disruptor实现,同步消费
- 工具类
public class DisruptorUtil {
static Disruptor<SeckillEvent> disruptor;
static{
SeckillEventFactory factory = new SeckillEventFactory();
int ringBufferSize = 1024;
ThreadFactory threadFactory = runnable -> new Thread(runnable);
disruptor = new Disruptor<>(factory, ringBufferSize, threadFactory);
disruptor.handleEventsWith(new SeckillEventConsumer());
disruptor.start();
}
public static void producer(SeckillEvent kill){
RingBuffer<SeckillEvent> ringBuffer = disruptor.getRingBuffer();
SeckillEventProducer producer = new SeckillEventProducer(ringBuffer);
producer.seckill(kill.getSeckillId(),kill.getUserId());
}
}
- 生产者
public class SeckillEventProducer {
private final static EventTranslatorVararg<SeckillEvent> translator = (seckillEvent, seq, objs) -> {
seckillEvent.setSeckillId((Long) objs[0]);
seckillEvent.setUserId((Long) objs[1]);
};
private final RingBuffer<SeckillEvent> ringBuffer;
public SeckillEventProducer(RingBuffer<SeckillEvent> ringBuffer){
this.ringBuffer = ringBuffer;
}
public void seckill(long seckillId, long userId){
this.ringBuffer.publishEvent(translator, seckillId, userId);
}
}
- 消费者
public class SeckillEventConsumer implements EventHandler<SeckillEvent> {
private final static Logger LOGGER = LoggerFactory.getLogger(SeckillEventConsumer.class);
private ISeckillService seckillService = (ISeckillService) SpringUtil.getBean("seckillService");
@Override
public void onEvent(SeckillEvent seckillEvent, long seq, boolean bool) {
Result result =
seckillService.startSeckilAopLock(seckillEvent.getSeckillId(), seckillEvent.getUserId());
if(result.equals(Result.ok(SeckillStatEnum.SUCCESS))){
LOGGER.info("用户:{}{}",seckillEvent.getUserId(),"秒杀成功");
}
}
}