空谈系统架构设计之高并发、高可用

2020-11-10 10:29:04 浏览数 (1)

对于一个应用系统,特别是互联网系统,高并发、高可用是两个非常重要的非功能性需求,这篇文章尝试从应用系统架构角度分析如何满足这两个特性。

1

高并发,high concurrency,简单讲就是说系统可以同时支撑大量的并发请求,反映的是系统的计算能力。个人理解,高并发都可以基于scalability来实现,而scalability通常可分为两个维度:垂直扩展(scale up)和水平扩展(scale out)。垂直扩展(scale up)就是通过不断增加单机计算资源(cpu、memory等)来提高并发处理能力,而水平扩展(scale out)则是通过增加机器数量来提高并发处理能力;相对来说,scale up可能更加容易,因为对于单机系统,不用考虑分布式事务等很tricky的问题,但是由于计算机硬件发展的瓶颈,并且基于成本的考虑,通常我们优先考虑的方向是scale out,因为scale out不但可以实现高并发,同时也可以一定程度上实现高可用。

高可用,high availability,简单讲就是说系统可以在“任何时候”都保持服务在线,即使是在有服务器宕机的情况下,反映的是系统的稳定性。从理论上来说,高可用则是通过增加冗余(replication)来实现,当有节点发的故障时,自动切换到备份节点上。

2

一个典型的应用系统架构可能都包含这三层结构:web server层(比如nginx),app server层(比如tomcat)和数据库层(比如postgres),要实现高并发和高可用,一般来说都是针对这三层分别考虑实现。

2.1

web server层,通常架构在app server层前面,主要功能是负载均衡、反向代理等,显然这提高了app server层的并发处理能力,但是随之而来的就是web sever本身可能成为性能瓶颈和单点故障点。web server层可选的方案,除了常见的nginx,还有一些其它流行的方案,比如haproxy、lvs等,甚至基于硬件优化的F5。

web server的单点故障问题,反过来讲也就是高可用问题,主流的解决方案是部署多台web server,使用keepalived把多台web server组成一个集群,对外暴露一个虚拟IP(VIP),集群在任意时候只有主web server提供服务,其它从web server作为备份,如果主web server宕机,VIP则自动漂移到一台从web server。

这个方案虽然实现了高可用,但却还是存在单点性能瓶颈的问题,因为即使是号称再好的web server组件,也终究会有高并发的极限,其实本质上这就是一个如何实现web server层高并发的问题。要解决这个问题,只能做水平扩展(scale out),部署多个web server集群,每个暴露一个VIP,通过DNS轮询(或者gslp、gtm等技术)把用户请求分散到不同的web server集群上,以此实现高并发。

2.2

app server层,常规的做法就是直接scale out,部署多个duplicated的app server,借助web server层的负载均衡,一定程度既实现了高可用,也实现了高并发。

在app server层做scale out之前,需要考虑另外一个问题:由于application常常需要保存一些状态信息(比如用户的session等),如果直接scale out,用户的session可能会丢失或者不能保持。针对这个问题,现在流行的解决方案是把这些状态信息持久化到缓存中(比如redis等),这样application就是一个无状态的服务,对于无状态的服务,scale out就不会有问题。

从高并发实现的角度,可以分别从scale up和scale out两个方向考虑;回到application功能实现层面,也可以有scale up和scale out两个方向,其实它们分别对应的就是monolith模式和micro service模式,个人认为,正如scale out所展现的扩展性优点,micro service的最大好处则是一种团队合作开发模式的提高,更加敏捷高效,易于扩展。

2.3

数据库层,由于所有用户的数据都是存储在数据库,一旦数据库挂了,即使上面层的高可用、高并发做得再好,整个系统也不可用,所以数据库层的高可用、高并发尤其重要。由于数据库层是有状态的,如果和app server层类似,通过简单的scale out实现高可用、高并发,并且各个数据库节点都可以读写的话,则可能会产生数据不一致的问题(CAP理论);并且,即使可以规避这个问题,比如采用读写分离(只允许主节点写,从节点读),最终也会由于单个数据库表太大,而导致读数据性能降低,这种情况也许可以通过分表解决,但是由于只能是主节点接收写请求,主节点则很容易成为高并发写的性能瓶颈。

所以,对于有状态的数据库层,要实现高并发、高可用,应该怎样设计呢?简单讲,高可用还是通过增加冗余(replication)实现,主从节点无缝切换可以使用上面提到的keepalived方法。而高并发则是通过分库实现,把数据通过一定的算法分散到不同的数据库节点存放,这样每个数据库节点只需要处理部分流量,相比简单的scale out,这种方式一定程度减少了数据冗余,并且支持高并发写,所以我们也许可以称这种scale out方式为“复杂的scale out”。

从整体上来讲,数据库层实现高并发、高可用的方式和 kafka、redis等中间件实现的原理类似,即分别通过partition和replication来实现。

3

提高系统的高并发、高可用,除了上面提到的infrastructure层面的一些设计之外,有时候我们还需要从application本身架构层面考虑,比如:对于一个秒杀系统,也需要支持高并发、高可用,但是由于这类系统可能只是用于促销,意即可能只会在活动开始一段时间有高并发请求,并不会持续,所以可能不需要从基础架构层面提升,因为那样也意味着更高的成本。那么,针对这类场景,如何从application本身架构层面实现高并发、高可用呢?

其实,网上有很多文章介绍得很详细,但总结下来,有两个方面:

一、限流,防止突然出现大流量把系统搞挂。具体实现可以有:1)rate limit,限制用户发达请求的频率;2)把商品库存信息维护在redis中,库存扣减完了就直接返回后续所有请求。

二、削峰,秒杀请求先不做校验,直接存数据库,然后马上返回给用户“正在排队中”等信息,接着通过queue通知订单处理服务异步处理请求,完成下单操作。这样做的一个好处是,由于真正完成下单处理的时间较长,而秒杀请求直接存数据库可以很快,利用queue做一个缓冲,提高了用户可见的高并发。并且,基于这样的实现,使得系统更加resilient,在面对突发的高并发时,可以保证高可用。

4

对于高并发,除了架构层面的考虑,从服务器物理连接层面,也需要能支持更多的并发连接;随着技术的发展,现在主流的tomcat、nginx、redis等都是基于多路复用技术epoll实现,大大提高了并发连接数,进而从这的个层面有效的实现了高并发。

5

对于高可用,为了兼顾一致性(consistency)和可用性(availability),分布式数据系统常常会借助于一些分布式共识算法(consensus algorithm)来实现replication,比如zookeeper、etcd等;或者再考虑到性能,会采用一些基于分布式共识算法延伸而来的折中方案,比如kafka的ISR。数据库的高可用,除了上面提到的keepalived方法,也可以使用zookeeper来管理主从节点,并且实现主从节点自动切换(选举),类似于kafka。

0 人点赞