目录导读
- 变更通知在开源SpringBoot/SpringCloud微服务中的最佳实践
-
- [1. 什么是变更通知](#1. 什么是变更通知)
- [2. 变更通知的场景分析](#2. 变更通知的场景分析)
- [3. 变更通知的技术方案](#3. 变更通知的技术方案)
-
- [3.1 变更通知的技术实现方案](#3.1 变更通知的技术实现方案)
- [4. 变更通知的最佳实践总结](#4. 变更通知的最佳实践总结)
- [5. 参考资料](#5. 参考资料)
变更通知在开源SpringBoot/SpringCloud微服务中的最佳实践
1. 什么是变更通知
变更通知
是指数据发生变化后,相对实时地通知到关联端的技术实现方案;变更通知
是在服务拆分的发展过程中,逐渐衍生出的,解耦关联服务的一种技术解决方案;- 微服务架构中常说的
配置中心
就是变更通知
的技术集成方案;
2. 变更通知的场景分析
变更通知
首先是数据发生了变化,数据变化后的通知实现,数据变化的交互场景列举分析如下:
通知类型 | 模块1 | 模块1数据变更 | 模块2 | 模块2实时变更 | 技术实现方式 | 本文讨论范围 |
---|---|---|---|---|---|---|
消息通知 | 前端1 | ✔ | 前端2 | ✔ | 前端总线等 | ✖ |
消息保存 | 前端1 | ✔ | 后端2 | ✔ | Post Ajax请求等 | ✖ |
消息通知 | 后端1 | ✔ | 前端2 | ✔ | WebSocket等推送到前端(推数据),也叫消息推送 | ✖ |
消息通知 | 后端1 | ✔ | 前端2 | ✔ | 前端Http主动轮询(拉数据) | ✖ |
变更通知 | 后端1 | ✔ | 后端2 | ✔ | Redis/ZooKeeper/Etcd/Nacos/MQ等 | ✔ |
总结:
变更通知
首先是数据(也可叫消息,下同。)发生了变化,然后引起了关联方同步变化;数据变化
可以发生在前端
和后端
之间的任意组合之间,包括前端+前端
、前端+后端
、后端+后端
,一般只有后端+后端
的连锁反应叫变更通知
,这也是本文关注的重点;- 上表串联了所有常规的交互过程,大家也可以从中理解
消息通知
和变更通知
之间的关联关系:只是不同交互方式下的数据变化的叫法罢了;- 前端感知后端的数据变化,主要有
前端轮询
和后端推送
两种,二者技术实现差异较大,前者技术实现简单,但是后端资源消耗较大,无法承担高并发;后者技术实现较复杂,但是点对点通信效率高(长连接)。这不是本文讲述的重点,但希望能够类比分析下各种交互方式下的技术方案,做到一通百通;变更通知
主要关注的是后端数据变化,但是不是所有的后端数据变化都需要变更通知
。一般来说,跨了服务实例,才比较适用变更通知
,单个服务实例内,完全可以通过接口引用等方式自行获取到变化的数据;- 本文重点关注的是
变更通知
而不是配置中心,像Nacos
、SpringCloud-Config
、Apollo
等配置中心
并不会过多介绍;
- 本文主要关注后端与后端之间的
数据变化
机制及技术实现,其发展阶段如下:
变更类型 | 应用发展阶段 | 技术方案 | 技术实现 | 说明 |
---|---|---|---|---|
主动获取 | 单实例 | 共享数据库 | 查询数据库 | 数据库查询量不大 |
主动获取 | 单实例 | 共享内存缓存 | 查询时先查缓存 | 数据库查询量非常大 |
主动获取 | 多实例 | 分布式缓存 | 使用读写性能极高的分布式缓存组件 | 如:Memcached/Redis |
变更通知 | 多实例 | 消息中间件 | Redis/ZooKeeper/Etcd/MQ/binlog+canal等 | 订阅通知机制 |
总结:
变更通知
是随着服务的高并发、分布式发展而发展的,在单体架构时,因为都在一个服务内,仅通过数据库或者共享缓存即可达到数据共享的目的,不一定需要变更通知
;- 在微服务架构中,也可以采用共享缓存方案,而不是必须使用
变更通知
。变更通知
适用于并发高、实时性要求高,且服务解耦的场景;实时
是一个相对概念,在变更通知
语境里,一般是指异步监听的方式获取变化的数据(专业术语叫订阅通知
);
3. 变更通知的技术方案
变更通知
中间件种类较多,基于本人的理解,对比列举如下:
中间件类型 | 实现特点 | 适用场景 | 不足 | 补充说明 |
---|---|---|---|---|
Redis | 基于key的订阅/通知 | 并发高、消息量大 | NoSQL可读性差,持久化不是必选项,存在数据丢失和审计风险 | Redis是极度常用的高效内存组件,建议优选; |
ZooKeeper | 基于分布式临时Node创建的订阅/通知 | 可靠性高、实时性高 | 使用的是内存存储,不适合高并发和大量数据的消息变更场景 | 一般是项目中有ZooKeeper,正好可以用作变更通知组件,而不是因为变更通知诉求而引入ZooKeeper |
Etcd | 基于分布式的Key/Value创建的订阅/通知 | 可靠性高、实时性高 | 使用的是内存存储,不适合高并发和大量数据的消息变更场景 | Etcd的实现参考了ZooKeeper,一个是Go语言编写、一个是Java语言 |
MQ | 异步消息协议 | 可靠性高、并发高、消息量大 | 组件较重 | 包括:Kafka/RocketMQ/RabbitMQ/ActiveMQ等,非常适合电商优惠卷等场景使用 |
binlog+canal | 针对MySQL的数据变更监听方案 | 直接监听数据库表字段变化 | 只适用于MySQL,而MySQL使用量正逐年下降 | 只是监听了MySQL的数据表变化,一般还需要配合其它的变更通知组件来配合使用,如:ZooKeeper |
配置中心 | 服务端推送 | 把配置中心组件当成业务配置中心 | 组件重、可靠性低、实时性低 | 配置中心 严格来说是个变更通知的解决方案,而上面列举的中间件是纯技术组件,二者的维度不太一样; 配置中心 一般和注册中心 配合使用,因为它本身也需要注册至服务中心,如:Apollo 、SpringCloud-Config ;有些干脆就被注册中心 兼任,如Nacos |
总结:
变更通知
一般来说是微服务中的增强功能,不建议因为有变更通知
需求就新增一个组件。如果系统中已经有了Redis,就建议优先选择Redis,因为其性能高、消息存储量大。但Redis是NoSQL存储结构,可读性较差;Redis也有可能没有开启持久化导致数据丢失;Redis也缺失了类似关系数据库自带的操作审计,一旦数据出现了异常,将很难知道是谁做了什么;ZooKeeper
/Etcd
则比较适合系统中已经引入该组件了,且变更通知
消息数量较小的场景;MQ
比较适用于可靠性高、消息量巨大的场景,值得单独引入。如:大型电商的活动卡券配置等场景;binlog+canal
这个特定组合一般不建议单独使用,一般是canal
把变更数据发送给其它变更通知
Server,然后在业务模块订阅变更通知
Server的这个变化数据,并做相应的业务处理。其它Server可以是ZooKeeper/Etcd/Redis等;- 一般来说,
配置中心
包含了单独的配置规则界面和变更通知
的能力,拆箱即用,效果当然较好。但并不是所有的变更通知
都需要重量级的配置中心
,不是非要在配置中心
去配置变更数据的,就都没有必要用它;业务需要使用到配置中心
组件时,建议在选定注册中心后,再来决策选择其配置中心
;
变更通知
有非常多的实现方式,讲讲本人实际经历的业务场景:
场景类型 | 业务诉求 | 微服务架构 | 微服务技术栈 | 方案说明 |
---|---|---|---|---|
场景1 | DB数据变更立即触发定时任务 | 微服务架构 | spring+mysql+binlog+canal+ZooKeeper | 基于spring自研微服务框架 |
场景2-1 | 业务阈值在1天内生效 | 微服务云原生架构 | SpringBoot+Redis+PostgreSQL | 1.依赖k8s提供服务发现等; 2.依赖redis限流; |
场景2-2 | 业务阈值在1天内生效 | 微服务私部署架构 | SpringBoot | 1.部署至客户机房,不依赖Redis/DB; 2.使用Nginx做负载均衡,也不需要服务注册和发现; |
场景3 | 业务阈值在5分钟内生效 | 微服务云原生架构 | SpringBoot+Redis | 1.依赖k8s提供服务发现等; 2.依赖redis限流; |
场景4 | 业务阈值在立即生效 | SpringCloud微服务架构 | SpringBoot+Redis+Nacos | 1.依赖Nacos提供服务发现等; 2.依赖redis限流; |
- 补充说下上述业务场景的技术选型限定条件:
场景1:DB数据变更立即触发定时任务
:当时刚刚时兴微服务,我所在公司1的部门基于spring自研了一个低代码平台,我们需要实时监听数据的变化,以触发不同的定时任务,正好平台中也引入了canal开源组件,用于监听mysql的binlog
变化,于是就选定了canal方案,这样就不用定时轮询数据库了,也不用和增删改数据的服务耦合了;场景2:业务阈值在1天内生效
:场景2-1
和场景2-2
其实对应同一个目标:我所在公司2的部门希望设计一套架构、一套代码,既能满足云上微服务架构,也能够支持私部署微服务架构。当时云上选型为云原生微服务架构,完全基于k8s+非侵入式的链路追踪中间件,我们基本上只使用最简单的SpringBoot+Redis即可;而私部署则继续沿用了这套架构和代码,只不过移除了k8s、redis、服务注册和服务发现,仅使用Nginx做服务负载均衡;场景3:业务阈值在5分钟内生效
:则是我所在公司2的业务团队认为一天生效对业务影响较大,需要调整为5分钟内生效;场景4:业务阈值在立即生效
:则是我所在公司2的另一个新项目,在场景3
代码架构的基础上,使用SpringCloud套件替换了k8s,同时业务也更复杂,业务上必须保证实时生效;
- 单独来说上面的每个业务场景,都可以多种技术实现。本人仅站在过来人的角度,逐一展开分析。
3.1 变更通知的技术实现方案
-
场景1
的实现方案:单独部署了一个Canal服务,用于监听binlog变化,在Canal服务中又集成了ZooKeeper客户端,Canal收到变化的数据后,通过ZooKeeper推送至订阅的业务微服务; -
场景2
的实现方案:经过分析,私部署不需要变更通知,因为私部署不带数据库,业务阈值是配在yaml中,修改后隔离重启服务即可;云原生微服务则因为数据刷新后1天内生效即可,但为了考虑私部署和云原生架构的统一,所以采用了Guava+持久层的本地缓存方案,Guava缓存的有效期设置为24小时,过期后就会重新从PostgreSQL/yaml中获取。但由于云原生的运维不接收直接修改数据库阈值数据,于是又配套开发了一个小的命令行工具(也可以做成Web运维平台),用于专门更改数据库表的字段值。从中可以看出:
- 因为要兼顾云原生和私部署场景,所以需要选择两种场景下都能使用的本地缓存方案:Guava;
- 因为缓存刷新的时效要求低,不使用
变更通知
也是完全可行的。
-
场景3
的实现方案:场景3
其实是场景2
的延续,只是现在数据生效的时间从1天变成了5分钟,考虑技术方案的延续性,在云原生方案中,新增了一个定时任务+Redis,用于刷新缓存,交互逻辑如下图所示:
说明:
- 第1次请求到业务服务时,Guava缓存中也没有数据,则需要业务服务查询一次Database并把数据缓存至Guava,流程为蓝色①→②箭头所示;
- 第2次请求到业务服务时,Guava缓存中已经有了数据,则只需要直接返回数据即可,流程为绿色①所示;
- 当用运维工具更新数据时,同时也会清理掉Redis的缓存标记,一旦ScheduleJob获取的Redis Key(采用redis的setNX语法)不一致时,则会让Guava缓存失效,流程为红色的①→②→③→④箭头所示;
- 后面再有请求过来时,会重新执行上面的第1步和第2步;
总结:
场景3
仅在场景2
的基础上迭代增加了虚线框框中的2个小功能点代码就可以了,代码延续性较好;- ScheduleJob设置为1分钟就可以满足5分钟内生效的业务诉求了;
- 回过头来看看
场景2
:其实并没有做变更通知
,只是采用了Guava自带的缓存过期机制而已;场景3
其实也可以采用Guava自带的缓存过期机制,但是会导致微服务需要频繁的穿透缓存去查询数据库,得不偿失;场景3
的实际做法则兼顾了准实时性和便利性:只有命令行工具变更了数据时,缓存才会刷新。另外场景3
也没有做到变更通知
,只是变相的达到了准实时
的变更通知
的效果。
-
场景4
的实现方案:场景4
是在复用场景3
的代码框架的基础上,要求做到数据变更实时通知。此时项目中虽然已引入了SpringCloud的多个组件,但是业务参数配置都有单独的配置界面,无须使用配置中心
。考虑到Redis其实也有变更通知的能力,此处正好可以在场景3
的基础上继续迭代,去掉ScheduleJob,增加Redis订阅通知的代码即可。
4. 变更通知的最佳实践总结
- 需要搞清楚什么是
变更通知
,什么是配置中心
,不要因为有变更通知
的需求就上配置中心
,这样有可能把系统搞得非常复杂; - 好的设计都要顺势而为,首先需要了解系统的需求到底是什么,是不是一定要做
变更通知
,如本人列举的项目实践中就多次未使用变更通知
组件,但是达到了变更通知
的效果; - 如果只需要做
变更通知
,不需要独立的配置中心
,建议优选Redis,因为它可以兼顾业务限流、高速缓存、不规则数据的处理(NoSQL)等,很有可能Redis就已经存在于项目中了; - 如果数据量非常庞大,还要支持复杂的规则,比如消息确认和重传等,则建议采用MQ(Kafka/RocketMQ/RabbitMQ/ActiveMQ);
- 有些场景下的
变更通知
非常适合使用配置中心
,如:SpringCloud-Gateway的路由规则yaml配置,就非常适合放在配置中心(如:Nacos等);当然如果使用的是k8s,则建议直接使用其ConfigMap;