服务发现相关
服务发现
服务发现中分为三个角色
- 服务提供者
将自己能提供的服务注册到服务中介 - 服务消费者
从服务中介查找自己需要的服务的地址 - 服务中介
提供多个服务,每个服务对应多个服务提供者
服务提供者
可以是一个 HTTP 服务器,提供 API 服务,有一个 IP 端口作为服务地址
服务消费者
可以是 client 或者其他服务,需要服务提供者提供的服务
服务中介
从使用上来说就是一个字典,字典里有很多 key/value 键值对,key 是服务名称,value 是服务提供者的地址列表。服务注册就是调用字典的 Put 方法添加 k/v 对,服务查找就是调用字典的 Get 方法.
当服务提供者节点不可用时,希望服务中介及时注销该服务,并且通知消费者重新获取服务地址
当服务提供者新加入时,要求服务中介能及时告知服务消费者,有新的服务可用。
服务发现的实现
目前较为通用的服务发现使用的开源框架有 consul, 现在学习下其实现原理

这张图来自 consul 官网。
首先 Consul 支持多数据中心,在上图有两个 DataCenter ,之间通过 Internet 互联,同时为了提高通信效率,只有 Server 节点才加入跨数据中心的通信。
在单个 DC 中,Consul 分为 Client 和 Server 两种节点
- Server
负责保存数据 - Client
负责健康检查及转发数据请求到 Server
Server 节点有一个 Leader 和多个 Follower, Leader 节点会将数据同步到 Follower, Server 的数量推荐是 3 个或者 5 个,在 Leader 挂掉之后会启动选举机制产生一个新的 Leader.
gossip 协议
DC 内的 Consul 节点通过 gossip 协议(流言协议)维护成员关系,也就是某个节点了解集群内现在还有哪些节点,这些节点是 Client 还是 Server. 单个数据中心的流言协议同时使用 TCP 和 UDP 通信,并且都是用 8301 端口。跨数据中心的流言协议也同时使用 TCP 和 UDP 通信,端口使用 8302.
流言协议由种子节点发起,当一个种子节点有状态需要更新到网络中的其他节点时,它会随机选择周围几个节点散播消息,收到消息的节点也会重复该过程,直到最终网络中所有节点都收到了消息,这个过程需要一定时间,但是理论上最终所有节点都会收到消息,因此是一个最终一致性协议。
演示如下

服务发现原理
服务发现就是在服务中介中添加一行 set 结构存储服务的 IP:PORT 字符串。如果服务提供者加入,将服务地址添加到服务中介的数据库中,如果服务挂掉,则将其服务地址移除。
以上的方式并不健壮,存在以下问题
- 服务提供者进程被暴力杀死
- 服务列表变动时如何通知消费者
- 服务中介如果宕机
服务提供进程被杀死
此时需要引入服务包活和检查机制,并更换数据结构。服务提供者需要每隔 5s 左右向服务中介汇报存活,服务中介记录服务地址和汇报时间。服务中介每隔 10s 检查 zset 数据结构,踢掉汇报时间严重落后的服务地址项。这样就可以准实时地保证服务列表中服务地址的有效性,
服务列表变动
有两种解决方案,第一种是轮询,第二种是 pubsub.
轮询
消费者需要每隔几秒查询服务列表是否有改变。如果服务很多,服务列表很大,消费者很多,redis 会有一定一定压力。
所以可以引入服务列表的版本号机制,给每个服务提供一个 key / value 设置服务的版本号,就是在服务列表发生变动时,递增版本号。消费者只需要轮询这个版本号的变动即可知道服务列表是否发生了变化。
pubsub
这种方式及时性要明显好于轮询。缺点是每个 pubsub 都会占用消费者一个线程和一个额外的 redis 连接。为了减少对线程和连接的浪费,我们使用单个 pubsub 广播全局版本号的变动。全局版本号变动即任意服务列表发生了变动,这个版本号都会递增。接收到版本变动的消费者再去检查各自的依赖服务列表的版本号是否发生了变动。
服务中介宕机
为了避免服务中介在单机情况下的宕机,目前流行的服务发现系统都是使用分布式数据库实现 zookeeper/etcd/consul 等来作为服务中介,这几种分布式数据库是多节点的,一个节点宕机仍能保证整个服务中介的正常运行。