聊聊微服务治理到底在治什么

拆完微服务,服务从一个变几十个,调用关系乱成一团,一个挂了全链路雪崩。这篇把微服务治理拆开讲:注册发现、负载均衡、熔断限流、链路追踪、配置中心这几块到底各管什么、为什么缺一不可,用大白话把治理的来龙去脉讲清楚。

单体应用拆成微服务,这事现在几乎是默认操作。

但拆完之后很多人会发现一个问题:以前一个服务好好的,现在拆成几十个,天天出奇怪的毛病。

A 调 B 超时,B 又在等 C,C 其实早挂了,结果整条链路卡死。

这就是“治理”要解决的事。

我自己踩过不少这种坑,这篇就把微服务治理拆开,一块一块讲讲它到底在治什么。

为什么拆完反而更麻烦

先说个扎心的事实:微服务不是银弹,它是用一堆新问题换掉旧问题。

单体的时候,所有模块在一个进程里,函数调函数,内存里走一圈就完事。

拆成微服务之后,模块之间变成了网络调用。

网络这东西,天生不可靠。

一次本地函数调用,耗时是纳秒级,几乎不会失败。

一次跨服务调用,要经过序列化、网络传输、对端处理、再反序列化回来,耗时是毫秒级,而且随时可能超时、丢包、对端宕机。

我们把一个确定性很高的系统,拆成了一堆充满不确定性的小系统。

所以治理的本质,就是把这些不确定性重新管起来,让一堆小服务协同起来还能像个整体。

下面这几块,就是治理的核心拼图。

服务怎么找到彼此

第一个问题最朴素:A 要调 B,A 怎么知道 B 在哪?

单体时代不存在这个问题,大家在一个进程里。

拆开之后,B 可能部署了 3 个实例,分布在 3 台机器上,IP 还可能随时变(容器一重启 IP 就换了)。

你总不能把 IP 硬编码在代码里吧。

这就需要服务注册与发现。

思路很简单,搞一个“通讯录”,这个通讯录就是注册中心(比如 Nacos、Consul、Eureka)。

# 服务注册与发现的基本流程

1. B 服务启动时,主动把自己的地址报给注册中心
   “我是 order-service,我在 192.168.1.10:8080,我还活着”

2. 注册中心记下来,并定期跟 B 做心跳检测
   心跳断了就把 B 从通讯录里摘掉,不再给别人

3. A 要调 B 时,先问注册中心
   “order-service 现在有哪些可用地址?”

4. 注册中心返回当前健康的实例列表
   A 从里面挑一个去调
​

是不是一下就清爽了?

服务上线下线、扩容缩容,对调用方都是透明的,大家只认服务名,不认具体 IP。

这里有个细节值得一提:注册中心自己也得高可用。

它要是挂了,所有服务都找不到彼此,那就是全局灾难。

所以注册中心一般都是集群部署,这块后面讲配置的时候再提。

请求该发给哪个实例

接着上一个问题。

注册中心告诉 A,B 有 3 个实例可用。

那 A 这次请求到底发给哪一个?

这就是负载均衡要管的事。

最简单的策略是轮询,一人一次轮着来。

但轮询有个毛病:它假设所有实例的处理能力一样强。

现实里往往不是。有的机器配置高,有的低;有的实例刚启动还在预热,有的已经扛了一堆请求。

所以就有了更聪明的策略:

  • 加权轮询:给配置高的机器多分点流量,按权重来
  • 最少连接:谁手头活最少就给谁,避免忙的更忙
  • 一致性哈希:同一个用户的请求尽量落到同一个实例,对有本地缓存的场景友好
  • 响应时间优先:谁响应快就多给谁,自动避开慢节点

选哪个看场景。

我个人的习惯是,没有特殊需求就先用加权轮询,简单稳定,等真遇到性能瓶颈了再针对性换策略。

过早上一堆花哨策略,往往是给自己埋坑。

还有一个点要分清:负载均衡有客户端和服务端两种。

服务端负载均衡就是请求先打到一个中心节点(比如 Nginx),由它再转发。

客户端负载均衡是调用方自己拿到实例列表,自己挑一个直连,省掉中间那一跳。

微服务里客户端负载均衡用得更多,因为它少一次网络转发,延迟更低。

一个服务挂了别拖垮全家

这块是治理里最要命的一环,叫熔断、降级、限流。

先讲个场景你就懂了。

假设订单服务要调库存服务。

某天库存服务数据库卡住了,每个请求都要等 10 秒才超时返回。

订单服务这边呢,请求一个接一个打过去,每个都傻等着。

等着等着,订单服务的线程全被占满了,自己也卡死了。

然后调订单服务的网关也跟着卡死……

这就是雪崩。一个点的故障,顺着调用链一路蔓延,最后整个系统全趴下。

怎么防?三板斧。

熔断

熔断就像家里的保险丝。

电流过载了,保险丝先熔断,保护后面的电器,而不是让整条线路烧起来。

放到服务上就是:A 发现调 B 老是失败(比如 10 秒内失败率超过 50%),就暂时“断开”,不再调 B 了。

接下来一段时间,A 调 B 的请求直接走失败逻辑,根本不发出去。

# 熔断器的三个状态(经典的三态机)

关闭(Closed):正常放行请求,同时统计失败率
  ↓ 失败率超过阈值
打开(Open):直接拒绝请求,不再真正调用下游,快速失败
  ↓ 过了冷却时间(比如 10 秒)
半开(Half-Open):试探性放几个请求过去
  - 成功了 -> 回到关闭,恢复正常
  - 又失败 -> 回到打开,继续熔断
​

这个半开状态的设计很妙。

它不是死等人工干预,而是自己定期试探下游有没有恢复,恢复了就自动放行。

降级

熔断之后,请求不发了,那总得给个交代吧?

直接报错给用户体验太差。

降级就是:主逻辑走不通,退一步给个兜底结果。

比如商品详情页,推荐模块挂了,那就不展示推荐,或者展示一批默认的热门商品,别让整个页面打不开。

核心服务保住,非核心的坏了就先舍掉。

这是一种取舍,保命要紧。

限流

限流是防患于未然。

你的服务能扛 1000 QPS,突然来了 5000,与其全部放进来一起崩,不如只放 1000,剩下的直接拒绝。

保住已经进来的请求能正常处理,总比大家一起死强。

常见的限流算法:

  • 计数器:固定时间窗口内计数,超了就拒。简单,但有临界问题
  • 滑动窗口:把时间窗口切细,缓解临界突刺
  • 漏桶:请求匀速流出,不管进来多猛,出去的速率恒定
  • 令牌桶:按固定速率发令牌,有令牌才放行,能应对一定的突发流量

Sentinel、Hystrix 这类框架,基本把熔断、降级、限流打包一起给你了,不用自己从零造。

我的建议是别自己手搓这套东西,水很深,用成熟框架。

出了问题怎么定位

服务多了之后,还有个头疼的日常问题:排障。

单体的时候,一个请求的处理过程全在一个进程、一份日志里,grep 一下就看明白了。

微服务里,一个用户点击,背后可能串了七八个服务。

出了问题你都不知道卡在哪一环,日志散在七八台机器上,根本对不上。

这就需要链路追踪。

做法是给每个进来的请求打一个全局唯一的 ID(一般叫 TraceID)。

这个 ID 跟着请求在整条调用链里传递,每经过一个服务就记一笔。

# 一次请求的链路追踪示意

用户下单   TraceID=abc123
  -> 网关         [abc123] 耗时 2ms
  -> 订单服务     [abc123] 耗时 15ms
    -> 库存服务   [abc123] 耗时 8ms
    -> 优惠券服务 [abc123] 耗时 320ms  <- 慢在这!
  -> 支付服务     [abc123] 耗时 20ms
​

拿着 TraceID 一查,整条链路的耗时分布一目了然。

哪一环慢、哪一环报错,清清楚楚。

上面那个优惠券服务 320ms,不用猜就知道该去查它了。

Jaeger、Zipkin、SkyWalking 都是干这个的。

接入之后排障效率不是提升一点半点,强烈建议早接。

配置别散落在各处

最后说配置中心。

服务一多,配置管理也成了麻烦事。

数据库连接串、第三方密钥、各种开关参数……以前写在每个服务自己的配置文件里。

现在几十个服务,想改一个公共配置,难道要一个个改、一个个重启?

而且不同环境(开发、测试、生产)的配置还各不一样,手工管理迟早出错。

配置中心的思路是:把配置集中管起来,服务启动时去拉,运行时还能动态推送更新。

好处很直接:

  • 配置改一处,推送到所有相关服务,不用逐个重启
  • 不同环境的配置隔离管理,发布时不容易拿错
  • 敏感配置集中加密存储,比散在一堆文件里安全
  • 配置变更有记录,改坏了能回滚

Nacos 这类工具就是注册中心和配置中心二合一的,一套搞定两件事,挺省心。

小结

把这几块串起来看,微服务治理其实就一句话:让一堆独立的小服务,重新协同成一个可靠的整体。

  • 注册发现解决“找得到”
  • 负载均衡解决“分得匀”
  • 熔断限流解决“死不了全家”
  • 链路追踪解决“查得到”
  • 配置中心解决“管得住”

缺哪一块,系统就在哪一块上漏风。

说到底,微服务拆分很容易,治理才是真功夫。

拆分是把蛋糕切开,治理是让切开的蛋糕还能整整齐齐摆回盘子里。

别光顾着拆,治理这块的基建一定要早点铺,不然服务越多窟窿越大。

那就从搭一个注册中心开始吧,一步步来。

更多推荐

章节目录