大家好,我是砸锅。一个摸鱼八年的后端开发。熟悉 Go、Lua。今天和大家一起学习分布式技术😊
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 15 天,点击查看活动详情
注册发现
单体服务面临的问题
-
异构工作负载
因为单体服务会包含很多的功能模块。有一些是 IO 密集型的模块,例如对数据库操作的功能模块;有一些是计算密集型的模块,例如对图片、音频和视频转码的功能模块;如果能将 IO 密集型和 CPU 密集型的模块拆分不同的服务,分开部署到更合适的硬件上,就可以节省大量的机器成本
-
不同的保障级别 不同的业务的保障级别不一样,对于核心模块必须保证资源充足,对于非核心模块,保障的资源可以相对少一些,而对于单体服务来说,没有办法对不同的模块实施不同的保障级别
-
研发效率 单体服务模式导致的串行编译、测试和发布,以及研发团队只能选择单一的研发语言和生态
-
稳定性问题 一个局部非核心功能崩溃、死锁等异常会影响全部业务,没有办法把故障隔离。 由于单体业务不能分开发布,所以在业务功能迭代的时候,底层核心功能也必须频繁发布
服务注册发现的背景
如果决定按照资源和业务等维度来对单体服务进行拆分,拆分多个服务之后,如何调用其他服务的函数呢? 可以通过 REST API 或者 RPC 来进行跨服务调用,但是需要知道调用服务的 IP 和 Port。最简单的方式是在配置文件里面维护调用服务的 IP 和 Port 列表,但是一旦服务的 IP 或者 Port 更改了,就需要每一份用到的配置文件更新一次,比较繁琐和容易出错。 如果使用域名解析的方式,将调用服务的 IP 和 Port 列表修改成域名和 Port,就算服务的 IP 变了,只需要更改域名解析就可以了,配置文件不需要改。但是如果服务出现问题了或者网络不通的情况,不能实时感知服务实例的状态变更,导致在 DNS 解析时仍然拿到异常实例的 IP,从而访问错误
服务发现需要解决的两个关键问题
- 统一的中介存储。调用方在唯一的地方获得被调用服务的所有实例信息
- 状态更新和通知。服务实例的信息能够及时更新并且通知到服务调用方
如何实现服务注册发现
中介存储
计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决,所以需要找一个外部存储来做解决问题的中间层,这个存储必须满足:
- 可用性非常高。如果服务注册发现出现问题,整个分布式系统就不可用了
- 性能中等。性能要求不能太差
- 数据容量要求低。因为存储的都是实例的 IP 和 Port 等元数据,数据量不大
- API 友好。是否可以很好的支持服务注册发现场景里的 “发布 / 订阅” 模式
MySQL 和 Redis 在高可用性和 API 友好程度上不满足要求,所以更合适的存储系统为 etcd、ZooKeeper 和 Eureka
如何实现服务状态的更新和通知
首先是服务的状态更新,即服务注册:如上图中的 1,服务的每一个实例每隔一段时间,比如 30 秒,主动向中介存储上报一次自己的 IP 和 Port 信息,同时告诉中介存储这一信息的有效期,比如 90 秒。这样如果实例一直存活,那么每隔 30 秒,它都会将自己的状态信息更新到中介存储。如果实例崩溃或者被 Kill 了,那么 90 秒后,中介存储就会自动将该实例的信息清除,避免了实例信息的不一致。所以这里的数据同步是最终一致性的。
然后是服务的状态通知,即服务发现:如上图中的 2,服务的调用方通过中介存储监听被调用服务的状态变更信息。这里可以采用“发布 / 订阅”模式,也可以采用轮询模式,比如每 30 秒去中介存储获取一次。所以这里的数据同步也是最终一致性的。
互联网系统通过 DNS 实现服务注册和发现。所以中介存储是 DNS。DNS 存在缓存,修改 DNS 之后会有生效时间,所以 DNS 是最终一致性而不是强一致性系统。同时 DNS 的可用性几乎是 100% 的。所以 DNS 是 AP 系统
此文章为2月Day10学习笔记,内容来源于极客时间《深入浅出分布式技术原理》