Nacos技术面试系列
来源:Nacos技术面试系列
简历一写就是微服务项目,什么服务注册、配置中心。面试一问 Nacos 怎么实现服务发现,让你设计一个注册中心,你会怎么做?
你绞尽脑汁想了半天,就蹦出了几个词:什么心跳机制、配置推送、集群部署。
- 问你"心跳机制具体怎么实现的",你支支吾吾半天说不清楚。
- 问你临时实例和永久实例有什么区别,你磕磕巴巴说"一个临时一个永久",这不是废话吗?
- 问你 Nacos 的 AP 和 CP 模式是什么意思,你居然反问我"CAP 理论是啥?"
这就是你简历上写的"精通微服务、精通 Nacos、精通分布式架构"?
Look at me! 要实现一个注册中心,你首先要知道什么是服务注册。服务启动的时候,把自己的 IP、端口、服务名等信息报告给 Nacos,这就是服务注册。消费者想调用某个服务,去 Nacos 查询这个服务有哪些实例,这就是服务发现。
那服务注册上去之后,Nacos 怎么知道这个服务还活着呢?Tell me why, why baby why?
用心跳机制啊!服务每隔几秒钟(默认 5 秒)向 Nacos 发送一次心跳,告诉 Nacos:"我还活着呢!"如果 Nacos 在一定时间内(默认 15 秒)没收到心跳,就认为这个服务挂了,把它标记为不健康。再过一段时间(默认 30 秒)还是没有心跳,直接把这个实例从注册表里删除。
那么问题来了,如果服务真的挂了,被删除了,那这很合理。但如果只是网络抖动,心跳包丢了几个,服务就被删除了,岂不是很冤?怎么办?
回头看,临时实例和永久实例搞清楚。Nacos 的实例分两种:
- 临时实例(默认):服务自己主动发心跳,心跳停了就自动剔除。适合那些会动态扩缩容的服务,比如你的微服务应用。
- 永久实例:Nacos 主动去探测服务健康状态,即使服务挂了也不会删除,只是标记为不健康。适合那些数据库、第三方服务等相对固定的实例。
那临时实例的问题还是没解决啊,网络抖动导致误删怎么办?嗯哼?
Look at me! Nacos 有一个保护阈值机制。你可以给每个服务设置一个保护阈值(0 到 1 之间的小数),比如设置为 0.5。当健康实例数 / 总实例数 < 0.5 的时候,说明超过一半的实例都不健康了,这时候 Nacos 就会触发保护机制:把所有实例(包括不健康的)都返回给消费者。
为什么要这么做?因为大量实例同时不健康,很可能不是服务真的挂了,而是网络出问题了或者 Nacos 自己出问题了。这时候与其让消费者一个实例都拿不到导致服务全挂,不如把不健康的实例也返回,让消费者试着调用,说不定还能调通呢。这就是"雪崩保护"的思想。
那这样就能保证不出问题了吗?嗯哼?
更复杂的来了!Nacos 不仅是注册中心,还是配置中心。你把配置写到 Nacos 里,在 Nacos 控制台改了配置,应用怎么能实时感知到配置变了呢?
有人说了,应用定时去 Nacos 拉取配置不就行了吗?比如每 3 秒拉取一次。那我问你,如果这 3 秒内配置改了,应用岂不是要等到下一次拉取才能知道?延迟太高了吧?而且你有 1000 个应用实例,每个都 3 秒拉取一次,Nacos 的压力得多大?
那怎么办?用推送机制啊!Nacos 配置改了,主动推送给所有订阅的应用。应用立刻就能收到,实时性拉满。
那么问题又来了,推送的时候应用正好重启了,或者网络抖动推送失败了,岂不是应用就收不到配置变更了?怎么办?
Look in my eyes! Nacos 用的是 长轮询(Long Polling) 机制。应用不是傻傻地每隔几秒拉取一次,而是发起一个长轮询请求:"Nacos 啊,我现在的配置版本是 v1,如果配置没变,你就先别返回,让我这个请求挂着;如果 30 秒内配置变了,你立刻返回最新配置;如果 30 秒到了配置还没变,你就返回一个'没变化'的响应,我再立刻发起下一次长轮询。"
这样既保证了实时性(配置变了立刻返回),又保证了可靠性(即使推送失败,长轮询也能兜底),还减轻了服务器压力(没变化就不返回)。
刚才提到了"长轮询",那 Nacos 2.0 之后是不是还在用长轮询呢?嗯哼?
以后谁再说 Nacos 2.0 还用长轮询,就拉出去枪毙!Nacos 2.0 做了重大升级,从 短连接 + 长轮询 改成了 长连接 + gRPC。客户端和 Nacos 服务端之间建立长连接,配置变更直接通过长连接推送,性能提升了好几倍,支持的客户端连接数也大幅增加。
那这样就能保证不出问题了吗?干嘛?
Nacos 肯定要部署集群啊,单节点挂了整个系统就瘫痪了。那问题来了,集群环境下,服务注册到一个 Nacos 节点,其他节点怎么同步数据?数据一致性怎么保证?
Look at me! Nacos 支持两种一致性协议:
- AP 模式(默认):采用 Distro 协议,类似 Gossip 协议的变种。每个 Nacos 节点负责一部分服务实例,节点之间异步同步数据。特点是高可用、高性能,但可能出现短暂的数据不一致。适合临时实例,因为临时实例本来就是会变化的,短暂不一致问题不大。
- CP 模式:采用 Raft 协议,保证强一致性。所有写操作都要经过 Leader 节点,Leader 把数据同步到半数以上节点才算成功。特点是数据强一致,但性能相对较低,而且 Leader 挂了要重新选举,期间服务不可用。适合永久实例和配置中心,因为配置数据必须保证一致性。
你想想 Zookeeper、Eureka、Nacos 这三个注册中心:
- Zookeeper:走的是 CP,数据强一致,但是 Leader 挂了要选举,期间服务注册和发现都不可用。
- Eureka:走的是 AP,高可用优先,允许数据短暂不一致,各个节点都能提供服务。
- Nacos:既支持 AP 又支持 CP,你可以根据业务场景自己选。这也是为什么 Nacos 能逐渐取代 Eureka 和 Zookeeper。
那这样就完全没有问题了吗?回头 look my eyes!
如果你的 Nacos 集群部署了 3 个节点,都用的默认配置,没有做任何优化。高峰期有 1 万个服务实例,每个实例 5 秒发一次心跳,那 Nacos 每秒要处理多少次心跳?算一下:10000 ÷ 5 = 2000 次/秒。
再加上服务发现的查询、配置的长轮询、集群间的数据同步,Nacos 的压力是非常大的。怎么办?
- 客户端缓存:服务列表拉取到之后,客户端会在本地缓存一份。即使 Nacos 挂了,应用还能用缓存的服务列表继续调用。
- 定时拉取 + 订阅推送:服务列表除了订阅推送,还会定时全量拉取一次(默认 10 秒),双重保险。
- 心跳优化:Nacos 2.0 的长连接本身就能判断客户端是否在线,心跳频率可以降低。
- 集群扩容:实在扛不住就加机器,Nacos 集群可以水平扩展。
那这样就能保证不出问题了吗?嗯哼?
如果你的应用在容器环境(比如 Kubernetes)部署,Pod 被杀掉重建,IP 地址变了,但是旧的实例还在 Nacos 里没来得及剔除,导致消费者拿到旧实例去调用,调用失败怎么办?
这就需要配合 健康检查 和 客户端重试机制。Nacos 的健康检查(临时实例默认 15 秒标记不健康,30 秒删除)要调优,K8s 的优雅停机要配置好。客户端也要做好容错,调用失败自动重试其他实例。
更复杂的是,如果你的微服务有几十上百个,每个服务有几十个实例,配置文件有几百个,全都注册到 Nacos,你怎么管理?怎么保证不同环境(开发、测试、生产)的配置不会搞混?
Look at me! Nacos 提供了 命名空间(Namespace) 、分组(Group) 、Data ID 三层隔离:
- Namespace:用来隔离不同环境,比如 dev、test、prod。
- Group:用来隔离不同业务或应用,比如订单服务、用户服务。
- Data ID:具体的配置文件名,比如 application.yml。
三层隔离下来,你的配置管理就清清楚楚,不会乱套了。
那这样总该没问题了吧?嗯哼?
如果你的 Nacos 集群所有节点都挂了,怎么办?虽然客户端有本地缓存,但是缓存的是服务列表,新服务上线、旧服务下线,客户端感知不到,怎么办?
所以生产环境 Nacos 集群一定要做好高可用:
- 多节点部署(至少 3 节点)
- 持久化到数据库(MySQL 集群)
- 监控告警(及时发现节点异常)
- 异地多活(如果有条件,多机房部署)
最后告诉你,Nacos 的数据持久化有两种方式:
- 嵌入式数据库(Derby):单机模式用的,不要在生产环境用!
- 外置数据库(MySQL):集群模式必须用外置数据库,所有 Nacos 节点共享同一个 MySQL 集群。
以后谁再在生产环境用 Derby,就拉出去枪毙!
现在你知道 Nacos 的坑有多深了吗?服务注册、心跳机制、临时实例、永久实例、保护阈值、配置推送、长轮询、长连接、AP 模式、CP 模式、Distro 协议、Raft 协议、命名空间、数据持久化——这才是你简历上应该写的"精通 Nacos"!
下次面试官再问你 Nacos,你就这么回答,保证他对你刮目相看!Look at me,记住了吗?