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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容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 抓包来查看


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
21天前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
25天前
|
存储 消息中间件 NoSQL
Redis 数据结构与对象
【10月更文挑战第15天】在实际应用中,需要根据具体的业务需求和数据特点来选择合适的数据结构,并合理地设计数据模型,以充分发挥 Redis 的优势。
54 8
|
24天前
|
NoSQL 网络协议 算法
Redis 客户端连接
10月更文挑战第21天
27 1
|
25天前
|
存储 NoSQL Java
介绍下Redis 的基础数据结构
本文介绍了Redis的基础数据结构,包括动态字符串(SDS)、链表和字典。SDS是Redis自实现的动态字符串,避免了C语言字符串的不足;链表实现了双向链表,提供了高效的操作;字典则类似于Java的HashMap,采用数组加链表的方式存储数据,并支持渐进式rehash,确保高并发下的性能。
介绍下Redis 的基础数据结构
|
1月前
|
消息中间件 存储 缓存
redis支持的数据结构
redis支持的数据结构
30 2
|
20天前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
21天前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
74 6
|
8天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
10天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构