精华内容
下载资源
问答
  • 我对生鲜电商秒杀系统文章的规划:从零开始打造简易秒杀系统:乐观锁防止超卖2.从零开始打造简易秒杀系统:令牌桶限流3. 从零开始打造简易秒杀系统:Redis 缓存4. 从零开始打造简易秒杀系统:消息队列异步处理订单...

    本文主要是通过实际代码讲解,帮助你一步步搭建一个简易的秒杀系统。从而快速的了解生鲜电商秒杀系统的主要难点,并且迅速上手实际项目。

    我对生鲜电商秒杀系统文章的规划:

    从零开始打造简易秒杀系统:乐观锁防止超卖

    2. 从零开始打造简易秒杀系统:令牌桶限流

    3. 从零开始打造简易秒杀系统:Redis 缓存

    4. 从零开始打造简易秒杀系统:消息队列异步处理订单

    秒杀系统

    秒杀系统介绍

    秒杀系统相信网上已经介绍了很多了,我也不想黏贴很多定义过来了。

    废话少说,秒杀系统主要应用在商品抢购的场景,比如:

    电商抢购限量商品

    卖周董演唱会的门票

    火车票抢座

    秒杀系统抽象来说就是以下几个步骤:

    用户选定商品下单

    校验库存

    扣库存

    创建用户订单

    用户支付等后续步骤…

    听起来就是个用户买商品的流程而已嘛,确实,所以我们为啥要说他是个专门的系统呢。。

    为什么要做所谓的“系统”

    如果你的项目流量非常小,完全不用担心有并发的购买请求,那么做这样一个系统意义不大。

    但如果你的系统要像12306那样,接受高并发访问和下单的考验,那么你就需要一套完整的流程保护措施,来保证你系统在用户流量高峰期不会被搞挂了。(就像12306刚开始网络售票那几年一样)

    这些措施有什么呢:

    严格防止超卖:库存100件你卖了120件,等着辞职吧

    防止黑产:防止不怀好意的人群通过各种技术手段把你本该下发给群众的利益全收入了囊中。

    保证用户体验:高并发下,别网页打不开了,支付不成功了,购物车进不去了,地址改不了了。这个问题非常之大,涉及到各种技术,也不是一下子就能讲完的,甚至根本就没法讲完。

    我们先从“防止超卖”开始吧

    毕竟,你网页可以卡住,最多是大家没参与到活动,上网口吐芬芳,骂你一波。但是你要是卖多了,本该拿到商品的用户可就不乐意了,轻则投诉你,重则找漏洞起诉赔偿。让你吃不了兜着走。

    不能再说下去了,我这篇文章可是打着实战文章的名头,为什么我老是要讲废话啊啊啊啊啊啊。

    上代码。

    说好的做“简易”的秒杀系统,所以我们只用最简单的SpringBoot项目

    建立“简易”的数据库表结构

    一开始我们先来张最最最简易的结构表,参考了其他人的生鲜电商系统的秒杀系统文章。

    等未来我们需要解决更多的系统问题,再扩展表结构。

    一张库存表stock,一张订单表stock_order

    -- ----------------------------

    -- Table structure forstock-- ----------------------------DROP TABLE IF EXISTS `stock`;

    CREATE TABLE `stock` (

    `id`int(11) unsigned NOT NULL AUTO_INCREMENT,

    `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',

    `count`int(11) NOT NULL COMMENT '库存',

    `sale`int(11) NOT NULL COMMENT '已售',

    `version`int(11) NOT NULL COMMENT '乐观锁,版本号',

    PRIMARY KEY (`id`)

    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------

    -- Table structure forstock_order-- ----------------------------DROP TABLE IF EXISTS `stock_order`;

    CREATE TABLE `stock_order` (

    `id`int(11) unsigned NOT NULL AUTO_INCREMENT,

    `sid`int(11) NOT NULL COMMENT '库存ID',

    `name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称',

    `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT'创建时间',

    PRIMARY KEY (`id`)

    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    通过HTTP接口发起一次购买请求

    Controller层代码

    提供一个HTTP接口: 参数为商品的Id

    @RequestMapping("/order/{sid}")

    @ResponseBodypublic String createWrongOrder(@PathVariable intsid) {

    LOGGER.info("购买物品编号sid=[{}]", sid);int id = 0;try{

    id=orderService.createWrongOrder(sid);

    LOGGER.info("创建订单id: [{}]", id);

    }catch(Exception e) {

    LOGGER.error("Exception", e);

    }returnString.valueOf(id);

    }

    Service层代码

    @Overridepublic int createOrder(int sid) throwsException {//校验库存

    Stock stock =checkStock(sid);//扣库存

    saleStock(stock);//创建订单

    int id =createOrder(stock);returnid;

    }private Stock checkStock(intsid) {

    Stock stock=stockService.getStockById(sid);if(stock.getSale().equals(stock.getCount())) {throw new RuntimeException("库存不足");

    }returnstock;

    }private intsaleStock(Stock stock) {

    stock.setSale(stock.getSale()+ 1);returnstockService.updateStockById(stock);

    }private intcreateOrder(Stock stock) {

    StockOrder order= newStockOrder();

    order.setSid(stock.getId());

    order.setName(stock.getName());int id =orderMapper.insertSelective(order);returnid;

    }

    发起并发购买请求

    我们通过JMeter(https://jmeter.apache.org/) 这个并发请求工具来模拟大量用户同时请求购买接口的场景。

    注意:POSTMAN并不支持并发请求,其请求是顺序的,而JMeter是多线程请求。希望以后PostMan能够支持吧,毕竟JMeter还在倔强的用Java UI框架。毕竟是亲儿子呢。

    如何通过JMeter进行压力测试,请参考百度。(基础知识的学习就不用我来说了。)

    我们在表里添加一个Iphone,库存100。(请忽略订单表里的数据,开始前我清空了)

    96d9bfd520422d92f8f6516c09048e89.png

    在JMeter里启动1000个线程,无延迟同时访问接口。模拟1000个人,抢购100个产品的场景。点击启动:

    你猜会卖出多少个呢,先想一想。。。

    答案是:

    卖出了14个,库存减少了14个,但是每个请求Spring都处理了,创建了1000个订单。

    我这里该夸Spring强大的并发处理能力,还是该骂MySQL已经是个成熟的数据库,却都不会自己锁库存?

    避免超卖问题:更新商品库存的版本号

    为了解决上面的超卖问题,我们当然可以在Service层给更新表添加一个事务,这样每个线程更新请求的时候都会先去锁表的这一行(悲观锁),更新完库存后再释放锁。可这样就太慢了,1000个线程可等不及。

    我们需要乐观锁。

    一个最简单的办法就是,给每个商品库存一个版本号version字段

    我们修改代码:

    Controller层

    /*** 生鲜电商--乐观锁更新库存

    *@paramsid

    *@return

    */@RequestMapping("/order/{sid}")

    @ResponseBodypublic String createOptimisticOrder(@PathVariable intsid) {intid;try{

    id=orderService.createOptimisticOrder(sid);

    LOGGER.info("购买成功,剩余库存为: [{}]", id);

    }catch(Exception e) {

    LOGGER.error("购买失败:[{}]", e.getMessage());return "购买失败,库存不足";

    }return String.format("购买成功,剩余库存为:%d", id);

    }

    Service层

    @Overridepublic int createOrder(int sid) throwsException {//校验库存

    Stock stock =checkStock(sid);//乐观锁更新库存

    saleStockOptimistic(stock);//创建订单

    int id =createOrder(stock);return stock.getCount() - (stock.getSale()+1);

    }private voidsaleStockOptimistic(Stock stock) {

    LOGGER.info("查询数据库,尝试更新库存");int count =stockService.updateStockByOptimistic(stock);if (count == 0){throw new RuntimeException("并发更新库存失败,version不匹配") ;

    }

    }

    Mapper

    update stocksale= sale + 1,

    version= version + 1,WHERE id= #{id,jdbcType=INTEGER}

    AND version= #{version,jdbcType=INTEGER}

    我们在实际减库存的SQL操作中,首先判断version是否是我们查询库存时候的version,如果是,扣减库存,成功抢购。如果发现version变了,则不更新数据库,返回抢购失败。

    发起并发购买请求

    这次,我们能成功吗?

    再次打开JMeter,把库存恢复为100,清空订单表,发起1000次请求。

    这次的结果是:

    卖出去了39个,version更新为了39,同时创建了39个订单。我们没有超卖,可喜可贺。

    由于并发访问的原因,很多线程更新库存失败了,所以在我们这种设计下,1000个人真要是同时发起购买,只有39个幸运儿能够买到东西,但是我们防止了超卖。

    手速快未必好,还得看运气呀!

    OK,今天先到这里,之后我们继续一步步完善这个生鲜秒杀系统,大家努力一起把事情做好.

    展开全文
  • 1 悲观锁解决方案悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。可以采用redis队列+mysql事务控制的方案,下面是流程图: mysql的执行代码:beginTranse(开启...

    1 悲观锁解决方案

    悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。可以采用redis队列+mysql事务控制的方案,下面是流程图:

    e00d503daee3bbc38acb31a76d7e4e08.png

    mysql的执行代码:

    beginTranse(开启事务)

    try{

    //quantity为请求减掉的库存数量

    $dbca->query('update s_store set amount = amount - quantity where postID = 12345');

    $result = $dbca->query('select amount from s_store where postID = 12345');

    if(result->amount < 0){

    throw new Exception('库存不足');

    }

    }catch($e Exception){

    rollBack(回滚)

    }

    commit(提交事务)

    先执行update锁住本条记录,这样就能保证其他线程执行不了更新操作,可以避免超扣现象。

    上述的方案的确解决了线程安全的问题,但是,别忘记,我们的场景是“高并发”。也就是说,会很多这样的修改请求,每个请求都需要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。针对这个问题我们稍微修改一下上面的场景,我们直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。下面是整个执行流程图:

    166e32d4244400a7ececd8f2e732b3d3.png

    2 乐观锁解决方案

    悲观锁的解决方案解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,系统处理队列内请求的速度根本无法和疯狂涌入队列中的数目相比,很可能一瞬间将队列内存“撑爆”,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。

    这个时候,我们就可以讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。

    乐观锁的执行流程如下:

    1eb8fb5d3da8059f436af6044daab096.png

    有很多软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。

    下面是利用Redis中的watch实现乐观锁的代码

    while (true) {

    System.out.println(Thread.currentThread().getName());

    jedis = RedisUtil.getJedis();

    try {

    jedis.watch("mykey");

    int stock = Integer.parseInt(jedis.get("mykey"));

    if (stock > 0) {

    Transaction transaction = jedis.multi();

    transaction.set("mykey", String.valueOf(stock - 1));

    List result = transaction.exec();

    if (result == null || result.isEmpty()) {

    // 可能是watch-key被外部修改,或者是数据操作被驳回

    System.out.println("Transaction error...");

    }

    } else {

    System.out.println("库存为0");

    break;

    }

    } catch (Exception e) {

    e.printStackTrace();

    RedisUtil.returnResource(jedis);

    }finally{

    RedisUtil.returnResource(jedis);

    }

    }

    参考资料:

    http://www.cnblogs.com/php5/p/4362244.html

    http://www.kuqin.com/shuoit/20141203/343669.html

    http://www.cnblogs.com/shihaiming/p/6062663.html

    展开全文
  • //利用redis 做分布式锁,此处实现需要根据实际场景进行优化. 好处是能分担数据库压力,但加锁的时间无法确定,需要另启线程进行延时处理//有个问题是 如果子线程延期6次后 主线程还未运行完毕 后续又会引发很多问题,...

    //利用redis 做分布式锁,此处实现需要根据实际场景进行优化. 好处是能分担数据库压力,但加锁的时间无法确定,需要另启线程进行延时处理//有个问题是 如果子线程延期6次后 主线程还未运行完毕 后续又会引发很多问题,这里可以 结合case1的乐观锁一起使用,用version字段双重保险,或许本来就应该这么做

    static ThreadPoolExecutor pool = new ThreadPoolExecutor(10,20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20));//这里最高并发限制50,我这里只有拿到锁了 才开启线程,早已有余,商品种类多,可以考虑加大队列/最大线程数

    static{

    pool.prestartAllCoreThreads();//预先创建核心线程

    }

    @GetMapping("/payII")public String testPayLockByRedis() throwsInterruptedException {//这里最好还是用userId

    String name =Thread.currentThread().getName();for (int i = 0; i < 5; i++) {

    Boolean lock= redisTemplate.opsForValue().setIfAbsent("1", name, 10000, TimeUnit.MILLISECONDS);if(lock) {

    MyRunnable m= newMyRunnable(name);try{

    pool.execute(m);

    Goods goods= goodsMapper.selectByPrimaryKey(1);int storage =goods.getGoodsStorage();

    goods.setGoodsStorage(storage- 1);if (goods.getGoodsStorage() >= 0) {

    goodsMapper.updateByPrimaryKey(goods);

    log.info("本次购买商品一件,剩余库存{}件", goods.getGoodsStorage());return "本次购买商品一件,剩余库存" + goods.getGoodsStorage() + "件";

    }else{

    log.info("库存不足");return "库存不足";

    }

    }catch(Exception e) {//这里抛出异常让事务回滚 , 异常部分让切面处理,优先级和事务一致,优先级一致事务先执行

    throw newRunTimeException(e);

    }finally{

    m.flag= false;//这里的处理是因为 子线程在延时6次后,没有中断主线程的运行(这里无法中断,线程之间的运行是独立的,子线程抛出异常无法被主线程捕获,至多让thread设置一个//UncaughtExceptionHandler,在子线程抛出异常后,子线程内部自己进行捕获处理逻辑,然而还是不能影响主线程),既然如此,存在30s过后主线程仍未执行完毕的可能性,此时锁已易主,如果未//和version字段一起做保险处理,建议抛出异常,回滚事务

    Object o = redisTemplate.opsForValue().get("1")if (o!=null &&name.equals(o.toString())) {

    redisTemplate.delete("1");

    }else{throw new RuntimeException("锁已失效")}

    }

    }else{continue;

    }

    }return "网络延迟,请稍后尝试";

    }private class MyRunnable implementsRunnable {boolean flag = true;int time; //延期次数

    String name; //线程name 用userId好些

    private MyRunnable(String name) {this.name =name;}

    @Overridepublic voidrun() {try{//给予一定的处理时间 再给任务做延时处理

    Thread.sleep(2000);while (flag && time++ < 6) {

    Object o= redisTemplate.opsForValue().get("1");//锁存在 且是自身加的锁 给锁延期

    if (o != null && o.toString().equals(name) &&flag) {//如果出现判定通过

    /*case1: 外面修改flag 这里延期成功 外面删除 并不影响

    case2: 外面修改flag 删除 还未往redis 重新set 这里就延期 也不影响

    case3: 外面修改flag 删除 其他用户往redis 重新set 这里再延期 好像也不会发生什么*/redisTemplate.expire("1", 10000, TimeUnit.MILLISECONDS);

    System.out.println("延时一次");

    }

    Thread.sleep(3000);

    }

    }catch(InterruptedException e) {

    e.printStackTrace();

    }

    }

    }

    展开全文
  • } } } /** * 超卖实现二 */ public function OverSoldFunctionWithRedisQueue() { $redis = new \Redis(); $redis->connect('127.0.0.1', 6379); $queueKey = 'queueKey'; if ($redis->rPop($queueKey)) { Goods...

    环境准备:mac,php7,redis,laravel

    新建个数据库表用来测试

    CREATE TABLE `goods_sold` (

    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,

    `goods_id` bigint(20) DEFAULT NULL,

    `num` bigint(20) DEFAULT NULL,

    PRIMARY KEY (`id`)

    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

    #插入一条数据

    INSERT INTO `goods_sold` (`goods_id`, `num`) VALUES (1, 100);

    使用redis watch 和 事务

    public function OverSoldFunctionWithRedisTransaction()

    {

    // todo 从数据库获取库存 这里忽略,直接写死一个 提前取出放入缓存中

    $total = 100;

    $redis = new \Redis();

    $redis->connect('127.0.0.1', 6379);

    $key = 'watchKey';

    $watchKey = $redis->get($key);

    if ($watchKey < $total) {

    $redis->watch($key);

    // 开启redis事务

    $redis->multi();

    // sleep(1);

    $redis->set($key, $watchKey + 1);

    $transResult = $redis->exec();

    if ($transResult) {

    GoodsSold::find(1)->decrement('num');

    exit('抢购成功');

    } else {

    exit('抢购失败,下次再来');

    }

    } else {

    exit('商品已抢光');

    }

    }

    ab压测看实际效果

    ab -n 1000 -c 100 -k http://laravel.tyl.com/over/sold

    5bef96e88d07

    压测数据

    重复测试需要删除redis值、同时更新数据库的库存总数和代码中一致(100)

    redis-cli -h 127.0.0.1 -p 6379

    127.0.0.1:6379> get watchKey

    "100"

    127.0.0.1:6379> del watchKey

    (integer) 1

    127.0.0.1:6379> get watchKey

    (nil)

    127.0.0.1:6379>

    使用List存储(队列)

    #先使用第一步取出库存放入队列中

    /**

    * 取出库存放入队列中

    */

    public function PushQueue()

    {

    $data = GoodsSold::find(1);

    $total = $data->num;

    if ($total > 0) {

    $redis = new \Redis();

    $redis->connect('127.0.0.1', 6379);

    $queueKey = 'queueKey';

    for ($i = 1; $i <= $total; $i++) {

    $redis->lPush($queueKey, $i);

    }

    }

    }

    /**

    * 超卖实现二

    */

    public function OverSoldFunctionWithRedisQueue()

    {

    $redis = new \Redis();

    $redis->connect('127.0.0.1', 6379);

    $queueKey = 'queueKey';

    if ($redis->rPop($queueKey)) {

    GoodsSold::find(1)->decrement('num');

    exit('抢购成功');

    } else {

    exit('商品已抢光');

    }

    }

    ab压测看实际效果

    ab -n 1000 -c 100 -k http://laravel.tyl.com/over/sold/2

    总结

    方案一和二都需要将库存数据提前同步到redis当中

    方案二使用链表效果更好

    展开全文
  • Java单体锁到分布式环境下锁的实践。超卖的第一种现象案例其实在电商业务场景中,会有一个这样让人忌讳的现象,那就是“超卖”,那么什么是超卖呢?举个例子,某商品的库存数量只有10件,最终却卖出了15件,...
  • 首先我们要知道超卖的原因是什么:超卖的原因主要是用户下的订单的数目和我们要促销的商品的数目不一致导致的,每次总是订单的数比我们的促销商品的数目要多。究其深层原因,是因为数据库底层的写操作和读操作可以...
  • 1 packagecom.github.distribute.lock....23 importjava.util.List;4 importjava.util.Set;5 importjava.util.concurrent.ExecutorService;6 importjava.util.concurrent.Executors;78 importredis.clients.jedis...
  • 前言在商品秒杀活动中,比如商品库存只有100,但是在抢购活动中可能有200人同时抢购,这样就出现了并发,在100件商品下单完成库存为0了还有可能继续下单成功,就出现了超卖。为了解决这个问题,今天我主要讲一下用...
  • } 完后利用redis的lpop或rpop对list进行裁剪,之前采用llen或incr的方式对数据进行判断,都会出现超卖的现象,所以这里使用lpop的逻辑解决了超卖的问题public function ru() { //判断计数器 if (Predis::getInstance()...
  • 过程需要检验库存是否足够,保证库存不被超卖。场景一:买家需要购买数量可以多件场景二:秒杀活动,到时间点只能购买一件目的防止相同用户重复下单检查库存准确数量防止扣错库存数量扣库存时性能效率提升、不阻塞...
  • 库存,不会有超卖问题,但会存在实际有库存,但是没有卖的情况 如果redis库存 > mysql库存,就会超卖超卖的订单,在出库的过程中会失败 这样总体不会出问题,mysql数据库层,保证库存最终不会出问题。 问题 数据库...
  • 希望以后PostMan能够支持吧,毕竟JMeter还在倔强的用Java UI框架。毕竟是亲儿子呢。 如何通过JMeter进行压力测试,请参考下文,讲的非常入门但详细,包教包会: https://www.cnblogs.com/stulzq/p/8971531.html 我们...
  • 抢购是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少(“超卖”问题)对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,...
  • @param value 当前时间+超时时间 * @return */ public boolean lock(String key, String value){ if (redisTemplate.opsForValue().setIfAbsent(key, value)) { //这个其实就是setnx命令,只不过在java这边稍有变化...
  • 解决超卖问题,常见的方式,利用redis 的原子性去递减;利用队列,队列入队计数。或者直接打到mysql 层。由mysql 保证不超卖,有几个玩法。利用属性不一样,挺有意思,记录下。首先,mysql 隔离级别是RR,或者是串行...
  • 我们项目中的抢购订单采用的是分布式锁来解决的,有一次,运营做了一个飞天茅台的抢购活动,库存100瓶,但是却超卖了100瓶!要知道,这个地球上飞天茅台的稀缺性啊!!! 事故定为P0级重大事故...只能坦然接受。...
  • 第一步:在减少库存的时候进行判断stock_count>0 ... @Mapper public interface GoodsDao { @Update("update seckill_goods set stock_count=stock_count-1 where goods_id=#{...以上2个步骤就能解决秒杀超卖问题
  • 亿级流量java高并发与网络编程实战99.1元(需用券)去购买 >背景在电商系统中买商品过程,先加入购物车,然后选中商品,点击结算,即会进入待支付状态,后续支付。过程需要检验库存是否足够,保证库存不被超卖。...
  • 作者:涛哥谈篮球来源:toutiao.com/i6836611989607809548问题描述在众多抢购活动中,在有限的商品数量的限制下如何保证抢购到商品的用户数不能大于商品数量,也就是不能出现超卖的问题;还有就是抢购时会出现大量...
  • 过程需要检验库存是否足够,保证库存不被超卖。场景一:买家需要购买数量可以多件场景二:秒杀活动,到时间点只能购买一件目的防止相同用户重复下单检查库存准确数量防止扣错库存数量扣库存时性能效率提升、不阻塞...
  • 一、刚来公司时间不长,看到公司原来的同事写了这样一段代码,下面贴出来: 1、这是在一个方法调用下面代码的部分: ...[java] view plain copy if (!this.checkSoldCountByRedisDat
  • @param value 当前时间+超时时间 * @return */ public boolean lock(String key, String value) { if (redisTemplate.opsForValue().setIfAbsent(key, value)) { //这个其实就是setnx命令,只不过在java这边稍有变化...
  • 经过前面的过滤,超卖的可能性比较低了提前将商品库存缓存起来,到下单购买的时候,用户购买了就减1,每次都通过库存缓存判断一下,如果为0就显示已抢完。 五、页面静态设计:尽量静态缓存化【CDN那些这里不做考虑】...
  • 我对生鲜电商秒杀系统文章的规划:从零开始打造简易秒杀系统:乐观锁防止超卖2.从零开始打造简易秒杀系统:令牌桶限流3. 从零开始打造简易秒杀系统:Redis 缓存4. 从零开始打造简易秒杀系统:消息队列异步处理订单...

空空如也

空空如也

1 2 3 4 5 ... 13
收藏数 255
精华内容 102
关键字:

java超卖

java 订阅