高可用原则:验证高可用
系统的高可用包含了一系列质量目标,比如可靠性、容错、可伸缩,可用性等,从上一章质量驱动架构,我们了解到实现系统高可用演进,比如从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 filter | iptables 操作的是filter表,表示过滤表,其他表还有NAT表,NAT表示转发, Mangle表用来修改数据包(TODO) |
| -I OUTPUT | filter它有以下三种内建链(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 DROP | DROP表示丢弃,但不响应客户端。另外一个可选的是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");
ENDRULEByteman通过开启监听器允许动态的指定规则
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官网创建的一个演练。

最快体验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