变更通知在开源SpringBoot/SpringCloud微服务中的最佳实践

目录导读

  • 变更通知在开源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等

总结:

  • 变更通知首先是数据(也可叫消息,下同。)发生了变化,然后引起了关联方同步变化;
  • 数据变化可以发生在前端后端之间的任意组合之间,包括前端+前端前端+后端后端+后端,一般只有后端+后端的连锁反应叫变更通知,这也是本文关注的重点;
  • 上表串联了所有常规的交互过程,大家也可以从中理解消息通知变更通知之间的关联关系:只是不同交互方式下的数据变化的叫法罢了;
  • 前端感知后端的数据变化,主要有前端轮询后端推送两种,二者技术实现差异较大,前者技术实现简单,但是后端资源消耗较大,无法承担高并发;后者技术实现较复杂,但是点对点通信效率高(长连接)。这不是本文讲述的重点,但希望能够类比分析下各种交互方式下的技术方案,做到一通百通;
  • 变更通知主要关注的是后端数据变化,但是不是所有的后端数据变化都需要变更通知。一般来说,跨了服务实例,才比较适用变更通知,单个服务实例内,完全可以通过接口引用等方式自行获取到变化的数据;
  • 本文重点关注的是变更通知而不是配置中心,像NacosSpringCloud-ConfigApollo配置中心并不会过多介绍;
  • 本文主要关注后端与后端之间的数据变化机制及技术实现,其发展阶段如下:
变更类型 应用发展阶段 技术方案 技术实现 说明
主动获取 单实例 共享数据库 查询数据库 数据库查询量不大
主动获取 单实例 共享内存缓存 查询时先查缓存 数据库查询量非常大
主动获取 多实例 分布式缓存 使用读写性能极高的分布式缓存组件 如: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
配置中心 服务端推送 把配置中心组件当成业务配置中心 组件重、可靠性低、实时性低 配置中心严格来说是个变更通知的解决方案,而上面列举的中间件是纯技术组件,二者的维度不太一样; 配置中心一般和注册中心配合使用,因为它本身也需要注册至服务中心,如:ApolloSpringCloud-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;

5. 参考资料

相关推荐
soulteary21 分钟前
突破内存限制:Mac Mini M2 服务器化实践指南
运维·服务器·redis·macos·arm·pika
天天扭码25 分钟前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
FIN技术铺30 分钟前
Spring Boot框架Starter组件整理
java·spring boot·后端
小码的头发丝、1 小时前
Spring Boot 注解
java·spring boot
午觉千万别睡过1 小时前
RuoYI分页不准确问题解决
spring boot
跳跳的向阳花1 小时前
03-03、SpringCloud第三章,负载均衡Ribbon和Feign
spring cloud·ribbon·负载均衡
2301_811274311 小时前
大数据基于Spring Boot的化妆品推荐系统的设计与实现
大数据·spring boot·后端
wkj0011 小时前
php操作redis
开发语言·redis·php
菠萝咕噜肉i2 小时前
超详细:Redis分布式锁
数据库·redis·分布式·缓存·分布式锁
编程重生之路2 小时前
Springboot启动异常 错误: 找不到或无法加载主类 xxx.Application异常
java·spring boot·后端