因为考虑到比如业务需求的性质、托管基础设施的类型(例如云或本地)、成本等许多因素,本文不是一种放之四海而皆准的方法。只是提供了一些通用的经验总结。
高并发意味着什么?高并发指的是系统在同一时间段内同时处理大量的请求或任务。包括大量读取数据、写入数据、计算任务等。
特征:瞬时、大量请求。系统需要同时、短时处理大量的请求,可能是数千甚至数百万个并发请求,毫秒级别。
在面对高并发的情况下,系统需要进行合理的架构设计、性能优化和资源管理,以确保系统能够有效地应对大量的并发请求,提供稳定、高效的服务。
高并发意味着暴风骤雨来临 ,非常挑战我们的处理经验。
衡量并发度的指标2016 年“双 11”支付宝每秒峰值达 12 万笔支付。
2017 年春节微信红包收发红包每秒达到 76 万个。
这些都是描述并发程度的内容。
一般通过以下几个指标来描述并发程度:
吞吐量(Throughput):吞吐量是指系统在单位时间内能够处理的任务数量。这是衡量系统并发处理能力的一个重要指标。通常,吞吐量越高,表明系统的并发处理能力越强。响应时间(Response Time):响应时间是指系统从接收请求到返回响应所花费的时间。在高并发环境下,响应时间可能会增加,因此,保持较低的响应时间是提高用户体验的关键。并发用户数(Concurrent Users):并发用户数是指在同一时间内,系统正在服务的用户数量。这是衡量系统并发处理能力的另一个重要指标。请求并发数(Concurrent Requests):请求并发数是指系统在同一时间内正在处理的请求数量。对于Web服务器或应用服务器来说,这是一个重要的性能指标。带来的问题和挑战几乎所有的系统面临暴风骤雨来临,随着流量变大,会遇到各种各样的技术问题,如果没有,可能只是你的并发度还不够高。在超高并发下依然面临着普遍的问题,从系统整体来看,会表征为以下情况:
性能问题,如访问超时,CPU load升高、GC频繁、死锁、大数据量存储等等。
系统可用性降低,可能带来大量5xx、数据库压力增大甚至崩溃
数据一致性
这些问题以及正常的业务逐渐复杂,另外一般还需关注到的安全、成本等,系统的复杂度会几何增长,因此如何综合解决这些复杂的问题,我们必须跳出代码,有总揽全局的思维,从架构的层面去分析解决这些问题。
这里我们从宏观架构思维,讲解如何在无限的约束下,从全局系统,利用已有模式解决gao'bing'f系统的复杂性。
如何提升性能性能问题是一个综合性问题,只是一个表征,伴随着性能问题的出现,一般会出现内存、cpu过载、网络带宽不足等。
所以从架构上来说,一般的解决模式,总结起来三点
分治,拆,分布式集群化,合理规划容量或者可以支持弹性伸缩,避免单点资源不足引起的过载问题资源复用或者再利用,如缓存、池化延迟处理,如异步化其它还有一些优化逻辑或者技术实现的方法,如合并请求减少IO次数、IO多路复用、多线程、索引优化等。
性能优化原则问题导向,不要过早进行优化,避免增加系统复杂度,同时也浪费研发人力遵循二八原则 要抓住主要矛盾,优先优化主要的性能瓶颈优化需要有数据支撑 要时刻了解你的优化让响应时间减少了多少,提升了多少的吞吐量。可以使用平均值、极值(最大/最小值)、分位值等作为统计的特征值分治分治的目的有两个,一个是人类的思维有限,拆分子问题抓主要矛盾,另一个是单机的性能有限,虽然从第1台计算机诞生至今已经可以支持从每秒几次提升到每秒几亿次的运算,现代计算集群化是不可避免的态势。
无论微观的进程拆分线程到多进程、多线程等技术,还是宏观的从单体到SOA到微服务其实都是是追循的这个思路。
宏观上的这种解决方案是有效的,但带来很多的复杂性:
每个子问题如何分配到不同机器子问题的拆分逻辑针对第一个问题,很自然的我们可以使用一个调度器来进行机器的分配。
在web开发中调度器基本等价于负载均衡器的作用,负载均衡的种类及算法很多,包括:
硬件负载均衡和软件负载均衡。硬件负载均衡通常是指专用的负载均衡设备,如 F5、Radware 等;软件负载均衡是指基于软件实现的负载均衡,如 Nginx、HAProxy 等。负载均衡算法:轮询(Round Robin)加权轮询(Weighted Round Robin)最少连接(Least Connections)加权最少连接(Weighted Least Connections)随机(Random)哈希与一致性哈希高性能数据库
腾讯云MySQL8.0,只有部分数据可以放到缓存里,查询过程中需要读写磁盘更新缓存场景下的基准测试。
CPU
内存 (MB)
并发度
单表数据量
表总数
SysBench TPS
SysBench QPS
avg_lat
1
1000
8
800000
6
582.76
11655.1
13.73
1
2000
8
800000
12
588.92
11778.4
13.58
2
4000
16
800000
24
899.06
17981.2
17.8
4
8000
32
800000
48
1915.83
38316.6
16.7
4
16000
32
6000000
13
1884.2
37684
16.98
8
16000
64
6000000
13
3356.71
67134.2
19.06
8
32000
64
6000000
25
3266.73
65334.6
19.59
16
32000
128
6000000
25
5370.18
107404
23.83
16
64000
128
6000000
49
5910.85
118217
21.65
16
96000
128
6000000
74
5813.94
116279
22.01
16
128000
128
6000000
98
5700.06
114001
22.45
单机的MySQL性能有限,在高并发的场景下,如果大量请求访问到数据库上,MySQL单机很难应付。所以按照上面的理论,从两个方面对数据做集群化架构提升性能。
读写分离,主节点(Master) :负责处理写操作(如插入、更新、删除),是数据的主要来源。从节点(Slave) :负责处理读操作(如查询),从主节点同步数据,并提供读取数据的服务。可能主从延迟导致的数据一致性问题,这需要一些权衡及优化策略。
分库分表,数据库水平拆分的策略,用于解决单一数据库存储容量有限、性能瓶颈。 分库(Sharding) :将数据按照一定规则分散存储到多个数据库实例中,每个数据库实例称为一个分片(Shard),可以通过分片键(Shard Key)来确定数据存储在哪个分片中。 分表(Sharding) :在每个数据库实例中,将数据按照一定规则分散存储到多个表中,每个表称为一个分表,可以通过分表键(Shard Key)来确定数据存储在哪个分表中。冷热分离其它:根据成本、规模、资源做好数据库选型,当然也可以使用其他NoSQL。
缓存化缓存是构建高性能系统的必要且常用的手段,很多场景下,单纯依靠存储系统的性能是不够的,这时候便有了缓存的应用场景。
缓存可以减少对数据库的访问、减少网络IO,从而实现整个系统的性能。
构建缓存常用的组件包括Redis、Memcached等。使用缓存时一定要做好的原则,以Redis为例:
使用cluster,合理规划容量,做好监控预案好的key命名规范及长度避免大key缓存策略使用缓存时整个系统架构变得更复杂,同样可能会遇到一些新问题,以下是一些问题及相应的解决思路:
缓存穿透:指查询一个不存在的数据,由于缓存中没有该数据,每次查询都会穿透到数据库,导致数据库压力过大。 解决思路包括使用布隆过滤器拦截不存在的数据、设置空值缓存、使用缓存击穿解决方案等。缓存击穿:指某个热点数据突然失效,导致大量请求直接访问数据库,造成数据库压力过大。 解决思路包括设置热点数据永不过期、使用互斥锁更新缓存、预先加载热点数据等。缓存雪崩:指大量缓存数据同时失效,导致大量请求直接访问数据库,造成数据库压力过大。 解决思路包括设置不同的过期时间、使用分布式锁更新缓存、使用备份缓存等。热点问题: 指在一个系统中频繁被访问的数据,通常是一些热门、热点的数据。特点包括:高频、重要、量大。 解决思路包括 : 预热缓存:在系统启动或高峰期之前,预先将热点数据加载到缓存中,提前缓存热点数据,以提高缓存命中率。 设置合适的缓存过期时间:对于热点数据,可以设置较长的缓存过期时间,避免频繁地更新缓存,提高缓存命中率。 使用缓存淘汰策略:针对热点数据,可以采用合适的缓存淘汰策略,保证缓存中始终存储重要的热点数据。 缓存预取:根据用户的访问模式和行为,预先将可能被访问的数据加载到缓存中,提高缓存命中率。 多级缓存 key 水平拆分缓存数据一致性:在分布式环境下,缓存数据的一致性是一个挑战,可能会出现缓存脏数据的情况。 解决思路包括使用缓存更新策略、使用缓存锁、使用缓存失效通知等。池化在高性能应用中,池化技术是一种常见且有效的技术,用于管理和复用资源,以提高系统的性能和效率。常用的池化技术包括几个方面:
连接池(Connection Pool) :在数据库访问、网络通信等场景中,连接池是一种常见的池化技术。连接池会预先创建一定数量的连接,当需要进行数据库查询或网络通信时,可以从连接池中获取连接,而不是每次都创建新连接,从而减少连接创建和销毁的开销,提高系统性能。线程池(Thread Pool) :在多线程应用中,线程池可以管理和复用线程,避免频繁地创建和销毁线程,减少线程切换的开销,提高系统的并发性能和响应速度。对象池(Object Pool) :对象池用于管理和复用对象实例,避免频繁地创建和销毁对象,提高系统的内存利用率和性能。对象池可以用于复杂对象的创建,例如数据库连接、HTTP连接、线程等。内存池(Memory Pool) :内存池是一种用于管理内存分配和释放的技术,通过预先分配一块连续的内存空间,并按需分配给应用程序,减少内存碎片和频繁的内存分配操作,提高内存管理效率和系统性能。服务器模式通常用于实现高性能服务器的模式有Reactor和Proactor是两种设计模式,它们在处理I/O密集型任务时非常有用,可以提高系统的并发性能和响应速度。
Reactor模式:Reactor模式是一种事件驱动的设计模式,通过一个事件循环(Event Loop)来监听和分发事件,当有事件发生时,Reactor会调用相应的处理程序来处理事件。Reactor模式通常用于实现基于事件驱动的网络编程,例如基于事件的服务器。Proactor模式:Proactor模式也是一种事件驱动的设计模式,不同于Reactor模式的是,Proactor模式中的I/O操作是由操作系统或框架来完成,而不是应用程序自己处理。应用程序只需要在I/O操作完成时得到通知,然后进行相应的处理。Proactor模式通常用于实现异步I/O操作。异步化异步化模式是一种通过异步处理请求来提高系统性能的方法。在异步化模式下,请求不会直接等待处理结果,而是将请求放入队列或其他数据结构中,然后立即返回。这样,请求处理过程不会阻塞主线程,从而提高系统的吞吐量和响应速度。
异步化通常情况下使用消息队列进行解耦,延迟处理提升整个系统的吞吐,它本质上是一种延迟处理的技术。
CQRSCQRS(Command Query Responsibility Segregation,命令查询职责分离)是一种软件架构模式,它将数据的读操作(查询)和写操作(命令)分离到不同的模型中。这种分离有助于提高系统的可扩展性、性能和安全性。
CQRS的核心概念是将应用程序分为两个部分:
命令模型(Command Model):负责处理数据的修改操作,如创建、更新或删除。命令模型通常会修改数据存储并产生相应的事件。这些事件可以用于更新查询模型或触发其他业务逻辑。查询模型(Query Model):负责处理数据的读取操作,如获取数据或执行搜索。查询模型通常会从数据存储中获取数据并将其返回给客户端。查询模型可以针对特定的查询进行优化,以提高性能。事件驱动&事件溯源事件驱动架构(Event-Driven Architecture,EDA)是一种软件架构模式,它支持应用程序、系统或服务之间通过事件进行异步通信。在事件驱动架构中,事件是状态变化或特定条件的发生,它们可以触发其他组件或服务的响应。事件驱动架构允许组件或服务相互独立,这有助于提高可扩展性、灵活性和故障隔离。
事件驱动架构的主要组成部分包括:
事件生产者:负责创建和发布事件的组件或服务。事件生产者不关心谁订阅了其发布的事件,也不关心事件的处理方式。事件消费者:订阅并处理特定事件的组件或服务。事件消费者与事件生产者之间的通信是间接的,通常通过事件总线或消息队列实现。事件总线/消息队列:用于传输事件的中间件。事件总线或消息队列负责将事件从生产者传递到消费者,同时确保事件的传递可靠、有序和安全。此外还有一些更微观的提升性能的技术包括不限于如批量打包、零拷贝、编码方式、SQL调优、无锁编程。
可用性和弹性伸缩可用性是指系统无中断地执行其功能的能力,代表系统的可用性程度,是进行系统设计时的准则之一。
高可用一般衡量的指标为sla 几个9描述。高可用的设计过程其实也是一个取舍的过程。这也就是为什么系统可用性永远只是说几个九,永远缺少那个一。
基本思路架构方面
冗余、故障转移恢复、治理
观测方面
日志、指标、追踪全方位建设
工具方面
做好演练、压测、故障反馈等
CAP理论CAP理论是分布式计算领域的一个重要理论,它指出在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)这三个核心指标无法同时达到最优。换句话说,当我们设计一个分布式系统时,必须在这三个指标中权衡取舍。
CAP理论的三个指标定义如下:
一致性(Consistency):一致性是指分布式系统中的所有节点在同一时刻具有相同的数据副本。简单来说,当一个客户端对数据进行写操作后,其他客户端能立即读取到最新的数据。可用性(Availability):可用性是指分布式系统在正常运行和故障情况下都能对客户端提供服务。换句话说,当客户端发出请求时,系统总是能在有限的时间内返回一个响应,无论是成功还是失败。分区容错性(Partition Tolerance):分区容错性是指分布式系统在网络分区(即节点之间的通信故障)发生时仍能正常运行。在这种情况下,系统可能会出现不一致或不可用的情况,但它会尽力保持正常运行。根据CAP理论,我们可以将分布式系统划分为以下三种类型:
CA(一致性和可用性):这类系统在没有网络分区的情况下可以保证一致性和可用性。然而,当出现网络分区时,这类系统很难维持正常运行。传统的关系型数据库(如MySQL和PostgreSQL)通常属于这类系统。CP(一致性和分区容错性):这类系统在网络分区发生时仍能保证一致性,但可能会牺牲可用性。当出现网络分区时,这类系统可能会拒绝一部分请求,以保证数据的一致性。ZooKeeper和Google Spanner等分布式数据库属于这类系统。AP(可用性和分区容错性):这类系统在网络分区发生时仍能保证可用性,但可能会牺牲一致性。当出现网络分区时,这类系统可能会返回过时的数据,以保证对客户端的响应。Cassandra、Couchbase和Amazon Dynamo等NoSQL数据库属于这类系统。也就是说在实际应用中,基本无法保证CA。
C 与 A 之间的取舍可以在同一系统内以非常细小的粒度反复发生,而每一次的决策可能因为具体的操作,乃至因为牵涉到特定的数据或用户而有所不同。
BASEBASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency),核心思想是即使无法做到强一致性(CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性。
它主要是对AP方案的一个延伸,为我们提供思路。
冗余多机高可用
设计目标是当出现部分硬件损坏时,计算任务能够继续正常运行。
常用的存双机冗余架构有主备、主从、集群、分区
其中主备/主从复制一般都会在各种存储组件如DB、reddis、MongoDB、kafka中实现。
集群和分区则在计算、存储架构中都有应用,集群分为互备式和独立式两种,分别对应对称式和非对称式。
容灾-异地多活
在原有单中心的基础上,复制多个中心,覆盖应用、存储,形成异地双中心。
故障转移对等节点的故障转移,Nginx和服务治理框架均支持一个节点失败后访问另一个节点。
非对等节点的故障转移,通过心跳检测并实施主备切换(比如redis的哨兵模式或者集群模式、MySQL的主从切换等)。
另外,接口层面的超时设置、重试策略和幂等设计。
治理策略限流处理
限流是一种控制请求速率的策略,旨在防止系统过载。通过对请求进行限制,可以确保系统在高并发场景下能够应对突发流量不会因为资源耗尽而崩溃,常用的包含几种算法:
固定窗口计算器优点是简单,但存在临界场景无法限流的情况。漏桶是通过排队来控制消费者的速率,适合瞬时突发流量的场景,面对恒定流量上涨的场景,排队中的请求容易超时饿死。令牌桶允许一定的突发流量通过,如果下游(callee)处理不了会有风险。滑动窗口计数器可以相对准确地完成限流。自适应限流熔断处理
熔断是一种自动切断对故障服务访问的策略,以防止故障扩散和系统雪崩。当某个服务出现故障(如连续失败、超时等)时,熔断器会自动打开,阻止对该服务的进一步访问。在熔断器打开期间,请求会被快速拒绝,而不会消耗系统资源。经过一段时间后,熔断器会自动进入半开状态,尝试放行部分请求以测试服务是否恢复。如果服务恢复正常,熔断器会关闭;如果仍然故障,熔断器会继续保持打开状态。
降级处理
降级是一种在系统压力过大或出现故障时,临时关闭部分非关键功能或降低服务质量的策略。通过降级,可以确保关键功能的正常运行,同时减轻系统的负担。降级可以根据不同的策略触发,如错误率、响应时间、系统负载等。降级后的服务可以返回默认值、缓存数据或错误信息,以便客户端能够处理降级情况
前提:做好服务分级。
灰度发布
能支持按机器维度进行小流量部署,观察系统日志和业务指标,等运行平稳后再推全量。
可观测指标、日志和链路追踪构成可观测的三大基石,为我们提供架构感知、瓶颈定位、故障溯源等能力。借助可观测性,我们可以对系统有更全面和精细的洞察,发现更深层次的系统问题,从而提升可用性。
Logging
ELK大法,一般的日志流程会涉及到埋点、采集、加工、索引、检索等。是一个复杂的大数据加工工程。
Metrics
主流的指标建设工具包括Prometheus、grafana、influxdb、elastic等。
四大黄金指标:Traffic(QPS)、Latency(延时)、Error(成功率)、Staturation(资源利用率)。
指标设计原则:RED
Tracing
在微服务架构的复杂分布式系统中,一个客户端请求由系统中大量微服务配合完成处理,这增加了定位问题的难度。如果一个下游服务返回错误,我们希望找到整个上游的调用链来帮助我们复现和解决问题,类似gdb的backtrace查看函数的调用栈帧和层级关系。 Tracing在触发第一个调用时生成关联标识Trace ID,我们可以通过RPC把它传递给所有的后续调用,就能关联整条调用链。Tracing还通过Span来表示调用链中的各个调用之间的关系。
常用的构建全链路追踪的开源组件很多,比如skywalking、jager等。
工具链变被动为主动?在故障之前,尽可能多地识别风险,针对性地加固和防范,而不是等着故障发生。业界有比较成熟的理论和工具,混沌工程和全链路压测。
总结高可用的方案主要从冗余、治理、取舍、运维等几个方向考虑,同时需要有配套的值班机制和故障处理流程,当出现线上问题时,可及时跟进处理。
弹性弹性架构(Elastic Architecture)是一种能够自动适应不同负载和故障场景的系统设计。弹性架构具有高度的可扩展性、可用性和容错性,能够在面临流量波动、硬件故障或其他异常情况时保持稳定运行。弹性架构的目标是确保系统能够在不同的运行环境和业务需求下自动调整资源分配,从而实现优化性能和成本的平衡。
要实现弹性的目的,需要做好以下几个方面:
可扩展容错自动化资源管控一致性问题前面已经介绍了CAP和BASE理论,在分布式系统重处理一致性问题是一个非常棘手的问题。如何确保多个节点在某一时刻具有相同的数据副本,非常具有挑战性的,因为需要处理节点故障、网络延迟、消息丢失等问题。
常用的一致性解决方案包括:
两阶段提交(2PC,Two-Phase Commit):两阶段提交是一种原子性提交协议,用于确保分布式事务的一致性。在两阶段提交中,有一个协调者(Coordinator)负责管理参与者(Participants)的提交过程。协议分为两个阶段:预提交阶段和提交阶段。在预提交阶段,协调者询问所有参与者是否准备好提交;在提交阶段,协调者根据参与者的反馈决定是提交还是回滚事务。两阶段提交可以确保分布式一致性,但可能会导致阻塞和性能问题。三阶段提交(3PC,Three-Phase Commit):三阶段提交是两阶段提交的改进版本,通过引入超时机制和额外的准备阶段来减少阻塞问题。在三阶段提交中,协议分为准备阶段、预提交阶段和提交阶段。相比于两阶段提交,三阶段提交具有更好的容错性和性能,但仍然存在一定的局限性,如依赖于可靠的网络和时钟同步。Paxos算法:Paxos算法是一种基于消息传递的分布式一致性算法。它通过多轮投票过程来达成分布式节点之间的共识。Paxos算法可以容忍一定数量的节点故障,但在实际应用中可能会受到消息延迟和复杂性的影响。Raft算法:Raft算法是一种为分布式系统提供强一致性的算法,与Paxos算法具有相似的功能,但更易于理解和实现。Raft算法通过选举一个领导者(Leader)来协调分布式节点的一致性。领导者负责处理客户端请求、日志复制和状态机更新。当领导者发生故障时,其他节点会自动选举新的领导者。分布式锁:分布式锁是一种在分布式系统中实现互斥访问共享资源的方法。通过使用分布式锁,可以确保在同一时刻只有一个节点能够访问共享资源,从而保证分布式一致性。分布式锁可以基于数据库、缓存(如Redis)或专用的分布式协调服务(如ZooKeeper)实现。最终一致性(Eventual Consistency):最终一致性是一种放宽一致性要求的方法,允许分布式系统在短暂的时间内出现数据不一致,但最终会达到一致状态。最终一致性可以通过异步复制、向量时钟、CRDT(Conflict-free Replicated Data Types)等技术实现。相比于强一致性,最终一致性具有更好的性能和可扩展性,但可能会导致数据不一致的问题。最后高并发确实是一个复杂且系统性的问题,由于篇幅有限,诸如分布式Trace、全链路压测、柔性事务都是要考虑的技术点。另外,如果业务场景不同,高并发的落地方案也会存在差异,但是总体的设计思路和可借鉴的方案基本类似。
作者:译译译链接:https://juejin.cn/post/7355685027857694761