Skip to content

高可用原则:验证高可用

About 3047 wordsAbout 10 min

架构新书

2026-10-10

系统的高可用包含了一系列质量目标,比如可靠性、容错、可伸缩,可用性等,从上一章质量驱动架构,我们了解到实现系统高可用演进,比如从99%高可用到99.99高可用,是非线性增长的演进,是系统的架构的质的变化,比如从单体到分布式,从本地日志观察到可观测系统建设,

在探寻实现高可用的战术之前,开发人员需要了解4个设计原则,以帮助开发人员更好的建设高可用系统,以及理解高可用战术方法,这4个原则是

  • 端到端原则:定义了分布系统的高可用不能依赖底层系统和中间件系统,需要端到端的实现
  • 万一原则:悲观的看待分布系统的运行环境以及内部实现,强调悲观设计和防御性编程
  • 有状态和无状态:建设分布式服务必须首先区分是否是无状态。无状态的分布式系统高可用目标容易实现,有状态则难的多
  • API设计原则:API构建了整个IT世界,API设计需要遵守若干原则实现可高用

除了这4个原则有助于理解和实施高可用的战术外,高可用系统实现还需要结合可观测性系统,可修改战术。以及通过混沌工程验证系统的高可用。高可用系统建设如下图所示

本章介绍高可用原则以及验证高可用,第9章介绍无状态系统高可用战术,第10章介绍有状态服务高可用战术,第11章介绍如何实现可修改性。

验证高可用

在分布系统中,我们进行了悲观设计和防御性编码,但如何验证系统高可用,这需要我们能注入各种故障以模拟预想的悲观事件发生,在障情况下系统仍然可用,以及能正确响应。比如模拟下游服务或者数据库可不用情况下,系统仍然能稳定,且当下游或者数据库恢复后,服务能自动恢复正常。

注入的故障包含在如下图,程序员需要了解如何向操作系统,网络,以及JVM和服务认为注入故障。

警告: 程序员在人工注入故障时候,请务必与运维一起,确保故障可恢复。

硬件资源故障注入

以模拟CPU使用率为例子,可以使用耗费CPU资源的计算类脚本,如下命令使用openssl命令,用来测试所有加密算法的速度

> openssl speed -multi 1

这里的1是使用1个CPU进行计算,这个命令后果是强迫多核服务器中,CPU使用率提高,如果系统有8C,参数1替换成8,则主机的CPU处于100%忙碌,建立在主机上的服务基本上不可用。这里可以验证此主机故障下,上下游是否能正常处理或者降级处理)

gzip命令也可以导致CPU使用率非常高,如下命令按照最大压缩比压缩/dev/urandom,会使用1C

cat /dev/urandom | gzip -9 > /dev/null

如果是多核系统,可以使用多次gzip,如下命令会占满4C

cat /dev/urandom | gzip -9 | gzip -9 | gzip -9 | gzip -9 > /dev/null

对于Java系统,很少遇到磁盘IO使用率高或者内存不足的问题,前者可能发生在大量日志操作上,后者可能发生在JVM堆外内存溢出。

可以使用linux命令模拟这IO使用高的情况,模拟磁盘IO读写需要使用dd命令,反复拷贝文件。

如下命令使用dd生成一个512M的文件

dd if=/dev/zero of=loadfile bs=1M count=512

在shell中执行一个循环cp命令

 > for i in {1..100}; do cp loadfile loadfile1; done
 // 或者一直循环
 > while true; do cp loadfile loadfile1; done

通过sar命令观测,可以看到磁盘使用负载增加,观察util使用率,从0增加到72%

     tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util
    0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00

     tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util
    0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00

     tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util
    0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00

     tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util
 1248.00      0.00 155424.00    124.54     92.63    140.09      0.58     72.40

     tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util
 1533.50      0.00 197408.00    128.73    112.03    143.17      0.58     89.60

模拟内存占用可以挂载一个ramfs或者tmpfs文件系统。他们都是基于RAM的文件系统,如下命令创建一个ramfs文件系统

mkdir memory
mount -t ramfs ramfs memory/

使用dd命令在memory目录下创建一个512M大小文件,使用free命令查看可用内存,会发现减少512M

dd if=/dev/zero of=file1 bs=1M count=512
free -m

网络捣乱

网络捣乱可以使用Linux自带流量控制命令tc或者防火墙iptables实现, 本节将介绍使用iptables 模拟网络故障。 iptables对网络数据包进出设备及转发的控制。当数据包需要进入设备、从设备中流出或者由该设备转发、路由时,都可以使用 iptables 进行控制。

使用用iptables模拟下游响应慢,不响应的故障

如下第一行命令对目标服务器10.205.243.247 访问限流,允许每秒通过50个MTU,即50*1500字节=75K.

第二行命令是对不符合第一条规则情况下的处理,采用了drop动作,即每秒超出75K后,不响应客户端。

$sudo iptables -t filter -I OUTPUT -p tcp  -d 10.205.243.247  -m limit --limit 50/s -j ACCEPT
$sudo iptables -t filter -A OUTPUT -p tcp  -d 10.205.243.247  -j DROP

此命令的具体含义如下

选项描述
-t filteriptables 操作的是filter表,表示过滤表,其他表还有NAT表,NAT表示转发, Mangle表用来修改数据包(TODO)
-I OUTPUTfilter它有以下三种内建链(chains):INPUT链 – 处理来自外部的数据。OUTPUT链 – 处理向外发送的数据。FORWARD链 – 将数据转发到本机的其他网卡设备上。这里-I表示插入,-A表示添加
-p tcp这里指定协议是tcp协议,还可以是 udp, icmp或者all表示所有协议
-d 10.205.243.247表示对外发送链路的ip地址
-m表示一个match
limit --limit 50/s这里表示限流,每秒50个包,1/m,表示每分钟1个包
-j ACCEPT这里j表示jump,跳转到一个个执行动作,ACCEPT表示接收
-j DROPDROP表示丢弃,但不响应客户端。另外一个可选的是REJECT,表示响应客户端并拒绝数据包。对于DROP,客户端不会收到任何响应,因此一直等待服务器的响应直到超时。这可以用于模拟服务器假死或者繁忙(TODO,解释)

命令sudo iptables -L -n -v --line-number 可以查看防火墙设置的内容, 可以在Chain OUTPUT 成功设置了限流

$ sudo iptables -L -n -v --line-number
Chain INPUT (policy ACCEPT 10879 packets, 788K bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 9347 packets, 692K bytes)
num   pkts bytes target     prot opt in     out     source               destination
1      867 1729K ACCEPT     tcp  --  *      *       0.0.0.0/0            192.168.56.1         limit: avg 5/sec burst 5
2      190  138K DROP       tcp  --  *      *       0.0.0.0/0            192.168.56.1

如果需要重新设置限流,可以调用iptables --flush 清空防火墙

作为服务器,需要设置输入限流,则使用INPUT链路,如下是一个例子,对来源请求10.205.243.15进行限流,每分钟5个包通过

sudo iptables -t filter -I INPUT -p tcp -s 10.205.243.15  -m limit --limit 5/s -j ACCEPT
sudo iptables -t filter -A INPUT  -p tcp  -s 10.205.243.15   -j DROP

警告:设置防火墙必须谨慎,需要运维人员参与把关,如果限流动作是 "--limit 1/m". 每分钟一个包,则有可能远程登录不上此服务器

JVM故障注入

故障注入JVM需要通过JVM提供的Instrumentation实例,其接口retransformClasses允许修改方法实现注入故障,包括对指定方法做如下修改

  • 延时返回结果
  • 抛出指定异常
  • 修改返回对象
  • 自造内存泄漏
  • 方法级CPU打满
  • 随机性或者按照某种条件完成上述动作

阿里的工具ChaosBlade 通过JVM的Java Attach ,一个允许在JVM运行中动态的热更新代码机制,而混沌工具Byteman 或者Byte-Monkey 则是在Java启动的时候,通过Agent机制(参考5.2.4了解agent机制和热更新),实现更新目标方法的字节码注入故障

以Byteman为例子,启动时候增加agent配置

java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:appmain.btm org.my.AppMain

这里的byteman.jar是官网提供的jiar包,而appmain.btm是一个规则说明文件,申明了需要在哪里注入故障。规则文件可以包含多个由RULE和ENDRULE封装的规则, 如下申明一个规则名称throwException,申明规则生效位置是AppMain类的main方法的开始处,规则执行的动作是抛出一个RuntimeException异常。

RULE throwException
CLASS org.my.AppMain
METHOD main
AT ENTRY
IF TRUE
DO
    THROW new java.lang.RuntimeException("byteman  error");
ENDRULE

Byteman通过开启监听器允许动态的指定规则

java -javaagent:${BYTEMAN_HOME}/lib/byteman.jar=script:appmain.btm,listener:true

然后使用bmsubmit脚本安装或者卸载一个规则

> bmsubmit.sh -l my.btm
install rule throwException start
> bmsubmit.sh -u my.btm
uninstall RULE throwException start

混沌工程

除了软件行业通过注入故障以验证高可用外,还有硬件,以及制造业,军工,以及金融行业都有这这种通过实验性方法,确认系统具备抵御生产环境各类动荡异常的能力,称之为混沌工程。

  • 汽车通过高速碰撞验、高出坠落等手段验证汽车安全可靠性
  • 军工产品模拟极端气候的变化、周围环境极端震荡下保证武器的稳定性。
  • 金融行业压力测试,把金融机构放到"极端但可能发生"的灾难情景下,比如存款大量取出、GDP下滑严重等场景看看金融行业能不能扛得住。

软件行业实现混沌工程的利器是国内的ChaosBlade,前面介绍手工注入故障,面向的是开发人员和验证环境,ChaosBlade是一个工程化的工具,面向的是运维人员和生成环境,ChaosBlade或者其他工程化的工具提供如下好处

  • 全面提供硬件,网络和JVM故障,以及容器的自动注入实现
  • 对注入故障进行编排(ChaosBlade称为演练),模拟各种复杂故障的组合,以及允许反复执行这些演练
  • 指定哪些应用或者主机注入故障以及注入故障的事件,以控制爆炸半径,即在生产环境中,模拟故障应该控制影响的范围。
  • 可视化的管理这些故障注入,并对系统运行过程进行记录。以验证系统是否恢复正常运行。

下图是来自ChaosBlade官网创建的一个演练。

img

最快体验ChaosBlade是使用其提供的demo,运行如下容器命令

>docker pull chaosbladeio/chaosblade-demo
>docker run -it --privileged chaosbladeio/chaosblade-demo
Using CATALINA_BASE:   /usr/local/tomcat
Using CATALINA_HOME:   /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME:        /usr/lib/jvm/java-1.8-openjdk/jre
Using CLASSPATH:       /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Tomcat started.

use[ curl http://localhost:8080/dubbo/hello?name=dubbo ]command to request demo

You can use blade command to execute a chaos experiment.

Please read README.txt first!

参考README.txt 验证给dubbo应用手工注入故障,设定目标方法DemoService.sayHello延时3秒返回结果

>curl http://localhost:8080/dubbo/hello?name=dubbo  # 正常调用
Hello dubbo, response from provider: 172.17.0.2:20880
>blade prepare jvm --process business   # 加载blade到虚拟机
> blade create dubbo delay --time 3000 --service \
    com.example.service.DemoService --methodname sayHello --consumer
>curl http://localhost:8080/dubbo/hello?name=dubbo # 再次调用超时
chaosblade-mock-TimeoutException,timeout=1000

知行合一