当前位置: 首页 > 科技观察

说说高可用的11个关键技巧

时间:2023-03-18 14:19:19 科技观察

大家好,我是Tom,一个大型互联网架构设计,关注一个四件套的组合拳法,高并发,高性能,高可用,高膨胀。如果能掌握这四个方面,在日常工作中应对大厂的面试和架构方案的设计基本不是问题。今天Tom哥就带大家学习一下高可用有哪些设计技巧?面对一个庞然大物,如果没有合理的分工和分层。任何一个小错误都会被无限放大,酿成一场巨大的灾难。一切都连接起来,回到我们的软件架构。以前的系统都是单体系统,电商业务、会员、产品、订单、物流、营销等模块都堆在一个系统里。每到节假日,都有大促活动。系统扩容时,一扩满,一挂全部。只要一个接口出现问题,整个系统就无法使用。“你不能把所有的鸡蛋都放在一个篮子里。”任何人都不能承担相关风险。因此,系统拆分成为了更多人的选择。慢慢的就有了现在看到的微服务架构,按照DDD的思想把一个复杂的业务域划分成若干个子系统。做好它们之间的边界隔离,降低传播风险。2.解耦软件开发有一个重要的原则就是“高内聚低耦合”。小到接口抽象和MVC分层,大到SOLID原则和23种设计模式。核心是降低不同模块之间的耦合度,防止一个错误的改动影响到整个系统。以开闭原则为例,对扩展开放,对修改关闭。随着业务功能的迭代,如何保证每次改动不影响原来的旧代码。Spring框架为我们提供了一个很好的思路。有一个重要的设计AOP,全称(AspectOrientedProgramming),面向方面的编程。核心是利用动态代理技术,通过增强字节码来拦截方法调用,从而在方法调用前后添加我们需要的额外处理逻辑。当然,还有一个重要的思想就是事件机制。通过发布订阅模型,对于新的需求,只需要订阅相应的事件通知,并有针对性地消费即可。不会对原有代码进行侵入式修改,会不会好很多?3、异步同步是指当一个进程在执行一个请求时,如果请求需要一段时间才能返回信息,那么该进程会一直等到收到返回信息后才继续执行。效率会大大降低,聪明的人想到了异步的方式。如果是非实时响应动作,可以异步完成,线程不需要一直等待,而是继续执行后面的逻辑。如:线程池(ThreadPoolExecutor)、消息队列等都是基于这个原理。例如,用户在淘宝下单时,关心的是订单是否创建成功,后续的支付流程能否进行。至于其他的业务动作,比如短信通知、邮件通知、生成订单快照、创建加班任务记录等,这些非核心动作的用户并不是特别关心。我们可以使用消息队列的发布/订阅机制。数据库插入订单记录后,向MQ发布消息,通知用户下单成功。其他的事情由不同的Task任务订阅消息异步处理,互不干扰。4、RetryRetry主要体现在远程RPC调用上。受网络抖动、线程资源阻塞??等因素影响,无法及时响应请求。为了提高用户体验,调用者可以通过retry重新发送请求,尝试得到结果。比较:浏览器的F5刷新机制类似。接口重试是一把双刃剑。虽然客户端收到了响应超时结果,但是我们无法确定服务端是否已经执行完毕。盲目重试会产生严重后果。例如:银行转账。重试通常与幂等结合使用。如果一个接口支持幂等性,那么你可以随意重试。关于幂等方案:在插入前进行查询,看是否存在,再决定是否插入;添加唯一索引;建立防重表;引入一个状态机,比如支付后,订单状态调整为已支付,更新SQL记录前添加条件判断;添加分布式锁;采用Token机制,在服务端加入token校验,只有第一次请求合法。5.补偿我们知道并不是所有的请求都能得到成功的响应。除了上面的重试机制,我们还可以通过补偿的方式来实现数据的最终一致性。业务补偿按处理方向分为两部分:正向。多个操作构成一个分布式事务。如果部分成功,部分失败,我们会通过尽力而为机制将失败的任务推向成功状态。撤销。同理,我们也可以通过逆向操作,将一些成功的任务恢复到初始状态。注意:补偿操作有一个重要前提,即业务能够在短时间内接受数据不一致。实现补偿的方式有很多种:在本地建表,存储相关数据,然后通过定时任务扫描提取,借助反射机制触发执行。也可以使用简单的消息中间件构造业务消息体,由下游消费任务执行。如果失败,可以使用MQ的重试机制多次重试。6.备份任何服务器都有宕机的可能。一旦存储数据并带上状态,如果出现故障导致数据丢失,后果是我们无法承受的。因此,容灾和备份成为互联网的基本能力。如何备份,不同的框架有不同的玩法。我们以Redis为例:Redis使用RDB和AOF实现两台服务器之间的数据同步:RDB,全量数据同步。AOF,增量数据同步,回放日志。主节点挂了怎么办?哨兵机制就介绍到这里。sentinel机制可以实现主从库的自动切换,有效解决failover。整个过程分为三个阶段:监控、选举和通知。除了Redis中间件,其他常见的MySQL、Kafka消息中间件、HBase、ES等,所有与数据存储相关的介质都有备份机制。一旦主节点挂掉,备份节点就会启用,保证数据不丢失。7.多活策略有了上面的备份策略,就万事大吉了吗?在一些极端情况下,如:机房停电、机房火灾、地震、山洪等不可抗力因素,所有服务器都可能发生故障,无法对外提供服务,导致整体业务瘫痪。为了降低风险并确保24小时服务可用性,我们将采用多主动策略。常见的多活方案有同城双活、两地三中心、三地五中心、异地双活、异地多活等。不同的方案有不同的技术要求、建设成本和运行维护成本。多活的技术方案比较复杂,需要考虑的问题也很多,这里仅举几个例子。8.Isolation隔离是物理层面的划分,在物理上将几个系统隔离开来,低耦合设计,独立部署。每个子系统都有自己独立的代码库,独立开发,独立发布。如果发生故障,它们不会相互干扰。当然,如果不同子系统之间存在相互依赖,这种情况比较特殊,需要默认值或者对异常进行特殊处理,属于业务层面的解决方案。隔离是分布式技术的衍生,是我们最常用的微服务解决方案。将大型复杂系统拆分为多个微服务系统。这些微服务子系统通常由不同的团队开发和维护,独立部署,通过RPC远程调用服务。隔离使得系统之间的边界更清晰,故障可以更隔离,问题发现和解决更快,系统可用性也更高。9、对于限流、高并发的系统,如果遇到流量高峰,会超过当前系统的承载能力。我们应该做什么?一种解决方案是一切按顺序接受,CPU、内存、Load负载高到最后无法处理,导致所有请求超时,无法正常响应。另一种解决方案,“放弃,就会得到”,我们直接丢弃多余的流量。限流的定义:限制到达系统的并发请求数,保证系统可以正常响应部分用户请求,对于超过限制的流量,通过拒绝服务来保证整个系统的可用性。按作用范围分:限流分为单机限流和分布式限流。单机版的限流主要是利用本地内存来实现计数器,比如通过AtomicLong#incrementAndGet(),但是需要注意的是要定期清理之前不用的key,释放内存。纯内存实现,无需与其他节点汇总统计,性能最高。但是优点也是缺点,不可能做到全球统一限流。分布式限流单机版的限流只能保护自己的节点,不能保护应用所依赖的各种服务,扩容或缩容节点时也不能准确控制整个服务的请求限额。分布式限流,以集群为维度,可以轻松控制本集群的请求限制,从而保护下游所依赖的各种服务资源。限流支持多个维度:在一定时间内(比如每分钟)整个系统处理了多少请求。在一定时间内单个接口处理多少流量。一定时间内单个IP、城市、频道、设备id、用户id等发送的请求数。如果是开放平台,为每个appkey设置独立的访问速率规则。常见的限流算法:计数器限流滑动窗口限流漏桶限流token-barrel限流10、熔断和熔断,其实是当调用链路中某个资源处于不稳定状态(比如as:calltimeoutorabnormalratioElevated),限制对该资源的调用,使请求快速失败,避免影响其他资源造成级联错误。熔断的主要方法是使用断路器来阻止对故障服务器的调用。断路器有闭合、断开和半开三种状态。状态机:关闭(Closed)状态:在该状态下,请求将被转发到后端服务。同时记录请求失败的次数。当一段时间内请求失败次数超过一定次数后,就会进入open状态。打开(Open)状态:在该状态下,断路器将直接拒绝请求并返回错误,而不调用后端服务。同时,会有一个计时器,时间一到,就会变成半开状态。目的是假设服务将在一段时间内恢复正常。半开状态:在该状态下,断路器会尝试将一些请求转发给后端服务,以检测后端服务是否恢复。请求失败则进入open状态,成功则进入closed状态,同时清零计数。目前市面上比较流行的解决方案是阿里的开源框架Sentinel,它提供了一个Dashboard控制台,用于定义资源和配置规则。11.降级降级是系统保护的重要手段。就像“好钢用在刀刃上”一样,为了让有限的资源发挥最大的价值,我们会暂时关闭一些非核心的功能,以减轻系统压力,为核心业务保留有限的资源。例如电商大促期间,业务高峰期,系统无法承受所有流量时,系统负载和CPU使用率已经超过警戒线,可以通过降级一些非核心功能来降低系统压力。商品评价、交易记录等功能暂时关闭。弃车保帅,保证订单创建、订单支付等核心功能的正常使用。当然,不同的商家,不同的公司,处理方式是不一样的。需要结合实际场景与业务同学共同商讨,最终达成统一认可的降级方案。总结一下:降级就是通过暂时关闭一些非核心服务或组件来保护核心系统的可用性。