Redis 源码分析客户端数据结构(serverDb)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 数据库存储在 redisDb 结构中,而服务端 redisServer 结构中保存着 redisDb 对象和个数,个数可以在配置文件中进行更新。

数据库存储在 redisDb 结构中,而服务端 redisServer 结构中保存着 redisDb 对象和个数,个数可以在配置文件中进行更新。


image.png


数据结构


typedef struct redisDb {
    // 保存 k,v 数据
    dict *dict;                 /* The keyspace for this DB */
    // 保存 k, exprie 时间
    dict *expires;              /* Timeout of keys with a timeout set */
    // 阻塞的 key 
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    // 
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    // 监控的 key
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;


而  redisServer 中都包含了 redisDb 数据结构。表示当前使用的是那个 db。


struct redisServer {
    /* General */
    pid_t pid;                  /* Main process pid. */
    pthread_t main_thread_id;         /* Main thread id */
    char *configfile;           /* Absolute config file path, or NULL */
    char *executable;           /* Absolute executable file path. */
    char **exec_argv;           /* Executable argv vector (copy). */
    int dynamic_hz;             /* Change hz value depending on # of clients. */
    // ...
};


数据库切换


数据库切换是使用 select 命令来执行,实际上第 n 个 db 指向客户端的 db 对象 c -> db = &server.db[id]


int selectDb(client *c, int id) {
    if (id < 0 || id >= server.dbnum)
        return C_ERR;
    c->db = &server.db[id];
    return C_OK;
}


键空间


由前文可以知道 redis 可以有多个数据库,而不同数据库之间是不相互影响的,如何做到的呢?键空间可以理解成 C++ 里面的命名空间,用来隔离,数据存在 redisDb   中的dict 对象,因此对于键的操作,基本都是基于键空间来操作的。


本质其实就是每个 redisDb 中的 dict 对象。很好理解,因为选择了不同的 db,肯定下面的键也不一样。


void dbAdd(redisDb *db, robj *key, robj *val) {
    sds copy = sdsdup(key->ptr);
    int retval = dictAdd(db->dict, copy, val);
    serverAssertWithInfo(NULL,key,retval == DICT_OK);
    signalKeyAsReady(db, key, val->type);
    if (server.cluster_enabled) slotToKeyAdd(key->ptr);
}


过期键


  1. 在 redis 中过期键保存在 redisD中的 expres 变量里中, expires 是 dict 指针类型


  1. 存储方式


  • key 保存的是数据库的键对象
  • value 保存的是数据对象的过期时间,长整型 Unix 时间。


void setExpire(client *c, redisDb *db, robj *key, long long when) {
    dictEntry *kde, *de;
    /* Reuse the sds from the main dict in the expire dict */
    kde = dictFind(db->dict,key->ptr);
    serverAssertWithInfo(NULL,key,kde != NULL);
    dictSetSignedIntegerVal(de,when);
    de = dictAddOrFind(db->expires,dictGetKey(kde));
    int writable_slave = server.masterhost && server.repl_slave_ro == 0;
    if (c && writable_slave && !(c->flags & CLIENT_MASTER))
        rememberSlaveKeyWithExpire(db,key);
}


  1. 设置时间


相对方式:expire


绝对方式:expireat


如 expire 命令:


setExpire(c, c->db, key, when)

---> kde = dictFind(db->dict,key->ptr);

---> de = dictAddOrFind(db->expires,dictGetKey(kde));


  1. 删除过期时间


persist


  1. 查看过期时间:ttl


lookupKeyReadWithFlags --> getExpire --> ttl = expire-mstime();


  1. 键过期策略


  • 定期删除,每间隔一段时间,程序对数据进行一次勘查,删除里面的过期键。对内存友好,尽可能的删掉一些过期的键,但是对 CPU 不友好,而且需要设置好定期时间以及每次删除数量
  • 定时删除,在设计过期键的时候,建立定时器,让定时器在键过期时间达到时,立即删除对键的操作,可以看作一种时删除的一种,但是该方案坑你创建较多的定时器,而且 redis 定时器采用的是链表,查找某个 key 时间负载度为 O(n)
  • 惰性删除:每次在获取键的时候,来处理过期键,确定是对内存不友好
  • 触发清理策略:当设置了 maxmemory , 且超过过期时间。


redis 中采用的是定期删除和惰性删除两种


  1. 定期删除分为两种模式,分别在不同的场景下使用。因此结束判断不一样。


在定时任务 serverCorn 中的 databaseCron 中,为 ACTIVE_EXPIRE_CYCLF_SLOW, 这就意味着我们可以花费更多的时间来处理。而在十佳循环之前的 beforeSleep 函数中则为 ACTIVE_EXPIRE_CYCLE_FAST,因为不能影响处理时间,还做一个优化


if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
        /* Don't start a fast cycle if the previous cycle did not exit
         * for time limit, unless the percentage of estimated stale keys is
         * too high. Also never repeat a fast cycle for the same period
         * as the fast cycle total duration itself. */
        if (!timelimit_exit &&
            server.stat_expired_stale_perc < config_cycle_acceptable_stale)
            return;
        if (start < last_fast_cycle + (long long)config_cycle_fast_duration*2)
            return;
        last_fast_cycle = start;
    }


  1. REDIS_EXPIRELOOKUPS_TIME_PERC 是单位时间内分配给 activeExpireCycle 函数执行的 CPU 比例, 默认为。25


  1. 每次循环最多 16 个库。


  1. 每个库要求找到过期键达到 5 个就行(注意的是,因为那个库随机选取一个key 的,所以数量不能太少,太少随机效果不好)。


  1. 每个库里面,每次随机选取 20 个 key 。 检查是否过期键,过期就记数一次。每间隔 16 次检查一下时间是否超过限制,如果超过需要推出玄幻。不在继续查找。


对于过期键 RDB/AOF/主从的影响,后期讲述。


通知和事件


数据库通知主要是利用 redis 中支持发布订阅模式,让客户端通过订阅给顶的频道或者模式(保存在 client 和 server 的 pubsub_channels 和 pubsub_patterns, 但保存的内容不一样)来获取数据库中的键的变化。主要是分为键空间通知和事件通知两类,要开启该功能需要在配置文件中设置参数/动态设置 notify-keyspace-events


键空间通知


keyspace@ 某个键执行了什么命令


事件通知


keyevent@ 某个事件被哪些命令执行


启用事件通知有两种方式:


  1. 配置文设置:格式: notify-keyspace-events Elg. 如果要配置 K 和 E 必须要开启一个。


  1. 命令设置


config set notify-keyspace-events Elg


image.png


image.png


3、查看发布订阅的 key


PSUBSCRICE __keyevent@*__:expired


4、也可以使用 tcpdump 抓包来查看


相关文章
|
17天前
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
144 86
|
1月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
91 0
|
2月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
17天前
|
存储 缓存 NoSQL
Redis基础命令与数据结构概览
Redis是一个功能强大的键值存储系统,提供了丰富的数据结构以及相应的操作命令来满足现代应用程序对于高速读写和灵活数据处理的需求。通过掌握这些基础命令,开发者能够高效地对Redis进行操作,实现数据存储和管理的高性能方案。
57 12
|
16天前
|
存储 消息中间件 NoSQL
【Redis】常用数据结构之List篇:从常用命令到典型使用场景
本文将系统探讨 Redis List 的核心特性、完整命令体系、底层存储实现以及典型实践场景,为读者构建从理论到应用的完整认知框架,助力开发者在实际业务中高效运用这一数据结构解决问题。
|
25天前
|
存储 缓存 NoSQL
【Redis】 常用数据结构之String篇:从SET/GET到INCR的超全教程
无论是需要快速缓存用户信息,还是实现高并发场景下的精准计数,深入理解String的特性与最佳实践,都是提升Redis使用效率的关键。接下来,让我们从基础命令开始,逐步揭开String数据结构的神秘面纱。
|
4月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
10天前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
70 1
Redis专题-实战篇二-商户查询缓存
|
4月前
|
缓存 NoSQL Java
Redis+Caffeine构建高性能二级缓存
大家好,我是摘星。今天为大家带来的是Redis+Caffeine构建高性能二级缓存,废话不多说直接开始~
708 0