3.3 解耦的服务网格——让股、债、商、金各自独立运行

3.3 解耦的服务网格——让股、债、商、金各自独立运行

2018年双十一,我参与过一个电商系统的全链路压测。压测过程中有一个场景让我至今难忘:当模拟流量达到某个阈值时,一个负责生成推荐列表的微服务响应时间突然从200毫秒飙升到了8秒。紧接着,调用它的商品详情页服务开始大量超时,超时重试又加剧了推荐服务的负载。几分钟之内,这个故障沿着服务之间的调用链逐级传播,从推荐服务到商品详情,从商品详情到购物车,从购物车到下单——最终整个交易链路全部堵塞。事后复盘,故障的起点只有一个推荐服务,但整个系统的链路追踪图上,红色的错误节点像癌变一样从中心向四周扩散。

这就是服务网格需要解决的经典问题:不是防止单个服务出故障,而是防止一个服务的故障通过同步调用链把整个集群都拖死。

你的投资组合里,股、债、商品、黄金,这四类资产就像你集群里四个独立部署的微服务。你有没有想过,它们之间当前的调用关系,可能比你那台被压测打爆的推荐服务还要混乱?

服务发现:资产应该在它们该响应的场景下才被调用

在微服务架构里,服务发现是一个基础组件。一个服务不需要硬编码其他服务的IP地址,它只需要向服务注册中心查询:“我要调用支付服务,告诉我现在有哪些健康的实例在线。”注册中心返回一个可用实例的列表,然后负载均衡器选择一个实例转发请求。如果某个实例下线了,注册中心会从列表里把它摘掉,调用方不需要做任何修改。

你的四类核心资产——股票、债券、商品(包括黄金)、现金——每一类都有自己的“健康运行场景”。它们在各自擅长的宏观经济象限里响应良好,在不擅长的象限里强行调用只会超时和报错。

股票这个服务,最擅长响应的是经济复苏和温和增长的环境。企业盈利上升,消费者信心增强,信贷扩张顺畅——这时候你调用股票这个服务,它会给你返回一个漂亮的响应。但在高通胀加速、央行被迫暴力加息的场景下,股票服务会严重超时。你在这个场景下反复重试——加仓、补仓、抄底——就相当于在对方已经挂掉的时候还拼命发请求,结果只会把你的整个组合拖进超时队列。

债券这个服务恰恰相反。它在经济衰退、央行降息、市场恐慌的时候响应最快。资金从风险资产撤出,涌向避险资产,国债价格上涨,收益率下行。你在2020年3月或者2008年秋天调用债券服务,它是全场最快返回200 OK的节点。但在高通胀、央行加息的环境下,债券服务会直接返回5xx错误。2022年全球债市遭遇了半个世纪以来最惨烈的下跌,就是因为通胀这个场景不是债券擅长处理的请求类型。

商品和黄金这个服务更特殊。它专门响应通胀高企、货币贬值、地缘政治动荡的场景。黄金在大部分时间里是一个不太活跃的服务,涨跌不明显,甚至跑不过通胀。但当极端场景来临时——比如2020年全球央行集体放水、2022年俄乌冲突爆发——黄金突然成了整个集群里唯一返回200的节点。

你的资产配置要做的事情,不是预测下一个被调用的应该是哪个服务。你没有这个预测能力,没有任何人有。你要做的事情是服务发现层的静态配置:提前在你的配置文件里写清楚,每一类资产在什么场景下是健康节点,什么场景下可能会超时。然后,当某些资产表现优异而另一些资产表现不佳时,不要慌张,不要把所有流量都切到当前表现最好的那个服务上去。这个集群之所以是集群,就是因为它需要不同类型的节点来应对不同类型的请求。

一个只在晴天能用的系统,不是高可用系统。一个必须在牛市里才能赚钱的组合,不是组合,是赌注。

断路器模式:连续回撤超过阈值,自动熔断

服务网格里有一个至关重要的弹性设计模式:断路器。

它的工作原理和电路断路器完全一致。你给一个服务调用设置一个失败阈值——比如连续5次调用失败,或者一分钟内错误率超过50%。一旦触发阈值,断路器跳闸,后续的调用请求不再发送到故障服务上,而是直接返回一个fallback响应。同时,断路器会每隔一段时间放一个探测请求过去,检查故障服务是否已经恢复。如果恢复了,断路器闭合,流量重新接入;如果还没恢复,继续保持熔断状态。

这个机制的唯一目的就是防止雪崩。你知道,当一个服务已经开始超时的时候,不断重试只会让它死得更快。更重要的是,重试请求会占用调用方自己的线程池和连接池,把调用方自己也拖死。断路器就是那个在所有人都失去理智的时候,强制所有人停止重试的机制。

现在,请你想一想,你是怎么对待一个正在下跌的资产的。

一只股票开始下跌。你买入的时候,它估值合理,基本面健康,行业前景光明。但市场不买账,它跌了10%。你觉得市场错了,这是机会,你加了仓。它继续跌,跌到20%。你开始焦虑,但你告诉自己,好公司越跌越有价值,你又加了仓。跌到30%的时候,你已经不敢看账户了。跌到40%的时候,你割肉了。

这个过程,就是你不断向一个已经超时的服务重试请求的过程。每一次加仓,都是一次重试。每一次重试,都占用了你更多的资金——你的线程池和连接池正在被一个无响应的服务耗尽。等到你终于决定停止重试的时候,你的现金已经打光了,你的其他资产也被迫变卖来补仓。一个服务的故障,通过你不断重试的操作,成功地把你的整个组合都拖成了雪崩。

断路器模式给你的解决方案很简单:给每一个资产类别预设一个回撤熔断阈值。

比如,任何一个单一行业的配置,如果从近期高点回撤超过25%,同时你在这个行业上的仓位已经超过了预设比例(2.5节说的10%单票上限和30%单行业上限),就不再允许新增资金进入这个类别。它可以在你的组合里继续持有,但你不能再往里面追加任何一块钱。这就是跳闸。你的理性在行情暴跌的时候可能不工作,但你在冷静时写下的这条熔断规则,可以替你做决定。

熔断期不是永久的。你设定一个探测间隔——比如一个季度。到了下一个季度的架构评审日(2.8节),你重新评估这个资产的情况。如果基本面已经恶化,直接把剩余仓位清掉,释放全部资源。如果基本面没有问题,估值已经进入深度价值区间,你可以把仓位恢复到目标比例的50%,但不能一次性全部恢复。这就是探测请求。慢慢地、有控制地放开流量,而不是在情绪最激动的时候全量切换。

边车代理:每一笔交易都必须经过规则引擎的过滤

在服务网格的架构里,每个服务实例旁边都部署着一个边车代理。这个边车是一个轻量级的网络代理,它不参与业务逻辑,但它拦截所有进出该服务实例的网络流量。服务实例自己不需要知道服务发现、负载均衡、熔断、加密、监控这些能力的存在,这些全部由边车在流量通过的瞬间透明地完成。

你的每一次投资决策,都是一次进出你财富系统的网络请求。问题是,你的这些请求大多数时候是裸奔的——它们没有经过任何边车代理的过滤,直接从你的情绪中枢发起到你的证券账户,中间没有任何安全校验。

你需要一个边车。它不是一个真实的软件,而是你为每一类资产提前写好的一组“准入规则”。在你每一次买入、卖出、加仓、减仓之前,这个边车必须拦截你的操作请求,回答以下三个校验问题:

第一个问题:这笔操作是否符合你在冷静时设定的资产配置比例?你当前的权益类仓位是否已经超过了60%的目标比例?如果是,边车直接拒绝这笔买入请求,不管你现在多想买。

第二个问题:这笔操作的触发条件是什么?是你提前写在交易计划里的止盈止损条件,还是你看到K线突然拉了一根大阳线之后的冲动?如果触发条件不在你预设的规则清单里,边车返回403 Forbidden,不允许执行。

第三个问题:这笔操作发生之后,它会不会导致你违反任何一条你已经写下来的铁律?比如单票不超过10%,单行业不超过30%,不加杠杆,不动用应急储备金。如果你即将违反其中任何一条,边车直接熔断,并且在你的投资日志里写一条告警记录。

这三个校验逻辑,需要你在冷静的、理性的、不处于任何市场情绪中的时候,一条一条写下来。写在你每个季度的投资计划书里,写在你每只股票的元数据记录里(2.6节)。然后在每一次你想要操作的时候,强迫自己先打开这份规则清单,一条一条对照。如果你发现自己在对照的过程中产生了“这次特殊情况可以不遵守”的念头,恭喜你——你刚刚捕获了一次差点绕过边车的非法请求。边车的存在意义,就是在这一刻拦住你。

如果你觉得自己一个人执行这套边车规则太困难——大多数人确实执行不了,包括我自己在早期也经常绕过自己定的规矩——那就给你的边车加一个物理级别的执行层。让你信任的配偶持有交易账户的二次验证密码。任何超过一定金额的操作,必须经过两个人的确认。这不是不信任你自己,而是你充分理解自己作为人类在情绪波动时的不可靠性,然后主动给自己设计了一个更可靠的系统。

服务网格的这三个组件——服务发现、断路器、边车代理——合在一起,做的事情和你运维微服务集群时做的事情一模一样:不消灭故障,但限制故障的爆炸半径;不预测流量,但确保每一种流量都能被正确地路由到最合适的处理节点;不阻止你发出请求,但保证每一个请求都经过了预设规则的校验。

在下一节,我们会进入负载均衡的具体策略。当你的资产集群已经通过服务网格实现了解耦和熔断之后,你还需要一个动态权重调整机制,让资金在不同的市场环境里自动流向那些性价比更高的资产节点。这个机制,用Nginx的负载均衡算法来翻译,会比你想象中更简单。