笑话一则
篮球场上怎么能用一句话击垮对方?答:你的鞋是假的。
大家好呀,我是码财同行。今天继续聊聊微服务管理的一个重要(好像都挺重要)话题:服务注册与发现。
有关微服务管理的前两个话题,为大家奉上传送门:
好了,我们开始谈谈 服务注册与发现。
在之前的文章中,我们提到过这样一种观点:
微服务架构解决了历史架构中的一些问题,但是其本身也新增了问题,而对于这些新增的问题,还会衍生出更多的子问题,这就需要我们不断地用各式各样的技术和手段来解决这些问题。
服务注册与发现也不例外,就是为了解决一些问题而产生的。
解决什么问题?
在服务器的通信过程中,一直以来存在着一个神秘的问题:怎么找到对方的服务。
比如一个后台系统,存在两个服务 A 和 B, A 服务需要调用 B 服务,就需要建立和 B 服务的连接,通常需要 B 服务的地址。
在计算机领域,服务的地址通常是由 ip 地址 和 端口 port 来组合表达。
此时,问题就来了:A 服务怎么知道 B 服务的地址(ip + port)呢?
简单方式
服务较少时,可以使用简单粗暴的方式:
-
硬编码:代码中写死服务地址(ip+port)。但总觉得这种方式不太靠谱,毕竟它是死的,是吧?
-
给每个服务分配一个唯一的名字,并将其对应的地址(ip+port)一起写入configure文件。
所有服务的名字 : 地址都被列在这个配置文件中,一目了然。需要的时候,就根据名字来查询对应的地址。
如果地址发生变更,则更新所有服务机器上的配置文件。
这就好像我需要打电话给朋友,要么我脑子里记住他的号码,要么存入我自己的电话本中。这里的配置文件就是一个电话本的作用。
当朋友电话变更时,需要及时得更新电话本(配置文件)。
在不久之前的过去,存在一个古老的东西叫做黄页,就是一种公开的 "电话本",里面记录了大量的企业和组织的电话号码。马云爸爸最开始创业可就是做了一款网上的中国黄页。
我们可以把记录各个服务地址的配置文件类似的看作这种黄页。
DNS 查询
前面这种配置文件(电话本)的方式,简单,但是更新不太方便。只要有一项变更,则需要更新所有服务的配置文件。如果是实体的电话本,成本可能就更高。
互联网时代到来后,带来了一种叫做 DNS 查询的方式。
网络上无穷无尽的网站,变化和更新很快,不可能通过一个什么配置来做查询。
新的方式是这样的:每次访问网站时,我们在浏览器输入网站的名字(域名,如 www.sistrix.com ),然后名字就被 DNS 服务器翻译为对应的 ip 地址:
DNS 查询的这种方式,让人想起了座机时代电话系统中 114 查号台:
由于每次在请求网站服务之前都会做 DNS 查询,这样即使网站服务的地址发生变更,也能第一时间获知。
DNS 应用的非常广泛,一般用于对外部系统,如暴露给用户端的服务、数据库服务等。
DNS 有一定的起效时间(分钟级别),因此,如果有非常频繁的变更,尤其是在服务器内部通信中,就不太适合使用 DNS 查询的方式。
服务发现
当进入微服务时代,服务的数量急剧增多(相对独立的功能都会被拆分为一个个服务),服务之间像齿轮一样密切配合。而云计算大行其道的现在,服务的地址也是经常变化的,尤其是 k8s 自动调度的引入,服务经常会被调度到不同的机器,这样,服务的地址变化更频繁。
这么多的微服务,地址管理是个难题。
此时,我们就需要引入一种称为 ServiceRegistry 的服务注册与发现的组件。本质上,我们就是需要一种随时注册、更新、获取并监听其他服务地址变更的机制。
可以将 ServiceRegistry 理解成一个 key-value 形式的数据存储,我们将服务的名字和地址一一对应的存进来。同时,它也提供了读取其他服务地址及地址变更的通知功能。
ServiceRegistry 有很多开源实现,流行的有 zookeeper,ETCD,consul 等,基本上是大同小异,一般根据项目的技术栈选择一个,掌握其基本的 API 用法即可。
服务发现的基本需求
有了 ServiceRegistry 做存储和通知,我们要来梳理一下服务发现的基本需求:
- 服务提供者 Service 将自己的名称和地址(port+port)注册到 ServiceRegistry;
- 客户端 Client 根据要访问的 Service 的名字向 ServiceRegistry 查询服务的地址;
- 拿到地址后,Client 就可以向 Service 发起连接,请求服务;
- 如果 Service 地址变更,只需要重新注册新地址,同时由 ServiceRegistry 负责通知 Client 地址变更。
整个流程大概是下面这样的:
具体来说,服务注册和查询又可以有更多的细节。我们分别加以说明。
服务地址注册
- 首先,服务启动好之后就可以将自己的地址(ip+port)写入 ServiceRegistry 中;
- 同时,为了对应网络状况的不稳定,还需要定期与 ServiceRegistry 保持心跳;
- 最后,当服务下线时,需要注销自己。
服务地址发现
- 客户端 Client 启动后,如果要和 Service 通信,需要来 ServiceRegistry 查询 Service 的地址;
- 监听 Service 各个实例地址的变更;
- 收到变更通知后,及时做出对应的调整;
一般来说,一种类型的 Service 服务往往有多个实例,因此我们查询到的地址可能也有多个。
例如,Service A 启动 1,2,3 这3个实例,如果注册到 ServiceRegistry,那 Client 能读取到 3 个地址,都会提供 Service A 服务。
选择哪一个呢?
此时 Client 可以有自己的负载均衡策略:轮询、随机、取模等。根据负载均衡策略选择一个即可。
实现模式
上面,我们对服务注册和发现的具体需求做了分析。具体到代码实现时,又可以分为两种模式:
- 请求端模式
- 服务器模式
请求端模式
如下图所示,我们可以将实现代码以 SDK 的形式嵌入到请求端中。这样,请求端模式复杂度在请求端。 Service 因为涉及服务注册的逻辑,因此也是一种形式的请求端。
服务往往是相互的,A请求B,B也可能请求A。所以,一般的 SDK 同时带有服务注册和发现的功能。这部分 SDK 被嵌入每个 Service:
服务器模式
服务器模式,顾名思义,就是请求端本身不关注服务地址的事情,服务地址的管理交由一个单独的服务来做:
这个单独的服务也就是下图中的 Load Balance 部分,这部分其实也可以叫做路由服务(Route)。
请求端在发包时,并不直接和对方 Service 进行通信,而是通过 LoadBalance/Route 服务来转发,LoadBalance/Route 来查询其他的服务地址,并与之建立连接。
既然服务之间不直接建立连接来通信,就不需要服务发现的功能了,统统由别人代劳。
实践
服务名称
在实际实现服务注册与发现时,服务的名称需要确定,并保证唯一。不同的项目可以采取不同的策略。
一种策略是采用集群名 + 服务名的方式,如下图中 data.1791 是集群名,Children 里的节点就是各个服务的名称。
还有的项目使用:模块 + 子模块 + 实例 port 的方式,也是可以的。
融合主从逻辑
除了普通的服务注册功能,我们还可以将主从选主逻辑融合进来。
很多情况下,某些特定的服务只能有一个单一的实例来提供服务。为了避免单点(单一节点挂球)问题,就设计了一主多从的结构:
- 平常时间,由主节点提供服务,使用者只需要感知主节点的存在;
- 如果主节点挂了,从节点要能立即顶上去,使用者也能立即感知到新的主节点并更新相关信息;
前面我们讨论服务注册的时候,每个服务有自己唯一的名字,如 service.1, service.2, service.3 等。
如果 Service 的几个实例(instance)都尝试注册同一个名字,那么只会有一个成功,这其实就是抢主的逻辑:
而从实例可以 standby 守在一旁,并持续待命:监听主实例的状态,如果收到主实例消失的通知,则再次触发抢主逻辑,如此往复。
遇到的问题
好了,服务发现的原理讲完了,其实还比较简单,读到这里还没看明白的同学,可以找我,申请赔偿10块钱。
只花费一成内力就看懂的的同学也别得意,如果我们马上撸起袖子捣鼓几天,写好了代码,就准备上线,就会发现,事情没有想象的这么美好。
还有很多问题主要存在于系统运行过程中异常情况的处理。
大家知道,外网生产环境上,机器间的环境比较复杂,经常会有连接断开、丢包的情况,如果与 ServiceRegistry 断开后发起重连,对应的节点注册、监听都要同步恢复。
我们曾遇到一个重连时再注册,但是 ServiceRegistry 中老节点还在的情况,此时如果直接不处理,则老节点过一会又会自己消失。
类似的坑还有很多,往往需要很多次测试及完善,整体系统才能稳定。
可优化点
我们项目在实现时,采用了请求端模式。请求端模式的好处是掌控力较强,可以实现很多复杂的定制。但是,如果微服务系统中涉及的技术栈比较多,用了较多的语言,那痛苦面具就要戴上了:每种语言实现一套客户端 sdk,维护成本很高。
解决方案也有,那就是再加一个中间层,也就是 side car 模式:服务注册与发现的功能独立为一个 agent,使用者通过 http 方式与 agent 通信。
如果项目本身就有路由系统,可以考虑做成服务器模式,将服务注册与发现统一交由路由系统来管理,这样也是可以的。
小结
好了,微服务管理的一个重要话题:服务注册与发现我们就谈完了,现在给大家总结一下:
- 服务注册与发现诞生于微服务的环境中,相比配置文件及 DNS 方式更适合做服务器内部的地址管理;
- 服务发现的逻辑可以细分为服务注册和发现的基本需求;
- 有两种模式可以实现服务注册与发现:请求端模式和服务器模式,各有优缺点,项目可以结合实际选择一个;
- 在实践过程中,可以灵活处理,融合主从选主逻辑。此外,使用过程中,还有很多细节问题需要解决,全面细致的测试必不可少;
- 系统需要持续优化,找到更合理的方式。
最后,请大家来个点赞、收藏、转发吧,您的鼓励是我持续创作的动力,蟹蟹!
【参考文档】