系统“三高”的解决策略和简单优化
系统“三高”的解决策略和简单优化
摘要:当前,数字化在给企业带来业务创新,推动企业高速发展的同时,也给企业的IT软件系统带来了严峻的挑战。面对流量高峰,高并发”、“高性能”、“高可用”是互联网系统架构设计永恒的主题。本文介绍了高并发系统的度量指标,高并发系统的设计思路。
1 高并发
高并发(High Concurrency)通常是指通过设计保证系统能够同时并行处理很多请求。高并发是现在互联网分布式框架设计必须要考虑的因素之一,它是可以保证系统能被同时并行处理很多请求。对于高并发来说,从微观角度看,它的指标有:
1.响应时间:系统对进来的请求反应的时间,比如你打开一个页面需要1秒,那么这1秒就是响应时间。
2.吞吐量:吞吐量是指每秒能处理多少请求数量。秒查询率:秒查询率是指每秒响应请求数,和吞吐量差不多。
3.并发用户数:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数。
目前所有集群分布式,微服务,云原生,中台,数据湖,大数据等等,包括SpringCloud一系列解决方案组件等等,最终目的都是为了这三点。
从宏观角度看,高并发系统设计的目标有三个:高性能、高可用,以及高可扩展。
1.高性能:性能体现了系统的并行处理能力,在有限的硬件投入下,提高性能意味着节省成本。同时,性能也反映了用户体验,响应时间分别是100毫秒和1秒,给用户的感受是完全不同的。
2.高可用:表示系统可以正常服务的时间。一个全年不停机、无故障;另一个隔三差五出线上事故、宕机,用户肯定选择前者。另外,如果系统只能做到90%可用,也会大大拖累业务。
3.高扩展:表示系统的扩展能力,流量高峰时能否在短时间内完成扩容,更平稳地承接峰值流量,比如双11活动、明星离婚等热点事件。
系统性总结下高并发的设计方案,会从以下两部分展开:先总结下通用的设计方法,然后再围绕高性能、高可用、高扩展分别给出具体的实践方案。通用的设计方法主要是从「纵向」和「横向」两个维度出发,俗称高并发处理的两板斧:纵向扩展和横向扩展。
1.1 纵向扩展(scale-up)
它的目标是提升单机的处理能力,方案又包括:
1、提升单机的硬件性能:通过增加内存、 CPU核数、存储容量、或者将磁盘 升级成SSD 等堆硬 件 的 方 式 来 提升 。
2、提升单机的软件性能:使用缓存减少IO次数,使用并发或者异步的方式增加吞吐量。
1.2 横向扩展(scale-out)
因为单机性能总会存在极限,所以最终还需要引入横向扩展,通过集群部署以进一步提高并发处理能力,又包括以下2个方向:
1、做好分层架构:这是横向扩展的提前,因为高并发系统往往业务复杂,通过分层处理可以简化复杂问题,更容易做到横向扩展。
2、各层进行水平扩展:无状态水平扩容,有状态做分片路由。业务集群通常能设计成无状态的,而数据库和缓存往往是有状态的,因此需要设计分区键做好存储分片,当然也可以通过主从同步、读写分离的方案提升读性能。
2 高性能
高性能计算(High performance computing, 缩写HPC,是指程序处理速度非常快,所占内存少,cpu占用率低。高性能的指标经常和高并发的指标紧密相关,想要提高性能,那么就要提高系统发并发能力,两者互相捆绑在一起。应用性能优化的时候,对于计算密集型和IO密集型还是有很大差别,需要分开来考虑。还有可以增加服务器的数量,内存,IO等参数提升系统的并发能力和性能,但不要浪费资源,要考虑硬件的使用率最高才能发挥到极致。我们常见的特征值有以下几类。
平均值:顾名思义,平均值是把这段时间所有请求的响应时间数据相加,再除以总请求数。平均值可以在一定程度上反应这段时间的性能,但它敏感度比较差,如果这段时间有少量慢请求时,在平均值上并不能如实的反应。
最大值:这个更好理解,就是这段时间内所有请求响应时间最长的值,但它的问题又在于过于敏感了。
分位值:分位值有很多种,比如 90 分位、95 分位、75 分位。以 90 分位为例,我们把这段时间请求的响应时间从小到大排序,假如一共有 100 个请求,那么排在第 90 位的响应时间就是 90 分位值。分位值排除了偶发极慢请求对于数据的影响,能够很好地反应这段时间的性能情况,分位值越大,对于慢请求的影响就越敏感。
由上来看,分位值是最适合作为时间段内,响应时间统计值来使用的,在实际工作中也应用最多。除此之外,平均值也可以作为一个参考值来使用。
怎么样提高性能呢?
2.1 高性能的实践方案
1.集群部署,通过负载均衡减轻单机压力。
2.多级缓存,包括静态数据使用CDN、本地缓存、分布式缓存等,以及对缓存场景中的热点key、缓存穿透、缓存并发、数据一致性等问题的处理。
3.分库分表和索引优化,以及借助搜索引擎解决复杂查询问题。
4.考虑NoSQL数据库的使用,比如HBase、TiDB等,但是团队必须熟悉这些组件,且有较强的运维能力。
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 两个维度考虑所有可能的优化点,需要有配套的监控系统实时了解当前的性能表现,并支撑你进行性能瓶颈分析,然后再遵循二八原则,抓主要矛盾进行优化。
3 高可用
高可用性H.A.(High Availability)指的是通过尽量缩短因日常维护操作(计划)和突发的系统崩溃(非计划)所导致的停机时间,以提高系统和应用的可用性。它与被认为是不间断操作的容错技术有所不同。HA系统是企业防止核心计算机系统因故障停机的最有效手段。
高可用通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性。高可用注意如果使用单机,一旦挂机将导致服务不可用,可以使用集群来代替单机,一台服务器挂了,还有其他后备服务器能够顶上。或者使用分布式部署项。比如现在redis的高可用的集群方案有: Redis单副本,Redis多副本(主从),Redis Sentinel(哨兵),Redis Cluster,Redis自研。
要保证架构的高可用,就要保证架构中所有组件以及其对外暴露服务都要做高可用设计,任何一个组件或其服务没做高可用,都意味着系统存在风险。
那么这么多组件该怎么做高可用设计呢,其实任何组件要做高可用,都离不开「冗余」和「自动故障转移」,众所周知单点是高可用的大敌,所以组件一般是以集群(至少两台机器)的形式存在的,这样只要某台机器出现问题,集群中的其他机器就可以随时顶替,这就是「冗余」。简单计算一下,假设一台机器的可用性为 90%,则两台机器组成的集群可用性为 1-0.1*0.1 = 99%,所以显然冗余的机器越多,可用性越高。
但光有冗余还不够,如果机器出现问题,需要人工切换的话也是费时费力,而且容易出错,所以我们还需要借助第三方工具(即仲裁者)的力量来实现「自动」的故障转移,以达到实现近实时的故障转移的目的,近实时的故障转移才是高可用的主要意义
怎样的系统可以称之为高可用呢,业界一般用几个九来衡量系统的可用性,如下
目前多数互联网都会采用微服务架构,常见架构如下:
接下来我们就来一起看看架构中的各个组件如何借助「冗余」和「自动故障转移」来实现高可用。
1.接入层&反向代理层
这两层的高可用都和keepalived有关,所以我们结合起来一起看。
keepalived 的心跳检测主要通过发送 ICMP 报文,或者利用 TCP 的端口连接和扫描检测来检测的,同样的,它也可以用来检测 Nginx 暴露的端口,这样的话如果某些 Nginx 不正常 Keepalived 也能检测到并将其从 LVS 能转发的服务列表中剔出。
借用 keepalived 这个第三方工具,同时实现了 LVS 和 Nginx 的高可用,同时在出现故障时也可以将宕机情况发送到对应开发人员的邮箱以让他们及时收到通知处理,确实很方便,Keepalived 应用广泛,下文我们会看到它也可以用在 MySQL 上来实现 MySQL 的高可用。
2.中间件
(1)ZooKeeper
可以看到由于只有一个 Leader,很显然,此 Leader 存在单点隐患,那么 ZK 是怎么解决此问题的呢,首先 Follower 与 Leader 会用心跳机制保持连接,如果 Leader 出现问题了(宕机或者因为 FullGC 等原因无法响应),Follower 就无法感知到 Leader 的心跳,就会认为 Leader 出问题了,于是它们就会发起投票选举,最终在多个 Follower 中选出一个 Leader 来(这里主要用到了 Zookeeper Atomic Broadcast,即 ZAB 协议,它是为 ZK 专门设计的一种支持崩溃恢复的一致性协议),选举的细节不是本文重点,就不在此详述了。
除了 ZAB 协议,业界上常用的还有 Paxos,Raft 等协议算法,也可以用在 Leader 选举上,也就是是在分布式架构中,这些协议算法承担了“第三者”也就是仲裁者的作用,以承担故障的自动转移
(2)Redis
Redis 的高可用需要根据它的部署模式来看看,主要分为「主从模式」和「Cluster 分片模式」两种
(3)ES
再来看一下 ES 是如何实现高可用的,在 ES 中,数据是以分片(Shard)的形式存在的,一个节点中索引数据共分为三个分片存储。但只有一个节点的话,显然存在和 Redis 的主从架构一样的单点问题,这个节点挂了,ES 也就挂了,所以显然需要创建多个节点。一旦创建了多个节点,分片(图中 P 为主分片,R 为副本分片)的优势就体现出来了,可以将分片数据分布式存储到其它节点上,极大提升了数据的水平扩展能力,同时每个节点都能承担读写请求,采用负载均衡的形式避免了单点的读写压力。
3.存储层
接下来我们再来看一下最后一层,存储层(DB),这里我们以 MySQL 为例来简单地讨论一下其高可用设计, MySQL 的高可用思想类似,它也分主从和分片(即我们常说的分库分表)两种架构。主从的话与 LVS 类似,一般使用 keepalived 的形式来实现高可用,如果 master 宕机了,Keepalived 也会及时发现,于是从库会升级主库,并且 VIP 也会“漂移”到原从库上生效,所以说大家在工程配置的 MySQL 地址一般是 VIP 以保证高可用。数据量大了之后就要分库分表了,于是就有了多主,就像 Redis 的分片集群一样,需要针对每个主配备多个从。
4 结语
高并发确实是一个复杂且系统性的问题,如果业务场景不同,高并发的落地方案也会存在差异,但是总体的设计思路和可借鉴的方案基本类似。高并发设计同样要秉承架构设计的3个原则:简单、合适和演进。" 过早的优化是万恶之源 ",不能脱离业务的实际情况,更不要过度设计,合适的方案就是最完美的。