一, 什么是服务发现?
问题:
我们现在有多少个服务?
服务越来越多时, 服务 URL 配置管理变得非常乱
服务对外的地址变了, 其他所有有使用到的服务都要改地址
增加服务, 增加服务实例等, 都要做运维工作
1.1 服务端发现模式
客户端通过服务端的负载或路由访问目标服务.
服务端负责负载和转发请求, 自身或者通过第三方工具, 自动发现新的服务实例, 并定时检测心跳维护注册表.
优点:
服务发现功能对于客户端而言是透明的
缺点:
增加了一次转发, 集中式的做法没有去中心化
提问:
1.nignx/F5 有服务地址列表, 也有负载均衡功能, 属于服务端发现吗?
主要缺了 "发现" 功能, 服务实例动态增减并不能主动识别
2. 服务实例自己往服务端发送注册请求不可以吗?
服务实例是服务提供者, 也可能是服务消费者; 如果往注册中心发送注册请求, 那么也可以从注册中心拉注册表, 这样直接做客户端发现不是更好?
1.2 客户端发现模式
客户端自身就可以确定服务提供者的可用实例地址列表和使用负载均衡策略.
服务提供者向注册中心发送注册和心跳.
服务端只负责维护注册表, 并对外提供服务.
优点:
客户端可以灵活, 智能地制定负载均衡策略
去中心化
缺点:
客户端与注册中心耦合, 需要实现与注册中心交互的代码
二, 服务发现的关键角色提取
服务提供者:
向注册中心发送请求, 发送信息
服务消费者:
向注册中心发送请求, 获取信息
注册中心:
提供接口给服务发现客户端调用
维护服务实例信息集合
集群之间信息同步
三, 关键角色要干些什么事情
3.1 服务提供者
服务提供者: 向注册中心发送请求, 发送信息
1. 发送请求问题:
发送请求的工具用什么, okhttp,resttemplate?
请求失败怎么办, 要重试吗? 重试的策略是什么?
2. 注册中心地址问题:
发送信息给所有注册中心还是发送一个成功就行了?
注册中心有多个的话, 优先访问哪个呢, 访问失败的要记录起来下次不访问了吗?
地址写在配置里, 如果有增加或调整, 所有客户端都要改?
3. 发送注册 / 心跳问题:
注册和心跳调同一个接口还是分开两个接口比较好?
注册后, 除了要定时发送心跳, 实例信息有改变的情况下, 要发给注册中心吗?
3.1.1 服务提供者 - 发送请求问题
作为一个中间件如何提高可扩展性?
eureka 的答案是装饰器模式
JerseyClient:
执行 REST 请求的工具, eureka 有 RestTemplate 的替换实现方案
统计信息:
根据请求类型进行统计, 请求时间总计, 请求失败次数总计等
重定向:
返回的 http code 是 302 则表示要重定向 (注册中心迁移?) 更换重定向的 url 再次发起请求, 最多重定向 10 次
失败重试机制:
已经请求失败的 server url 记录下来下次不再请求;
如果失败的 url 过多(比如全部都失败的情况, 可能是客户端网络有问题), 超过阀值, 则清空失败的 url 记录, 把他们重新当成可用的 url , 这里要获取注册中心地址列表, 用于重试
会话:
请求失败的 url 记录多久, 永久记录吗? 使用 session 机制如果 20 分钟没请求过, 则重置上面的所有机制
session 有效时长 20 分钟左右(有随机增减)
3.1.2 服务提供者 - 注册中心地址问题
1. 发送信息给所有注册中心还是一个?
不管是 AP 还是 CP, 服务提供者只需与一个注册中心交互成功就行, 服务提供者不关心注册中心之间的数据同步
2. 客户端如何保证发送到任意一个注册中心成功呢?
配置多个注册中心地址, 失败了就换下一个再试
3. 注册中心地址变更问题
配置了多个注册中心地址后, 如果注册地址有增删改怎么解决
将注册中心地址放到配置中心 或 DNS txt 记录, 客户端定时去刷新获取
(就算是存在本地配置文件, eureka 也会开启定时任务定时去取)
3.1.3 服务提供者 - 发送心跳 / 注册问题
注册后, 除了要定时发送心跳, 实例信息有改变的情况下, 要发给注册中心吗?
eureka 的答案是需要:
1. 实例状态变更
2. 配置信息变更
LeaseExpirationDurationInSeconds 心跳有效期
LeaseRenewalIntervalInSeconds 心跳频率
3. 健康检查处理器的增减(用于获取实例状态)
3.2 服务消费者
服务消费者: 向注册中心发送请求, 获取注册表信息
1. 发送请求问题(同服务提供者)
2. 注册中心地址变更问题(同服务提供者)
3. 全量还是增量获取应用实例信息
如果消费者本地实例信息为空, 则全量获取, 否则增量获取, 关键逻辑在注册中心, 这里较简单
将上面结论进行汇总
3.3 注册中心
注册中心:
提供接口给服务发现客户端调用
维护服务实例信息集合
集群之间数据同步
3.3.1 注册中心 - 接口
如何做增量更新?
查询是否需要缓存?
如何做增量更新?
eureka 的答案是维护一个队列 recentlyChangedQueue
细心的观众会发现, 为什么接收到心跳, 不会增加到 recentlyChangedQueue?
队列里存的是引用类型, 和应用实例列表是同一个地址, 接收到心跳进行实例更新, 定时维护最近变动实例队列时查到实例信息变动, 然后继续将其保留在队列里, 这样增量查询下次一样能查到该实例的信息
为什么要自我保护?
如果失效的实例过多, 有没有可能是注册中心出了问题?
上一分钟接收到心跳总数 <= 实例总数 * 2(默认 30s 一次心跳) * RenewalPercentThreshold()(默认 0.85) 进入自我保护则不会剔除任何实例
3.3.2 注册中心 - 集群同步
如何做集群同步?
eureka 的答案还是维护队列, 这里注册, 心跳, 下线都会入队, 批量进行同步, 失败的加到失败重试队列
接收到的所有请求, 都要同步吗?
要经过过滤, 每种类型只取最新一条去同步即可
注册中心地址变更
同服务发现客户端一样, 定时刷新注册中心地址, 然后更新集群节点
3.4 架构图
(右键新标签打开可查看大图)
四, 结合 Region 和 AZ(Available Zone)
区域(Region):
从设计而言, 每个 Amazon EC2 区域都与其他 Amazon EC2 区域完全隔离. 这可实现最大程度的容错能力和稳定性. 在区域之间传输数据需要收费.
可用区(Availability Zone ,AZ):
如果您的实例分布在多个可用区且其中的某个实例发生故障, 则您可对您的应用程序进行相应设计, 以使另一可用区中的实例可代为处理相关请求. 可用区由区域代码后跟一个字母标识符表示; 例如, us-east-1a.
地域(Region):
不同地域的云服务器 ECS, 关系型数据库 RDS, 对象存储服务 OSS 内网不互通.
不同地域之间的云服务器 ECS 不能跨地域部署负载均衡, 即在不同的地域购买的 ECS 实例不支持跨地域部署在同一负载均衡实例下.
可用区(Zone):
可用区是指在同一地域内, 电力和网络互相独立的物理区域. 同一可用区内实例之间的网络延时更小.
理解 region 和 az 的概念后, 我们需要做什么是不是就简单多了?
1. 服务发现客户端, 优先与相同的 AZ 进行通讯
2. 服务发现客户端, 查询注册中心地址列表时, 只取相同 region 下所有 AZ 的注册中心地址
2. 注册中心, 也只需要将数据同步给相同 region 的所有 AZ 即可
不过属于哪个区, 好像没有特殊的配置吧?
eureka.client.availability-zones.us-east-1=zone2,zone1
eureka 中取上面配置的第一个可用区为优先使用的可用区
注: 虽然 region 之间是数据隔离的, 但是 eureka 也支持从其他 region 同步信息(通过 http 请求)
常见问题探讨
连连看, 将如下类图与我们架构图里对象进行对应(右键新标签打开可查看大图)
eureka server 是 ap 还是 cp?
服务启动后会不会马上注册到注册中心?
服务提供者注册到注册中心后, 服务消费者最快多久能够查询到该提供者?
为什么接收心跳后不入队, 那么更新心跳后启不是增量查询查不到了?
集群同步的队列, 为什么要经过一道中转才进行消费?
部分图片摘自如下文章:
《聊一聊微服务架构下的服务发现模式》
来源: https://www.cnblogs.com/roytian/p/12125478.html