May 8, 2025
本文主要解决在服务重构过程中如何保证新旧服务行为一致性的问题。
场景描述
#
现有一个 python 开发的 gRPC 微服务提供了一些 数据查询 接口 供 上层应用使用,随着业务流量的增加运维这个服务的成本也逐渐增加,为了降低运维成本和提高性能 (木有擅长 python 高性能的开发),因此选择了使用 go 语言对这个服务进行重写。在开发完成之后,需要对新服务的 gRPC 接口进行验证。
这种场景对测试开发人员来说,实在是太熟悉了吧?典型的 重放验证,马上能想到的验证手段就是:
- 如果有存量的单元测试,那么直接重新跑一遍单元测试就能快速的完成验证。
- 没有单元测试的情况,那么可以将新服务部署起来,通过流量复制的方式将旧服务的流量复制到新服务上,然后对比两个服务的返回结果是否一致。
flowchart LR
%% 定义布局方向和间距
subgraph s1["方案一: 单元测试验证"]
direction TB
UT[单元测试] -->|执行| NS1[新服务]
end
subgraph s2["方案二: 流量复制验证"]
direction TB
C[客户端] -->|请求| OS[旧服务]
OS -->|响应| C
OS -->|复制流量| NS2[新服务]
NS2 -->|对比响应| OS
end
%% 设置布局方向和对齐方式
s1 ~~~ s2
但是很遗憾 😭,并没有成熟的单元测试;测试人员也都是人肉测试,对于内部服务的接口验证帮助不大,因此这里采用第二种方式进行验证。
...
March 25, 2024
更新 2024-04-01
#
通过调整 tcp_proxy 的 idle_timeout 参数后,部分中间件(redis, mongo)的异常问题不再出现,但是 mysql(sharding-spere) 和 memcached 仍然存在 “invalid connection” 错误,所以还需要找到能够解决 mysql(sharding-spere) 和 memcached 的方法。
上述描述的现象非常主观,不一定正确也不能作为最终结论,但是 idle_timeout 配置确实没有解决所有的问题。
这里,需要知道 istio 在 inject 时会通过 iptables 对应用的流量进行劫持,对于 outbound 的流量 iptables 规则拦截转发到 OUTPUT 链。OUTPUT 的链转发流量到 ISTIO_OUTPUT,这个链会决定服务访问外部服务的流量发往何处。
这样产生的效果是,除了应用自己建立的连接之外 envoy 也会创建一个代理连接。
$ netstat -notp | grep 3307
tcp 0 172.23.105.25:41030 x.x.x.x:3307 ESTABLISHED - off(0.00/0/0)
tcp 0 172.23.105.25:41022 x.x.x.x:3307 ESTABLISHED 1/./app keepalive(0.88/0/0)
我遇到的问题可以确定问题就出在 envoy 建立的连接上,因为应用自己建立的连接是正常的,同时还可以看到应用自己的连接开启了 keepalive, 而 envoy 建立的连接没有开启。这样可能会出现这个连接会因为超时而被关闭的情况,或者其他原因导致连接被释放。那如果可以避免将中间件的流量通过 envoy 代理,这样就可以避免这个问题。
在官方的文档中也提到,https://istio.io/latest/zh/docs/tasks/traffic-management/egress/egress-control/ 对于外部服务,可以通过配置 excludeOutboundPorts
或者 excludeOutboundIPRanges
来使得某些服务的流量不经过 istio sidecar。其背后的原理是通过 iptables 的规则中排除这些端口或者 IP 地址。
...