• RocketMQ学习(3) 秒杀实战


    学习完RocketMQ的用法,现在用它来做一个简单的秒杀项目练练手。

    关于秒杀,我之前其实有专门的学习过其中的一些业务逻辑和常见问题,我在这篇博客中有写过多并发场景下的秒杀场景,需要考虑哪些问题?也可以学习一下

    除了RocketMQ,本文还需要会springBoot + Redis + Mysql的基本使用。

    前置知识

    QPS、jmeter压测

    QPS:每秒处理请求的数量。tomcat一般QPS是多少呢?大家说的500?我们看一下tomcat默认是多少个线程:

    在这里插入图片描述

    boot 默认是200个线程 假设一个请求50ms 1个线程1s能处理20次 20*200 =4000qps
    4000也是理论数值,线程切换需要时间,请求进来时的处理也需要时间,所以是<4000的

    我们启动个boot服务,写两个接口测试一下:

    @RestController
    public class QpsTestController {
       
    
        /**
         * 
         * @return
         */
        @GetMapping("test")
        public String qpsTest() {
       
            return "ok";
        }
    
    
        @GetMapping("test2")
        public String qpsTest2() {
       
            // dosth. 处理处理 操作数据库 操作redis ....  假设为50ms
            try {
       
                Thread.sleep(50L);
            } catch (InterruptedException e) {
       
                e.printStackTrace();
            }
            return "ok";
        }
    }
    

    用jmeter压测一下,这个软件用过吧,没用过也没关系,很好上手,下载后在bin目录,双击jmeter.bat启动,Zoom in放大字体,还可以选中文。

    在这里插入图片描述

    在这里插入图片描述

    在默认的Test Plan加一个线程组:

    在这里插入图片描述

    5000个线程1s内循环2次,所以1s内有10000个线程。

    在这里插入图片描述

    然后Test Plan添加Http请求:

    在这里插入图片描述

    在这里插入图片描述

    再搞个采样的数据监测器,取结果。在Http请求里加:

    在这里插入图片描述

    然后启动就会开始打请求了。当然我们这里压测和被压测的都在同一台电脑肯定是不准的。
    在这里插入图片描述

    像我这种异常率很高的,那数据没有参考价值,qps都测出来15000了,异常率要低于0.5%才有参考价值,要点击下面的清除按钮再启动,一定要清楚,否则指标是平均值。
    在这里插入图片描述

    然后是test2接口测试,因为是一个电脑测试,异常率太高,正常测试的话就是小于4000的一个值了。

    在这里插入图片描述

    在这里插入图片描述


    根据经验的话,如果tomcat的线程数设置为默认的200,如果一个接口的处理时间很短,在10ms内,那么QPS大概在4k左右,如果接口时间在50ms内,那么QPS大概在2k左右。直观的感受就是处理时间越短,QPS越大,可以自己尝试一下,然后也可以修改一下tomcat线程数尝试一下。




    一般的秒杀架构如何设计


    根据上一节的经验法则,一个tomcat也就支持2k的qps,代码写得不好可能2k都没有。
    那如果是1w/s的请求来了怎么办呢?可以集群部署tomcat,然后用nginx来做负载均衡,但是nginx本身也就顶得住5~10w/s的请求,一般也就5w左右,所以一个nginx也就能配大概25个tomcat。

    在这里插入图片描述



    那么如果请求QPS大于5w,是20w/s咋办呢,底层是可以堆无数个tomcat,但是nginx也顶不住了,nginx是中心化的方案,所有的请求都要经过它去做负载均衡。而且nginx也是做不了集群的,中心化怎么做集群,就算做了也是需要一个nginx来顶住所有请求的,总得有个入口吧?

    在这里插入图片描述



    上面的方案不合理,那20w/s的请求咋办呢?入口让硬件来顶,对外提供一个虚拟ip,用Lvs或者F5这样的机器做入口,它能顶30w/s的QPS。

    在这里插入图片描述

    LVS是一个开源的负载均衡软件,基于Linux内核,可以将网络流量分发到多个后端服务器,实现高可用性和高性能。
    F5是一种硬件负载均衡设备,广泛应用于企业级网络环境中。性能要比LVS更高,价格更贵。



    那么如果QPS是100w/s呢?现在还没有能顶100w/s的硬件,现在大型的购物平台高峰期可能都是千万级的QPS了,怎么弄呢?做流量分发,比如优惠券,可以分成不同地区的。具体实现可以使用域名–DNS轮询策略,一个域名下面对应了很多个IP,不同的IP对应不同地区的机房,把流量分散开来。

    在这里插入图片描述



    这里再补充我实习接触到的一些延伸的知识,有兴趣可以查资料深入了解。VIPServer是阿里中间件团队开发的一个基于P2P模式的七层负载均衡产品,它旨在解决网络中间层负载均衡的需求,并替代传统的硬件和软件解决方案如F5和LVS。

    P2P架构,即对等网络架构(Peer-to-Peer),是一种分布式网络架构,其中所有节点都是对等的,每个节点既可以作为客户端也可以作为服务器。没有单一的集中式服务器,所有节点相互通信,共同承担网络任务。在传统的集中式架构中,所有请求都会集中到一个或几个中心点上(如服务器),而P2P架构则避免了这种瓶颈。每个节点可以独立处理和转发请求,实现了负载的均衡分布。

    VIPServer如何抗住几十万QPS?

    • 分布式节点:在P2P架构下,VIPServer由多个分布式节点组成,这些节点均匀地分布在网络中。每个节点都具有处理和转发请求的能力。当一个客户端发出请求,它可以被网络中的任何一个VIPServer节点接收到,而不是集中到某一台服务器上。
    • 请求分担:在VIPServer系统中,每个节点都会共享负载。这样,单个节点不会成为瓶颈或单点故障。例如,如果一个VIPServer节点当前负载很高,它会自动将一部分流量转发给其他负载较低的节点来处理,从而实现高级别的负载均衡。
    • 动态域名解析:VIPServer依赖动态域名解析(DNS)来实现智能流量调度。DNS根据当前的网络状况、节点健康状态和负载情况,将客户端请求解析到最合适的VIPServer节点。
      这样,DNS服务能够根据实时情况,将流量分散到不同的VIPServer节点上,从而避免某个节点负载过高导致系统崩溃。
    • 智能调度策略:VIPServer使用高级的智能调度策略,根据地理位置、业务单元、机房位置等因素来选择最优路径,并分发请求。这减少了网络延迟和带宽消耗,提高了响应速度和系统吞吐量。
      例如,同一业务单元的请求优先分配到同一个区域的节点处理,降低跨区域通信的延迟。
    • 多级容灾体系:VIPServer建立了多级容灾体系,保证即使一个或多个节点出现故障,系统仍然可以正常运行。通过健康监测和自动故障转移机制,实时检测节点状态,动态调整负载分配,确保高可用性。
    • 并行处理能力:每个VIPServer节点能够充分利用现代服务器的多核多线程能力,并行处理大量请求。利用异步I/O和事件驱动模型,可以进一步提高请求处理效率。

    总结 为了应对超高QPS,VIPServer的设计采取了以下策略:
    分布式多节点架构:避免单点瓶颈,人为地增加了系统的弹性和容错能力。
    实时流量调度:利用动态域名解析和智能调度策略,将流量动态分配到不同的节点上,保证每个节点的负载都是均衡的。
    高效并行处理:每个节点内部利用多核多线程、异步I/O和事件驱动模型,实现高并发请求处理能力。



    然后我们写这个秒杀项目还接触不到这些架构设计,写这个秒杀项目的核心目的还是一开始的问题,尽量把每一个接口请求的处理时间给压缩,提高单tomcat的QPS,承受更大的并发量。那如何优化呢——关键是能异步就异步,这就是学完了RocketMQ来写秒杀的原因。

    下图是一些后端代码层面优化的一些方法,其实优化方法有很多很多,前端、网络传输、服务端、应用层各种各样的优化方法,比如如果真的做秒杀,前端也会帮我们做限流分流,比如前端可能会搞人际校验,验证码、拼图之类的。

    在这里插入图片描述





    本项目结构设计

    那最后说了这么多,我们项目的结构如何设计,如下图,买东西要先判断库存,如果直接查数据库效率太低,加入redis基于内存提高IO效率,秒杀开始之前数据库的库存数量先预同步到redis,用redis做预扣减,redis理论支持11w读、8w写,真正能达到8w读、6w写。

    库存减成功了之后要操作数据库,但是商品种类很多,比如10w件商品在秒杀,操作数据库太慢了,可以将这个操作异步化。用户下单是一个服务(seckill-web),数据库操作(seckill-service)是一个服务,通过rocketmq来解耦。

    然后消息消费,即数据库操作也是需要时间的,所以用户也不能立马看到效果,使用异步通知,或者让用户一会再去查看结果。

    然后还有一个问题,秒杀场景经常有一个逻辑是一个用户只能对一件商品抢一次,那么这个工作主要是去重,可以通过mysql去重表,但是它不适合大量并发的场景,所以还是通过redis,它的setnx命令也天然支持去重。

    在这里插入图片描述



    综上,技术选型:springBoot + Redis + Mysql + RocketMq + Security(后期登录可以加上) …
    设计: (抢优惠券…)
    设计seckill-web接收处理秒杀请求
    设计seckill-service处理秒杀真实业务的

    部署细节:
    用户量: 50w
    日活量: 1w-2w 1%-5%
    qps: 2k+

    如何知道自己的接口有多少qps。方法一:代码统计,自己log打印,统计同一分钟内有多少请求,分布式则=机器台数*单台QPS
    方法二,看nginx(access.log),它会记录所有请求,可以写个脚本统计一分钟内某个接口被访问了多少次
    方法三,市面上一些监控工具比如阿尔萨斯
    在这里插入图片描述

    几台服务器(什么配置):8C16G 4台 seckill-web : 4台 seckill-service 2台
    带宽: 100M

    技术要点:
    1.通过redis的setnx对用户和商品做去重判断,防止用户刷接口的行为
    2.每天晚上8点通过定时任务 把mysql中参与秒杀的库存商品,同步到redis中去,做库存的预扣减,提升接口性能
    3.通过RocketMq消息中间件的异步消息,来将秒杀的业务异步化,进一步提升性能
    4.seckill-service使用并发消费模式,并且设置合理的线程数量,快速处理队列中堆积的消息
    5.使用redis的分布式锁+自旋锁,对商品的库存进行并发控制,把并发压力转移到程序中和redis中去,减少db压力
    6.使用声明式事务注解Transactional,并且设置异常回滚类型,控制数据库的原子性操作
    7.使用jmeter压测工具,对秒杀接口进行压力测试,在8C16G的服务器上,qps2k+,达到压测预期
    8.使用sentinel的热点参数限流规则,针对爆款商品和普通商品的区别,区分限制



    数据库准备

    先准备一个数据库,设计了一个商品表和订单表,用户表没有设计问题不大后面传进来就行了。

    在这里插入图片描述

    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    -- ----------------------------
    -- Table structure for goods
    -- ----------------------------
    DROP TABLE IF EXISTS `goods`;
    CREATE TABLE `goods`
    (
        `id`          int(11)                                                       NOT NULL AUTO_INCREMENT,
        `goods_name`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
        `price`       decimal(10, 2)                                                NULL DEFAULT NULL,
        `stocks`      int(255)                                                      NULL DEFAULT NULL,
        `status`      int(255)                                                      NULL DEFAULT NULL,
        `pic`         varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
        `create_time` datetime(0)                                                   NULL DEFAULT NULL,
        `update_time` datetime(0)                                                   NULL DEFAULT NULL,
        PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB
      AUTO_INCREMENT = 4
      CHARACTER SET = utf8mb4
      COLLATE = utf8mb4_unicode_ci
      ROW_FORMAT = Dynamic;
    -- ----------------------------
    -- Records of goods
    -- ----------------------------
    INSERT INTO `goods`
    VALUES (1, '小米12s', 4999.00, 1000, 2, 'xxxxxx', '2023-02-23 11:35:56', '2023-02-23 16:53:34');
    INSERT INTO `goods`
    VALUES (2, '华为mate50', 6999.00, 10, 2, 'xxxx', '2023-02-23 11:35:56', '2023-02-23 11:35:56');
    INSERT INTO `goods`
    VALUES (3, '锤子pro2', 1999.00, 100, 1, NULL, '2023-02-23 11:35:56', '2023-02-23 11:35:56');
    -- ----------------------------
    -- Table structure for order_records
    -- ----------------------------
    DROP TABLE IF EXISTS `order_records`;
    CREATE TABLE `order_records`
    (
        `id`          int(11)                                                       NOT NULL AUTO_INCREMENT,
        `user_id`     int(11)                                                       NULL DEFAULT NULL,
        `order_sn`    varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
        `goods_id`    int(11)                                                       NULL DEFAULT NULL,
        `create_time` datetime(0)                                                   NULL DEFAULT NULL,
        PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB
      AUTO_INCREMENT = 1
      CHARACTER SET = utf8mb4
      COLLATE = utf8mb4_unicode_ci
      ROW_FORMAT = Dynamic;
    SET FOREIGN_KEY_CHECKS = 1;
    


    接收用户秒杀服务

    pom和配置文件

    pom:

    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=
  • 相关阅读:
    程序员接单都在用这六大平台,你呢?
    猿创征文|开发常用工具
    [附源码]Python计算机毕业设计SSM京东仓库管理系统(程序+LW)
    SFTP(Secure File Transfer Protocol)的文件下载和上传
    算法分析与设计(持续更新……)
    ARM+Codesys标准通用型控制器
    【React源码】(十七)React 算法之深度优先遍历
    SpringBoot入门
    关于go协程超时退出控制条件与方式分析
    【软件测试】05 -- 软件测试的原则
  • 原文地址:https://blog.csdn.net/weixin_43739821/article/details/139377790