在高并发的系统中如何实现系统的高可用?方案有很多种,大概可以概括为下面几点:
消除单点
集群或者分布式,可以说是高可用系统设计的最有效方案,也是消除单点的常用方案。云原生最核心的优势是弹性伸缩,通过横向扩展来解决应用容量的瓶颈。为了达到更加精细化的应用弹性,我们在系统设计过程中做了应用分层、分布式设计、微服务架构设计。
一个应用系统要实现分布式,所有从流量接入到服务调用、数据存储、缓存、消息队列、对象存储等,都可以是分布式,这样每个环节都不存在单点的问题,这样的系统才具备良好的弹性能力以及高可用性。
无状态化
为了实现应用容量的横向扩缩容,服务设计为可伸缩且可部署到任意基础设施中,前提条件及时服务不应该为有状态的,比如如果服务器中的session信息只保存到应用服务器,那么该应用就是有状态的。
无状态化并不意味着应用系统完全没有状态,而是通过状态外置来实现可伸缩部分服务的无状态化。整个架构分为无状态部分和有状态部分,而业务逻辑部分往往作为无状态部分,而将状态保存在有状态的中间件中,如缓存、数据库、对象存储、大数据平台、消息队列等,这就是我们常说的状态外置。这样无状态部分可以很容易地横向扩展。
幂等设计
幂等一般针对后台服务而言,“服务幂等性”是指针对某一服务的多次调用,只要请求的参数是一样的,那么服务返回结果一定是一致的,且后台系统不会因为多次调用而产生任何影响,比如脏数据。
在分布式环境下,前端应用的一次请求可能会调用多个后台服务,可能会发生某些后台服务超时或者网络闪断等情况。这时为了保证业务的成功,前端应用一般会通过重试的机制达到数据的最终一致性。因此,在云原生分布式微服务架构下,服务幂等设计是作为一种解决应用高可用和数据一致性的基本实践手段。
一般情况下,保证服务的幂等性有下面几种方案:
-
全局唯一id
如果使用全局唯一ID,就是根据业务的操作和内容生成一个全局ID,在执行行操作前先根据这个全局唯ID是否存在,来判断这个操作是否已经执行。如果不存在则把全局ID,存储到存储系统中,比如数据库、redis等。如果存在则表示该方法已经执行。分布式锁是该方案经常使用的一种实现。 -
去重表
这种方法适用于在业务中有唯-标的插入场景中,比如在以上的支付场景中,如果-一个订单只会支付一次,所以订单ID可以作为唯一标识。 这时,我们就可以建一张去重表, 并且把唯一标识作为唯一 索引, 在我们实现时,把创建支付单据和写入去去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。 -
插入或更新
这种方法插入并且有唯一索引的情况, 比如我们要关联商品品类,其中商品的ID和品类的ID可以构成唯一索引,并且在数据表中也增加了唯一索引。这时就可以使用InsertOrUpdate操作。
弹性伸缩
弹性伸缩包括弹性扩展和弹性收缩,是实现应用高可用的重要技术手段。在业务高峰期,当系统负载较大时,通过横向扩展新的应用节点或者拉起新的容器来分摊原有压力节点上的负载,让系统平稳渡过前端应用高并发流量的冲击。而在业务低峰期,通过释放部分应用节点以提高资源的有效利用率。
弹性伸缩分为手动弹性伸缩和自动弹性伸缩。手动弹性伸缩是运维人员通过观测资源的水位或者收到资源告警信息后,人工为应用集群添加新的服务节点来解决资源负载过高的问题。自动弹性伸缩则是系统根据管理员预先设置的规则自动触发进行资源节点的动态管理。
容错设计
容错性是指软件检测应用程序所运行的软件或硬件中发生错误并从错误中恢复的能力,通常可以从系统的可靠性、可用性、可测性等方面来衡量。为了消除单点故障,应用部署时采用集群部署、分布式部署,都是为了把软硬件故障给应用系统带来的影响降到最小。容错设计分为应用、系统、服务等不同的层面。
容错设计还有一个很重要的思路——冗余设计,通过一定的额外成本投入换取系统的可靠性提升,包括资源冗余(防止不可预期的业务增长对于系统的压力)、数据冗余(防止核心数据异常丢失)、架构冗余(如双通信链路)。
异步设计
在一定程度上,同步可以看作单线程,这个线程请求一个方法后就等待这个方法给它回复,否则它不往下执行。异步可以看作多线程的,请求一个方法后就不管了,继续执行其他的方法。在实现方面,通常把同步的请求通过消息队列转成异步化订阅处理,以减少系统之间的耦合,避免核心应用被非核心应用拖垮。同时,消息队列起到了很好的削峰填谷的作用,让瞬间的高并发请求有一个缓冲,从而保护后台应用系统的平稳运行。
缓存设计
缓存的主要作用是降低应用和数据库的负载,提高系统性能和客户端访问速度。在架构和业务设计上,可以考虑将访问量较大、不经常修改的(如字典表和系统参数),或对数据库性能影响较大的查询的结果进行缓存,以提高系统整体性能。
动静分离
动静分离是指静态页面与动态页面分开在不同的系统上访问的架构设计方法,静态资源(如html、js、css、img等)与后端服务分离部署。静态资源放在CDN、Nginx等设施上,访问路径短,访问速度快(几毫秒);动态页面访问路径长,访问速度相对较慢(数据库的访问、网络传输、业务逻辑计算),需要几十毫秒甚至几百毫秒,对架构扩展性的要求更高。
流控降级
无论一个系统的容量预估做得多么充分,我们总是无法避免一些不可预期的并发流量的冲击。这些流量既可能是正常的业务流量,也可能是非法的行为。流控降级相当于给后端应用系统上了一道保险,让系统具有一定的抗压能力,被广泛用于秒杀、消息削峰填谷、集群流量控制、实时熔断等场景中,从多个维度保障客户的业务稳定性。
流控降级包括流控和降级两个概念。流控,即流量控制(flow control),根据流量、并发线程数、响应时间等指标,把随机到来的流量调整成合适的形状,即流量塑形,避免应用被瞬时的流量高峰冲垮,从而保障应用的高可用性。熔断降级会在调用链路中某个资源出现不稳定状态(例如调用超时或异常比例升高)时对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源而导致级联错误。
健康检查
应用健康检查是弹性伸缩和弹性自愈的基础,传统的负载均衡只能根据IP:port来进行节点探活,但无法处理应用假死的情况。应用健康检查是为了确保当前节点的应用能够正常对外提供服务,一旦应用健康检查失败,管控节点(如负载均衡、服务注册中心、K8s管控等)必须及时把该故障节点摘除,防止请求再打到故障节点而导致业务失败。
快速失败
面向失败设计原则是所有的外部调用都有容错处理,我们希望失败的结果是可预期的、经过设计的。系统发生异常时能够快速失败,然后快速恢复,以保证业务永远在线,不能让业务半死不活地僵持着。比如Jedis连接池不够时,直接抛异常返回,不让请求卡着,占用服务器资源。
异常监控
在一个高可用的系统中,很多时候系统宕机是一个渐进的结果,我们可以通过一些监控,持续监控系统的运行状态,通过邮件或者信息的方式发送给运维或者开发人员就可以及时的避免系统宕机。
优雅上下线
优雅上下线是实现业务7×24小时不间断运行的重要保障。为了实现应用版本变更的发布过程对在线业务的无感知,应用发布时我们一般采用分批发布的策略。
优雅下线是指应用节点平滑下线,停应用之前会有一段时间的“静默期”(如10s),静默期内负载均衡不会再把新的请求打到该节点,同时该节点已经接收到的请求能够正常处理完。通过这种机制实现节点下线过程对业务平滑无感。