大家好,我是 Snow Hide,作为《左耳听风》这个专栏的学员之一,这是我打卡的第 21 天,也是我第 21 次进行打卡这种操作。

今天我温习了该专栏里叫《弹力设计篇之“补偿事务”》、《弹力设计篇之“重试设计”》、《弹力设计篇之“熔断设计”》、《弹力设计篇之“限流设计”》、《弹力设计篇之“降级设计”》、《弹力设计篇之“弹力设计总结”》的文章。

关键词总结:ACID、BASE、原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)、基本可用性(Basic Availability)、Soft-state(软状态)、Eventual Consistency(最终一致性)、业务补偿、靠谱的业务补偿机制、业务补偿的设计重点、重试的场景、重试的策略(Exponential Backoff / 指数级退避)、Spring 的重试策略(一般策略、Backoff 相关策略)、重试设计的重点(确定重试的触发、重试时间/次数、被调用方幂等性设计、重试代码无侵入、事务相关的重试)、熔断设计(闭合 / Closed、断开 / Open、半开 / Half-Open)、熔断器实现逻辑(是否存在熔断中?、调用成功和失败的次数?、是否进行熔断?、元素是否超过设置的时长?)、熔断设计的重点(错误类型、日志监控、测试服务是否可用、手动重置、并发问题、资源分区、重试错误的请求)、限流的策略(拒绝服务、服务降级、特权请求、延迟处理、弹性伸缩)、限流的实现方式(计数器方式、队列算法、漏斗算法 / Leaky Bucket、令牌桶算法 / Token Bucket、基于响应时间的动态限流)、限流的设计要点、降级的牺牲(降低一致性、停止次要功能、简化功能)、降低数据一致性(失效、命中、更新)、降级设计要点(梳理和分析业务、降级的关键条件、可简化业务流程设计、流水账记录法、降级开关、限流程度参数、降级标签、降级演练)、弹力设计概览(负载均衡、服务发现、动态路由、自动化运维、服务调度、服务伸缩、故障迁移)、服务界耦合拆分(bulkheads 模式、自包含系统、异步通讯、自动化运维)、容错设计(错误方面、一致性方面、流控方面、自动化运维方面)弹力设计开发和运维(比较 Spring Cloud 和 Kubernetes、服务涵盖面比较、服务的功能特性)。

 

所学总结:

 

弹力设计篇之“补偿事务”

ACID(强一致性)

  • 原子性 / Atomicity
  • 一致性 / Consistency
  • 隔离性 / Isolation
  • 持久性 / Durability

BASE(最终一致性)

  • 基本可用性 / Basic Availability
  • 软状态 / Soft-state
  • 最终一致性 / Eventual Consistency

CAP 中的 ACID(酸)和 BASE(碱)

  • ACID:代表了 CAP 理论的 C(Consistency);
  • BASE:代表了 CAP 理论的 A(Availability)。

业务补偿

靠谱的业务补偿机制
  • 清晰地描述服务要达到的目标;
  • 在业务流程开始之后串行或并行地进行所有的操作。

业务补偿的设计重点

两个最重要的事情
  • 将业务流程执行完毕;
  • 过程出错的话将就还原至操作之前的状态。
其余设计重点
  • 服务需要支持幂等性;
  • 让工作流引擎来管理操作过程的状态及步骤;
  • 考虑业务的返乡补偿流程;
  • 业务补偿无法做到通用的程度;
  • 提供临时的资源预留机制。
     

弹力设计篇之“重试设计”

重试的场景

适合重试的场景
  • 调用超时;
  • 被调用端返回了可以重试的提示:
  • 繁忙中;
  • 流控中;
  • 维护中;
  • 资源暂时不足等。
不适合重试的场景
  • 业务级错误:
  • 权限不足;
  • 非法数据。
  • 系统错误:
  • 503 状态码;
  • 代码 Bug。

重试的策略

Exponential Backoff / 指数级退避
  • 每次重试所消耗的时间会翻倍
通过枚举定义错误返回类型
  • SUCCESS;
  • NOT_READY;
  • TOO_BUSY;
  • NO_RESOURCE;
  • SERVER_ERROR。
声明指数级退避函数
  • 该函数的作用是返回 2 的指数,第一次重试等待 200ms、第二次等待 200ms x 2、第三次等待 200ms x 4,依此类推。
声明重试逻辑函数

在两种情况下不会进行重试:

  • 请求成功
  • 返回的错误不是我们定义的

Spring 的重试策略

  • @Retryable 注解配置的是对 SQLException 异常进行重试,最多两次,每次等待 5000ms。
一般策略
  • NeverRetryPolicy:不允许重试;
  • AlwaysRetryPolicy:允许无限重试,但需条件来限制;
  • SimpleRetryPolicy:RetryTemplate 的默认策略,默认次数为 3;
  • TimeoutRetryPolicy:在指定超时时间内重试,默认为 1 秒;
  • CircuitBreakerRetryPolicy:设置 openTime、resetTimeout 和 delegate 三个参数后可以进行熔断重试;
  • CompositeRetryPolicy
  • 乐观组合:能否重试取决于是否有至少一个策略允许重试;
  • 悲观组合:能否重试取决于是否所有的策略都允许重试;
Backoff 相关策略
  • NoBackOffPolicy:立即重试;
  • FixedBackOffPolicy:设置 sleeper 和 backOffPeriod 参数:
  • sleeper:等待策略,默认是 Thread.sleep 线程休眠;
  • backOffPeriod:休眠时间,默认 1 秒。
  • UniformRandomBackOffPolicy:设置 sleeper、minBackOffPeriod 和 maxBackOffPeriod 参数,该策略在最大和最小 backOffPeriod 中随机选取一个:
  • minBackOffPeriod:默认 500 毫秒;
  • maxBackOffPeriod:默认 1500 毫秒。
  • ExponentialBackOffPolicy:设置 sleeper、initialInterval、maxInterval 和 multiplier:
  • initialInterval:初识休眠时间,默认100 毫秒;
  • maxInterval:最大休眠时间,默认 30 秒;
  • multiplier:乘数,下一次休眠时间是当前休眠时间乘以 multiplier。
  • ExponentialRandomBackOffPolicy:引入随机乘数,可以通过随机休眠时间来避免该策略使用过程中潜在的 DDoS 问题。

重试设计的重点

确定重试的触发

确定好需要重试的条件。

重试时间/次数

非关键问题的重试时间不需要太长也不需要太多次。而对于关键问题的重试则需要延长重试的时间以及重试的次数。

被调用方幂等性设计

重试里的被调用方必须要保证它自身的幂等性,一个变幻莫测的被调用方是危险的。

重试代码无侵入
  • Java Annotation 方式实现;
  • Service Mesh 方式实现。
事务相关的重试

将请求上下文暂存之服务本机或数据库中,而不是当请求不成功时通过业务补偿的机制来执行复杂的回退流程。
 

弹力设计篇之“熔断设计”

熔断设计

闭合 / Closed
  • 没有问题时:熔断器一般都处在闭合状态;
  • 请求失败时:熔断器会将其累加至错误计数器中;
  • 错误计数器超时后:熔断器会将错误计数器重置;
  • 失败次数超过阈值:熔断器会将状态切换至断开。
断开 / Open
  • 断开后
  • 直接返回错误;
  • 返回之前的缓存。
  • 缓存机制:建议将缓存的范围设置成全局,而不是局部的;
  • 重置超时后:熔断器会将状态切换至半开。
半开 / Half-Open
  • 一定的服务调用成功时:熔断器会将状态切换至闭合;
  • 一定的服务调用失败时:熔断器会将状态切换至断开;

熔断器实现逻辑

是否存在熔断中?

从 allowRequest() 函数开始,判断当前状态是否是断开(熔断中)以及成功失败超时拒绝时间是否已过时,如果两个条件同时满足,则放行请求,否则返回服务不可用错误。

调用成功和失败的次数?

通过 markSuccess(duration) 和 markFailure(duration) 两个函数来进行成功和失败请求的计数。

是否进行熔断?

根据 failure / (success + failure) 这个计算公式来得出是否需要通过 isOpen() 函数来进行断开操作的结论,超过计数器阈值切换至断开状态,否则切换至闭合状态。

计数桶是否已超时?

熔断器会创建一个新的桶,将旧桶数据迁移至新桶中,并删除旧桶以及最老的一个计数记录。

熔断设计的重点

错误类型

根据错误类型,来估计被调用方大概需要花费的恢复时长,进而作出合理的决定,是进入重试环节,还是直接做断开操作。

日志监控

记录所有失败和成功的请求。

测试服务是否可用

在半开状态下,让熔断器自己定期检测被调用方的服务是否已经恢复正常,如果已恢复,则切回至闭合状态,让用户的新请求可以直接被放行,从而提高用户体验。

手动重置

提供可以手动将熔断器状态切换至闭合或断开的功能。

并发问题

使用无锁或原子性的方式来实现熔断器,以确保其本身不会产生阻塞的问题。

资源分区

熔断器只对有问题的分区进行断开操作,确保其他分区还是可用的。

重试错误的请求

被调用端需要支持幂等性,以确保当熔断器对其进行多次重试操作后不会产生每次请求成功的结果都不一致的情况。
 

弹力设计篇之“限流设计”

限流的策略

限流,是对并发访问进行限速。

拒绝服务

一般的做法,是当流量暴增的时候,将同一时间内发起请求数最多的客户端请求全部丢弃。这种方法可以抵挡住恶意发起的高并发请求。

服务降级

降级,通常是提高资源利用率的一种做法。
降级的方式有:

  • 保住关键服务:放弃非关键服务,将硬件/软件资源让给系统的关键服务;
  • 提高整体性能:抛弃非关键或不要求实时显示的数据,只返回关键的数据或缓存里的数据。
特权请求

将流量让给大客户,对小客户做限流处理(就像那个坑爹地某度网盘)。

延迟处理

在请求量达到峰值时,只能将大量的请求积压在消息队列里,慢慢处理。

弹性伸缩

当非数据层相关的服务处理不过来的时候,监控系统需要能够感知到,并对其做水平扩充操作,当服务不那么繁忙时,再将扩充的资源回收。

限流的实现方式

计数器方式

我们可以定义一个计数器,作用是统计未处理请求的数量,如果未处理请求的数量大于等于配置的阈值(计数器可以存储的未处理请求数量上限)时,系统将自动丢弃新的请求。

队列算法

先进,但是未必先出。消息队列按照请求的优先级,从高往低依次处理每一个请求。如果队列被挤满,则丢弃新的请求。

漏斗算法 / Leaky Bucket

匀速处理没有被丢弃的请求,队列被挤满后,丢弃新的请求。

令牌桶算法 / Token Bucket

按照设定的速度收录请求,正常情况下按照设定的速度处理请求,但并非一直按匀速来处理请求,在请求激增时,处理的速度也会相应的有所增长。

基于响应时间的动态限流
很难设定限流值的原因
  • 数据方面:请求数据所消耗的时间和资源,以及期间各种操作的处理速度一般由语句、数据量以及配置来决定;
  • API 方面:请求 API 所消耗的时间和资源,以及期间各种操作的处理速度一般由服务复杂度决定;
  • 平台方面:请求服务所消耗的时间的资源,以及期间各种操作的处理速度一般由其所处的平台决定。
拥塞控制算法设计要点
  • 两种方案:
  • 不记录所有请求,只采样;
  • 蓄水池近似算法。
  • 记录当前 QPS,若后端 P90/P99 响应太慢,则将 QPS 减半并以慢启动方式进行处理,依此类推,每次减半;
  • 参考 TCP 的算法,实现过程比较复杂。

限流的设计要点

限流的目的
  • 在指定的速度下要保持一个范围内的响应时间以及可用性;
  • 确保多租户间的情况下,整体系统的资源不会被某个租户所用超;
  • 流量激增时的措施,是封闭现有管道,还是临时增添几条管道;
  • 在尽可能节省成本的情况下将资源的利用率最大化。
需要考虑的地方
  • 兵马未动,粮草先行。在一开始就做好限流的准备;
  • 限流的实现不可以产生阻塞;
  • 允许人工干预限流操作;
  • 限流事件需要被监控;
  • 定制限流产生的错误码;
  • 让后端感知限流的等级。
     

弹力设计篇之“降级设计”

降级的牺牲

降低一致性

降级之后,只能保证流程最终结果的一致性。

停止次要功能

降级之后,对次要服务所占用的资源进行裁剪,将大部分资源分配给重要的服务。

简化功能

降级之后,必要的时候,忽略非关键数据并只返回重要的数据。

降低数据一致性

通过缓存来降低数据库的访问量。

失效

当缓存中没有对应数据时才从数据库中读取并将其保存至缓存。

命中

当缓存中有对应数据时直接将其返回即可。

更新

将数据更新至数据库后让缓存失效。

降级设计要点

梳理和分析业务

我们需要对业务进行非常详尽的梳理和分析工作。

降级的关键条件

定义出关键的降级条件之后,做好相应的防护准备,可以通过代码来实现,做成能够全自动或半自动执行的方案。

可简化业务流程设计

需要对功能的重要程度作区分,在降级时,可以决定留住哪些服务,丢弃哪些服务。

流水账记录法

需要记录服务执行到达的每一步,当有遗漏或问题时,可以对照每一步的执行结果,或某个请求操作停留的步骤。

降级开关

可以做成推送或拉取的方式。

限流程度参数

当流量激增或是达到一定数值时,网关自动给所请求的服务传递一个限流程度的参数,当服务接收到限流程度参数后,其根据限流的程度来判断是否需要进行降级操作。

降级标签

前端可以根据后端返回的协议头里的降级标签,来判断部分数据不可用的问题,是否是降级所导致的。

降级演练

为了确保降级操作在进行的过程中不会有意想不到的问题或结果,我们需要定期进行降级演习操作。
 

弹力设计篇之“弹力设计总结”

弹力设计概览

负载均衡

包含负载均衡 & 服务健康检查,可以通过 Nginx 或 HAProxy 等技术实现。

服务发现

包含服务发现 & 动态路由 & 服务健康检查,可以通过 Consul 或 Zookeeper 等技术实现。

自动化运维

包含自动化运维 & 服务调度 & 服务伸缩以及故障迁移,服务调度可以通过 Docker 结合 Kubernetes 来实现。

服务界耦合拆分

bulkheads 模式

涵盖了业务分片、用户分片、数据库拆分。

自包含系统

涵盖了从单体到微服务的中间状态,拆分一组相关的微服务,保证没有外部的依赖。

异步通讯

涵盖了服务发现、事件驱动、消息队列、物业工作流。

自动化运维

涵盖了服务调用链和性能的监控系统。

容错设计

错误方面

涉及调用重试、熔断以及服务幂等性涉及。

一致性方面

涉及强一致和弱一致,强一致性使用 2PC,最终一致性使用异步通讯方式。

流控方面

涉及限流以及降级的技术。

自动化运维方面

涉及网关流量调度以及服务的监控。

弹力设计开发和运维

比较 Spring Cloud 和 Kubernetes

微服务考量

Spring Cloud & Netflix OSS

Kubernetes

配置管理

Config Server, Consul, Netflix Archalus

Kubernetes ConfigMap & Secrets

服务发现

Netflix Eureka, Hashicorp Consul

Kubernetes Service & Ingress Resources

负载均衡

Netflix Ribbon

Kubernetes Service

API 网关

Netflix Zuul

Kubernetes Services & Ingress Resources

服务安全

Spring Cloud Security

-

日志中心

ELK Stack (LogStash)

ELK Stack (Fluentd)

指标中心

Netflix Spectator & Atlas

Heapster, Promethues, Grafana

分布追踪

Spring Cloud Slueth, Zipkin

OpenTracing, Zipkin

弹性容错

Netflix Hystrix, Turbine & Ribbon

Kubernetes Health Check & resource isolation

自伸缩自愈

-

Kubernetes Health Check, Self Healing, Autoscaling

打包发布调度

Spring Boot

Docker/Rkt, Kubernetes Scheduler & Deployment

作业管理

Spring Batch

Kubernetes Jobs & Scheduled Jobs

单例应用

Spring Cloud Cluster

Kubernetes Pods

服务涵盖面比较

关键技术

Spring Cloud

Kubernetes

IaaS

DevOps 经验

☑️

自伸缩自愈

☑️

弹性容错

☑️

☑️

分布追踪

☑️

☑️

指标中心

☑️

☑️

日志中心

☑️

☑️

API 网关

☑️

☑️

作业管理

☑️

☑️

单例应用

☑️

☑️

负载均衡

☑️

☑️

服务发现

☑️

☑️

配置管理

☑️

☑️

应用打包

☑️

☑️

发布调度

☑️

进程隔离

☑️

环境管理

☑️

资源管理

☑️

操作系统

☑️

虚拟化

☑️

硬件存储网络

☑️

服务的功能特性

能力

Spring Cloud(SC) 结合 Kubernetes(K)

DevOps 经验

自服务(K)、多环境能力(K)

自伸缩自愈

Pod/Cluster Autoscaler(K)、HealthIndicator(SC)、Scheduler(K)

弹性容错

HealthIndicator(SC)、Hystrix(SC)、HealthCheck(K)、Process Check(K)

分布追踪

Zipkin

指标中心

Heapster、Promethues、Grafana

日志中心

EFK

作业管理

Spring Batch(SC)、Scheduled Job(K)

负载均衡

Ribbon(SC)、Service(k)

服务发现

Service(K)

配置管理

Externalized Configurations(SC)、ConfigMap(K)、Secret(K)

服务逻辑

Apache Camel(SC)、Spring Framework(SC)

应用打包

Spring Boot maven plugin(SC)

发布调度

Deployment strategy(K)、A/B(K)、Canary(K)、Scheduler strategy(K)

进程隔离

Docker(K)、Pods(K)

环境管理

Namespaces(K)、Authorizations(K)

资源管理

CPU 内存管控(K)、命名空间资源配额(K)

IaaS

GEC、Azure、CenturyLink、VMware、Openstack

 

末了

重新总结了一下文中提到的内容:ACID、BASE、一致性、强一致性、最终一致性、业务补偿逻辑、业务补偿设计重点、重试的场景、重试的策略、指数及退避策略、Spring 实现的多种策略、重试设计的重点、基于 Java Annotation 实现策略、基于 Service Mesh 实现策略、熔断设计、闭合、断开、半开、正常、故障、故障后检测、熔断器实现、熔断器设计、限流的目的、限流的策略、限流的算法、计数器、队列、漏斗、令牌桶、基于响应时间限流、限流的设计要点、降级设计本质、资源不足、访问量过大、降级的方法、降低一致性、停止次要功能、简化功能、降级设计要点、弹力设计、弹力设计总概览、开发运维实践。