Redis 6.0 的客户端缓存是怎么肥事?一文带你了解!

来源 | 程序员历小冰

责编 | Carol

封图 | CSDN 付费下载于视觉中国

近日 Redis 6.0.0 GA 版本发布,这是 Redis 历史上最大的一次版本更新,包括了客户端缓存 (Client side caching)、ACL、Threaded I/O 和 Redis Cluster Proxy 等诸多更新。

我们今天就依次聊一下客户端缓存的必要性、具体使用、原理分析和实现。

为什么需要客户端缓存?

我们都知道,使用 Redis 进行数据的缓存的主要目的是减少对 MySQL 等数据库的访问,提供更快的访问速度,毕竟 《Redis in Action》中提到的, Redis 的性能大致是普通关系型数据库的 10 ~ 100 倍。

所以,如下图所示,Redis 用来存储热点数据,Redis 未命中,再去访问数据库,这样可以应付大多数情况下的性能要求。

但是,Redis 也有其性能上限,并且访问 Redis 必然有一定的网络 I/O 以及序列化反序列化损耗。所以,往往会引入进程缓存,将最热的数据存储在本地,进一步加快访问速度。

如上图所示(示意图,细节不必过度在意,下同),Guava Cache 等进程缓存作为一级缓存,Redis 作为二级缓存:

  1. 先去 Guava Cache 中查询数据,如果命中则直接返回。

  2. Guava Cache 中未命中,则再去 Redis 中查询,如果命中则返回数据,并在 Guava Cache 中设置此数据。

  3. Redis 也未命中的话,只有去 MySQL 中查询,然后依次将数据设置到 Redis 和 Guava Cache 中。

只使用 Redis 分布式缓存时,遇到数据更新时,应用程序更新完 MySQL 中的数据,可以直接将 Redis 中对应缓存失效掉,保持数据的一致性。

而进程内缓存的数据一致性比分布式的缓存面临更大的挑战。数据更新的时候,如何通知其他进程也更新自己的缓存呢?

如果按照分布式缓存的思路,我们可以设置极短的缓存失效时间,这样不必实现复杂的通知机制。

但是不同进程内的数据依然会面临不一致的问题,并且不同进程缓存失效时间不统一,同一个请求到了不同的进程,可能出现反复幻读的情况。

Ben 在 RedisConf18 给出了一个方案(视频和 PPT 链接在文末),通过 Redis 的 Pub/Sub,可以通知其他进程缓存对此缓存进行删除。如果 Redis 挂了或者订阅机制不靠谱,依靠超时设定,依然可以做兜底处理。

Antirez(Redis 的作者) 也正是听取 Ben 这个方案后,才决定在 Redis Server 支持客户端缓存的,因为在有服务端参与的情况下可以更好的处理上述这些问题。

功能介绍和演示

下面使用 Docker 安装 Redis 6.0.1,然后使用 telnet 来简单演示一下 Redis 6.0 的客户端缓存功能。所有相关的功能如下图所示,分别是使用RESP3 协议版本的普通模式和广播模式,以及使用 RESP2 协议版本的转发模式。我们先来看普通模式。

1、普通模式

先使用 redis-cli 设置缓存值 test=111,使用 telnet 连接上 Redis,然后发送 hello 3 开启 RESP3 协议。

[root@VM_0_3_centos ~]# telnet 127.0.0.16379
Trying127.0.0.1...
Connected to 127.0.0.1.
Escape character is'^]'.
hello 3
// telnet 输出结果格式化标准化后如下,否则换行太多并且是 RESP3 格式,不需要了解格式。
> HELLO 3
1# "server" => "redis"
2# "version" => "6.0.1"
3# "proto" => (integer) 3
4# "id" => (integer) 10
5# "mode" => "standalone"
6# "role" => "master"
7# "modules" => (empty array)

这里需要注意,Redis 服务端只会 track 客户端在一个连接生命周期内的获取的只读命令的 key值Redis 客户端默认不开启 track 模式,需要使用命令开启,然后必须要先获取一次 test 的值,这样 Redis 服务器才会记录它。

client tracking on
+OK
get test
$3
111

当键被修改,或者因为失效时间(expire time)和内存上限 maxmemory 策略被驱除时,Redis 服务端会通知这些客户端。我们这里简单地更新 test 的值,telnet 则会收到如下通知

>2// RESP3 中的 PUSH 类型,标志为 > 符号
$10
invalidate
*1
$4
test

如果你再一次更新 test 值,这次 telnet 就不会再收到失效(invalidate)消息。除非 telnet 再进行一次 get 操作,重新 tracking 对应的键值。

也就是说 Redis 服务端记录的客户端 track 信息只生效一次,发送过失效消息后就会删除,只有下次客户端再次执行只读命令被 track,才会进行下一次消息通知

取消 tracking 的命令如下所示。

client tracking off
+OK

2、广播模式

Redis 还提供了一种广播模式(BCAST),它是另外一种客户端缓存的实现方式。这种方式下 Redis 服务端不再消耗过多内存存储信息,而是发送更多的失效消息给客户端

这是服务端存储过多数据,消耗内存和客户端收到过多消息,消耗网络带宽之间的权衡(tradeoff)。

// 已经 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 资源,所以注意别使用过长的前缀

3、转发模式

上述操作时客户端都需要先开启 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 服务端代替业务系统发送消息通知。

4、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 的失效消息(前缀匹配的)。

5、NOLOOP 选项

默认情况下,失效消息会发送给所有需要的 Redis 客户端,但是有些情况下触发失效消息也就是更新 key 的客户端不需要收到该消息。

设置 NOLOOP,可以避免这种情况,更新 Key 的客户端将不再收到消息,该选项在普通模式和广播模式下都适用。

6、最大 tracking 上限 trackingtablemax_keys

由上文可以知道,普通模式下需要存储大量的被 tracking 的 key 和客户端信息(具体存储的数据下文中会讲解),所以当 10k 客户端使用该模式处理百万个键时,会消耗大量的内存空间,所以 Redis 引入了 trackingtablemax_keys 配置,默认为无,不限制。

当有一个新的键被 tracking 时,如果当前 tracking 的 key 的数量大于 trackingtablemax_keys,则会随机删除之前 tracking 的 key,并且向对应的客户端发送失效消息。

原理和源码实现

1、普通模式原理

我们也先讲解普通模式的原理,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 相关标志位删除掉。

2、广播模式原理

广播模式与普通模式类似,Redis 同样使用 PrefixTable 存储广播模式下的客户端数据,它存储前缀字符串指针和(需要通知的key和客户端ID)的映射关系。它和广播模式最大的区别就是真正发送失效消息的时机不同:

  • 当客户端开启广播模式时,会在 PrefixTable的前缀对应的客户端列表中加入该客户端ID。

  • 当某一个 key 被修改或删除时,Redis 会调用 trackingInvalidateKey 方法, trackingInvalidateKey 方法中如果发现 PrefixTable 不为空,则调用 trackingRememberKeyToBroadcast 依次遍历所有前缀,如果key 符合前缀规则,则记录到 PrefixTable 对应的位置。

  • 在 Redis 的事件处理周期函数 beforeSleep 函数里会调用 trackingBroadcastInvalidationMessages 函数来真正发送消息。

3、处理最大 tracking 上限

Redis 会在每次执行过命令后(processCommand方法)调用 trackingLimitUsedSlots 来判断是否需要进行清理:

  • 判断 TrackingTable 中键的数量是否大于 trackingtablemax_keys;

  • 在一定时间段内(不能太长,阻塞主流程),随机从 TrackingTable 中选出一个键删除,直到数量小于或者时间用完为止。

4、具体源码

关于源码,在 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,(unsignedchar*)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,(unsignedchar*)sdskey,sdslen(sdskey),NULL);
}

源码如上所示,trackingInvalidateKey 方法主要做了 7 件事情:

  • 根据键的指针去 TrackingTable 查找客户端ID列表;

  • 使用迭代器遍历列表;

  • 根据 clientId 查找 client 实例;

  • 如果 client 实例未开启 track 或者是广播模式则跳过;

  • 如果 client 实例开启了 NOLOOP 并且是导致key发生变化的client则跳过;

  • 调用 sendTrackingMessage 方法发送失效消息;

  • 减少数据统计,根据sdskey删除对应的记录

下面来看真正发送消息的 sendTrackingMessage 函数,它主要做了6件事:

  • 如果 clienttrackingredirection 不为空,则开启了转发模式;

  • 找到转发的客户端实例;

  • 如果转发客户端关闭了,则必须通知原客户端;

  • 如果是客户端使用 RESP3 则发 PUSH 消息;

  • 如果是转发模式,往 TrackingChannelName 也就是 _redis_:invalidate 信道中发送失效消息的头部信息;

  • 发送键等信息。

void sendTrackingMessage(client *c, char*keyname, size_t keylen, int proto) {
int using_redirection = 0;
// 1 如果 client_tracking_redirection 不为空,则开启了转发模式
if(c->client_tracking_redirection) {
// 2 找到转发的客户端实例client *redir = lookupClientByID(c->client_tracking_redirection);
if(!redir) {
// 3 如果转发客户端关闭了,则必须通知原客户端
....
return;
}c = redir;using_redirection = 1;
}
if(c->resp > 2) {
// 4 如果是 RESP3 则发PUSHaddReplyPushLen(c,2);addReplyBulkCBuffer(c,"invalidate",10);
} elseif(using_redirection && c->flags & CLIENT_PUBSUB) {
// 5 转发模式,往 TrackingChannelName 信道中发送消息addReplyPubsubMessage(c,TrackingChannelName,NULL);
} else{
return;
}
// 6 发送键等信息,和上边4,5操作连在一起的。addReplyProto(c,keyname,keylen);
}

欢迎点赞评论,后续还会学习其他 Redis 6.0.0 的其他亮点功能,欢迎继续关注~

 

添加小助手:vipcsdn

回复关键词:遇见OFFER快速入群!

推荐阅读

  • 手把手教你配置VS Code 远程开发工具,工作效率提升N倍

  • 用大白话彻底搞懂 HBase RowKey 详细设计

  • 后端程序员必备:书写高质量SQL的30条建议

  • Go 远超 Python,机器学习人才极度稀缺,全球 16,655 位程序员告诉你这些真相!

  • 任正非谈“狼文化”:华为没有 996,更没有 007

  • 区块链必读“上链”哲学:“胖链下”与“瘦链上”

  • 在商业中,如何与人工智能建立共生关系?

真香,朕在看了!

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

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

相关文章

AI时代,你的职业会是?99%的人都无法直面!

在我10岁的时候,算命先生曾对说我30岁时我会每天与八阿哥玩在一起。 当时懵懂的我一脸茫然,想着谁是我的八阿哥,却在30岁的这一年意识到自己确实日以继夜的与八阿哥在一起。 曾经,我们也担心自己未来的工作岗位是否会被人工智能给…

Java 12 新特性概述

Java 12 已如期于 3 月 19 日正式发布,此次更新是 Java 11 这一长期支持版本发布之后的一次常规更新,截至目前,Java 半年为发布周期,并且不会跳票承诺的发布模式,已经成功运行一年多了。通过这样的方式,Jav…

5G +边缘计算,优酷如何做云渲染?

作者| 阿里文娱高级技术专家 伊耆责编 | 屠敏头图 | CSDN 下载自东方 IC出品 | CSDN(ID:CSDNnews)当5G来了,视频还是平面的影像吗,只能静静观看吗?一定不是!现在,你可以像玩游戏一样…

不做会死!互联网时代的企业必定都要实现中台

AI 前线导读: 自 2018 年底以来,伴随着阿里、腾讯、百度、京东等一众互联网巨头的大规模组织架构调整,“中台”的热度陡然攀升。一时间,各大互联网公司纷纷开始跟随建设中台。中台的概念是被阿里带火的,2015 年&#x…

包机制。。

包机制 为了更好的组织类,java提供了包机制,用于区别类的命名空间//本质就是文件夹 包语法格式 package pkj[.pkg[.pkg3...]];一般利用公司域名倒置作为包名:com.boss.xxx 导入包语法 import package1[.package2...].(classname|*);尽量不要…

ETL异构数据源Datax_使用querySql_08

使用说明 当用户配置了这一项之后,DataX系统就会忽略table,column 这些配置型,直接使用这个配置项的内容对数据进行筛选,例 如需要进行多表join后同步数据,使用select a,b from table_a join table_b on table_a.id t…

我被“非结构化数据包围了”,请求支援!

阿里妹导读:非结构化数据的内容占据了当前数据海洋的80%。换句话来说,就是我们都被“非结构化数据”包围了。由于非结构化数据的信息量和信息的重要程度很难被界定,因此对非结构化数据的使用成为了难点。如果说结构化数据用详实的方式记录了企…

82年 AI程序员征婚启示火了!年薪百万,女生神回复

最近在某社区,一则程序员征婚启示火了!很多女生在评论区表示“全中”,想交流看看。然后评论区就炸了,有人恶意说yp,有人说看中了楼主的钱。笔者一翻,发现楼主果然无意中透露了百万年薪收入,虽然…

AWS 专家教你使用 Spring Boot 和 DJL ,轻松搭建企业级机器学习微服务!

作者 | Qing Lan,Mikhail Shapirov责编 | Carol封图 | CSDN 下载自视觉中国出品 | CSDN云计算(ID:CSDNcloud)许多AWS云服务的用户,无论是初创企业还是大公司,都在逐步将机器学习 (ML) 和深度学习 (DL) 任务…

【从入门到放弃-ZooKeeper】ZooKeeper入门

前言 ZooKeeper是一个分布式服务协调框架,可以用来维护分布式配置信息、服务注册中心、实现分布式锁等。在Hbase、Hadoop、kafka等项目中都有广泛的应用。随着分布式、微服务的普及,ZooKeeper已经成为我们日常开发工作中无法绕过的一环,本文…

ln: failed to create symbolic link ‘/usr/bin/mysql’: File exists

问题描述: ln -s /usr/local/mysql/bin/mysql /usr/bin 在centos7进行软链接设置的时候,出现了这么问题:问题就是说这个文件已存在, 解决方法:覆盖之前的 ln -sf /usr/local/mysql/bin/mysql /usr/bin

读透《阿里巴巴数据中台实践》,其到底有什么高明之处?

最近阿里巴巴分享了《阿里巴巴数据中台实践》这个PPT(自行搜索原始文章),对于数据中台的始作俑者,还是要怀着巨大的敬意去学习的,因此仔细的研读了,希望能发现一些不一样的东西。 读这些专业的PPT&#xf…

如果你也想做实时数仓…

数据仓库也是公司数据发展到一定规模后必然会提供的一种基础服务,数据仓库的建设也是“数据智能”中必不可少的一环。本文将从数据仓库的简介、经历了怎样的发展、如何建设、架构演变、应用案例以及实时数仓与离线数仓的对比六个方面全面分享关于数仓的详细内容。 …

华为云战略投入政企市场,发布华为云Stack

2020年5月15日,华为云发布政企战略,并宣布华为云Stack系列新品正式上市。华为云Stack是位于政企客户本地数据中心的云基础设施,能为政企客户提供在云上和本地部署体验一致的云服务。随着政企智能升级进入深水区,华为云将战略投入政…

如何在 Apache Flink 中使用 Python API?

本文根据 Apache Flink 系列直播课程整理而成,由 Apache Flink PMC,阿里巴巴高级技术专家 孙金城 分享。重点为大家介绍 Flink Python API 的现状及未来规划,主要内容包括:Apache Flink Python API 的前世今生和未来发展&#xff…

阿里云HBase Ganos全新升级,推空间、时空、遥感一体化基础云服务

1、HBase Ganos是什么 Ganos是阿里云时空PaaS服务的自研核心引擎。Ganos已作为云数据库时空引擎与数据库平台融合,建立了以自研云原生数据库POALRDB为基础,联合NoSQL大数据平台(Ali-HBASE和X-Pack Spark)的完整时空地理信息云化管…

看完这篇操作系统,和面试官扯皮就没问题了!

作者 | Cxuan责编 | Carol来源 | 程序员 cxuan封图 | CSDN 付费下载于视觉中国1、解释一下什么是操作系统操作系统是运行在计算机上最重要的一种软件,它管理计算机的资源和进程以及所有的硬件和软件。它为计算机硬件和软件提供了一种中间层。通常情况下,…

Linux centos7 安装 MySQL5.7.x

文章目录一、下载安装2. wget 下载方式3. 安装4. 初始化数据库二、修改密码2.1. 修改密码2.2. 修改密码2.3. 允许远程访问2.4. 关闭防火墙2.4. 建立mysql软连接一、下载安装 下载地址:https://dev.mysql.com/downloads/mysql/5.7.html#downloads 下载地址&#xff…

顺序结构,选择结构,反编译

顺序结构 Java基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句的执行顺序结构是最简单的算法结构任何一个算法都离不开的一种基本算法结构 package com.boss.struct;public class ShunXuDemo {public static void main(String[] args) {Sys…

助力共享经济,芝麻信用背后的技术

近期,CCTV9播放了自制的系列纪录片《大数据时代》,该片是国内首部大数据产业题材纪录片,节目细致而生动地讲述了大数据技术在政府治理、民生服务、数据安全、工业转型、未来生活等方面给我们带来的改变和影响。在第四集中,讲述了芝…