【建议收藏】服务注册与发现原理+踩坑,不懂可索赔10元

笑话一则

篮球场上怎么能用一句话击垮对方?答:你的鞋是假的。

大家好呀,我是码财同行。今天继续聊聊微服务管理的一个重要(好像都挺重要)话题:服务注册与发现

有关微服务管理的前两个话题,为大家奉上传送门:

微服务管理 | 依赖管理

微服务管理 | 服务更新策略

好了,我们开始谈谈 服务注册与发现

在之前的文章中,我们提到过这样一种观点:

微服务架构解决了历史架构中的一些问题,但是其本身也新增了问题,而对于这些新增的问题,还会衍生出更多的子问题,这就需要我们不断地用各式各样的技术和手段来解决这些问题。

服务注册与发现也不例外,就是为了解决一些问题而产生的。

解决什么问题?

在服务器的通信过程中,一直以来存在着一个神秘的问题:怎么找到对方的服务。

比如一个后台系统,存在两个服务 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 做存储和通知,我们要来梳理一下服务发现的基本需求:

  1. 服务提供者 Service 将自己的名称和地址(port+port)注册到 ServiceRegistry;
  2. 客户端 Client 根据要访问的 Service 的名字向 ServiceRegistry 查询服务的地址;
  3. 拿到地址后,Client 就可以向 Service 发起连接,请求服务;
  4. 如果 Service 地址变更,只需要重新注册新地址,同时由 ServiceRegistry 负责通知 Client 地址变更。

整个流程大概是下面这样的:

具体来说,服务注册和查询又可以有更多的细节。我们分别加以说明。

服务地址注册

  1. 首先,服务启动好之后就可以将自己的地址(ip+port)写入 ServiceRegistry 中;
  2. 同时,为了对应网络状况的不稳定,还需要定期与 ServiceRegistry 保持心跳;
  3. 最后,当服务下线时,需要注销自己。

服务地址发现

  1. 客户端 Client 启动后,如果要和 Service 通信,需要来 ServiceRegistry 查询 Service 的地址;
  2. 监听 Service 各个实例地址的变更;
  3. 收到变更通知后,及时做出对应的调整;

一般来说,一种类型的 Service 服务往往有多个实例,因此我们查询到的地址可能也有多个。

例如,Service A 启动 1,2,3 这3个实例,如果注册到 ServiceRegistry,那 Client 能读取到 3 个地址,都会提供 Service A 服务。

选择哪一个呢?

此时 Client 可以有自己的负载均衡策略:轮询、随机、取模等。根据负载均衡策略选择一个即可。

实现模式

上面,我们对服务注册和发现的具体需求做了分析。具体到代码实现时,又可以分为两种模式:

  1. 请求端模式
  2. 服务器模式

请求端模式

如下图所示,我们可以将实现代码以 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 方式更适合做服务器内部的地址管理;
  • 服务发现的逻辑可以细分为服务注册和发现的基本需求;
  • 有两种模式可以实现服务注册与发现:请求端模式和服务器模式,各有优缺点,项目可以结合实际选择一个;
  • 在实践过程中,可以灵活处理,融合主从选主逻辑。此外,使用过程中,还有很多细节问题需要解决,全面细致的测试必不可少;
  • 系统需要持续优化,找到更合理的方式。

最后,请大家来个点赞、收藏、转发吧,您的鼓励是我持续创作的动力,蟹蟹!

【参考文档】

systemdesign.one/what-is-ser...

相关推荐
hai4058710 分钟前
Spring Boot中的响应与分层解耦架构
spring boot·后端·架构
Adolf_19931 小时前
Flask-JWT-Extended登录验证, 不用自定义
后端·python·flask
叫我:松哥2 小时前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
海里真的有鱼2 小时前
Spring Boot 项目中整合 RabbitMQ,使用死信队列(Dead Letter Exchange, DLX)实现延迟队列功能
开发语言·后端·rabbitmq
工业甲酰苯胺2 小时前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis
新知图书2 小时前
Rust编程的作用域与所有权
开发语言·后端·rust
wn5313 小时前
【Go - 类型断言】
服务器·开发语言·后端·golang
m0_635502203 小时前
Spring Cloud Gateway组件
网关·微服务·负载均衡·过滤器
希冀1234 小时前
【操作系统】1.2操作系统的发展与分类
后端
GoppViper4 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理