有时,为了实现系统级稳定性,放弃组件级稳定性就是所能做的最好的事情
![](https://img2023.cnblogs.com/blog/3076680/202306/3076680-20230627103223140-2108494309.png)
1. 快速失败而非缓慢响应
1.1. 如果响应缓慢比没有响应更糟,那么最坏的情况肯定是缓慢的失败响应
1.2. 如果系统能够预先确定某次调用会失败,那么最好快速失败
2. 快速失败模式通过避免响应缓慢来提高整个系统的稳定性
2.1. 当系统由于部分失效而面临压力时,快速失败模式还有助于保持系统容量
2.2. 与超时模式配合使用,快速失败模式有助于避免层叠失效
3. 预留资源并尽早验证集成点有效
3.1. 确保在开始之前就能完成事务
3.1.1. 关键资源不可用,比如所需调用的断路器已跳闸,那么就不要再浪费精力去调用
3.2. 在事务的开始阶段和中间阶段,关键资源可用状态发生变化的可能性极小
3.3. 应用程序或服务可以从传入的请求或消息中,大致了解需要哪些数据库连接和外部集成点,然后可以快速检查所需的连接,并查看集成点周围的断路器状态
4. 使用输入验证
4.1. 即使在预留资源之前,也要进行基本的用户输入验证
4.2. 找到未输入的必需参数才是关键
5. 任其崩溃并替换
5.1. 有时,为了实现系统级稳定性,放弃组件级稳定性就是所能做的最好的事情
5.1.1. 通过组件崩溃保护系统
5.1.2. 通过组件级不稳定性构建系统级稳定性
5.1.3. 这可能是将系统恢复到已知良好状态的最佳方式
5.1.4. 隔离组件以实现独立崩溃
5.2. 在Erlang语言中,这被称为“任其崩溃并替换”的哲学
5.2.1. 任其崩溃并替换的方法认为错误恢复难以完成且不可信赖,所以我们的目标应该是尽快回到刚完成启动时的干净状态
5.3. 程序所能拥有的最干净的状态,就是在刚刚完成启动的那一刻
5.4. 前提
5.4.1. 有限的粒度
5.4.1.1. 必须为崩溃定义边界
5.4.1.2. 发生崩溃的组件应该是独立的,系统的其余部分必须能够自我防护,避免受到层叠失效的影响
5.4.1.3. 在微服务架构中,服务的整个实例可能是正确的崩溃粒度
5.4.1.4. 在Erlang和Elixir中,崩溃的自然边界就是actor
5.4.2. 快速替换
5.4.2.1. actor这样的进程内组件,重启时间以微秒为单位
5.4.2.2. 在容器中运行Go二进制文件
5.4.2.2.1. 启动一个新容器及其内部一个进程的时间以毫秒为单位计算,此时就可以通过让整个容器崩溃实现快速替换
5.4.2.3. NodeJS服务在AWS中一个长时间运行的虚拟机上运行
5.4.2.3.1. 启动NodeJS进程需要花几毫秒,但启动一台新的虚拟机则需要几分钟
5.4.2.3.2. 只需要让NodeJS进程崩溃,就可以实现快速替换
5.4.2.4. 不要让单体系统崩溃
5.4.2.4.1. 运行时负载较大或启动时间较长的大型进程,不适合运用“任其崩溃并替换”策略
5.4.2.4.2. 将许多特性耦合到单个进程中的应用程序,也不推荐运用该策略
5.4.2.4.3. 在数据中心的几台虚拟机上,运行着一个老旧的前端安装API的JavaEE应用程序
5.4.2.4.3.1. 启动时间以分钟为单位计算,此时,“任其崩溃并替换”并不是正确的策略
5.4.3. 监管
5.4.3.1. 监管器不是服务的消费者
5.4.3.2. 管理工人与要求服务是两码事
5.4.3.3. 如果把两者混淆,就会损害系统
5.4.4. 重新归队
5.4.4.1. 在一个actor或实例先崩溃然后由监管器重新将其启动之后,系统必须要恢复对新启动的服务提供方的调用
5.4.4.2. 如果实例被直接调用,则调用方的断路器应该自动将该实例重新归队
5.4.4.3. 如果实例是负载均衡池中的一部分,那么必须能够将实例加回池中,接受工作