聊聊在集群环境中本地缓存如何进行同步

前言

之前有发过一篇文章聊聊如何利用redis实现多级缓存同步。有个读者就给我留言说,因为他项目的redis版本不是6.0+版本,因此他使用我文章介绍通过MQ来实现本地缓存同步,他的同步流程大概如下图


他原来的业务流程是每天凌晨开启定时器去爬取第三方的数据,并持久化到redis,后边因为redis发生过宕机事故,他碰巧看了我文章,就觉得可以用使用多级缓存的策略,用来做个兜底。他的业务流程就如上图,即每天凌晨开启定时器去爬取第三方数据,持久化到redis和其中一台服务的本地缓存,然后将爬取到的业务数据发送到kafka,其他业务服务通过订阅kafka,将业务数据保存到本地缓存。

他改造完,某天突然发现在集群环境中,只要其中一台服务消费了kafka数据,其他就消费不到。今天就借这个话题,来聊聊集群环境中本地缓存如何进行同步

前置知识

kafka消费topic-partitions模式分为subscribe模式和assign模式。subscribe模式需要指定group.id,该模式会为consumer自动分配partition,且同一个group.id下的不同consumer不会消费同样的分区。assign模式需要为consumer手动、显示的指定需要消费的topic-partitions,不受group.id限制,相当与指定的group.id无效。通俗一点讲就是assign模式下,所有消费者都可以订阅指定分区

我们要通过消息队列实现本地缓存同步,本质上就是需要利用消息队列提供广播能力,而kafka默认不具备。不过我们可以根据kafka提供的消费模式进行定制,从而是kafka也具备广播能力

集群本地缓存同步方案

方案一:利用MQ广播能力

因为读者项目是使用kafka,且项目是使用spring-kafka,我们也就以此为例

1、subscribe模式

通过前置知识,我们了解到在subscribe模式下,同一个group.id下的不同consumer不会消费同样的分区,这就意味我们可以通过指定不同group.id来消费同样分区达到广播的效果

那如何在同个集群服务实现不同的group.id?

此时Spring EL 表达式就派上用场了,我们通过 Spring EL 表达式,在每个消费者分组的名字上配合 UUID 生成其后缀。这样,就能保证每个项目启动的消费者分组不同,从而达到广播消费的目的

示例

  @KafkaListener(topics = "${userCache.topic}",groupId =  "${userCache.topic}_group_" + "#{T(java.util.UUID).randomUUID()})")public void receive(Acknowledgment ack, String data){System.out.println(String.format("serverPort:【%s】,接收到数据:【%s】",serverPort,data));ack.acknowledge();}

如果我们决定UUID不直观,我们也可以使用IP作为标识,只要能保证同个集群服务的group.id是唯一即可

不过如果要改成ip,我们得做一定的改造。改造步骤如下

a、 获取ip地址信息,并放入environment

public class ServerAddrEnvironmentPostProcessor implements EnvironmentPostProcessor{private String SERVER_ADDRESS = "server.addr";@Override@SneakyThrowspublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {MutablePropertySources propertySources = environment.getPropertySources();Map<String, Object> source = new HashMap<>();String serverAddr = InetAddress.getLocalHost().getHostAddress();source.put(SERVER_ADDRESS,serverAddr);MapPropertySource mapPropertySource = new MapPropertySource("serverAddrProperties",source);propertySources.addFirst(mapPropertySource);}}

b、 配置spi

在src/main/resource目录下配置META-INF/spring.factories,配置内容如下

org.springframework.boot.env.EnvironmentPostProcessor=\
com.github.lybgeek.comsumer.ip.ServerAddrEnvironmentPostProcessor

c、 @KafkaListener配置如下内容

 @KafkaListener(topics = "${userCache.topic}",groupId =  "${userCache.topic}_group_" + "${server.addr}" + "_${server.port}")

小结

该方式的实现优点是比较简单,但如果需要对服务进行运维监控统计,那就不怎么友好了,虽然指定IP会比随机UUID好点,但如果是容器化部署,每次部署其IP也是会变化,这样跟随机指定UUID,差别也不大了。其次如果是使用云产品,比如阿里云对comsume group是有数量上限,且消费者组需要提前创建,这种情况使用该方案就不是很合适了

assign模式

通过assign模式手动消费对应的分区

示例

   @KafkaListener(topicPartitions ={@TopicPartition(topic = "${userCache.topic}", partitions = "0")})public void receive(Acknowledgment ack, ConsumerRecord record){System.out.println(String.format("serverPort:【%s】,接收到数据:【%s】",serverPort,record));ack.acknowledge();}

小结

该方式实现也是很简单,如果我们不需要动态创建新的分区,用该方案实现广播,会是一个不错的选择。不过该方式的缺点很明显,因为是手动指定分区,当该分区有问题,也挺麻烦的

方案二:通过定时器触发

该方案主要基于读者目前的同步进行改造,改造后如下图

核心就是根据读者业务的特性,因为他是定时每天晚上同步爬取,那就意味着他这个数据至少在当天基本不变,就可以让集群里的服务都定时执行,此时仅需将xxl-job的调度策略改成分片广播就行,这样就可以持久化到redis的同时,也持久化到本地缓存

小结

该方案改动量比较小,有个小缺点就是,因为集群内所有服务都执行调度,这样就会使redis重复持久化,不过问题也不大就是好。最后读者选择该方案

总结

本文主要阐述集群环境中本地缓存如何进行同步,之前还有读者问我说,使用了多级缓存,数据一致性要如何保证?以前我可能会从技术角度来回答,比如你可以延迟双删,或者如果你是mysql,你可以使用canal+mq,更甚者你可以使用分布式锁来保证。但现在我更多从业务角度来思考这件事情,你都考虑使用缓存,是不是意味着你在业务上是可以容忍一定不一致性,既然可以容忍,是不是最终可以通过一些补偿方案来解决这个不一致性

没有完美的方案,你此时感觉的完美方案,可能是当时在那个业务场景下,做了一个贴合业务的权衡

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-kafka-broadcast

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/90893.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

游戏类APP如何提升用户的活跃度?

移动游戏行业&#xff0c;追求使用率的营销能发挥强大的功效&#xff0c;可帮助减少玩家流失、追回流失的玩家、提高活跃玩家所带来的价值以及增加付费玩家贡献的收入。 一、了解玩家需求 想要提升玩家的活跃&#xff0c;首先要知道&#xff0c;玩家喜欢玩哪些平台的游戏&…

Win10安装GPU支持的最新版本的tensorflow

我在安装好cuda和cudnn后&#xff0c;使用pip install tensorflow安装的tensorflow都提示不能找到GPU&#xff0c; 为此怀疑默认暗转的tensorflow是不带GPU支持的。 在tensorflow官网提供了多个版本的GPU支持的windows的安装包 https://www.tensorflow.org/install/pip?hlz…

Debian 10驱动Broadcom 无线网卡

用lspci命令查询无线网卡品牌&#xff1a; 运行下面代码后&#xff0c;重启即可。 apt-get install linux-image-$(uname -r|sed s,[^-]*-[^-]*-,,) linux-headers-$(uname -r|sed s,[^-]*-[^-]*-,,) broadcom-sta-dkms

SSH远程连接MacOS catalina并进行终端颜色配置

一、开关SSH服务 在虚拟机上安装了MacOS catalina&#xff0c;想要使用SSH远程进行连接&#xff0c;但是使用“系统偏好设置”/“共享”/“远程登录”开关进行打开&#xff0c;却一直是正在启动“远程登录”&#xff1a; 难道是catalina有BUG&#xff1f;不过还是有方法的&…

Docker本地镜像发布到阿里云

1. 本地镜像发布到阿里云 2. 镜像的生成方法 OPTIONS说明&#xff1a; -a :提交的镜像作者&#xff1b; -m :提交时的说明文字&#xff1b; 本次案例centosubuntu两个&#xff0c;当堂讲解一个&#xff0c;家庭作业一个&#xff0c;请大家务必动手&#xff0c;亲自实操。 docke…

论文复现--关于多视角动作捕捉工具箱 --XRMoCap的研究

分类&#xff1a;动作捕捉 github地址&#xff1a;https://github.com/openxrlab/xrmocap 所需环境&#xff1a; Ubuntu18.04&#xff0c;conda22.9.0&#xff0c;CUDA11.4 目录 一.环境配置conda list 一.环境配置 # 1.创建环境 conda create -n XRmocap python3.7 -y conda …

值传递、形参实参的关系、地址传递(指针和函数)

1 值传递 实现两个数字进行交换&#xff0c;代码和运行结果如下所示&#xff1a; #include<iostream> using namespace std;void change(int a, int b) {int temp a;a b;b temp;cout << "change a " << a << endl;cout << "…

【考研数学】高等数学第三模块——积分学 | Part II 定积分(反常积分及定积分应用)

文章目录 前言三、广义积分3.1 敛散性概念&#xff08;一&#xff09;积分区间为无限的广义积分&#xff08;二&#xff09;积分区间有限但存在无穷间断点 3.2 敛散性判别法 四、定积分应用写在最后 前言 承接前文&#xff0c;梳理完定积分的定义及性质后&#xff0c;我们进入…

C++ 二叉树(建立、销毁、前中后序遍历和层次遍历,寻找双亲结点等)

&#xff08;1&#xff09;结构体和类定义 struct BTreeNode {T data;BTreeNode* left, * right;BTreeNode() :data(0), left(nullptr), right(nullptr) {}BTreeNode(T val, BTreeNode<T>* leftChild nullptr, BTreeNode<T>* rightChild nullptr):data(val), le…

Redis_事务操作

13. redis事务操作 13.1事务简介 原子性(Atomicity) 一致性(Consistency) 隔离性(isolation) 持久性(durabiliby) ACID 13.2 Redis事务 提供了multi、exec命令来完成 第一步&#xff0c;客户端使用multi命令显式地开启事务第二步&#xff0c;客户端把事务中要执行的指令发…

Docker-使用数据卷、文件挂载进行数据存储与共享

一、前言 默认情况下&#xff0c;在Docker容器内创建的所有文件都只能在容器内部使用。容器删除后&#xff0c;数据也跟着删除&#xff0c;虽然通常我们不会删除容器&#xff0c;但是一旦宿主机发生故障&#xff0c;我们重新创建容器恢复服务&#xff0c;那么之前容器创建的文…

Transformer(一)简述(注意力机制,NLP,CV通用模型)

目录 1.Encoder 1.1简单理解Attention 1.2.什么是self-attention 1.3.怎么计算self-attention 1.4.multi-headed&#xff08;q&#xff0c;k&#xff0c;v不区分大小写&#xff09; 1.5.位置信息表达 2.Decoder&#xff08;待补充&#xff09; 3.BERT 参考文献 1.Encode…

DevOps系列文章 之 SpringBoot整合GitLab-CI实现持续集成

在企业开发过程中&#xff0c;我们开发的功能或者是修复的BUG都需要部署到服务器上去&#xff0c;而这部分部署操作又是重复且繁琐的工作&#xff0c;GitLab-CI 持续集成为我们解决了这一痛点&#xff0c;将重复部署的工作自动化&#xff0c;大大的节省了程序员们的宝贵时间。本…

Android复习(Android基础-四大组件)——Broadcast

1. 广播分类 广播的发送方式&#xff1a;标准广播、有序广播、粘性广播广播的类型&#xff1a;系统广播、本地广播 1.1 标准广播 完全异步&#xff0c;无序的广播发出后&#xff0c;所有的广播接收器几乎都会在同一时间收到消息。&#xff08;异步&#xff09;但是消息无法截…

获取部门完整路径数据

1: 获取部门数据 (基础) SELECT id, CONCAT(pid_path,id) as pid_path2,pid_path,title FROM web_department;2: 获取部门数据 (进阶, 但是是,分隔) SELECT t1.id, t1.title, CONCAT(t1.pid_path, ,, t1.id) AS pid_path,(SELECT GROUP_CONCAT(t2.title ORDER BY FIND_IN_SET…

Nginx环境搭建以及Docker环境部署

目录 Nginx环境搭建 1.首先创建Nginx的目录并进入 2.下载Nginx的安装包 可以通过FTP工具上传离线环境包&#xff0c;也可通过wget命令在线获取安装包 没有wget命令的可通过yum命令安装 3.解压Nginx的压缩包 4.下载并安装Nginx所需的依赖库和包 安装方式一 安装方式二 --- 也…

NLP文本匹配任务Text Matching [无监督训练]:SimCSE、ESimCSE、DiffCSE 项目实践

NLP文本匹配任务Text Matching [无监督训练]&#xff1a;SimCSE、ESimCSE、DiffCSE 项目实践 文本匹配多用于计算两个文本之间的相似度&#xff0c;该示例会基于 ESimCSE 实现一个无监督的文本匹配模型的训练流程。文本匹配多用于计算两段「自然文本」之间的「相似度」。 例如…

TypeScript 关于对【泛型】的定义使用解读

目录 概念导读泛型函数多个泛型参数泛型约束泛型别名泛型接口泛型类总结&#xff1a; 概念导读 泛型&#xff08;Generics&#xff09;是指在定义函数、接口或类的时候&#xff0c;不预先指定具体的类型&#xff0c;而在使用的时候再指定类型的一种特性。使用泛型 可以复用类型…

C++入门

目录 一&#xff1a;关键字 二&#xff1a;命名空间 1.引入 2.命名空间的定义 <1>:命名空间中定义变量/函数/类型 <2>:命名空间可以嵌套 <3>:同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中 3.命名空…

【设计模式】建造者模式

建造者模式&#xff08;Builder Pattern&#xff09;使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。 介绍 …