Py学习  »  NGINX

十万个why:Nginx 已经能做负载均衡,为什么还需要服务注册发现?

IT服务圈儿 • 3 天前 • 40 次点击  

来源丨经授权转自 程序员小富(ID:chengxy-nds)

作者丨小富

知乎上看到一个问题,说 Nginx 的 upstream 不就是负载均衡嘛,配几台后端机器,请求自动分发,和 Nacos、Eureka 这些注册中心干的事有什么本质区别?为什么还要额外引入一整套服务注册发现的东西?

这个问题热度挺高,其实这个问题问得挺好好,因为很多人对这个似是而非,感觉挺明白,面试问他他又不知道怎么组织语言。

Nginx和注册中心解决的他就不是一个问题啊!

Nginx 解决的是「请求该转发给谁」,属于转发规则;注册中心解决的是「后端服务实例的状态」,属于实时状态感知。

Nginx 负载均衡的不足

Nginx 负载均衡缺点很明显,实例列表是写死的,Nginx 做负载均衡的配置大家都见过,非常的简单

upstream order-service{
    server192.168.1 .10:8080;
    server192.168.1.11:8080;
    server192.168.1.12:8080;
}

server{
    location/api/orders {
        proxy_passhttp://order-service;
    }
}

三台机器轮询转发,服务数量少、实例基本不变动的情况下,这完全没问题。

问题是这个列表是静态的,写死在配置文件里了。

我每次后端实例变化,比如扩容加机器、缩容摘机器、某台挂了,就需要修改配置文件,然后执行 nginx -s reload,如果部署了多台 Nginx,每台都得改,比较麻烦。

机器少的时候这还能接受,微服务都几十个服务打底、每个服务多个实例、还要支持弹性扩缩容,靠手工改配置,上个线那肯定要通宵啊!

Nginx 感知不到实例挂了

Nginx 有被动健康检查机制,后端连续返回错误时,会把那台机器暂时摘掉。但这个机制有个前提:必须有真实请求打过去失败了,才能发现这台机器有问题。

举个具体场景:

图片

T1 和 T2 的请求是用真实用户的流量去试错的,在这段时间内所有打到 .11 的请求全部超时。

注册中心的机制不一样

服务实例启动后会持续向注册中心发送心跳,默认 5 秒一次。心跳超时没收到,注册中心直接把这个实例从注册表里摘掉。消费方下次拉取实例列表时,这个实例就已经不在了,整个过程不需要用真实流量去探路。

Nginx 开源版没有主动健康检查,商业版 Nginx Plus 有,你也可以装 nginx_upstream_check_module 来补,但那又引入了额外的运维负担,问题只是转移了,没有根本解决。

弹性扩缩容 Nginx 做不到

云环境下,实例数量随时在变,没法处理。

业务高峰期自动扩容,从 3 个实例扩到 20 个,低峰期缩回去,发版时滚动更新,旧实例下线、新实例上线。K8s 上的  Pod 被驱逐后在另一个节点重新调度,IP 直接变了,每一次变化,Nginx 的 upstream 列表都需要同步更新。

靠人工或脚本去改配置再 reload,这条路在自动化伸缩的场景下根本走不通,根本不知道什么时候扩了、扩到哪台机器、IP 是什么。

注册中心解决的就是这个问题,实例启动时自动向注册中心注册自己的 IP 和端口,实例下线时自动注销,心跳断了自动被摘除。

消费方持续订阅注册中心的变更,实例列表实时更新,整个过程不需要任何人工介入。

对比一下两种流程:

图片

一个是秒级自动完成,一个是分钟级人工操作,而且依赖人不出错。

服务间调用全走 Nginx?

Nginx 是中心化的反向代理,如果微服务之间的所有内部调用都经过 Nginx,调用链路是:

服务A → Nginx → 服务B

微服务架构下,服务间调用非常频繁,订单服务可能同时依赖库存服务、用户服务、优惠服务。如果所有内部流量都经 Nginx 中转,Nginx 的吞吐量就成了整个系统的天花板,而且多了一跳网络转发,延迟也上去了。

有了注册中心,服务间调用走的是客户端负载均衡:

图片

去掉 Nginx 这一跳之后,延迟减少,也消除了 Nginx 单点的风险。每个服务自己做负载均衡,负载分散到各个调用方。

Spring Cloud 里的 Spring Cloud LoadBalancer 就是干这个的。从 Nacos 或 Eureka 拉实例列表,在客户端本地做负载均衡,然后直接发请求过去。

这些 Nginx 很难做

注册中心不只是存 IP 和端口,每个实例注册时还可以带元数据:

{
    "serviceName""order-service",
    "ip""192.168.1.10",
    "port"8080 ,
    "metadata": {
        "version""v2",
        "region""shanghai",
        "env""gray",
        "weight"80
    }
}

基于这些元数据能做一些 Nginx 做不到,或者实现起来很麻烦的事。

灰度发布

v1 和 v2 同时在线,希望 95% 流量走 v1,5% 走 v2。在注册中心里给 v2 的实例打上 version=v2 的标签,消费方路由规则按标签分流,配置改一下就生效。Nginx 实现同样效果要写 split_clients 或者 Lua 脚本,每次版本变更还得改配置再 reload。

同机房优先调用

服务在上海和北京都有实例,希望上海的请求优先打上海的实例,减少跨机房网络延迟。注册中心里每个实例带 region 元数据,消费方按 region 就近路由,直接配置路由策略就行。

这个需求放在 Nginx 上要做额外的 geo 路由配置,实现起来繁琐。

动态权重调整

某个实例性能差一些,想降低它的流量权重。Nacos 控制台上直接改权重,秒级生效,不需要改任何配置文件。Nginx 改 weight 参数还得 reload。

这几个需求在微服务场景下都很常见,注册中心天然支持,Nginx 要实现得费很大力气。

Nginx 适合的场景

Nginx 是必须要有的,Nginx 和注册中心不是互相替代的关系,分工不同。

图片

外部用户请求进入系统,走 Nginx 或 API Gateway——统一入口、SSL 终止、限流、WAF,这是 Nginx 的主场,没有任何东西能替代它。

微服务内部互相调用,走注册中心加客户端负载均衡——动态发现实例、直连、去中心化。

两者各管一段,没有重叠,也不需要选择。

如果你的系统就三五个服务、实例基本不变动,Nginx 完全够用,不需要引入注册中心。注册中心是解决动态环境下实例管理问题的,规模上不去的时候引入反而增加复杂度。

说在最后

Nginx 负责把外部请求转进来,注册中心就是负责让内部服务知道彼此在哪、还活着没有。服务实例数量一多、变化频率一高,静态的 upstream 列表就跟不上了,需要一个能实时感知变化的动态机制。


1、一个 npm install,坑了前端 15 年!终被废!

2、微软开源的 Skill 进化神器,GitHub 上斩获 5000 多 Star 了。

3、全网爆火的Loop到底是什么?

4、明明 Spring 二级缓存就能解决循环依赖,为什么非弄个三级缓存?

5、 腾讯面试官:“用过Codex吗?来聊聊”我懵了:“不是?都用AI写代码了?”面试官:“大部分是,另外想看你对于Transformer的理解!”

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/198024