Redis 的客户端缓存(Client-Side Caching)是一项重要特性,允许客户端在本地缓存 Redis 数据,从而减少与 Redis 服务器的通信频率,提高应用的响应速度和可扩展性。Redis 客户端缓存的实现主要依赖于以下几个核心组件和机制:
- 订阅机制:客户端通过订阅特定的键空间事件,获取键的变更通知。
- 通知机制:Redis 服务器在键发生变更时,通过发布/订阅(Pub/Sub)机制将变更通知推送给客户端。
- 缓存一致性:确保客户端缓存与 Redis 服务器的数据一致性。
核心概念和数据结构
1. 客户端缓存模式
Redis 提供了两种客户端缓存模式:
- 主动推模式(Tracking Mode):服务器主动向客户端推送键的变更通知。
- 被动拉模式(Polling Mode):客户端定期从服务器拉取键的变更信息。
2. 客户端状态
每个客户端的状态通过 client 结构体维护,其中包含缓存相关的信息:
typedef struct client {
// ...
uint64_t client_tracking_redirection; /* Redirected ID for tracking invalidation messages */
uint64_t client_tracking_locks; /* Number of tracking locks */
uint8_t client_tracking_prefixes; /* Number of tracking prefixes */
// ...
} client;
启用客户端缓存
客户端缓存可以通过 CLIENT CACHING 命令启用和配置。
redis> CLIENT CACHING yes
OK
订阅键空间事件
通过订阅键空间事件,客户端能够接收到指定键的变更通知。订阅通过 PSUBSCRIBE 命令实现:
redis> PSUBSCRIBE "__keyspace@0__:mykey"
通知机制
Redis 服务器通过发布/订阅机制将变更通知推送给客户端。这通过 trackingInvalidateKey 和相关函数实现。
1. 启用键空间通知
首先,需要在 Redis 配置文件中启用键空间通知:
notify-keyspace-events Ex
2. 处理键变更事件
当键发生变更时,Redis 会调用 trackingInvalidateKey 函数来处理变更事件。
void trackingInvalidateKey(client *c, robj *keyobj) {
if (!keyobj) return;
sds sdskey = keyobj->ptr;
size_t keylen = sdslen(sdskey);
// 查找所有订阅该键的客户端并发送无效化消息
listNode *ln;
listIter li;
listRewind(server.tracking_clients, &li);
while ((ln = listNext(&li)) != NULL) {
client *target = listNodeValue(ln);
if (target == c) continue;
// 发送无效化消息
addReplyArrayLen(target, 2);
addReplyBulkCString(target, "invalidate");
addReplyBulk(target, keyobj);
}
}
缓存一致性管理
为了确保客户端缓存的一致性,Redis 通过以下机制管理缓存:
1. 追踪表
Redis 维护一个追踪表,用于记录每个客户端订阅的键。
typedef struct trackingTableEntry {
sds key;
client *c;
} trackingTableEntry;
dict *tracking_table;
2. 键变更通知
当键发生变更时,Redis 会遍历追踪表,查找订阅该键的客户端,并发送变更通知。
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
// 构建订阅频道名称
sds channel = sdsnewlen("__keyspace@", 11);
channel = sdscatprintf(channel, "%d__", dbid);
channel = sdscatsds(channel, key);
// 发送通知
list *clients = dictFetchValue(tracking_table, channel);
if (clients != NULL) {
listIter li;
listNode *ln;
listRewind(clients, &li);
while ((ln = listNext(&li)) != NULL) {
client *c = listNodeValue(ln);
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) continue;
addReplyArrayLen(c, 3);
addReplyBulkCString(c, "message");
addReplyBulk(c, channel);
addReplyBulkCString(c, event);
}
}
sdsfree(channel);
}
代码示例
以下是一个简单的代码示例,展示了如何在客户端启用缓存并处理键变更通知:
#include <stdio.h>
#include <stdlib.h>
#include <hiredis/hiredis.h>
void onMessage(redisAsyncContext *c, void *reply, void *privdata) {
redisReply *r = (redisReply *)reply;
if (r == NULL) return;
printf("Received message: %s\n", r->element[2]->str);
}
int main(int argc, char **argv) {
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {
printf("Error: %s\n", c->errstr);
return 1;
}
// 启用客户端缓存
redisAsyncCommand(c, NULL, NULL, "CLIENT CACHING yes");
// 订阅键空间事件
redisAsyncCommand(c, onMessage, NULL, "PSUBSCRIBE __keyspace@0__:mykey");
// 事件循环
redisAsyncSetConnectCallback(c, NULL);
redisAsyncSetDisconnectCallback(c, NULL);
redisAsyncCommand(c, NULL, NULL, "PING");
redisAsyncContext *sub = redisAsyncConnect("127.0.0.1", 6379);
redisAsyncCommand(sub, NULL, NULL, "SUBSCRIBE __keyspace@0__:mykey");
redisEventLoop(sub);
redisAsyncFree(c);
return 0;
}
总结
Redis 的客户端缓存通过订阅机制和通知机制实现,确保客户端能够及时获取键的变更信息,从而保持缓存的一致性。通过启用客户端缓存和订阅键空间事件,客户端可以有效地减少与 Redis 服务器的通信频率,并显著提升应用的响应速度。上述代码示例展示了如何启用客户端缓存并处理键变更通知,以及 Redis 在后台如何管理和发送变更通知的实现细节。
Redis客户端缓存实现原理
173万+

被折叠的 条评论
为什么被折叠?



