Redis高并发缓存设计问题与性能优化

Redis高并发缓存设计问题与性能优化

  • 缓存设计典型问题
    • 缓存穿透
    • 缓存失效(击穿)
    • 缓存雪崩
    • 热点缓存key重建优化
    • 缓存与数据库双写不一致
  • 开发规范与性能优化
    • 一、键值设计
      • 1. key名设计
      • 2. value设计
      • big key的危害:
        • 1.导致redis阻塞
        • 2.网络拥塞
        • 3. 过期删除
      • big key的产生:
      • 如何优化big key
        • 1. 拆
        • 2. 如果bigkey不可避免 ,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要 hmget ,而不是hgetall) ,删除也是一样 ,尽量使用优雅的方式来处理。
    • 二、命令使用
      • 1.【推荐】 O(N)命令关注N的数量
      • 2.【推荐】:禁用命令
      • 3.【推荐】合理使用select
      • 4.【推荐】使用批量操作提高效率
      • 5.【建议】 Redis事务功能较弱 ,不建议过多使用 ,可以用lua替代

缓存设计典型问题

缓存穿透

缓存穿透是指查询一个根本不存在的数据 , 缓存层和存储层都不会命中 , 通常出于容错的考虑 , 如果从存储
层查不到数据则不写入缓存层。
缓存穿透将导致不存在的数据每次请求都要到存储层去查询 , 失去了缓存保护后端存储的意义。
造成缓存穿透的基本原因有两个:
第一 , 自身业务代码或者数据出现问题。
第二 , 一些恶意攻击、 爬虫等造成大量空命中。
缓存穿透问题解决方案:
1、缓存空对象

1 String get(String key) {
2 // 从缓存中获取数据
3 String cacheValue = cache.get(key);
4 // 缓存为空
5 if (StringUtils.isBlank(cacheValue)) {
6 // 从存储中获取
7 String storageValue = storage.get(key);
8 cache.set(key, storageValue);
9 // 如果存储数据为空, 需要设置一个过期时间(300秒)
10 if (storageValue == null) {
11 cache.expire(key, 60 * 5);
12 }
13 return storageValue;
14 } else {
15 // 缓存非空
16 return cacheValue;
17 }
18 }

2、布隆过滤器
对于恶意攻击 ,向服务器请求大量不存在的数据造成的缓存穿透 ,还可以用布隆过滤器先做一次过滤 ,对于不 存在的数据布隆过滤器一般都能够过滤掉 ,不让请求再往后端发送。当布隆过滤器说某个值存在时 ,这个值可 能不存在;当它说不存在时 ,那就肯定不存在。


向布隆过滤器询问 key 是否存在时 ,跟 add 一样 ,也会把 hash 的几个位置都算出来 ,看看位数组中这几个 位置是否都为 1 ,只要有一个位为 0 ,那么说明布隆过滤器中这个key 不存在。如果都是 1 ,这并不能说明这 个 key 就一定存在 ,只是极有可能存在 ,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位 数组比较稀疏 ,这个概率就会很大 ,如果这个位数组比较拥挤 ,这个概率就会降低。
这种方法适用于数据命中不高、 数据相对固定、 实时性低(通常是数据集较大) 的应用场景 , 代码维护较为 复杂 , 但是缓存空间占用很少
布隆过滤器就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得 比较均匀。
向布隆过滤器中添加 key 时 ,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度 进行取模运算得到一个位置 ,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就 完成了 add 操作。

可以用redisson实现布隆过滤器 ,引入依赖:

1
2 org.redisson 3 redisson
4 3.6.5
5

示例伪代码:
1 package com.redisson;
2
3 import org.redisson.Redisson;
4 import org.redisson.api.RBloomFilter;
5 import org.redisson.api.RedissonClient;
6 import org.redisson.config.Config;
7
8 public class RedissonBloomFilter {
9
10 public static void main(String[] args) {
11 Config config = new Config();
12 config.useSingleServer().setAddress(“redis://localhost:6379”);
13 //构造Redisson
14 RedissonClient redisson = Redisson.create(config);
15
16 RBloomFilter bloomFilter = redisson.getBloomFilter(“nameList”);
17 //初始化布隆过滤器:预计元素为100000000L,误差率为3%,根据这两个参数会计算出底层的bit数组大小

18 bloomFilter.tryInit(100000000L,0.03);
19 //将zhuge插入到布隆过滤器中
20 bloomFilter.add(“zhuge”);
21
22 //判断下面号码是否在布隆过滤器中
23 System.out.println(bloomFilter.contains(“guojia”));//false
24 System.out.println(bloomFilter.contains(“baiqi”));//false
25 System.out.println(bloomFilter.contains(“zhuge”));//true
26 }
27 }

使用布隆过滤器需要把所有数据提前放入布隆过滤器 ,并且在增加数据时也要往布隆过滤器里放 ,布隆过滤器 缓存过滤伪代码:
1 //初始化布隆过滤器
2 RBloomFilter bloomFilter = redisson.getBloomFilter(“nameList”);
3 //初始化布隆过滤器:预计元素为100000000L,误差率为3%
4 bloomFilter.tryInit(100000000L,0.03);
5
6 //把所有数据存入布隆过滤器
7 void init(){
8 for (String key: keys) {
9 bloomFilter.put(key);
10 }
11 }
12
13 String get(String key) {
14 // 从布隆过滤器这一级缓存判断下key是否存在
15 Boolean exist = bloomFilter.contains(key);
16 if(!exist){
17 return “”;
18 }
19 // 从缓存中获取数据
20 String cacheValue = cache.get(key);
21 // 缓存为空
22 if (StringUtils.isBlank(cacheValue)) {
23 // 从存储中获取
24 String storageValue = storage.get(key);
25 cache.set(key, storageValue);
26 // 如果存储数据为空, 需要设置一个过期时间(300秒)
27 if (storageValue == null) {
28 cache.expire(key, 60 * 5);
29 }
30 return storageValue;
31 } else {
32 // 缓存非空
33 return cacheValue;
34 }
35 }

注意:布隆过滤器不能删除数据 ,如果要删除得重新初始化数据。

缓存失效(击穿)

由于大批量缓存在同一时间失效可能导致大量请求同时穿透缓存直达数据库 ,可能会造成数据库瞬间压力过大 甚至挂掉 ,对于这种情况我们在批量增加缓存时最好将这一批数据的缓存过期时间设置为一个时间段内的不同 时间。
示例伪代码:

1 String get(String key) {
2 // 从缓存中获取数据
3 String cacheValue = cache.get(key);
4 // 缓存为空
5 if (StringUtils.isBlank(cacheValue)) {
6 // 从存储中获取
7 String storageValue = storage.get(key);
8 cache.set(key, storageValue);
9 //设置一个过期时间(300到600之间的一个随机数)
10 int expireTime = new Random().nextInt(300) + 300;
11 if (storageValue == null) {
12 cache.expire(key, expireTime);
13 }
14 return storageValue;
15 } else {
16 // 缓存非空
17 return cacheValue;
18 }
19 }

缓存雪崩

缓存雪崩指的是缓存层支撑不住或宕掉后 , 流量会像奔逃的野牛一样 , 打向后端存储层。
由于缓存层承载着大量请求 , 有效地保护了存储层 , 但是如果缓存层由于某些原因不能提供服务(比如超大并 发过来 ,缓存层支撑不住 ,或者由于缓存设计不好 ,类似大量请求访问bigkey ,导致缓存能支撑的并发急剧下
降) , 于是大量请求都会打到存储层 , 存储层的调用量会暴增 , 造成存储层也会级联宕机的情况。 预防和解决缓存雪崩问题 , 可以从以下三个方面进行着手。
1) 保证缓存层服务高可用性 ,比如使用Redis Sentinel或Redis Cluster。
2) 依赖隔离组件为后端限流熔断并降级。比如使用Sentinel或Hystrix限流降级组件。
比如服务降级 ,我们可以针对不同的数据采取不同的处理方式。当业务应用访问的是非核心数据(例如电商商 品属性 ,用户信息等)时 ,暂时停止从缓存中查询这些数据 ,而是直接返回预定义的默认降级信息、空值或是 错误提示信息;当业务应用访问的是核心数据(例如电商商品库存)时 ,仍然允许查询缓存 ,如果缓存缺失 , 也可以继续通过数据库读取。
3) 提前演练。 在项目上线前 , 演练缓存层宕掉后 , 应用以及后端的负载情况以及可能出现的问题 , 在此基 础上做一些预案设定。

热点缓存key重建优化

开发人员使用“缓存+过期时间”的策略既可以加速数据读写 , 又保证数据的定期更新 , 这种模式基本能够
满足绝大部分需求。 但是有两个问题如果同时出现 , 可能就会对应用造成致命的危害:
当前key是一个热点key(例如一个热门的娱乐新闻) ,并发量非常大。
重建缓存不能在短时间完成 , 可能是一个复杂计算 , 例如复杂的SQL、 多次IO、 多个依赖等。 在缓存失效的瞬间 , 有大量线程来重建缓存 , 造成后端负载加大 , 甚至可能会让应用崩溃。
要解决这个问题主要就是要避免大量线程同时重建缓存。
我们可以利用互斥锁来解决 ,此方法只允许一个线程重建缓存 , 其他线程等待重建缓存的线程执行完 , 重新
从缓存获取数据即可。
示例伪代码:

1 String get(String key) {
2 // 从Redis中获取数据
3 String value = redis.get(key);
4 // 如果value为空, 则开始重构缓存
5 if (value == null) {
6 // 只允许一个线程重建缓存, 使用nx, 并设置过期时间ex
7 String mutexKey = “mutext🔑” + key;
8 if (redis.set(mutexKey, “1”, “ex 180”, “nx”)) {
9 // 从数据源获取数据
10 value = db.get(key);
11 // 回写Redis, 并设置过期时间
12 redis.setex(key, timeout, value);
13 // 删除key_mutex
14 redis.delete(mutexKey);
15 }// 其他线程休息50毫秒后重试
16 else {
17 Thread.sleep(50);
18 get(key);
19 }
20 }
21 return value;
22 }

缓存与数据库双写不一致

在大并发下 ,同时操作数据库与缓存会存在数据不一致性问题
1、双写不一致情况


2、读写并发不一致

解决方案:
1、对于并发几率很小的数据(如个人维度的订单数据、用户数据等) ,这种几乎不用考虑这个问题 ,很少会发生 缓存不一致 ,可以给缓存数据加上过期时间 ,每隔一段时间触发读的主动更新即可。
2、就算并发很高 ,如果业务上能容忍短时间的缓存数据不一致(如商品名称 ,商品分类菜单等) ,缓存加上过期 时间依然可以解决大部分业务对于缓存的要求。
3、如果不能容忍缓存数据不一致 ,可以通过加读写锁保证并发读写或写写的时候按顺序排好队 ,读读的时候 相当于无锁
4、也可以用阿里开源的canal通过监听数据库的bin log日志及时的去修改缓存 ,但是引入了新的中间件 ,增加 了系统的复杂度。


以业务名(或数据库名)为前缀(防止key冲突) ,用冒号分隔 ,比如业务名:表名:id


总结:
以上我们针对的都是读多写少的情况加入缓存提高性能 ,如果写多读多的情况又不能容忍缓存数据不一致 ,那 就没必要加缓存了 ,可以直接操作数据库。当然 ,如果数据库抗不住压力 ,还可以把缓存作为数据读写的主存 储 ,异步将数据同步到数据库 ,数据库只是作为数据的备份。
放入缓存的数据应该是对实时性、一致性要求不是很高的数据。切记不要为了用缓存 ,同时又要保证绝对的一 致性做大量的过度设计和控制 ,增加系统复杂性!

开发规范与性能优化

一、键值设计

1. key名设计

(1)【建议】: 可读性和可管理性

1 trade:order:1

(2)【建议】:简洁性
保证语义的前提下 ,控制key的长度 ,当key较多时, 内存占用也不容忽视 ,例如:

1 user:{uid}:friends:messages:{mid} 简化为 u:{uid}🇫🇷m:{mid}
(3)【强制】:不要包含特殊字符
反例:包含空格、换行、单双引号以及其他转义字符

2. value设计

(1)【强制】:拒绝bigkey(防止网卡流量、慢查询)
在Redis中 ,一个字符串最大512MB ,一个二级数据结构(例如hash、list、set、zset)可以存 储大约40亿个(2 ^32-1)个元素 ,但实际中如果下面两种情况 ,我就会认为它是bigkey。
1. 字符串类型:它的big体现在单个value值很大 ,一般认为超过10KB就是bigkey。 2. 非字符串类型:哈希、列表、集合、有序集合 ,它们的big体现在元素个数太多。
一般来说 ,string类型控制在10KB以内 ,hash、list、set、zset元素个数不要超过5000。 反例:一个包含200万个元素的list。

非字符串的bigkey ,不要使用del删除 ,使用hscan、sscan、zscan方式渐进式删除 ,同时要注 意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期 ,会触发del操作 , 造成阻塞)

big key的危害:

1.导致redis阻塞

2.网络拥塞

bigkey也就意味着每次获取要产生的网络流量较大 ,假设一个bigkey为1MB ,客户端每秒访问 量为1000 ,那么每秒产生1000MB的流量 ,对于普通的千兆网卡(按照字节算是128MB/s)的服务 器来说简直是灭顶之灾 ,而且一般服务器会采用单机多实例的方式来部署 ,也就是说一个bigkey
可能会对其他实例也造成影响 ,其后果不堪设想。

3. 过期删除

有个bigkey ,它安分守己(只执行简单的命令 ,例如hget、lpop、zscore等) ,但它设置了过 期时间 ,当它过期后 ,会被删除 ,如果没有使用Redis 4.0的过期异步删除(lazyfree-lazy-
expire yes) ,就会存在阻塞Redis的可能性。

big key的产生:

一般来说 ,bigkey的产生都是由于程序设计不当 ,或者对于数据规模预料不清楚造成的 ,来看几
个例子:
(1) 社交类:粉丝列表 ,如果某些明星或者大v不精心设计下 ,必是bigkey。
(2) 统计类:例如按天存储某项功能或者网站的用户集合 ,除非没几个人用 ,否则必是bigkey。 (3) 缓存类:将数据从数据库load出来序列化放到Redis里 ,这个方式非常常用 ,但有两个地方 需要注意 ,第一 ,是不是有必要把所有字段都缓存;第二 ,有没有相关关联的数据 ,有的同学为 了图方便把相关数据都存一个key下 ,产生bigkey。

如何优化big key

1. 拆

big list: list1、list2、…listN
big hash:可以讲数据分段存储 ,比如一个大的key ,假设存了1百万的用户数据 ,可以拆分成 200个key ,每个key下面存放5000个用户数据

2. 如果bigkey不可避免 ,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要 hmget ,而不是hgetall) ,删除也是一样 ,尽量使用优雅的方式来处理。

(2)【推荐】:选择适合的数据类型。
例如:实体类型(要合理控制和使用数据结构 ,但也要注意节省内存和性能之间的平衡) 反例:

1 set user:1:name tom
2 set user:1:age 19
3 set user:1:favor football

但要注意控制一次批量操作的元素个数(例如500以内 ,实际也和元素字节数有关)。 注意两者不同:

正例:

1 hmset user:1 name tom age 19 favor football
3.【推荐】:控制key的生命周期 ,redis不是垃圾桶。
建议使用expire设置过期时间(条件允许可以打散过期时间 ,防止集中过期)。

二、命令使用

1.【推荐】 O(N)命令关注N的数量

例如hgetall、l range、smembers、zrange、s inter等并非不能使用 ,但是需要明确N的值。有 遍历的需求可以使用hscan、sscan、zscan代替。

2.【推荐】:禁用命令

禁止线上使用keys、flushall、flushdb等 ,通过redis的rename机制禁掉命令 ,或者使用scan的 方式渐进式处理。

3.【推荐】合理使用select

redis的多数据库较弱 ,使用数字进行区分 ,很多客户端支持较差 ,同时多业务用多数据库实际 还是单线程处理 ,会有干扰。

4.【推荐】使用批量操作提高效率

1 原生命令:例如mget、mset。
2 非原生命令:可以使用pipeline提高效率。

1 1. 原生命令是原子操作,pipeline是非原子操作。
2 2. pipeline可以打包不同的命令,原生命令做不到
3 3. pipeline需要客户端和服务端同时支持。

5.【建议】 Redis事务功能较弱 ,不建议过多使用 ,可以用lua替代

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大白鲨生成Windows木马(仅供参考不可实践)

一、学习方法 一个正确的学习方法往往比学习更为重要 方法一:学习技术的本质性作用 (第一性定律) — 帮助我们解决的问题是什么 — 产生的原因/价值 方法二:在工作中到底如何使用? 方法三:技术是由人…

PyQt6 QTimer计时器控件

锋哥原创的PyQt6视频教程: 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计52条视频,包括:2024版 PyQt6 Python桌面开发 视频教程(无废话版…

AI日报:2024年人工智能对各行业初创企业的影响

欢迎订阅专栏 《AI日报》 获取人工智能邻域最新资讯 文章目录 2024年人工智能对初创企业的影响具体行业医疗金融服务运输与物流等 新趋势 2024年人工智能对初创企业的影响 2023年见证了人工智能在各个行业的快速采用和创新。随着我们步入2024年,人工智能初创公司正…

Linuxapache安装

Apache 介绍 Apache HTTP Server(简称Apache)是Apache软件基金会的一个开放源码的网页服务器,Apache是世界使用排名第一的Web服务器软件。它可以运行在几乎所有广泛使用的计算机平台上,由于其跨平台和安全性被广泛使用&#xff0…

【数据库系统概论】第3章-关系数据库标准语言SQL(2)

文章目录 3.4 数据查询3.4.1 单表查询3.4.2 连接查询3.4.3嵌套查询3.4.4 集合查询3.4.5 基于派生表的查询3.4.6 select 语句的目标列 3.4 数据查询 格式 SQL执行顺序 3.4.1 单表查询 基础查询 select * from student // 不重复 select distinct sname from student // 命名…

动态规划算法练习题

45. 跳跃游戏 II 中等 2K 相关企业 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j < nums[i] i j &…

LTE之物理信道

信道是不同类型的信息&#xff0c;按照不同传输格式、用不同的物理资源承载的信息通道。根据信息类型的不同、处理过程的不同可将信道分为多种类型。 重点介绍LTE的逻辑信道、传输信道、物理信道等常见的信道类型&#xff0c;并和3G相应的信道类型作了比较&#xff0c;通过比较…

NNDL 作业12-优化算法2D可视化 [HBU]

老师作业原博客地址&#xff1a;【23-24 秋学期】NNDL 作业12 优化算法2D可视化-CSDN博客 目录 简要介绍图中的优化算法&#xff0c;编程实现并2D可视化 1. 被优化函数 ​编辑 深度学习中的优化算法总结 - ZingpLiu - 博客园 (cnblogs.com) SGD: Adagrad: RMSprop: Mom…

基于ERC20代币协议实现的去中心化应用平台

文章目录 内容简介设计逻辑ERC20TokenLoanPlatform 合约事件结构体状态变量函数 Remix 运行实现部署相关智能合约存款和取款贷款和还款 源码地址 内容简介 使用 solidity 实现的基于 ERC20 代币协议的借贷款去中心化应用平台(极简版)。实现存款、取款、贷款、还款以及利息计算的…

宜春万申智能装备携粉体自动化产线解决方案盛装亮相2024济南生物发酵展

宜春万申智能装备股份有限公司受邀盛装亮相2024第12届济南国际生物发酵展 展位号&#xff1a;1号馆A16-2展位 2024第12届国际生物发酵产品与技术装备展览会&#xff08;济南&#xff09;于3月5-7日在山东国际会展中心盛大召开&#xff0c;全方面展示&#xff1a;生物发酵、生…

分布式锁功效初探——以电商问题为例

文章目录 电商库存问题单机处理-Sychronized多机器处理-分布式锁入门级别&#xff0c;用redis实现&#xff0c;setnx问题1&#xff1a;逻辑可能异常&#xff0c;造成死锁问题2&#xff1a;机器宕机问题3&#xff1a;锁一直失效&#xff0c;乱套锁续命 redisson分布式丢锁问题主…

数独 -- 合法数独与完全数独

一、数独的介绍 从2004年底开始&#xff0c;数独游戏在英国变得非常流行。数独(Sudoku)是一个日语单词意思是数字位置之类的单词(或短语)。谜题的理念非常简单;面对一个9 9的网格&#xff0c;被分成9个3 3的块: 在其中的一些盒子里&#xff0c;设置者放一些数字1-9:求解者的目…

前端未死,顺势而生

随着人工智能和低代码的崛起&#xff0c;“前端已死”的声音逐渐兴起。前端已死&#xff1f;尊嘟假嘟&#xff1f;快来发表你的看法吧&#xff01; 一、“前端已死”因何而来&#xff1f; 在开始讨论之前&#xff0c;首先要明确什么是“前端”。 所谓前端&#xff0c;主要涉及…

vue使用ElementUI搭建精美页面入门

ElementUI简直是css学得不好的同学的福音 ElementUI官网&#xff1a; Element - The worlds most popular Vue UI framework 安装 在vue文件下&#xff0c;用这个命令去安装Element UI。 npm i element-ui -S step1\先切换到vue的目录下去&#xff0c;注意这里面的WARN不是…

每日一题:LCR 095.最长公共子序列(DP)

题目描述&#xff1a; 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些…

R语言基础 | 安徽某高校《统计建模与R软件》期末复习

第一节 数字、字符与向量 1.1 向量的赋值 c<-(1,2,3,4,5) 1.2 向量的运算 对于向量&#xff0c;我们可以直接对其作加&#xff08;&#xff09;&#xff0c;减&#xff08;-&#xff09;&#xff0c;乘&#xff08;*&#xff09;&#xff0c;除&#xff08;/&#xff09…

使用Python实现发送Email电子邮件【第19篇—python发邮件】

文章目录 &#x1f47d;使用Python实现发送Email电子邮件&#x1f3b6;实现原理&#x1f3c3;Python实现发送Email电子邮件-基础版&#x1f46b;实现源码&#x1f646;源码解析 &#x1f487;Python实现发送Email电子邮件-完善版&#x1f46b;实现源码&#x1f646;源码解析&am…

随机无限采集JK妹妹高清壁纸下载HTML网页源码

源码介绍 美图网站千千万&#xff0c;美图自己说了算&#xff01;本源码由宋佳乐博客 开发&#xff0c;首页图片做了浏览器窗口自适应&#xff0c;最大化占满PC浏览器和移动浏览器的窗口&#xff0c;并且防止出现滚动条。 功能介绍 首页图片设置了4个点击功能区&#xff0c;…

【数据结构入门精讲 | 第十一篇】一文讲清树

在上一篇中我们进行了排序算法的专项练习&#xff0c;现在让我们开始树的知识点讲解。 目录 树二叉搜索树二叉排序树哈夫曼树折半查找判定树kruskal算法、prim算法、最小生成树完全二叉树 树 树是一种非线性的数据结构&#xff0c;也是一种表示一对多关系的数据结构&#xff0…

Flink CDC 1.0至3.0回忆录

Flink CDC 1.0至3.0回忆录 一、引言二、CDC概述三、Flink CDC 1.0&#xff1a;扬帆起航3.1 架构设计3.2 版本痛点 四、Flink CDC 2.0&#xff1a;成长突破4.1 DBlog 无锁算法4.2 FLIP-27 架构实现4.3 整体流程 五、Flink CDC 3.0&#xff1a;应运而生六、Flink CDC 的影响和价值…