在ImportNew公众号看到一篇高并发的文章,之前自己也写过一个简单的秒杀项目,这里记录一些文章中的知识点。

原文连接

如何理解高并发

  1. 不能只看数字,要看具体的业务场景。就是不能只看10WQPS、100WQPS或者并发量,需要根据具体的业务逻辑来衡量。

  2. 业务是慢慢做大的,业务量也是逐渐变为10倍、100倍的。要用高并发的处理方法去演进系统,从架构设计、编码实现、甚至产品方案等多维度去预防和解决高并发问题。

  3. 高并发有很多场景:如有读多写少的信息流场景、有读多写多的交易场景等。

高并发系统设计的目标是什么?

宏观目标

  1. 高性能:系统的并行处理能力;用户体验,降低服务的响应时间
  2. 高可用:系统可以正常服务的时间。隔三差五服务器就挂肯定会影响业务和用户体验。
  3. 高扩展:系统的扩展能力。流量高峰时能否在短时间内完成扩容,平稳承接峰值流量。如秒杀活动等。

微观目标

性能指标

  1. 平均响应时间:最常用,但是缺陷很明显,对于慢请求不敏感。
  2. TP90、TP99等分位值:将响应时间按照从小到大排序,TP90表示排在第90分位的响应时间, 分位值越大,对慢请求越敏感。
  3. 吞吐量:和响应时间成反比,比如响应时间是1ms,则吞吐量为每秒1000次。

​ 通常,设定性能目标时会兼顾吞吐量和响应时间,比如这样表述:在每秒1万次请求下,AVG控制在50ms以下,TP99控制在100ms以下。对于高并发系统,AVG和TP分位值必须同时要考虑。

​ 另外,从用户体验角度来看,200毫秒被认为是第一个分界点,用户感觉不到延迟,1秒是第二个分界点,用户能感受到延迟,但是可以接受。

可用性指标

可用性 = 正常运行时间 / 系统总运行时间

通常以几个9来描述。如3个9,表示99.9%,年故障时间就是8小时,平均日故障1.44分钟。

可扩展性指标

比如面对秒杀、热搜等突发流量,没时间改架构,因此最快的方式就是增加机器来线性提高系统的处理能力。

扩展性 = 性能提升比例 / 机器增加比例

理想的扩展能力是:资源增加几倍,性能提升几倍。

但是,服务的扩容会增大数据库的压力。因此,高扩展性需要考虑:服务集群、数据库、缓存和消息队列等中间件、负载均衡、带宽、依赖的第三方等。

高并发的实践方案

通用的设计方案

纵向扩展

它的目标是提升单机的处理能力:

  1. 提升单机的硬件性能:加内存、加CPU核数、加存储等
  2. 提升单机的软件性能:用缓存减少IO次数,使用并发或者异步的方式增加吞吐量

比如我们在秒杀项目中,使用Redis作为缓存

横向扩展

解决单机性能瓶颈,即集群部署来进一步提升并发能力。

  1. 做好分层架构

    比如秒杀系统,首先用Nginx做反向代理,负载均衡到多个业务服务器上,采用动静分离,引入CDN;使用Redis作为缓存等

  2. 各层进行水平扩展:无状态水平扩容,有状态做分片路由。业务集群通常能设计成无状态的,而数据库和缓存往往是有状态的,因此需要设计分区键做好存储分片,当然也可以通过主从同步、读写分离的方案提升读性能。

具体的实践方案

以下原文中的每一条方案,都值得深入的研究以及实际操作

高性能的实践方案

1、集群部署,通过负载均衡减轻单机压力。

2、多级缓存,包括静态数据使用CDN、本地缓存、分布式缓存等,以及对缓存场景中的热点key、缓存穿透、缓存并发、数据一致性等问题的处理。

3、分库分表和索引优化,以及借助搜索引擎解决复杂查询问题。

4、考虑NoSQL数据库的使用,比如HBase、Redis等,但是团队必须熟悉这些组件,且有较强的运维能力。

5、异步化,将次要流程通过多线程、MQ、甚至延时任务进行异步处理。

6、限流,需要先考虑业务是否允许限流(比如秒杀场景是允许的),包括前端限流、Nginx接入层的限流、服务端的限流。

7、对流量进行削峰填谷,通过MQ承接流量。

8、并发处理,通过多线程将串行逻辑并行化。

9、预计算,比如抢红包场景,可以提前计算好红包金额缓存起来,发红包时直接使用即可。

10、缓存预热,通过异步任务提前预热数据到本地缓存或者分布式缓存中。

11、减少IO次数,比如数据库和缓存的批量读写、RPC的批量接口支持、或者通过冗余数据的方式干掉RPC调用。

12、减少IO时的数据包大小,包括采用轻量级的通信协议、合适的数据结构、去掉接口中的多余字段、减少缓存key的大小、压缩缓存value等。

13、程序逻辑优化,比如将大概率阻断执行流程的判断逻辑前置、For循环的计算逻辑优化,或者采用更高效的算法。

14、各种池化技术的使用和池大小的设置,包括HTTP请求池、线程池(考虑CPU密集型还是IO密集型设置核心参数)、数据库和Redis连接池等。

15、JVM优化,包括新生代和老年代的大小、GC算法的选择等,尽可能减少GC频率和耗时。

16、锁选择,读多写少的场景用乐观锁,或者考虑通过分段锁的方式减少锁冲突。上述方案无外乎从计算和 IO 两个维度考虑所有可能的优化点,需要有配套的监控系统实时了解当前的性能表现,并支撑你进行性能瓶颈分析,然后再遵循二八原则,抓主要矛盾进行优化。

高可用的实践方案

1、对等节点的故障转移,Nginx和服务治理框架均支持一个节点失败后访问另一个节点。

2、非对等节点的故障转移,通过心跳检测并实施主备切换(比如redis的哨兵模式或者集群模式、MySQL的主从切换等)。

3、接口层面的超时设置、重试策略和幂等设计。

4、降级处理:保证核心服务,牺牲非核心服务,必要时进行熔断;或者核心链路出问题时,有备选链路。

5、限流处理:对超过系统处理能力的请求直接拒绝或者返回错误码。

6、MQ场景的消息可靠性保证,包括producer端的重试机制、broker侧的持久化、consumer端的ack机制等。

7、灰度发布,能支持按机器维度进行小流量部署,观察系统日志和业务指标,等运行平稳后再推全量。

8、监控报警:全方位的监控体系,包括最基础的CPU、内存、磁盘、网络的监控,以及Web服务器、JVM、数据库、各类中间件的监控和业务指标的监控。

9、灾备演练:类似当前的“混沌工程”,对系统进行一些破坏性手段,观察局部故障是否会引起可用性问题。

高可用的方案主要从冗余、取舍、系统运维3个方向考虑,同时需要有配套的值班机制和故障处理流程,当出现线上问题时,可及时跟进处理。

高扩展的实践方案

1、合理的分层架构:比如上面谈到的互联网最常见的分层架构,另外还能进一步按照数据访问层、业务逻辑层对微服务做更细粒度的分层(但是需要评估性能,会存在网络多一跳的情况)。

2、存储层的拆分:按照业务维度做垂直拆分、按照数据特征维度进一步做水平拆分(分库分表)。

3、业务层的拆分:最常见的是按照业务维度拆(比如电商场景的商品服务、订单服务等),也可以按照核心接口和非核心接口拆,还可以按照请求源拆(比如To C和To B,APP和H5)。