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 抓包来查看


相关实践学习
基于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
相关文章
|
1月前
|
存储 消息中间件 缓存
Redis 5 种基础数据结构?
Redis的五种基础数据结构——字符串、哈希、列表、集合和有序集合——提供了丰富的功能来满足各种应用需求。理解并灵活运用这些数据结构,可以极大地提高应用程序的性能和可扩展性。
38 2
|
2月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
47 5
|
2月前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
3月前
|
NoSQL 网络协议 算法
Redis 客户端连接
10月更文挑战第21天
50 1
|
3月前
|
存储 NoSQL Java
介绍下Redis 的基础数据结构
本文介绍了Redis的基础数据结构,包括动态字符串(SDS)、链表和字典。SDS是Redis自实现的动态字符串,避免了C语言字符串的不足;链表实现了双向链表,提供了高效的操作;字典则类似于Java的HashMap,采用数组加链表的方式存储数据,并支持渐进式rehash,确保高并发下的性能。
介绍下Redis 的基础数据结构
|
2月前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
2月前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
8月前
|
JSON NoSQL Java
【Redis】2、Redis 的 Java 客户端(Jedis 和 SpringDataRedis)
【Redis】2、Redis 的 Java 客户端(Jedis 和 SpringDataRedis)
225 0
|
4月前
|
JSON NoSQL Java
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
这篇文章介绍了在Java中使用Redis客户端的几种方法,包括Jedis、SpringDataRedis和SpringBoot整合Redis的操作。文章详细解释了Jedis的基本使用步骤,Jedis连接池的创建和使用,以及在SpringBoot项目中如何配置和使用RedisTemplate和StringRedisTemplate。此外,还探讨了RedisTemplate序列化的两种实践方案,包括默认的JDK序列化和自定义的JSON序列化,以及StringRedisTemplate的使用,它要求键和值都必须是String类型。
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
|
6月前
|
Java Redis 数据安全/隐私保护
Redis14----Redis的java客户端-jedis的连接池,jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,最好用jedis连接池代替jedis,配置端口,密码
Redis14----Redis的java客户端-jedis的连接池,jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,最好用jedis连接池代替jedis,配置端口,密码