You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

115 lines
8.7 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 05 | 如何注册和发现服务?
专栏上一期我给你介绍了服务发布和引用常用的三种方式RESTful API、XML配置以及IDL文件。假设你已经使用其中一种方式发布了一个服务并且已经在一台机器上部署了服务那我想问你个问题如果我想调用这个服务我该如何知道你部署的这台机器的地址呢
这个问题就跟我想去吃肯德基一样,我可以去谷歌地图上搜索肯德基,然后谷歌地图会返回所有的肯德基店面的地址,于是我选择距离最近的一家去吃。这里面谷歌地图就扮演了一个类似注册中心的角色,收录了所有肯德基店面的地址。
同理,我想知道这台服务器的地址,那是不是可以去一个类似“谷歌地图”的地方去查呢?是的,在分布式系统里,就有一个类似的概念,不过它的名字可不是叫什么地图,而是叫注册中心。但原理和地图其实差不多,就是将部署服务的机器地址记录到注册中心,服务消费者在有需求的时候,只需要查询注册中心,输入提供的服务名,就可以得到地址,从而发起调用。
下面我来给你详细讲解下注册中心的原理和实现方式。
## 注册中心原理
在微服务架构下主要有三种角色服务提供者RPC Server、服务消费者RPC Client和服务注册中心Registry三者的交互关系请看下面这张图我来简单解释一下。
* RPC Server提供服务在启动时根据服务发布文件server.xml中的配置的信息向Registry注册自身服务并向Registry定期发送心跳汇报存活状态。
* RPC Client调用服务在启动时根据服务引用文件client.xml中配置的信息向Registry订阅服务把Registry返回的服务节点列表缓存在本地内存中并与RPC Sever建立连接。
* 当RPC Server节点发生变更时Registry会同步变更RPC Client感知后会刷新本地内存中缓存的服务节点列表。
* RPC Client从本地缓存的服务节点列表中基于负载均衡算法选择一台RPC Sever发起调用。
![](https://static001.geekbang.org/resource/image/75/d9/757231c3cde3d1e2fb805c861ea7a1d9.jpg)
## 注册中心实现方式
注册中心的实现主要涉及几个问题:注册中心需要提供哪些接口,该如何部署;如何存储服务信息;如何监控服务提供者节点的存活;如果服务提供者节点有变化如何通知服务消费者,以及如何控制注册中心的访问权限。下面我来一一给你讲解。
**1\. 注册中心API**
根据注册中心原理的描述注册中心必须提供以下最基本的API例如
* 服务注册接口:服务提供者通过调用服务注册接口来完成服务注册。
* 服务反注册接口:服务提供者通过调用服务反注册接口来完成服务注销。
* 心跳汇报接口:服务提供者通过调用心跳汇报接口完成节点存活状态上报。
* 服务订阅接口:服务消费者通过调用服务订阅接口完成服务订阅,获取可用的服务提供者节点列表。
* 服务变更查询接口:服务消费者通过调用服务变更查询接口,获取最新的可用服务节点列表。
除此之外为了便于管理注册中心还必须提供一些后台管理的API例如
* 服务查询接口:查询注册中心当前注册了哪些服务信息。
* 服务修改接口:修改注册中心中某一服务的信息。
**2\. 集群部署**
注册中心作为服务提供者和服务消费者之间沟通的桥梁,它的重要性不言而喻。所以注册中心一般都是采用集群部署来保证高可用性,并通过分布式一致性协议来确保集群中不同节点之间的数据保持一致。
以开源注册中心ZooKeeper为例ZooKeeper集群中包含多个节点服务提供者和服务消费者可以同任意一个节点通信因为它们的数据一定是相同的这是为什么呢这就要从ZooKeeper的工作原理说起
* 每个Server在内存中存储了一份数据Client的读请求可以请求任意一个Server。
* ZooKeeper启动时将从实例中选举一个leaderPaxos协议
* Leader负责处理数据更新等操作ZAB协议
* 一个更新操作成功当且仅当大多数Server在内存中成功修改 。
通过上面这种方式ZooKeeper保证了高可用性以及数据一致性。
![](https://static001.geekbang.org/resource/image/0c/6f/0c3e56272b08e58461e38bbbfd6c796f.jpg)
**3\. 目录存储**
还是以ZooKeeper为例注册中心存储服务信息一般采用层次化的目录结构
* 每个目录在ZooKeeper中叫作znode并且其有一个唯一的路径标识。
* znode可以包含数据和子znode。
* znode中的数据可以有多个版本比如某一个znode下存有多个数据版本那么查询这个路径下的数据需带上版本信息。
![](https://static001.geekbang.org/resource/image/8f/1e/8f28fca07e7455229763a0a214f5db1e.jpeg)
**4\. 服务健康状态检测**
注册中心除了要支持最基本的服务注册和服务订阅功能以外,还必须具备对服务提供者节点的健康状态检测功能,这样才能保证注册中心里保存的服务节点都是可用的。
还是以ZooKeeper为例它是基于ZooKeeper客户端和服务端的长连接和会话超时控制机制来实现服务健康状态检测的。
在ZooKeeper中客户端和服务端建立连接后会话也随之建立并生成一个全局唯一的Session ID。服务端和客户端维持的是一个长连接在SESSION\_TIMEOUT周期内服务端会检测与客户端的链路是否正常具体方式是通过客户端定时向服务端发送心跳消息ping消息服务器重置下次SESSION\_TIMEOUT时间。如果超过SESSION\_TIMEOUT后服务端都没有收到客户端的心跳消息则服务端认为这个Session就已经结束了ZooKeeper就会认为这个服务节点已经不可用将会从注册中心中删除其信息。
**5\. 服务状态变更通知**
一旦注册中心探测到有服务提供者节点新加入或者被剔除,就必须立刻通知所有订阅该服务的服务消费者,刷新本地缓存的服务节点信息,确保服务调用不会请求不可用的服务提供者节点。
继续以ZooKeeper为例基于ZooKeeper的Watcher机制来实现服务状态变更通知给服务消费者的。服务消费者在调用ZooKeeper的getData方法订阅服务时还可以通过监听器Watcher的process方法获取服务的变更然后调用getData方法来获取变更后的数据刷新本地缓存的服务节点信息。
**6\. 白名单机制**
在实际的微服务测试和部署时通常包含多套环境比如生产环境一套、测试环境一套。开发在进行业务自测、测试在进行回归测试时一般都是用测试环境部署的RPC Server节点注册到测试的注册中心集群。但经常会出现开发或者测试在部署时错误的把测试环境下的服务节点注册到了线上注册中心集群这样的话线上流量就会调用到测试环境下的RPC Server节点可能会造成意想不到的后果。
为了防止这种情况发生注册中心需要提供一个保护机制你可以把注册中心想象成一个带有门禁的房间只有拥有门禁卡的RPC Server才能进入。在实际应用中注册中心可以提供一个白名单机制只有添加到注册中心白名单内的RPC Server才能够调用注册中心的注册接口这样的话可以避免测试环境中的节点意外跑到线上环境中去。
## 总结
注册中心可以说是实现服务化的关键,因为服务化之后,服务提供者和服务消费者不在同一个进程中运行,实现了解耦,这就需要一个纽带去连接服务提供者和服务消费者,而注册中心就正好承担了这一角色。此外,服务提供者可以任意伸缩即增加节点或者减少节点,通过服务健康状态检测,注册中心可以保持最新的服务节点信息,并将变化通知给订阅服务的服务消费者。
注册中心一般采用分布式集群部署来保证高可用性并且为了实现异地多活有的注册中心还采用多IDC部署这就对数据一致性产生了很高的要求这些都是注册中心在实现时必须要解决的问题。
## 思考题
最后请你思考一下你觉得采用注册中心来实现服务发现与传统的DNS实现服务发现有什么不同吗
欢迎你在留言区写下自己的思考,与我一起讨论。