RPC框架编写实践--简介注册与发现的

本文总阅读量

前记

微服务比正常的单体服务的主要差别是小而多, 同时每个小服务的上下线频率也比较高, 经常会根据服务的使用量来增减服务, 使得整体服务能在不同的并发量情况下时刻保持着稳定, 同时也不会造成服务空闲。
但是如果每次都是手动的配置服务的连接地址, 那将会非常的繁琐,且无法自动化, 这时就需要通过注册中心来实现服务与发现功能了。

1.注册与发现

1.1什么是注册与发现

服务调用端在调用服务之前必须要得到服务提供方的地址, 在单体应用场景下, 服务端的地址基本上是不变的, 如果调用端和服务端都是位于同一个内网, 那么服务调用端一般都是通过写死配置的形式来获取服务提供方的地址, 但这对于处于不同网络环境的调用端和服务端的情况是非常的不友好, 因为机器的ip可能会发生改变, 同时用户也不一定会记住ip, 所以这时候就出现了基于DNS的服务与发现, 调用端只需要填写一个域名, 在请求的时候经过解析器解析就可以得到服务端的真实地址, 进而通过这个地址访问到了服务端。
在这个过程中,DNS服务充当着一个信息存储的载体, 它存放着域名与服务端真实地址的映射关系, 我们可以把DNS简单的认为是一个服务发现的组件, 运维人员把操作ip和域名的绑定可以认为是服务注册, 而客户端通过DNS解析域名得到的ip地址的操作认为是服务发现。

1.2微服务的注册与发现

DNS虽然很稳健也好用, 但是他有一个缓存的问题, 而对于微服务来说, 它的变动的十分频繁的, 它需要马上知道服务端的动态, 不然会出现服务端已经启动了, 但没有客户端连接上来以及服务端崩溃了, 客户端还一直请求的情况。
第一种情况还好, 不会造成请求异常, 但是对于提供这个服务的服务集群来说, 会跟负载不均衡一样, 而第二种则会造成请求异常, 如果没有其他服务治理功能, 影响范围会比较大, 同时这两种情况结合起来后可以发现, 不适合让这个提供整个服务的集群进行滚动更新, 所以微服务需要一个能针对自己需求的注册与发现功能, 功能与DNS一样, 只不过能实时监听到对应服务的变动, 这时就需要依赖到一个组件–注册中心。

目前有很多种注册中心组件, 他们的主要功能基本上都是一样的, 提供了注册服务地址,注销服务地址以及监听对应的服务地址变动的功能, 在与服务结合后的简单结构图如下:

在实际场景中,图中的注册中心一般都是3个以上的节点组成的集群, 通过集群提供了一个稳定的服务, 服务端在启动的时候会把自己的监听端口通过对应的协议发送到注册中心, 而客户端会在启动的时候从注册中心获取到对应的地址放在内存中, 用户每次调用服务的时候客户端都会从内存中获取对应的地址, 并通过负载均衡等服务治理功能后把请求发送到某个服务中。
此外, 客户端在启动的时候会请求到注册中心并hold住(如果是HTTP协议则是注册中心hold住这个请求, 如果是gRPC则是建立一个长连接通道), 如果服务端发生变动时, 注册中心会通知客户端具体的变动信息, 客户端根据这个信息增减内存中保存的地址。

1.3.服务端职责–服务注册

服务注册是针对服务端的, 服务端进程一般有3个生命周期: 启动,运行中,停止, 分别对应注册的三个阶段启动注册, 定时续期, 停止注销。

当一个服务节点启动后,会把自己的IP,端口注册到注册中心上面, 并且设置了一个有效期, 告诉注册中心过了多少时间后自动注销这个注册的值, 防止异常退出后注册中心依然保留着服务端的IP端口。
服务端在运行过程中, 会定时的告诉注册中心自己还活着, 客户端依然可以通过注册中心获取服务端的IP和端口并连接到服务端。
最后是停止注销阶段, 服务端在退出之前,主动告诉注册中心自己即将退出, 客户端不能在通过注册中心获取到服务端的IP和端口了, 不过这一步是有延时的, 服务端最好还能通过广播自动的告诉客户端自己即将退出, 客户端需要赶紧处理自己的事情, 然后关闭连接, 具体见RPC框架编写实践–优雅的重启

1.4.客户端职责–服务发现

服务发现是针对客户端的, 客户端的发现具体体现在启动的时候的发现以及运行中的增量发现。客户端启动的时候会通过注册中心获得自己要连接的服务的所有连接信息, 并建立连接, 在运行的时候根据注册中心返回的信息变化, 自动增加和删除连接。
但是注册中心不一定是可靠的, 随时都可能崩溃, 客户端还需要把注册中心的数据保存在自己的内存中, 当注册中心崩溃时, 还可以冻结当前的节点信息, 保持服务继续可用。

2.注册中心的选型

在微服务中,注册中心作为一个存储所有服务中心的地方必然不可能是单机的。因为如果注册中心无法使用,那么服务提供者就无法对外暴露自己的服务,消费者也无法获取自己需要调用的服务的具体地址,整个应用将会崩溃, 所以需要保证的就是注册中心的高可用。
这就涉及到经典的CAP理论了, 由于只能在一致性, 可用性, 分区容错性三选二, 而注册中心无法容忍单点故障, 所以分区容错是不可避免的(一般的分布式系统都需要确保分区容错性), 注册中心一般只会采用CP(优先保持一致性)或者AP(优先保持可用性), 使用者需要根据场景来进行选择, 由于我实现的客户端是支持冻结注册中心信息的, 所以注册中心在短时间内挂掉对服务的影响也不大, 所以我是优先选择CP类型的注册中心, 常见的CP类型的注册中心有etcd, consul以及ZooKeeper, 不过我对ZooKeeper不太熟悉, 这里就不做更多的讨论了。

2.1etcd

etcd名字是由 /etc 文件夹和”d”分布式系统组成。/etc文件夹是用来存储单系统配置数据的,而 etcd用于存储大规模分布式系统的配置数据,etcd集群可提供高稳定性,高可靠性,高伸缩性和高性能的分布式KV存储服务。etcd是基于复制状态机实现的,由Raft一致性模块,日志模块,基于boltdb持久化存储的状态机组成,可应用于分布式系统的配置管理,服务发现,分布式一致性等等。
etcd的特点:

  • 完全复制:集群中的每个节点都可以使用完整的存档
  • 高可用性:Etcd可用于避免硬件的单点故障或网络问题
  • 一致性:每次读取都会返回跨多主机的最新写入
  • 简单:包括一个定义良好、面向用户的API
  • 安全:实现了带有可选的客户端证书身份验证的自动化TLS
  • 可靠:使用Raft算法实现了强一致、高可用的服务存储目录

    2.2consul

    Consul与etcd解决的是不同的问题,etcd用于分布式一致性KV存储,而Consul侧重于端到端的服务发现,它提供了内置的健康检查,失败检测和DNS服务等注册中心功能,另外Consul通过RESTfulHTTPAPIs提供KV存储能力.但是当KV使用量达到百万级时,会出现高延迟和内存压力等问题。 不过由于Consul使用的Gossip一致性算法, 它然支持多数据中心,但是多数据中心内的服务数据并不会跨数据中心同步,各个数据中心的 Server 集群是独立的,Consul 提供了 Prepared Query 功能,它支持根据一定的策略返回多数据中心下的最佳的服务实例地址,使你的服务具备跨数据中心容灾。

EtcdConsule两者很像, 他们的具体对照表如下(来源于网上):
||etcd|consul|
|–|–|–|
|并发原语|支持|支持|
|线性读|支持|支持|
|多版本控制|支持|不支持|
|事务|支持|支持|
|变更通知|支持|支持|
|用户权限|RBAC|ACLs|
|HTTP|支持|支持|
|成员变更|支持|支持|
|一致性算法|Raft|Gossip|
|CAP|CP|CP|
|高负载读写性能|高|低|
|部署运维|简单|简单|
|监控|支持metric监控|支持metric监控|

可以看出, 两者的差异不是太大, 但是为了注册中心能一直保存高可用, 它也需要一些维护成本的, 所以一般来说公司有什么现成的配置中心, 就用什么什么配置中心, 在其他情况, 想要高性能的就采用etcd, 想要自带完备注册中新功能的以及多数据中心的则选择Consul

注册中心的调用接口很简单, 通常只有几个接口, 而微服务一般涉及到了多语言的调用, 为了能兼容多语言, 一般都要挑选支持HTTP协议的注册中心, 如果需要性能强劲的, 只需要挑选支持其他通讯协议的注册中心(当然, 生态完善的注册中心即使采用私有协议, 当它提供了众多语言的Sdk, 也是可以挑选的)。

查看评论