20、微服务架构:服务拆分原则、服务发现、负载均衡、API网关模式

微服务架构,说白了就是把一个大单体应用拆成一堆小服务。每个服务独立部署、独立演进。听起来很美好,但拆不好就是灾难。我见过太多团队一上来就拆了十几个服务,结果连个接口都调不通。

这一章,咱们就聊聊怎么拆、怎么找、怎么均衡、怎么统一入口。嗯,都是我在项目中踩过的坑。

一、服务拆分原则:别为了拆而拆

服务拆分不是越细越好。你想想看,拆成几十个服务,每个服务就两三个接口,那运维成本直接起飞。我个人习惯遵循以下几个原则:

  • 业务边界原则:按业务领域拆分。比如用户服务、订单服务、支付服务。每个服务有清晰的业务边界。
  • 数据独立原则:每个服务拥有自己的数据库。别共享数据库,否则耦合度比单体还高。
  • 团队自治原则:一个服务由一个团队全权负责。如果团队只有两三个人,别拆太细。
  • 变更频率原则:经常一起变更的功能,别拆开。否则一次改动要跨多个服务发布。

核心观点:服务拆分的目的是降低复杂度,而不是增加复杂度。拆完之后,每个服务应该比原来的单体更简单。

我在项目中遇到过这样一个案例:有个团队把用户管理拆成了「用户注册服务」「用户登录服务」「用户信息查询服务」三个独立服务。结果每次用户注册完还要调登录服务生成 token,再调查询服务获取用户信息。一个注册流程变成了三次 RPC 调用。后来我建议他们合并成一个用户服务,反而更清爽。

二、服务发现:服务之间怎么找到对方?

服务拆开了,A 服务怎么知道 B 服务的 IP 和端口?总不能写死在配置文件里吧。服务发现就是解决这个问题的。

服务发现有两种模式:

模式 说明 适用场景
客户端发现 客户端直接查询注册中心,获取服务实例列表 小规模、对延迟敏感
服务端发现 客户端请求负载均衡器,由负载均衡器查询注册中心 大规模、需要统一管理

在 Ktor 生态里,我常用 Consul 或者 Kubernetes 自带的 DNS 来做服务发现。Ktor 客户端支持通过 ServiceDiscovery 接口集成。

// Ktor 客户端集成 Consul 服务发现
val client = HttpClient(CIO) {
    install(ServiceDiscovery) {
        consul {
            host = "consul.example.com"
            port = 8500
        }
    }
}

// 直接通过服务名调用
val response = client.get("http://user-service/api/users/123")

小提示:服务发现一定要配合健康检查。如果服务挂了但注册中心没感知,那调用方就会疯狂报错。我曾经就因为健康检查间隔设得太长,导致线上故障持续了 5 分钟。

三、负载均衡:别让一个服务累死

一个服务通常部署多个实例。负载均衡就是决定请求发给哪个实例。常见的策略有:

  • 轮询:按顺序轮流分配。简单,但不考虑实例负载。
  • 最少连接:分配给当前活跃连接最少的实例。适合长连接场景。
  • 一致性哈希:根据请求的某个特征(如用户 ID)哈希到固定实例。适合需要缓存亲和性的场景。

在 Ktor 中,负载均衡通常和服务发现配合使用。Ktor 客户端内置了负载均衡支持:

val client = HttpClient(CIO) {
    install(ServiceDiscovery) {
        consul { /* ... */ }
        loadBalancer = LoadBalancer.roundRobin()
    }
}

注意:不要盲目使用轮询。如果某个实例处理慢,轮询会把请求继续发过去,导致该实例雪崩。我个人习惯用最少连接策略,或者配合熔断器一起使用。

四、API 网关模式:统一入口,统一治理

服务多了之后,客户端要记住每个服务的地址,还要处理认证、限流、日志等公共逻辑。API 网关就是把这些公共逻辑抽出来,放在一个统一的入口层。

API 网关的核心职责:

  • 路由转发:根据请求路径转发到对应的后端服务
  • 认证鉴权:统一校验 token,避免每个服务重复实现
  • 限流熔断:防止某个服务被突发流量打垮
  • 协议转换:比如将外部 HTTP 请求转为内部 gRPC 调用
  • 日志监控:统一记录请求日志和指标

在 Ktor 中,你可以自己写一个网关服务,也可以集成现成的方案如 Kong、Zuul、Spring Cloud Gateway。我个人倾向于用 Ktor 自建轻量级网关,因为灵活且性能好。

// 一个简单的 Ktor API 网关示例
fun Application.gatewayModule() {
    install(Routing) {
        // 路由到用户服务
        route("/api/users") {
            handle {
                val response = userServiceClient.get("/api/users${request.uri}")
                call.respond(response)
            }
        }
        // 路由到订单服务
        route("/api/orders") {
            handle {
                val response = orderServiceClient.get("/api/orders${request.uri}")
                call.respond(response)
            }
        }
    }
}

避坑指南:我曾经在网关层做了太多业务逻辑,结果网关变成了一个超级单体。记住,网关只做路由和公共治理,业务逻辑一定要下沉到后端服务。

五、整体架构图

下面这张图展示了微服务架构的核心组件和调用关系。你可以看到客户端只和 API 网关通信,网关负责路由到后端服务,服务之间通过服务发现找到彼此。

客户端 API 网关 用户服务 订单服务 支付服务 ...其他 注册中心 RPC 客户端 API 网关 后端服务 注册中心

六、总结

微服务架构不是银弹。拆得好,系统灵活可扩展;拆不好,运维复杂度翻倍。我个人建议:

  • 先从 2-3 个服务开始,别一上来就搞几十个
  • 服务发现和负载均衡一定要配合健康检查
  • API 网关只做路由和公共治理,别塞业务逻辑
  • 监控和日志一定要跟上,否则出问题你都不知道哪个服务挂了

嗯,这一章就到这里。记住,架构是演进的,不是设计出来的。先跑起来,再优化。

公众号:蓝海资料掘金营,微信 deep3321