营口网站制作,wordpress 无觅,项目进度管理软件app,做网站可以用什么数据库来源 | 程序员历小冰责编 | Carol封图 | CSDN 付费下载于视觉中国近日 Redis 6.0.0 GA 版本发布#xff0c;这是 Redis 历史上最大的一次版本更新#xff0c;包括了客户端缓存 (Client side caching)、ACL、Threaded I/O 和 Redis Cluster Proxy 等诸多更新。我们今天就依次聊… 来源 | 程序员历小冰责编 | 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 作为二级缓存先去 Guava Cache 中查询数据如果命中则直接返回。Guava Cache 中未命中则再去 Redis 中查询如果命中则返回数据并在 Guava Cache 中设置此数据。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 设置缓存值 test111使用 telnet 连接上 Redis然后发送 hello 3 开启 RESP3 协议。[rootVM_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、转发模式上述操作时客户端都需要先开启 RESP3Redis 为了兼容 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 发送键等信息和上边45操作连在一起的。addReplyProto(c,keyname,keylen);
}
欢迎点赞评论后续还会学习其他 Redis 6.0.0 的其他亮点功能欢迎继续关注~ 添加小助手vipcsdn回复关键词遇见OFFER 快速入群推荐阅读手把手教你配置VS Code 远程开发工具工作效率提升N倍用大白话彻底搞懂 HBase RowKey 详细设计后端程序员必备书写高质量SQL的30条建议Go 远超 Python机器学习人才极度稀缺全球 16,655 位程序员告诉你这些真相任正非谈“狼文化”华为没有 996更没有 007区块链必读“上链”哲学“胖链下”与“瘦链上”在商业中如何与人工智能建立共生关系真香朕在看了