聊聊高可用系统架构

用户头像
Jerry Tse
关注
发布于: 2020 年 08 月 26 日

0. 前言

在当前对互联网高度依赖的社会场景下,各大互联网公司都对保证系统高可用、持续对外提供服务做出了很多努力。本文就来聊一聊系统高可用相关内容。



1. 系统不可用原因

引起系统不可用的原因很多,主要分为外部和内部两大类:

  • 内部因素

  • 硬件故障:硬件设备故障的概率还是比较大的。内存,硬盘,网络设备等在长时间的运行过程中都有可能产生故障造成运行在硬件上的系统不可用

  • 软件bug:bug是永远无法避免的,只能尽量减少发生的概率,并且缩小产生后影响范围

  • 系统发布:系统发布的涉及新老版本转换,如果处理不当会产生问题

  • 运维操作:运维的日常操作也算一个风险点,简单的配置调整也可能造成系统不可用

  • 外部因素

  • 并发压力:如果瞬时并发请求超出系统承受能力,会将系统压垮造成不可用

  • 网络攻击:除了造成系统不可用外,网络攻击往往还会带来数据泄露和数据丢失等更加严重后果,需要我们重点防范

  • 网络故障:DNS解析服务异常,网站出口带宽网络异常,网络服务商光纤被挖断,总之这些故障我们防不胜防

2. 系统监控数据

既然造成系统不可用的原因很多且随时都有可能发生。那么能够及时帮助我们定位问题的监控系统就是系统必不可少的一部分。

一个广义上监控系统应该包含以下类型的数据:

  • 用户行为日志:

  • 采集用户访问的操作系统、浏览器版本、IP地址、页面访问路径、页面停留时间等数据

  • 用于统计PV/UV指标,分析用户行为,作为产品经理和运营人员设计产品功能和运营活动信息输入

  • 系统性能数据

  • 采集System load、内存占用、磁盘IO、网络IO等数据

  • 及时发现硬件故障并修复

  • 及时掌握系统负载情况并伸缩调整

  • 业务运行数据

  • 采集某个业务接口的调用次数、响应时长,某个缓存的命中率,某个离线任务系统任务处理数量(处理中、待处理),系统发送短信、邮件频率和次数等

  • 及时了解线上的性能问题,方便日后优化

  • 实时监控系统中各个服务的运行情况,发现问题,立即报警,及时定位解决



三类采集数据各有侧重:

用户行为数据:监控用户访问网站的行为,运营人员和产品经理相对更加关注。

系统性能数据:监控系统硬件运行情况,运维人员相对更加关注。

业务运行数据:监控系统业务服务运行情况,通常需要开发人员在程序中埋点植入。



综上所述,一个的监控系统不仅能通过采集软硬件运行时数据,监控系统硬件或服务发生的问题,方便运维、开发人员线上定位和后期性能优化。也可以采集用户访问行为数据,以分析用户使用方式及习惯,为精细化运维和定制化用户需求提供数据支撑。



3. 高可用系统架构设计原则

虽然有了监控系统之后我们可以及时发现问题并补救,做到“亡羊补牢,为时不晚”。但是我们还是希望采用积极主动的方式将我们的系统设计高可用的系统,以降低问题发生的概率,做到“未雨绸缪,防患于未然”。为达到高可用目标我们需要遵循一些基本原则。



3.1 保证整洁的架构

业务架构上我们可以采用DDD的思想,保证业务功能完备、清晰且易于扩展。

技术架构上我们需要遵循组件设计原则,面向对象设计原则,在代码运用各种设计模式,保证代码清晰,易于扩展和维护。

保证架构清晰、整洁不仅仅利于今后的功能扩展,也是保证系统高可用的必须条件。一个混乱的、如泥坑般的架构能保证系统能否正常运行都未尝可知,就更不要说对系统更高要求的高可用性。所以从设计伊始到系统运行中,我们使用要保证系统架构整洁。这是系统稳定运行的前提条件。



3.2 充分隔离

隔离也是保证系统可用性常用手段,就是限制问题发生后的影响范围。将系统从整体拆分为部分,某一部分出现问题后将问题限制在相对小的范围内,不影响系统整体功能。



业务维度隔离:将系统按照服务拆分为子系统,一个子系统异常不会影响系统整体对外提供服务。例如在电商网站中,评价子系统异常当时正常业务还可以进行,整体系统可用,对用户影响不大。但是一点交易子系统服务不可用,对用户影响很大。所以隔离只能从一定程度上降低系统异常之后的影响,并不能完全消除。



服务维度隔离:SOA架构和微服务架构不仅仅可以方便业务拆分支撑业务快速扩展,也可以起到服务维度隔离的目的,服务维度比业务维度有精细了一步。



硬件维度隔离:在系统运维层面,硬件隔离也是保证系统平稳运行的有效手段。通常使用多物理机、虚拟机或容器等手段部署。目的就是保证部署在不同地方的软件运行起来时不会相互影响。



3.3 保证冗余

高可用很重要的一个原则就是避免单点。为此我们需要实际各种各样的“冗余”,保证一个“节点”问题,有其他“节点”继续提供服务。



针对无状态的服务,通常采用集群部署、负载均衡的的方式提供服务级冗余,保证同时有多个服务对外提供服务。

针对有状态的服务,通常采用数据主从复制、主主复制、多次写入等方式保证数据冗余。



我们经常说的异地多活就是在数据中心层面做整体冗余,也是冗余思路的体现。



3.4 失效转移

有了上面冗余的底子,我不就担心服务或者数据出现问题,但是一旦某个服务出现我们要设计合理的失效转移机制,保证在短的时间内异常服务转移到正常服务,系统依然可用。

关系数据库主主复制主服务不可用下,其他主服务代替异常主服务对外服务场景。负载均衡后端服务不可用踢出不可用服务场景等都是失效转移的典型实现方式。

在我们自己的系统中我们也要根据业务特点实现自己失效转移能力。



3.5 异步架构

系统间同步调用方式下,调用方和被调用方式是强依赖关系。这通常会带来两个问题:

  • 异常传播:被调用方产生的异常会影响到调用方,引起系统不可用。同步调用的服务越多,依赖越多,被影响的可能性就越大。

  • 性能问题:同步调用每次必须等待返回结果,程序需要串行执行,调用方程序的执行时间等于所有被调用方执行时间的总和,无法充分利用CPU资源。在高并发下系统响应时长变长,造成系统间接不可用。



所以我们可以灵活使用多线程编程模式,事件驱动(反应式)架构,和消息队列方式。将系统间调用方式从同步转换为异步,提升系统的可用性。

当然无论是多线程编程或者是反应式编程都会比传统的同步编程带来复杂性上的增加,我们在使用中也要理清场景,在合适的场景下应用,才能真正体现异步编程的价值。

3.6 降低负载

一个系统是有自己的请求处理极限的,达到了这个极限,系统就无法在对外提供服务了。所以我们要在系统达到极限之前,限制用户并发访问,让系统不被请求洪峰冲垮。主要的方案有三种:

  • 限流:限制用户请求数,超过数量直接拒绝服务。

  • 熔断:主要针对被调用服务,一段时间内发现服务频发异常,可以采用半开模式限制部分调用或者关闭模式完全不调用,过一段时间视情况而恢复调用(开启)。

  • 降级:在高并发场景到来之前,主动关闭某些非核心服务,节省系统资源。



限流是主动将用户请求拦截在系统之外,想象一下超时搞促销,门口排队发牌进入。

熔断是一旦服务调用出现问题,不再继续调用,直接返回用户异常。想象一下排队买炸鸡,当炸鸡卖完了(产生了异常),通常会用大喇叭告诉后面的人今日售罄,明天请早(返回用户失败)。

降级就是主动不再提供服务,想象一下我们去饭店吃饭,发现被包场(预留资源,应对高并发),不再提供散座服务了(降级服务)



3.7 异常处理

最后要说一下异常处理问题,这里说的不是代码逻辑的异常,而是业务异常处理,正确的处理系统运行中各种业务异常,才能保证系统业务正确,只有业务正确的系统才是可用的系统。

我们讨论异常两种场景:

  • 保证事务回滚:事务执行过程中如果出现异常需要回滚

  • 本地事务保证:如果可以使用本地数据库事务处理事务那是最好的。

  • 分布式事务保证:分布式事务会带来性能问题,而且使用场景也不是很多。

  • 根据操作日志回滚操作:采用记录操作日志,出现异常的时候根据日志执行逆操作。这种方式使用场景相对广泛,但是需要单独开发。

  • 服务调用失败异常

  • 重试:调用失败可以选择重新调用,但是需要下游服务保证幂等性。

  • 幂等

  • 自幂等:有些服务天然有幂等性,例如查询操作,数据库更新操作等。

  • 流水号:但是有些服务没有幂等性,例如数据库更新加值(a=a+1)操作,此时需要在请求的时候携带一个流水号,通过流水号唯一性判断是否处理过相同业务。



4. 总结

本文我们主要聊了聊高可用系统框架。重点介绍了设计一个高可用框架需要遵循的七个原则,在实际工作中我们要根据自己的业务场景灵活使用这些原则。原则之间各有侧重,需要你自己去取舍和权衡。我们需要找到系统可用性与实现成本之间的平衡点,这也是架构设计的不二法则——平衡之道。

发布于: 2020 年 08 月 26 日 阅读数: 55
用户头像

Jerry Tse

关注

还未添加个人签名 2018.11.02 加入

还未添加个人简介

评论

发布
暂无评论
聊聊高可用系统架构