-
// 已经 hello 3 开启 RESP3 协议,不然无法收到失效消息,下同
-
client tracking on bcast
-
+OK
-
// 此时设置 key 为 a 的键值,收到如下消息。
-
>2
-
$10
-
invalidate
-
*1
-
$1
-
a
如果你不想所有的键值的失效消息都收到,则可以限制 key 的前缀,如下命令则表示只关注前缀为 test 的键值的消息。一般来说,业务的缓存 key 都是根据业务拥有统一的前缀,所以这一特性十分方便。
client tracking on bcast prefix test
与普通模式必须获取一次键的规则不同,广播模式下,只要键被修改或删除,符合规则的客户端都会收到失效消息,而且是可以多次获取的。
与普通模式相比,虽然少存储了一些数据,但是由于需要对前缀规则进行匹配,会消耗一定的 CPU 资源,所以注意别使用过长的前缀。
转发模式
上述操作时客户端都需要先开启 RESP3,Redis 为了兼容 RESP2 协议提供了转发(Redirect)模式,不再使用 RESP3 原生支持 PUSH 消息,而是将消息通过 Pub/Sub 通知给另外一个客户端,具体流程如下图所示:
这里需要两个 telnet,其中一个 telnet 需要订阅 redis:invalidate 信道。然后另一个 telnet 开启 Redirect 模式,并制定将失效消息通过订阅信道发送给第一个 telnet。
-
# telent B
-
client id
-
:368
-
subscribe _redis_:invalidate
-
# telnet A,开启 track 并指定转发给 B
-
client tracking on bcast redirect 368
-
# telent B 此时有键值被修改,收到 __redis__:invalidate 信道的消息
-
message
-
$20
-
__redis__:invalidate
-
*1
-
$1
-
a
你会发现,转发模式和文章开始提到的多级缓存中的更新机制很类似了,只不过那个方案中是业务系统修改完 key 后发送消息通知,而这里是 Redis 服务端代替业务系统发送消息通知。
OPTIN和OPTOUT选项
使用 OPTIN 可以选择性的开启 tracking。只有你发送 client caching yes (Redis 文档中是 CACHING 命令,但是实验时发现无效)之后的下一条的只读命令的 key 才会 tracking,否则其他的只读命令的 key 不会被 tracking。
-
client tracking on optin
-
client caching yes
-
get a
-
get b
-
// 此时修改 a 和 b 的值,发现只收到 a 的失效消息
-
>2
-
$10
-
invalidate
-
*1
-
$1
-
a
而 OPTOUT 参数与之相反,你可以有选择的退出 tracking。发送 client caching off 之后的下一条只读命令的 key 不会被 tracking,其他只读命令都会被 tracking。
OPTIN 和 OPTOUT 是针对非 BCAST 模式,也就是只有发送了某个 key 的只读命令后,才会追踪相应的 key。而 BCAST 模式是无论你是否发送某个 key 的只读命令,只有 Redis 修改了 key,都会发送相应的 key 的失效消息(前缀匹配的)。
NOLOOP选项
默认情况下,失效消息会发送给所有需要的 Redis 客户端,但是有些情况下触发失效消息也就是更新 key 的客户端不需要收到该消息。
设置 NOLOOP,可以避免这种情况,更新 Key 的客户端将不再收到消息,该选项在普通模式和广播模式下都适用。
trackingtablemax_keys
最大 tracking 上限 trackingtablemax_keys。
由上文可以知道,普通模式下需要存储大量的被 tracking 的 key 和客户端信息(具体存储的数据下文中会讲解),所以当 10k 客户端使用该模式处理百万个键时,会消耗大量的内存空间,所以 Redis 引入了 trackingtablemax_keys 配置,默认为无,不限制。
当有一个新的键被 tracking 时,如果当前 tracking 的 key 的数量大于 trackingtablemax_keys,则会随机删除之前 tracking 的 key,并且向对应的客户端发送失效消息。
原理和源码实现普通模式原理
我们也先讲解普通模式的原理,Redis 服务端使用 TrackingTable 存储普通模式的客户端数据,它的数据类型是基数树(radix tree)。
基数树是针对稀疏的长整型数据查找的多叉搜索树,能快速且节省空间的完映射,一般用于解决 Hash冲突和 Hash表大小的设计问题,Linux 的内存管理就使用了它。
Redis 用它存储键的指针和客户端 ID 的映射关系。因为键对象的指针就是内存地址,也就是长整型数据。客户端缓存的相关操作就是对该数据的增删改查:
-
当开启 track 功能的客户端获取某一个键值时,Redis 会调用 enableTracking 方法使用基数树记录下该 key 和 clientId 的映射关系。
-
当某一个 key 被修改或删除时,Redis 会调用 trackingInvalidateKey 方法根据 key 从 TrackingTable 中查找所有对应的客户端ID,然后调用 sendTrackingMessage 方法发送失效消息给这些客户端(会检查 CLIENT_TRACKING 相关标志位是否开启和是否开启了 NOLOOP)。
-
发送完失效消息后,根据键的指针值将映射关系从 TrackingTable中删除。
-
客户端关闭 track 功能后,因为删除需要进行大量操作,所以 Redis 使用懒删除方式,只是将该客户端的 CLIENT_TRACKING 相关标志位删除掉。
广播模式原理
广播模式与普通模式类似,Redis 同样使用 PrefixTable 存储广播模式下的客户端数据,它存储前缀字符串指针和(需要通知的key和客户端ID)的映射关系。它和广播模式最大的区别就是真正发送失效消息的时机不同:
-
当客户端开启广播模式时,会在 PrefixTable的前缀对应的客户端列表中加入该客户端ID。
-
当某一个 key 被修改或删除时,Redis 会调用 trackingInvalidateKey 方法, trackingInvalidateKey 方法中如果发现 PrefixTable 不为空,则调用 trackingRememberKeyToBroadcast 依次遍历所有前缀,如果key 符合前缀规则,则记录到 PrefixTable 对应的位置。
-
在 Redis 的事件处理周期函数 beforeSleep 函数里会调用 trackingBroadcastInvalidationMessages 函数来真正发送消息。
处理最大tracking上限
Redis 会在每次执行过命令后(processCommand方法)调用 trackingLimitUsedSlots 来判断是否需要进行清理:
-
判断 TrackingTable 中键的数量是否大于 trackingtablemax_keys;
-
在一定时间段内(不能太长,阻塞主流程),随机从 TrackingTable 中选出一个键删除,直到数量小于或者时间用完为止。
具体源码
关于源码,在 tracking.c 文件下,我们这里只看一下最为关键的 trackingInvalidateKey 函数和 sendTrackingMessage 函数,理解了这两个函数,广播模式和处理最大 tracking 上限等相关函数都与之类似。
-
void trackingInvalidateKey(client *c, robj *keyobj) {
-
if (TrackingTable == NULL) return;
-
sds sdskey = keyobj->ptr;
-
// 省略,如果广播模式的记录基数树不为空,则先处理广播模式
-
// 1 根据键的指针去 TrackingTable 查找
-
rax *ids = raxFind(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey));
-
if (ids == raxNotFound) return;
-
// 2 使用迭代器遍历
-
raxIterator ri;raxStart(&ri,ids);raxSeek(&ri,"^",NULL,0);
-
while(raxNext(&ri)) {
-
// 3 根据 clientId 查找 client 实例
-
client *target = lookupClientByID(id);
-
// 4 如果未开启 track 或者是广播模式则跳过。
-
if (target == NULL ||
-
!(target->flags & CLIENT_TRACKING)||
-
target->flags & CLIENT_TRACKING_BCAST)
-
{ continue; }
-
// 5 如果开启了 NOLOOP 并且是导致key发生变化的client则跳过。
-
if (target->flags & CLIENT_TRACKING_NOLOOP &&
-
target == c)
-
{ continue; }
-
// 6 发送失效消息
-
sendTrackingMessage(target,sdskey,sdslen(sdskey),0);
-
}
-
// 7 减少数据统计,根据sdskey删除对应的记录
-
TrackingTableTotalItems -= raxSize(ids);
-
raxFree(ids);
-
raxRemove(TrackingTable,(unsigned char*)sdskey,sdslen(sdskey),NULL);
-
}
源码如上所示,trackingInvalidateKey 方法主要做了 7 件事情:
-
根据键的指针去 TrackingTable 查找客户端ID列表;
-
使用迭代器遍历列表;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
写在最后
学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!
最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。
Mybatis面试专题
MySQL面试专题
并发编程面试专题
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
730)]
写在最后
学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!
最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。
[外链图片转存中…(img-vxhYSKDk-1712805064731)]
Mybatis面试专题
[外链图片转存中…(img-YU1n9vPG-1712805064731)]
MySQL面试专题
[外链图片转存中…(img-qLjM7pjy-1712805064731)]
并发编程面试专题
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-9upig74W-1712805064731)]