分布式系统弹力设计

弹力设计即容错设计,包括容错能力(服务隔离,异步调用,请求幂等),可伸缩性(有无状态),一致性(补偿,重试),应对大流量的能力(熔断,降级)
来源:极客时间左耳听风专栏

系统可用性测量

Availability = MTTF/(MTTF+MTTR)

MTTF:mean time to failure,平均故障前的时间
MTTR:mean time to recovery,平均修复时间

系统可用性 % 宕机时间/年 宕机时间/月 宕机时间/周 宕机时间/天
90% 36.5天 72小时 16.8小时 2.4小时
99% 3.65天 7.20小时 1.68小时 14.4分
99.9% 8.76小时 43.8分 10.1分钟 1.44分
99.99% 52.56分 4.38分 1.01分钟 8.66秒
99.999% 5.26分 25.9秒 6.05秒 0.87秒

隔离设计 bulkheads

  • 按服务种类隔离.但如果有跨服务的请求,会增加调用次数,并且会引入分布式事务的需求.
    两阶段提交?
  • 按用户隔离(多租户),可以有三种方案:
    • 完全独立资源
    • 服务共享,数据独立
    • 共享服务,共享数据
    从上到下,隔离度由高到低,资源共享度和实现复杂度从低到高

异步通讯

同步调用如果被调用方有问题,调用方会跟着出问题
异步调用可以增加吞吐量,服务间的解耦更加彻底

三种异步通讯方式:

  • 请求响应式:返回结果可能是’正在处理’之类.需要调用方去定时轮询结果或者给接收方提供回调,处理完毕后通过回调通知
  • 接收方向发送方订阅事件?
  • 通过Broker.对Broker提出了高可用高性能可横向扩展并且能够持久化数据的需求

异步通讯带来的问题:

  • 业务流程不够直观,需要有可视化工具去呈现
  • 事件可能会乱序
  • 需要有总控方来管理业务状态的变更逻辑,来保证业务的整体一致性.

请求幂等

幂等解决超时之后状态未知的情况.有两种解决方法

  • 下游提供查询接口,如果查到表明已成功执行
  • 上游直接重试,下游自己保证多次执行不会产生副作用

幂等需要有一个唯一性的ID来标识同一个事件.推荐snowflake,41bit毫秒级时间+5bit数据中心+5bit机器id+12bit毫秒内序列号.可以在每次插入时通过该唯一性ID来判断是否已经执行过.

数据库中插入可以用如下语句

1
insert into … values … on DUPLICATE KEY UPDATE...

更新使用

1
update table set status = "paid" where id = xxx and status = "unpaid";

因为POST不是幂等性的,表单设计中通过会有一个隐藏的token,token保存到数据表中,提交时需要判重防止重复提交
PRG模式:post提交表单,响应redirect,之后get刚才post的请求

有无状态

无状态便于运维和伸缩

补偿事务

需要有工作流引擎,每个步骤都有补偿机制,反向补偿流程

重试

场景:重试认为某个故障是暂时的,所以重试.如果错误是永久的,重试没有意义
例如如下场景:调用超时,繁忙,流控,维护中,资源不足等
没有权限或者非法访问等情形重试没有意义

策略:一般需要重试的场景下采用指数退避策略来重试,防止造成过大负载

要点:

  • 后端服务幂等性
  • 重试时间和次数依据场景而定
  • 如果不是一个短暂的错误,需要使用熔断,快速失败,防止重试浪费资源

熔断

熔断状态机:

  • 闭合(closed):在一定时间间隔之内统计失败的比率或者次数,超过之后需要切换到断开(open)状态.此时开启一个计时器,超时之后进入半开状态,给一个修正错误的机会
  • 断开(open):断开状态直接返回错误或者返回一个缓存数据
  • 半开(half-open):半开允许一定量的访问去访问服务,如果都成功切回闭合,否则重置计时器

设计要点:

  • 根据返回错误类型确定是重试还是熔断
  • 自动检测,不需要计时器,检测服务可用之后可以产品从open切换到closed
  • 手动重置,可以手动切换状态

开源实现:Netflix Hystrix

限流

NGINX的limit_conn及limit_request,TCP的拥塞控制,基于响应时间确定滑动窗口是一种基于响应时间的动态限流方案

常见限流方法:计数器,队列(带权重的优先级队列),漏桶及令牌桶
令牌桶可以平时积攒令牌之后有爆发的流量
漏桶会以恒定的速率处理,漏桶可以理解为一个队列加一个恒时处理的处理器
参考go实现:https://gobyexample.com/rate-limiting
限流之后可以带一个限流的标记(例如http头),这样后端服务可以根据该标记进行服务降级

降级

  • 强一致性到弱一致性:例如同步返回变为异步处理,返回”正在处理中…”;数据从缓存读取而不是从数据库
  • 停止次要功能:例如搜索评论等功能
  • 简化功能:例如只返回商品数据,不返回评论等.

功能降级需要梳理业务功能,确定哪些是must-have,哪些是nice-to-have.数据方面的降级需要前端配合,例如不返回评论时就不显示,并且有标记能够知悉是降级还是就是没有数据