聊聊微服务的服务注册与发现
聊起微服务的服务注册与发现,很多人立马就会脱口而出 zk、etcd、consul、eureka 这些组件,进而聊到 CAP 如何取舍,性能如何,高可用和容灾是怎么实现的。
在这之前,站在组件使用者的角度,我想先问这么几个问题:
-
注册的 IP 和端口怎么确定 ?
-
实现服务治理还需要注册哪些信息 ?
-
如何进行优雅的服务注册与服务下线 ?
-
注册服务的健康检查是如何做的 ?
-
当服务有节点退出或新的节点加入时,订阅者能不能及时收到通知 ?
-
我能方便地查看某个应用发布和订阅了哪些服务,以及所订阅的服务有哪些节点吗 ?
看完这些问题后,您也许会发现,对于服务注册与发现,首先应该关注的是服务注册发现本身的功能,然后才是性能和高可用。
一个好的服务注册发现中间件,应该是能完整地满足服务开发和治理的基础功能,然后才是性能和高可用。如果没有想清楚前面的功能,再高的可用性和性能都是浮云。最后,安全也同样重要。
-
服务端的性能如何 ?
-
服务发现的容灾策略是怎样的 ?
-
当我的应用和服务发现中心的网络连接出现问题时,会对我的调用产生什么影响 ?
-
服务注册中心某台机器宕机或者全部宕机时,会对我的调用产生什么影响 ?
-
服务注册和发现的链路安全吗,有没有做好权限控制 ?
下面将从 服务注册、服务发现、容灾和高可用三个大方面来回答这些问题的主流做法。
服务注册
注册的 IP 和端口怎么确定 ?
主流的 IP 获取有这几种方法。
-
最简单粗暴的方式,手动配置需要注册的IP。当然这种方式基本无法在生产环境使用,因为微服务基本都是支持水平扩容多机部署的,在配置中写死 IP 地址的方式无法支持一份代码水平扩容,会给运维带来极大的成本。
-
通过遍历网卡的方式去获取,找到第一个不为本地环回地址的 IP 地址。绝大多数情况下,这个方式比较好用,dubbo 等框架采用的就是这种方法。
-
在一些网络规划比较好的标准化机房中,我们还可以通过手动指定网卡名,即 interfaceName 的方式来指定使用哪一块网卡所对应的 IP 地址进行注册。
-
当上述三种方式都不能有效解决问题的时候,有一个方法就是直接与服务注册中心建立 socket 连接,然后通过
socket.getLocalAddress()
这种方式来获取本机的 IP。
端口如何确定
端口的获取,没有标准化的方案。
-
如果是 RPC 应用,启动的时候都有一个配置来指定服务监听的端口, 注册的时候直接使用配置项的端口值。
-
传统的 WEB 容器所提供的 HTTP 的应用,同样也存在一个配置文件来配置容器的监听端口,注册时候直接使用配置项的端口值。
-
特别的,在 Java 应用的 Spring Boot 框架中,可以通过 EmbeddedServletContainerInitializedEvent. getEmbeddedServletContainer().getPort()来获取。(Spring Boot 版本为 1.x)。
实现服务治理还需要注册哪些信息 ?
简单地将 IP 和 port 信息注册上去,可以满足基本的服务调用的需求,但是在业务发展到一定程度的时候,我们还会有这些需求:
-
想知道某个 HTTP 服务是否开启了 TLS。
-
对相同服务下的不同节点设置不同的权重,进行流量调度。
-
将服务分成预发环境和生产环境,方便进行AB Test功能。
-
不同机房的服务注册时加上机房的标签,以实现同机房优先的路由规则。
-
这些高级功能的实现,本质上是依赖于客户端调用时候的负载均衡策略和调用策略,但是如果服务元数据没有注册上来,也只能是巧妇难为无米之炊。一个良好的服务注册中心在设计最初就应该支持这些扩展字段。
如何进行优雅的服务注册与服务下线 ?
优雅发布
虽然服务注册一般发生在服务的启动阶段,但是细分的话,服务注册应该在服务已经完全启动成功,并准备对外提供服务之后才能进行注册。
-
有些 RPC 框架自身提供了方法来判断服务是否已经启动完成,如 Thrift ,我们可以通过 Server.isServing() 来判断。
-
有一些 RPC 框架本身没有提供服务是否启动完成的方式,这时我们可以通过检测端口是否已经处于监听状态来判断。
-
而对于 HTTP 服务,服务是否启动完毕也可以通过端口是否处于监听状态来判断。
-
特别的,在 Java 应用的 Spring Boot 框架中,可以通过事件通知的形式来通知容器已经启动完毕, > EmbeddedServletContainerInitializedEvent 事件来通知容器已经启动完成 (Spring Boot 版本为 1.x)。
优雅下线
绝大多数的服务注册中心都提供了健康检查功能,在应用停止后会自动摘除服务所对应的节点。但是我们也不能完全依赖此功能,应用应该在停止时主动调用服务注册中心的服务下线接口。
-
在 Java 应用中,通用的服务下线接口调用一般使用 JVM Shutdown Hook 的方式来实现。
-
特别的,在 Java 应用中的 Spring 框架中,可以通过 Spring Bean LifeCycle 来实现应用停止时主动调用服务下线接口。
-
当然上述两种方式还不够优雅,因为不能确保不出现 kill -9 这种粗暴的停止方式,而且应用调用服务下线接口也是尝试去调用,对于网络不通等异常场景并没有做异常处理。因此,调用客户端仍应该做好负载均衡与 failover 的处理。
-
更优雅的方式,先将即将停止的应用所对应的权重调成 0,此时上游将不再调用此应用。这时候的停止应用的操作对服务订阅者完全没有影响,当然这种场景需要订阅者实现按权重的负载均衡和运维部署工具深度结合。
服务的健康检查是如何做的 ?
健康检查分为客户端心跳和服务端主动探测两种方式。
-
客户端心跳
-
客户端每隔一定时间主动发送“心跳”的方式来向服务端表明自己的服务状态正常,心跳可以是 TCP 的形式,也可以是 HTTP 的形式。
-
也可以通过维持客户端和服务端的一个 socket 长连接自己实现一个客户端心跳的方式。
-
ZooKeeper 并没有主动的发送心跳,而是依赖了组件本身提供的临时节点的特性,通过 ZooKeeper 连接的 session 来维持临时节点。
但是客户端心跳中,长连接的维持和客户端的主动心跳都只是表明链路上的正常,不一定是服务状态正常。
服务端主动调用服务进行健康检查是一个较为准确的方式,返回结果成功表明服务状态确实正常。
- 服务端主动探测
- 服务端调用服务发布者某个 HTTP 接口来完成健康检查。
- 对于没有提供 HTTP 服务的 RPC 应用,服务端调用服务发布者的接口来完成健康检查。
可以通过执行某个脚本的形式来进行综合检查。
网友评论