秒杀接口尝试

目录

接口构造

第一次尝试

再次改进

第二次尝试

第三次试想

一个github秒杀接口项目

第四次

第5次


接口构造

秒杀数量放在缓存,然后进行秒杀,数量减少成功之后,将用户信息放在阻塞队列,使用mq去拿到队列元素发送消息进行数据库添加秒杀记录

 

第一次尝试

使用redisTemplate INCR函数进行实现

当INCR的结果超过商品数量的时候不让用户秒杀

double result = redisTemplate.opsForValue().increment("1", 1);
//商品数量为1500
        if (result > 1500) {
            return -1;
        } else {
            try {
                arrayBlockingQueue.put(name);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return 1;

redis商品数量初始化的时候为0 ,每次秒杀加一

 

使用CountDownLoad进行模拟并发操作

CountDownLatch countDownLatch=new CountDownLatch(150);

150个并发,2000多个请求量,存放到队列

 

static ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue(100);

    @Override
    public Integer shop(String name) {
        double result = redisTemplate.opsForValue().increment("1", 1);
        if (result > 1500) {
            return -1;
        } else {
            try {
                arrayBlockingQueue.put(name);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        return 1;
    }

 

package com.example.demo;

import java.util.concurrent.CountDownLatch;

/**
 * Created on 2019/5/7.
 *
 * @author yangsen
 */
public class MyThread implements Runnable {

    Service service;

    private String i;

    CountDownLatch latch;

    @Override
    public void run() {
        service.shop("dajitui" + i);
        latch.countDown();
    }

    public MyThread(String i, CountDownLatch latch,Service service) {
        this.service=service;
        this.latch = latch;
        this.i = i;
    }
}

 

最后执行到mq发送大概在0.5秒,相当快

 

github地址https://github.com/dajitui/shop

 

sql

CREATE TABLE `shop` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL,
  `count` int(11) DEFAULT NULL,
  `point` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=667 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

再次改进

当并发量到达1w的时候,报出redis连接数不够,所以我们对程序再次改写

@RequestMapping("/shop")
    public void shop() throws InterruptedException {
        long time=System.nanoTime();

        for(int i=0;i<10009;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    IServiceImpl.concurrentLinkedQueue.offer("1");
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        System.out.println(IServiceImpl.concurrentLinkedQueue.isEmpty());
        while (true){
            if(!IServiceImpl.concurrentLinkedQueue.isEmpty()){
                double result = redisTemplate.opsForValue().increment("1", 1);
                if (result > 10009) {
                    break;
                } else {
                    System.out.println("抢购"+atomicLong.incrementAndGet());
                }
            }
        }
        long time1=System.nanoTime();
        System.out.println("使用increment消耗时间"+(time1-time));
    }
public static ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue();

 

先把所有请求放在一个无界队列里面,然后再平稳的输出,再使用redis进行INCR操作,其实还可以再优化的,就是当一个因为数量不足失败之后,后面的可以都标识为失败。后续如果说支付不成功,我们再增加库存。

 

浏览器先访问http://localhost:8080/start进行redis数量重置,访问http://localhost:8080/shop进行操作,模拟1w并发,最后执行完大概在7秒多

github地址https://github.com/dajitui/shop1

 

第二次尝试

 

使用redis分布锁,每次请求都获取锁,得到锁再操作,没有的话循环去请求锁...

结果很蛋疼,因为变成了单线程去处理秒杀了(分布锁),虽然redis是单线程的,但是人家INCR函数使用时间肯定比你获取锁,执行本地操作,释放锁快,所以分布锁尝试失败

 

第三次试想

 

一般逻辑都是去减库存,所以我们可以使用lua脚本来控制原子性操作,redis执行lua脚本,查询数量之后如果符合数量>0原子性将它数量减一。

这个目前没有完成,大家可以在评论中提供参考路径哦,让我学习学习

 

最近看了一个github秒杀接口的项目:

大概使用了很多个方法:

1.悲观锁

2.乐观锁

3.jvm锁(ReentranLock)

4.AOP+jvm锁

5.进程队列然后再进行消费

6.Disruptor方法

https://gitee.com/52itstyle/spring-boot-seckill

 

个人看法:1,2点只适合小型并发,扛不住大型并发。第三种据作者反应会出现超卖的情况,可能是事务还没提交,就把锁释放导致的。而第四种可以解决第3种的问题。第5种其实在我个人的第一次尝试的改进使用了,效果算不错。第6种后面再讲。

 

第四次

这一次是看别人的代码,上菜(上面的AOP+jvm锁)

@Servicelock
@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;
    } 
}

其实也没有什么。就是在程序的开始还有结束获取锁,还有释放锁。

 

第5次

重量级出场Disruptor

这个我也是第一次接触

Martin Fowler在自己网站上写了一篇LMAX架构的文章,在文章中
他介绍了LMAX是一种新型零售金融交易平台,它能够以很低的延迟
产生大量交易。这个系统是建立在JVM平台上,其核心是一个业务
逻辑处理器,它能够在一个线程里每秒处理6百万订单。业务逻辑处
理器完全是运行在内存中,使用事件源驱动方式。业务逻辑处理器
的核心是Disruptor。

http://ifeve.com/disruptor/

 

大家可以看下Disruptor具体的实现,依赖RingBuffer环形的数据结构,Disruptor通过生成者还有消费者去获取共享数据。

为啥他如此优秀?这里只简单说下,它是一个无锁的队列,每次新增都会添加一个版本号,使用了cas去控制并发。好像牛逼的都是在数据结构还有使用cas实现的,比如concurrent

 

个人从之前的github项目里面抽出这一部分进行测试性能

https://github.com/dajitui/Disruptor

 

创建了一个生成者,一个消费者,1w并发,处理完是在120秒

 

总结

拿第一种跟第5种比较,第5种启动之后cpu很高,而且执行的时间比第一种还长。我们可以使用第一种来进行后续的改进,第5种对系统要求相当高

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值