笔记内容主要来源于极客时间的《高并发系统设计40问》,有改动。
高并发系统设计三大目标
- 高性能
- 高可用
- 易扩展
高性能
性能优化要以问题为导向,在合适的度量指标的参考下,持续地做性能优化。
常用的性能度量指标就是响应时间,相比平均响应时间和最大响应时间,分位响应时间是更合理的选择。90分位响应时间表示百分之90的请求在这个响应时间之内。
一般的,一个优秀系统的响应时间的99分位值应当控制在200ms以内,99.99%的响应时间应当在1s以内。
性能优化思路:
- 提高系统的处理核心数:最简单直接的方式,但是不能无限制地增加,随着并发进程数的增加,并行的任务对于系统资源的争抢也会愈发严重,在某一个临界点上继续增加并发进程数,反而会造成系统性能的下降,这就是性能测试中的拐点模型
- 降低单次处理的响应时间:具体需要看你的系统是CPU密集型还是IO密集型。CPU密集型,使用更高效的算法、减少运算次数等。IO密集型:常见的数据库系统、缓存系统、Web系统等都是IO密集型。
高可用
高可用指的是系统具备较高的无故障运行的能力。
如何度量可用性?
- MTBF(Mean Time Between Failure),平均故障间隔,也就是系统正常运转的平均时间
- MTTR(Mean Time To Repair),平均恢复时间,也就是平均故障时间
我们定义:
可用性 = MTBF / (MTBF + MTTR)
一般的,我们用几个九来描述可用性,比如,一个九指90%,两个九指99%,三个九指99.9%,四个九指99.99%,五个九指99.999%,…
一个九和两个九,人工运维基本就能达到。
三个九和四个九,需要建立完善的运维值班体系和故障处理流程。
五个九就要靠系统的容灾和自动恢复能力了。
一般的,核心业务系统的可用性应该达到四个九,非核心业务系统的可用性可以放宽到三个九。
故障转移(failover)
对等节点failover比较简单,某一个节点故障,随机选择其他节点就可以了。
主备节点failover比较复杂,需要做故障检测。一般通过心跳检测来做故障检测。主节点故障时触发选主操作,选主的结果需要在备份节点间达成一致,涉及分布式一致性算法。
超时控制
出现大面积延迟时,大量系统资源被占用,导致系统整体崩溃。超时控制就是要避免出现这种问题,当请求处理超过一定时间时,直接让请求失败,释放资源给其他请求。
超时控制的关键是超时时间的设定,超时时间过大或过小都不行,这个需要依据历史日志或经验来确定一个相对合理的超时时间,并根据实际情况做出调整。
限流、熔断、降级
限流指的是通过限制到达系统的并发请求数量,保证系统能够正常响应部分用户请求,而对于超出限制的流量,只能通过拒绝服务的方式来保证整体系统的可用性。
限流算法:
- 固定时间窗口算法:简单,但是有效性差,不能处理临界时间点的集中流量
- 滑动时间窗口算法:复杂度增加,解决了临界时间点集中流量的问题,但不能对流量塑形,无法控制流量让流量更加平滑
- 漏桶算法:在流量产生端和接收端之间增加一个漏桶,流量会进入和暂存到漏桶里面,漏桶按照固定频率将流量漏出到接收端,这样就可以起到平滑流量的效果
- 令牌桶算法:维护一个令牌桶,定期向桶内放入令牌,请求方需要成功获取到令牌才能调用服务方
熔断指的是当发起服务调用的时候,如果返回错误或者超时的次数超过一定阈值,则后续的请求不再发往远程服务而是暂时返回错误。
熔断可以看作断路器模式的实现,在断路器模式下,服务调用方为每一个调用的服务维护一个有限状态机:
- 关闭,调用远程服务
- 打开,不调用远程服务,直接返回错误
- 半打开,尝试调用远程服务

降级是为了保证核心服务的稳定而牺牲非核心服务的做法。广义上,降级是更抽象的概念,限流和熔断都可以看作是降级。狭义上,一般说到降级指的是开关降级,即通过在代码中预埋开关来控制业务逻辑。
灰度发布和故障演练
故障演练指的是对系统进行一些破坏性的手段,观察在出现局部故障时,整体的系统表现是怎样的。
易扩展
为什么不容易实现易扩展呢?因为扩展并不仅仅是业务服务器的扩展,还涉及到数据库、缓存、第三方服务等上下游全链路的各种服务。
横向扩展(Scale-out)
即分而治之,采用分布式集群的方式把流量分流开,让每个服务器承担一部分并发和流量。
与横向扩展对应的是纵向扩展(Scale-up),纵向扩展通过提高硬件配置来提升系统的并发处理能力。
比如,一个4核4G的系统现在能处理200QPS的流量,如果流量增大到400QPS呢?纵向扩展的思路是换一个8核8G性能更好的机器,横向扩展的思路是增加一台4核4G的机器组成一个集群来处理。
纵向扩展的问题在于受限于单机处理能力的极限。
横向扩展可以突破单机极限,但同时引入了一些复杂问题,比如:
- 不同节点信息的一致性
- 某些节点故障时整体的可用性
- 如何无感知的增加或删除节点
分层
为什么要分层?
- 分层可以简化系统设计,每一层做好自己的事
- 分层可以提高复用性
- 分层便于横向扩展
常见的分层设计:MVC模型,OSI七层模型,TCP/IP四层模型,Linux文件系统等。
如何进行分层设计?分层设计的关键是合理地界定不同层级的边界,当你觉得不同层级间逻辑混杂时,那可能就需要考虑增加新的层级了。
分层示例:

异步
与异步相对的是同步,那么什么是同步,什么是异步呢?
与同步和异步容易混淆在一起的概念是阻塞和非阻塞。一种常见的误解是同步等价于阻塞,异步等价于非阻塞,但其实同步异步和阻塞非阻塞没有直接关系。
同步和异步描述的是通信机制(communication mechanism):
- synchronous is, when we started a function call, the call will not return anything until it gets the result, the function needs to finish everything before it can give anything to us
- asynchronous does not need to wait for the function completes its operation, once we call it, it returns immediately without any result, the function uses callback function (or other notification method) to notify us to get the value after it completes the execution
阻塞和非阻塞描述的是the status of the program while waiting for the result from the function call:
- a blocking operation hangs up current thread before it gets the result, in other words, a blocking operation will let the current thread wait for the result returns, even if the target function will use a callback function to notify client side to fetch the result, the thread on the client side will still be blocked until it gets the returned result
- a non-blocking operation will not hang up the current thread if no result returned immediately
一个简单的例子说明同步异步、阻塞非阻塞概念,假设你打电话到一个酒店预定房间:
- 拨通电话以后,酒店前台查看是否能满足你的预定,并告知你结果,这就是同步
- 拨通电话以后,酒店前台确认收到你的预定申请了,等确认是否能预定有结果时回电告知你结果,这就是异步
- 在得到酒店前台的确认结果前,你就坐在电话旁等待,这就是阻塞
- 在告知酒店前台你的预定请求后,你就开始忙其他事情,这就是非阻塞
池化技术
池化技术的核心思想是空间换时间,期望使用预先创建好的对象来减少频繁创建对象的开销,同时可以对对象进行统一的管理,降低了对象的使用成本。
数据库连接池
两个重要参数:最小连接数和最大连接数。建议最小连接数控制在10左右,最大连接数控制在20~30左右。
连接过程:
- 如果当前连接数小于最小连接数,则创建新的连接处理请求
- 如果连接池中有空闲连接,则复用空闲连接
- 如果连接池中没有空闲连接,并且当前连接数小于最大连接数,则创建新的连接处理请求
- 如果当前连接数已经大于等于最大连接数,则按照设定的等待时间等待可用连接
- 如果等待超时,则抛出异常
连接池的管理维护: 最基本的问题就是如何保证连接池中的连接是有效的、可用的?一种方式是启动一个线程定期检测连接池中的连接是否可用。还有一种方式是获取到连接后先校验连接是否可用,这种方式在获取连接时引入了多余的开销,线上系统最好不要采取这种策略。
线程池
ThreadPoolExecutor是JDK 1.5引入的线程池实现。类似的,有两个重要参数,核心线程数和最大线程数。
连接过程:
- 如果线程池中的线程数小于核心线程数,则创建新的线程处理任务;
- 如果线程数大于核心线程数,则把任务丢到一个队列里,等待空闲的线程执行;
- 当队列中的任务堆积满了的时候,则继续创建线程,直到达到最大线程数;
- 当线程数达到最大线程数时,默认丢弃任务。
当线程数达到核心线程数时,JDK实现的线程池会把新任务放到一个等待队列里,而不是直接继续创建新线程。这种方式适用于CPU密集型任务,不适用于IO密集型任务。Tomcat使用的线程池就没有使用等待队列。
池化注意事项
- 池子的最大值和最小值的设置很重要,初期可以依据经验来设置,后面还是需要根据实际运行情况做调整
- 池子中的对象需要在使用之前预先初始化完成,这叫做池子的预热,比方说使用线程池时就需要预先初始化所有的核心线程。如果池子未经过预热可能会导致系统重启后产生比较多的慢请求
- 池化技术核心是一种空间换时间优化方法的实践,所以要关注空间占用情况,避免出现空间过度使用出现内存泄露或者频繁垃圾回收等问题
MySQL
主从复制
将一个数据库的数据拷贝为多份,原始的数据库称为主库,主要负责数据的写入,拷贝的数据库称为从库,主要负责数据的查询。
Mysql主从复制流程:主库会创建一个log dump线程来发送binlog给从库。从库在连接到主库时会创建一个IO线程,用来请求主库更新的binlog,并且把接收到的binlog信息写入一个叫做relay log的日志文件中。同时,从库还会创建一个sql线程读取relay log,并且在从库中回放,实现主从复制。

是不是可以无限增加从库呢?不是的,一般一个主库最多挂3~5个从库。
主从延迟
主从延迟带来的典型问题就是读从库查询不到信息,对于这种问题,可以通过消息、缓存等方式来处理,尽量不要读主库。
主从延迟时间应当作为重点监控指标,一般主从延迟是毫秒级的。
分库分表
分库分表是常用的数据分片方式之一。
将单一数据表根据某种规则拆分到多个数据库和多个数据表中,比如,根据id字段做哈希拆分、根据时间字段做区间拆分。
分库分表以后,任何操作都强依赖分区键。另外,不能做多表join、count操作也比较麻烦。
为什么需要发号器?因为数据库分库分表后,简单的使用自增id不能满足全局唯一性。
为什么不使用UUID?
- UUID占用的空间较大
- 我们希望生成的id具有单调递增性,一方面写入性能更好,另一方面业务可以根据ID排序
- 我们希望生成的id可以具备业务含义,这样通过反解ID可以排查问题
Snowflake算法的核心思想是将64bit的二进制数字分成若干部分,每一部分存储有特定含义的数据,由此生成全局唯一的有序id:

一般的,你可以根据需要调整Snowflake算法,定制自己的发号器实现。
数据迁移
级联迁移方案


这种方案的优点是简单易实施,业务上基本没有改造的成本;缺点是切写的时候需要短暂地停止写入,业务上是有损的。
双写方案

这种方案的优点是业务无损,缺点是时间周期比较长,业务有改造成本。
NoSQL
NoSQL指的是不同于传统的关系型数据库的其他数据库系统的统称。
为什么需要NoSQL?
- 关系型数据库存储的是行记录,不能存储数据结构
- 关系型数据库的schema不方便扩展
- 关系型数据库在大数据场景下IO太高
- 关系型数据库的全文搜索性能差(like性能差)
NoSQL可以作为关系型数据库的补充,弥补关系型数据库在某些场景下性能和扩展性的不足。
常见的NoSQL数据库
- K-V存储,解决关系型数据库不能存储数据结构的问题,以Redis为代表。
- 文档数据库,解决关系型数据库schema不方便扩展的问题,以MongoDB为代表。文档数据库最大的特点就是no-schema,可以存储和读取任意的数据,目前大多数文档数据库以JSON格式存储数据。
- 列式数据库,解决关系型数据库大数据场景下的IO问题,以HBase为代表。按照列来存储数据,一般应用在离线的大数据分析和统计场景中。
- 全文搜索引擎,解决关系型数据库的全文搜索问题,以Elasticsearch为代表。全文搜索引擎基于倒排索引,倒排索引适用于根据关键词来查询文档数据。如何将关系型数据转换为文档数据?目前常用的转换方式是将关系型数据按照对象的形式转换为JSON文档,然后将JSON文档输入全文搜索引擎进行索引。
缓存
广义上讲,凡是位于不同速度存储设备之间,用于协调存取速度差异的,都可以称为缓存。另外,存储复杂计算的结果以避免重复计算也是一种缓存。
为什么使用缓存可以提高系统的并发处理能力呢?因为不同存储介质的访问速度差异非常大,缓存就是避免存取访问速度慢的存储介质。
可以参考Latency Numbers Every Programmer Should Know
缓存分类:
- 静态缓存:用于缓存静态数据,减少后台服务器的压力
- 分布式缓存:用于缓存动态数据,以Redis和Memcached为代表
- 热点本地缓存:对于极端的热点数据查询可以使用热点本地缓存。热点本地缓存内嵌在代码中,用于阻挡热点数据查询对于分布式缓存或数据库的压力,比如Guava Cache
使用缓存的注意事项:
- 缓存适合用于读多写少,并且带有一定热点属性的场景
- 注意数据一致性、缓存容量评估、缓存命中率等问题。一般的,核心缓存的命中率应当在99%以上,非核心缓存的命中率应当在90%以上
旁路缓存策略(Cache Aside)
读策略:
- 从缓存中读取数据;
- 如果命中缓存,则直接返回数据;
- 如果未命中缓存,则查询数据库;
- 将查询到的数据写入缓存。
写策略:
- 更新数据库中的记录;
- 删除缓存记录。
为什么写数据时删除缓存而不是更新缓存?因为更新缓存比较麻烦,既要处理并发问题,又要注意数据一致性问题。
旁路缓存策略最大的问题是当写入比较频繁时,缓存中的数据会被频繁地清理,影响缓存命中率。
读穿/写穿策略(Read/Write Through)
读穿/写穿策略的核心是你只与缓存交互,由缓存和数据库交互。
读策略:
- 从缓存中读取数据;
- 如果命中缓存,则直接返回数据;
- 如果未命中缓存,则由缓存从数据库加载数据。
写策略:
- 查询要写入的数据在缓存中是否存在;
- 如果存在,则更新缓存,由缓存同步更新数据库;
- 如果不存在,那么可以选择(1)写缓存,由缓存同步更新数据库;(2)直接写数据库。
写回策略(Write Back)
写回策略的核心是写入数据时只写入缓存,并且把缓存块标记为“脏”,脏块只有被再次使用时才会将其中的数据写入到下一级存储中。Page Cache、异步刷盘等都是写回策略的应用。
读策略:
- 从缓存中读取数据;
- 如果命中缓存,则直接返回数据;
- 如果未命中缓存,则寻找可用缓存块,判断缓存块是否为脏。如果缓存块为脏,则将脏数据写入下一级存储,并且从下一级存储加载要读取的数据;如果缓存块不为脏,直接从下一级存储加载要读取的数据;
- 标记缓存块不为脏;
- 返回数据。
缓存高可用-客户端
在客户端配置多个缓存节点来提高缓存的可用性。一般的,4~6个节点。
写入数据:写入数据时需要做数据分片。一般的,我们使用一致性哈希算法,因为一致性哈希算法可以很好地解决增加或减少节点时缓存命中率下降的问题。
一致性哈希算法:将整个Hash值空间组织成一个虚拟的圆环,然后将缓存节点的IP地址或者主机名做Hash取值后,放置在这个圆环上。当我们需要确定某一个key需要存取到哪个节点时,在环上沿着顺时针方向找到的第一个缓存节点就是目标节点。在增加或减少节点时,只有少量的key会漂移到其他节点上,大部分key命中的节点保持不变,从而可以保证缓存命中率不会大幅下降。
一致性哈希算法的问题:
- 缓存节点分布不均匀:可以引入虚拟节点,即将一个缓存节点计算多个Hash值,对应圆环上多个位置,这样就避免了缓存节点分布不均匀的问题
- 脏数据问题:注意设置缓存过期时间
缓存高可用-中间代理层
客户端方案的劣势是通用性较差,不方便复用,把客户端方案的高可用逻辑单独抽离出来,就是中间代理层方案。
在应用程序和缓存节点之间增加代理层,客户端的写入和读取请求都通过代理层,代理层内置高可用策略。
中间代理层方案中所有的缓存读写请求都要经过中间代理层,代理层是无状态的,主要负责读写请求的路由功能,并且内嵌了高可用逻辑。
缓存高可用-服务端
Redis在2.4版本后提出了Redis Sentinel模式来解决主从Redis部署时的高可用问题。
Redis Sentinel也是集群部署的,Sentinel集群节点会监控主节点的状态,当主节点在一定时间内无响应,集群内部仲裁是否进行主从切换,主从切换则将某个从节点提升为主节点,并且把所有其他的从节点作为新主节点的从节点。

缓存穿透
缓存穿透是指未命中缓存而查询数据库。少量的缓存穿透是正常的,但是大量的缓存穿透就可能导致系统崩溃。
缓存穿透有两种典型的解决方案:
- 回种空值
- 布隆过滤器
回种空值并设置一个较短的过期时间,这种方案最简单,需要注意的是空值缓存占用缓存容量问题。
布隆过滤器基于一个二进制数组和一个哈希算法,可以高效地判断一个元素是否在一个集合中。
使用布隆过滤器需要注意以下两点:
- 哈希碰撞导致的误判
- 不支持删除元素
对于哈希碰撞导致的误判,问题不大,因为碰撞概率较低。当然,可以通过使用多个哈希算法计算多个哈希值,进一步降低碰撞概率。
对于删除元素,可以通过增减计数来实现,但是这样就需要占用更多的空间。
CDN(Content Distribution Network)
CDN,内容分发网络,通过将静态资源分发到位于多个地理位置机房中的服务器上,然后基于就近访问来加快静态资源的访问速度。
- 对静态资源请求做DNS解析,得到CNAME记录,映射到CDN域名;
- 通过GSLB(Global Server Load Balance)将请求映射到就近的CDN节点。

消息
消息有哪些应用场景?
- 削峰
- 异步
- 解藕



消息丢失
哪些场景下消息可能丢失呢?
生产者写消息队列时,比如网络抖动可能导致消息丢失,可以通过消息重传减少这种丢失。
消息队列内部存储出错,比如使用了异步刷盘机制,机器掉电或异常重启可能导致消息丢失,可以通过集群部署减少这种丢失。
消息重复
无论生产者还是消费者,都可以通过使用幂等id来保证幂等性。
消息延迟
首先,做好相关监控,可以通过官方或开源工具监控消息的堆积,也可以通过生成监控消息的方式来监控消息的延迟。
其次,从消费者的角度出发,可以考虑:
- 增加消费者数量
- 优化消息处理效率,比如使用线程池来提高并行度
消息乱序
微服务
随着业务的发展,一体化架构在技术和管理上都会面临很多挑战,微服务就是为了更好地解决这些问题。
微服务化时的几点建议
- 服务内高内聚,服务间低耦合
- 服务拆分粒度可以先粗一些,后面再细化
- 逐步拆分,先拆分比较独立的非核心服务,先拆分更被依赖的服务
微服务带来的挑战
- 服务治理
- 服务故障
- 服务监控和分布式追踪工具
RPC框架
RPC框架封装了网络调用的细节,让你像调用本地服务一样调用远程服务。

要想保证RPC框架的性能,可以从网络传输和序列化两方面来考虑。
网络传输:
- 选择高性能的I/O模型
- 调试网络参数,比如开启tcp_nodelay
序列化:
- 时间效率和空间效率
- 是否支持跨语言、跨平台
如果对于性能要求不高,可以使用JSON;如果对于性能要求较高,可以使用Thrift或Protobuf.
注册中心

有了注册中心之后,服务节点的变更对客户端就是透明的,方便我们动态地变更服务节点,实现graceful shutdown等功能。
服务节点探活有两种方式:
- 注册中心主动探测
- 服务节点心跳机制
主动探测不方便、成本高,一般的,我们使用心跳机制。

分布式追踪
分布式追踪用于问题排查、性能优化、调用链展示、服务依赖分析等。
一般的,注意以下技术点的使用:
- traceId + spanId
- 切面编程
- 日志采样
- ElasticSearch

负载均衡
负载均衡指的是将请求均衡地分配到不同服务节点中,避免单一节点流量过高或过低。同时,负载均衡可以起到对请求方屏蔽服务节点的部署细节,方便实现动态扩缩容。
负载均衡可以分为两大类:
- 代理类负载均衡
- 客户端负载均衡
代理类负载均衡以单独的服务方式部署,所有请求都要先经过这个服务,由这个服务选择合适的服务节点做流量的分发。

LVS(Linux Virtual Server)和Nginx是代理类负载均衡的范例。LVS工作在OSI模型的第四层传输层,Nginx工作在OSI模型的第七层应用层。对于大流量场景,可以同时部署LVS和Nginx来做HTTP应用服务的负载均衡,即在入口处部署LVS将流量分发到多个Nginx服务器上,再由Nginx服务器分发到应用服务器上。如果流量没有很大,也可以只通过Nginx做负载均衡,降低系统复杂度。
如何保证Nginx中配置的服务节点是可用的呢?可以通过nginx_upstream_check_module做服务探活,这个模块可以让Nginx定期地探测后端服务的一个指定的接口,根据接口返回的状态码来判断服务是否存活。当探测不存活次数达到一定阈值时,就自动将这个服务节点从负载均衡服务器中摘除。这种服务探活功能还可以用于Web服务的优雅关闭,通过修改探活接口返回的状态码来控制服务是否对外可用。
客户端负载均衡一般结合注册中心来使用,注册中心提供服务节点的列表,客户端拿到列表之后使用内嵌的负载均衡服务选择合适的节点做流量的分发。

负载均衡策略可以分为两大类:
- 静态策略:静态策略在选择服务节点时不会参考后端服务的实际运行状态,常见的静态策略包括,轮询策略(RoundRobin, RR)、带有权重的轮询策略、哈希算法、随机选取
- 动态策略:动态策略在选择服务节点时会参考后端服务的负载特性,选择负载最小、资源最空闲的服务来调用。
API网关

API网关可以分为两类:
- 入口网关
- 出口网关
入口网关部署在负载均衡服务器和应用服务器之间,可以提供以下功能:
- 应用服务统一接入地址,对客户端屏蔽不同应用服务的协议细节
- 客户端认证和授权,对应用服务屏蔽不同客户端的认证细节
- 服务限流、降级、熔断
- 设备、IP等黑白名单
出口网关部署在应用服务器和第三方系统之间,功能相对简单,主要用于对调用外部系统的API做统一的认证、授权以及审计等。
API网关需要注意的技术点:
- I/O模型
- 扩展性,比如,责任链模式
- 线程池,注意线程隔离或线程保护
多活
什么是多活?多活指的是在不同的IDC机房中部署多套服务,这些服务共享同一份业务数据,并且都可以承接来自用户的流量。
多活主要的难点在于跨机房数据传输导致的延迟对系统功能和性能的影响。
跨机房数据传输延迟时间:北京同城双机房延迟一般是1ms~3ms;北京和天津双机房延迟一般是10ms;北京和上海双机房延迟一般是30ms;北京和广州双机房延迟一般是50ms;国内和美国西海岸双机房延迟一般是100ms~200ms
同城多活相对简单,可以允许有跨机房数据写入的发生,数据的读取和服务的调用应当尽量保证在同一个机房中。
异地多活比较复杂,需要避免跨机房数据写入,涉及用户分片等问题。
监控
常用的监控指标包括:
- 延迟:比如接口的响应时间、访问缓存和数据库的响应时间等
- 吞吐量
- 错误数
- 饱和度:比如CPU使用率、内存使用率、磁盘使用率等
常用组件监控指标:

一般的,可以通过Agent、埋点、日志等方式来采集监控指标数据。
一般的,我们会通过消息队列承接监控数据。监控数据一方面会写入ElasticSearch,另一方面会通过流计算中间件来解析、聚合运算,然后写入时间序列数据库并形成报表对外展示。常见的报表包括:
- 访问趋势报表,用来展示服务的整体运行情况
- 性能报表,用来分析资源或者依赖的服务是否出现问题
- 资源报表,用来追查资源问题的根本原因

APM(Application Performance Management),应用性能管理,指的是对应用各个层面做全方位的监测,核心关注点是终端用户的使用体验,即端到端整体链路上的性能情况。

压力测试
压力测试指的是在高并发大流量下进行的测试,通过观察系统在大负载下的表现,寻找系统性能的瓶颈点或隐患。
压测注意点:
- 压测应当使用线上环境,但要注意流量隔离
- 压测应当使用线上流量而不是模拟流量
- 压测时应当从多台服务器发起流量
一个自动化的全链路压测平台应当包含以下几个模块:
- 流量构造模块:流量拷贝、流量清洗、流量打压测标
- 压测数据隔离模块:mock服务、影子存储
- 系统健康度检查和压测流量干预模块:逐步加压
