测试基础 业务高可用不可忽视配置的 “有效性”

Zero先生 · 2023年03月01日 · 3414 次阅读

本文主要想通过两个案例来分享下,以下实践的经验

  • 合理的配置以及配置的有效性,需要通过有效的测试来保障,任何理论上的推导都是 “靠不住”
  • 不建议在一些关键配置上采用默认配置,即便值是一样的,也需要 “显式” 的配置和测验

案例 1:nginx upstream health check 配置失效

故障背景

X 项目所在宿主机 A-内网光口网卡硬件故障,导致网络故障,服务不可达,简要架构如下

故障现象

业务接口请求持续有一定比例超时失败

故障复现

1.python+Flask 快速构建服务,两台主机上分别部署,修改 retDescp 为 “helloA” 和 “helloB”,请求路径保持一致

from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/hello")
def say_hi():
    return jsonify(retCode=200, retDescp="helloA")
if __name__ == '__main__':
    app.run(debug=True)

2.采用 uwsgi 启动 A、B 两台机器服务

uwsgi --chdir=$WORKSPACE --wsgi-file=hello.py --master --pidfile=$PIDPATH --socket=0.0.0.0:$PORT --http-socket=127.0.0.1:$LOCAL_HTTP_PORT --processes=4 --enable-threads --threads=8 --harakiri=600 --max-requests=5000 --vacuum --post-buffering=32768 --buffer-size=32768 --wsgi-disable-file-wrapper

3.测试环境模拟线上 nginx 配置,采用 uwsgi_pass 进行代理转发

uwsgi_pass xx-server;
upstream xx-server {   
    server A:8088 max_fails=3 fail_timeout=30s;   
    server B:8088 max_fails=3 fail_timeout=30s;
}

4.发起请求流量

for i in {1..2000}; do curl -X GET --connect-timeout 5 -m 5 "http://xx-test.xx.com/hello" >> log.txt 2>&1; done

5.中断宿主机 A 网络,复现线上故障场景

分析结果

宿主机 A 网络中断,客户端侧 5 秒超时会主动关闭连接,实际 nginx 侧 max_fails 不会计数失败,导致异常节点一直不会被触发 “下线” fail_timeout 长度的时间
经过沟通,发现此处存在 2 种 “失效”

  • 一是认知偏差,对于 max_fails、fail_timeout 的理解不到位,导致应用预期失效,一种误解是认为 fail_timeout 是业务请求超时,max_fails 达到失败次数后会 “下线” 节点
  • 二是功能失效,max_fails、fail_timeout 正确的一句话解释是:在 fail_timeout 时间窗口内业务请求失败 max_fails 次数,临时下线节点 fail_timeout 时长,但是 fail 的定义不同的协议中会有差异(故障中服务网络中断 ,客户端侧主动关闭连接的情况,对于 nginx 侧未识别到计数为 fail,所以导致失效)

官方解释:
fail_timeout – Sets the time during which a number of failed attempts must happen for the server to be marked unavailable, and also the time for which the server is marked unavailable (default is 10 seconds).
max_fails – Sets the number of failed attempts that must occur during the fail_timeout period for the server to be marked unavailable (default is 1 attempt).

延伸测试

一个链路 A 到 B 可能出现的故障场景可以简单分为以下 4 种,对于 health check 配置需要覆盖这 4 种可能出现的故障,前三种可以走 4 层 TCP CHECK,第 4 种 “服务进程假死” 需要真正的业务请求可达,需要走 7 层 HTTP CHECK

  • B 服务进程挂掉
  • B 所在宿主机挂掉
  • A 到 B 网络中断(这里可以细分)
  • 服务进程假死

优化建议

  • 添加 4 层 TCP CHECK,upstream 中添加 CHECK
check interval=3000 rise=2 fall=3 timeout=5000 type=tcp default_down=false;
  • 添加 7 层 HTTP CHECK,目前后台服务基于 uwsgi 协议,配置的是 socket,走的是 4 层,无法通过 HTTP CHECK 实现,需要变更 uwsgi 启动通过 http-socket 方式目前已知的性能损耗是多一次 http 解析,对于高性能要求、大并发流量的业务,这个是需要考虑的

案例 2:Redis 集群主从机制失效

问题现象

Redis 集群三主三从,挂掉从节点后,应用请求出现大量的超时

问题原因

从理论上来讲,挂掉从节点不会对业务造成任何影响,Redis 集群主 1 从 1 之前发生过一次主从切换,下线所谓的 “从” 节点时(之前的主 1),由于集群拓扑未刷新,连接请求仍然会请求到主 1,导致应用持续大量请求超时

问题分析

在分析问题的过程中,存在两个配置失效的问题,一是 spring.redis.timeout=100000 设置的超时时间过长,导致需要 100 秒后才会刷新集群拓扑,这期间会有大量的业务请求失败;二是集群拓扑刷新失效,redis 的客户端(lettuce、Jedis、redisson)对拓扑刷新的处理不同,spring-data-redis 包中实现的 Lettuce 客户端时,默认没有开启客户端刷新功能,需要 SpringBoot 版本>=2.3.0 后有属性配置支持开启自动刷新

spring:
  application:
    name: xxx
  #***********************redis***********************
  redis: #redis配置
    lettuce:
      cluster:
        refresh:
          adaptive: true #自动刷新集群 默认false关闭
          period: 30000
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册