Introduction
在聊注册与发现中心之前,请先看以下几个问题:
- 注册的IP和端口怎么确定?
- 实现服务治理还需要注册哪些信息?
- 如何进行优雅的服务注册与服务下线?
- 注册服务的健康检查是如何做的?
- 当服务有节点退出或新的节点加入时,订阅者能不能及时收到通知?
- 是否方便地查看某个应用发布和订阅了哪些服务,以及所订阅的服务有哪些节点吗?
看完这些问题后,你也许会发现,对于服务注册与发现,首先应该关注的是服务注册发现本身的功能,然后才是性能和高可用。一个好的服务注册发现中间件,应该是能完整地满足服务开发和治理的基础功能,然后才是性能和高可用。如果没有想清楚前面的功能,再高的可用性和性能都是浮云。最后,安全也同样重要。
在其他方面,我们应该关注什么?
- 服务端的性能如何?
- 服务发现的容灾策略是怎样的?
- 当我的应用和服务发现中心的网络连接出现问题时,会对我的调用产生什么影响?
- 服务注册中心某台机器宕机或者全部宕机时,会对我的调用产生什么影响?
- 服务注册和发现的链路安全吗,有没有做好权限控制?
Register Service
如何确定注册的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
- 对相同服务下的不同节点设置不同的权重,进行流量调度
- 将服务分成预发环境和生产环境,方便进行ABTest功能
- 不同机房的服务注册时加上机房的标签,以实现同机房优先的路由规则
这些高级功能的实现,本质上是依赖于客户端调用时候的负载均衡策略和调用策略,但是如果服务元数据没有注册上来,也只能是巧妇难为无米之炊。一个良好的服务注册中心在设计最初就应该支持这些扩展字段。
如何进行优雅的服务注册与服务下线
- 优雅发布
虽然服务注册一般发生在服务的启动阶段,但是细分的话,服务注册应该在服务已经完全启动成功,并准备对外提供服务之后才能进行注册。
- 有些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应用,服务端调用服务发布者的接口来完成健康检查。
- 可以通过执行某个脚本的形式来进行综合检查。
服务端主动调用服务进行健康检查是一个较为准确的方式,返回结果成功表明服务状态确实正常。
但是这种方式也存在一些问题:服务注册中心主动调用RPC服务的某个接口无法做到通用性;在很多场景下服务注册中心到服务发布者的网络是不通的,服务端无法主动发起健康检查。
所以如何取舍,还是需要根据实际情况来决定,根据不同的场景,选择不同的策略。
Discover Service
怎么找到服务发现服务端的地址
- 在应用的配置文件中指定服务注册中心的地址,类似于zookeeper和eureka。
- 指定一个地址服务器的地址,然后通过这个地址服务器来获取服务注册中心的地址,地址服务器返回的结果会随着服务注册中心的扩缩容及时更新。
当服务有节点退出或新的节点加入时,订阅者能不能及时收到通知
很经典的Push和Pull问题。Push的经典实现有两种,基于socket长连接的notify,典型的实现如zookeeper;另一种为HTTP连接所使用Long Polling。
但是基于socket长连接的notify和基于HTTP协议的Long Polling都会存在notify消息丢失的问题。
所以通过Pull的方式定时轮询也必不可少,时间间隔的选择也很关键,频率越高服务注册中心所承受的压力也越大。需要结合服务端的性能和业务的规模进行权衡。
还有一种方式,真实的Push,客户端开启一个UDPserver,服务注册中心通过UDP的方式进行数据推送,当然这个也受限于网络的连通性。
我能方便地查看我发布和订阅了哪些服务,订阅的服务有哪些节点吗
一个好的产品,用户使用体验和运维体验必须是优雅的,如果查看本机发布和订阅的服务,只能通过查看日志,甚至是jmap的方式来获取,显然体验非常糟糕。
服务注册中心应该提供了丰富的接口,支持根据应用名、IP、订阅服务名、发布服务名,来进行多层次的组合查询。同时,客户端的内存里,同样也应该保留服务发布与订阅的各种信息,并提供方式供人方便地查询。
比如在Java中的Spring Boot的应用,可以结合actuator endpoint,通过HTTP的方式来提供本机服务查询功能,查询此应用发布的服务,以及订阅的服务及各服务的对应节点。
容灾和高可用
性能
当服务节点数越来越多时,服务注册中心的性能会成为瓶颈,这时候就需要通过水平扩容来提升服务注册中心集群的性能。
对于那些采用了类Paxos协议的强一致性的组件,如ZooKeeper,由于每次写操作需要过半的节点确认。水平扩容不能提升整个集群的写性能,只能提升整个集群的读性能。而对于采用最终一致性的组件来说,水平扩容可以同时提升整个集群的写性能和读性能。
客户端容灾策略
- 首先,本地内存缓存,当运行时与服务注册中心的连接丢失或服务注册中心完全宕机,仍能正常地调用服务。
- 然后,本地缓存文件,当应用与服务注册中心发生网络分区或服务注册中心完全宕机后,应用进行了重启操作,内存里没有数据,此时应用可以通过读取本地缓存文件的数据来获取到最后一次订阅到的内容。
- 最后,本地容灾文件夹。正常的情况下,容灾文件夹内是没有内容的。当服务端完全宕机且长时间不能恢复,同时服务提供者又发生了很大的变更时,可以通过在容灾文件夹内添加文件的方式来开启本地容灾。此时客户端会忽略原有的本地缓存文件,只从本地容灾文件中读取配置。
服务端容灾与高可用
- 当有新节点加入集群时,节点启动后能自动添加到地址服务器中,并通过地址服务器找到其他节点,自动从其他节点同步数据,以达到数据的最终一致性。
- 当某个节点宕机时,此服务注册中心节点的信息会自动地址服务器中摘除,客户端能及时感知到此节点已下线。
服务端的无状态性保证了服务的容灾和高可用可以做的很薄。
服务器安全
链路安全,对于使用HTTP连接的服务注册中心,保护链路安全的最好方式是使用HTTPS。而使用TCP连接的服务注册中心来说,由于应用层协议一般使用的是私有协议,不一定存在现成的TLS支持方案。
在业务安全方面,应该在每一次的发布、订阅、心跳,都带上鉴权的信息就行验签和鉴权,确保业务信息的安全性。