redis 数据库实现

数据库的 server 端和 client 端

server 端

  • 数据库在 server 端的存储

    • // redisServer 结构
      struct redisServer {
      // ...
      // 数据库
      redisDb *db;
      // 数据库数量
      int dbnum; // ...
      }
    • redis server 在启动时, 会初始化 dbnum 个 db

      int main(int argc, char **argv) {
      // ...
      // 初始化服务器配置
      initServerConfig(); // ...
      // 创建并初始化服务器数据结构
      initServer(); // ...
      }
      • initServerConfig, 设置 dbnum, 默认为 16

        • #define REDIS_DEFAULT_DBNUM     16
          
          void initServerConfig() {
          // ...
          // 设置数据库数量
          server.dbnum = REDIS_DEFAULT_DBNUM; // ...
          }
      • initServer 创建 dbnum 个数据库

        • void initServer() {
          // ...
          // 创建 dbnum 个 redisDb
          server.db = zmalloc(sizeof(redisDb)*server.dbnum); // ...
          // 初始化每个数据库 redisDb 的值
          for (j = 0; j < server.dbnum; j++) {
          server.db[j].dict = dictCreate(&dbDictType,NULL);
          server.db[j].expires = dictCreate(&keyptrDictType,NULL);
          server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
          server.db[j].ready_keys = dictCreate(&setDictType,NULL);
          server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
          server.db[j].eviction_pool = evictionPoolAlloc();
          server.db[j].id = j;
          server.db[j].avg_ttl = 0;
          } // ...
          }

client 端

  • 数据库在 client 端的存储, 此处涉及到 redisClient 结构, redisClient 对象是在客户端与服务端连接时创建的, 对于客户端与服务端的连接过程此章节仅作大体描述, 深入讲解将在专门章节进行

    redis server 端在 initServer 时, 会创建请求接收事件, 当有客户端连接时触发事件

    当 redis client 进行连接时, 触发服务端定义的事件 acceptTcpHandler -> 调用 acceptCommonHandler -> 调用 createClient -> selectDb, 设置客户端连接的数据库, 存入 redisClient->db 中

  • redisClient 结构

    // 客户端结构
    typedef struct redisClient {
    // ...
    // 当前正在使用的数据库
    redisDb *db;
    // 当前正在使用的数据库 id
    int dictid; // ...
    }

    db 指向当前客户端选择的数据库

    redis 数据库在 redisServer 中的 db 数组中, 选择不同的库, 其实就是从 db 中获取不同的 db 地址

    目前 redis 还没有提供获取当前所在数据库的命令, 所以在执行一些危险命令的时候, 最好显式执行 SELECT 命令选择一下库

redis 数据库实例结构

redisDb 结构 (src/redis.h)

// redis 数据库
typedef struct redisDb {
// 数据库键空间,保存着数据库中的所有键值对
dict *dict; /* The keyspace for this DB */
// 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
dict *expires; /* Timeout of keys with a timeout set */
// 正处于阻塞状态的键
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */
// 可以解除阻塞的键
dict *ready_keys; /* Blocked keys that received a PUSH */
// 正在被 WATCH 命令监视的键
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */
// 数据库号码
int id; /* Database ID */
// 数据库的键的平均 TTL ,统计信息
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;
  • 数据库里面的键值对数据存放在一个大的字典结构 dict 中, 称为键空间 (keyspace)

    键空间的键就是数据库的键, 数据库的键均为字符串对象

    键空间的值就是数据库的值, 值的类型可以是 REDIS_STRING, REDIS_LIST, REDIS_SET, REDIS_ZSET, REDIS_HASH

    键空间是一个字典, 所有对数据库的增删改查操作, 均是使用字典操作来实现的

  • 对数据进行操作过程中, 还掺杂着一些其他的操作

    更新缓存命中率/不命中率

    当键有修改时, 通知相关客户端

过期删除策略

过期命令

  • EXPIRE (秒为单位, 值为时间间隔)
  • PEXPIRE (毫秒为单位, 值为时间间隔)
  • EXPIREAT (秒为单位, 值为时间戳)
  • PEXPIREAT (毫秒为单位, 值为时间戳)
  • TTL (秒为单位)
  • PTTL (毫秒为单位)
  • PERSIST (移除过期时间)

设置过期时间的命令 EXPIRE, PEXPIRE, EXPIREAT, PEXPIREAT 实际上都是转换过后使用 PEXPIREAT 实现的

过期时间的保存

  • 所有间的过期信息均是存储在 redisDb 结构中的 expires 字段
  • expires 字段为 dict 结构, 键为数据库键对象的指针, 值为以毫秒为单位的时间戳
  • expires 字段的键对象与 dict 字段的键对象共享一个地址, 不会出现重复对象浪费空间

过期键的判定逻辑

  • 键存在于 expires 中
  • 当前时间已经超过了 expires 中设置的过期时间

redis 的过期删除策略

  • 定期删除

    • 每隔一段时间, 会有程序主动清除 redis 当中的过期 key, 通过限制删除操作的时长和频率来尽量减少对 cpu 时间的占用, 对 cpu 时间是友好的
    • activeExpireCycle 函数实现, initServer 注册 serverCron 事件, serverCron 在 databasesCron 中调用 activeExpireCycle
      • 函数每次执行时, 都会从一定数量的数据库中随机取出一定数量的随机键进行检查, 并删除其中的过期键
  • 惰性删除
    • expireIfNeeded 函数实现, 对数据库数据进行读写操作时, 会调用 expireIfNeeded 函数, 此函数会判断 key 是否过期, 若过期会删除

数据库通知

redis 通知系统会在后续详细作为一节讲述

db api (src/db.c)

函数 作用 备注
lookupKey 从 db 中取出指定 key 的值对象 robj *lookupKey(redisDb *db, robj *key)
lookupKeyRead 为执行读操作从 db 中取出指定 key 的值, 相对于 lookupKey 来说, 增加了判断 key 是否过期以及更新缓存命中/未命中信息 robj *lookupKeyRead(redisDb *db, robj *key)
lookupKeyWrite 为执行写操作从 db 中取出指定 key 的值, 相对于 lookupKeyRead 来说, 不会更新缓存命中信息 robj *lookupKeyWrite(redisDb *db, robj *key)
lookupKeyReadOrReply 为执行读操作从 db 中取出指定 key 的值, 若未找到, 则向客户端发送 reply 信息 robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply)
lookupKeyWriteOrReply 为执行写操作从 db 中取出指定 key 的值, 若未找到, 则向客户端发送 reply 信息 robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply)
doAdd 向 db 添加键值对 void dbAdd(redisDb *db, robj *key, robj *val)
doOverwrite 修改 db 中指定 key 的值 void dbOverwrite(redisDb *db, robj *key, robj *val)
setKey set 命令, 设置 db 中指定 key 的值, 若存在, 则覆盖, 若不存在, 则新增, 会移除 key 的过期时间 void setKey(redisDb *db, robj *key, robj *val)
dbExists 判断 db 中指定的 key 是否存在 int dbExists(redisDb *db, robj *key)
dbRandomKey 从 db 中获取随机的一个 key robj *dbRandomKey(redisDb *db)
dbDelete 删除 db 中指定 key 的键值对数据以及键的过期时间 int dbDelete(redisDb *db, robj *key)
dbUnshareStringValue
emptyDb 清空所有 db 的数据 long long emptyDb(void(callback)(void*))
selectDb 将客户端的 db 指向 id 指定的数据库 int selectDb(redisClient *c, int id)
signalModifiedKey 每当 db 中的键被改动时, 此函数均会调用, 通知监视这个键的客户端 void signalModifiedKey(redisDb *db, robj *key)
signalFlushedDb 每当清空一个 db 时, 此函数均会调用, 通知相关客户端 void signalFlushedDb(int dbid)
removeExpire 移除指定 key 的过期时间 int removeExpire(redisDb *db, robj *key)
setExpire 设置指定 key 的过期时间为 when void setExpire(redisDb *db, robj *key, long long when)
getExpire 获取指定 key 的过期时间 long long getExpire(redisDb *db, robj *key)
propagateExpire 若一个 key 在主节点已过期, 删除从节点以及 aof 文件中的此 key, 构造 del 命令并调用 void propagateExpire(redisDb *db, robj *key)
expireIfNeeded 检查给定 key 是否过期, 若过期, 视情况判断是否删除: 服务器正在载入不删除, 从库不删除 int expireIfNeeded(redisDb *db, robj *key)
getKeysUsingCommandTable 获取命令中的所有 key int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys)
getKeysFromCommand 从命令中获取键, 相对于 getKeysUsingCommandTable, 多了集群转向时的支持 int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
getKeysFreeResult 释放 getKeysFromCommand 的结果 void getKeysFreeResult(int *result)
slotToKeyAdd 将给定 key 添加到槽中 void slotToKeyAdd(robj *key)
slotToKeyDel 从槽中删除给定 key void slotToKeyDel(robj *key)
slotToKeyFlush 清空所有槽保存的所有键 void slotToKeyFlush(void)
getKeysInSlot 从 hashslot 槽中获取 count 个 key unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count)
delKeysInSlot 删除 hashslot 槽中所有 key unsigned int delKeysInSlot(unsigned int hashslot)
countKeysInSlot 返回指定 hashslot 槽中 key 的个数 unsigned int countKeysInSlot(unsigned int hashslot)

redis 数据库实现的更多相关文章

  1. MySQL、MongoDB、Redis数据库Docker镜像制作

    MySQL.MongoDB.Redis数据库Docker镜像制作 在多台主机上进行数据库部署时,如果使用传统的MySQL的交互式的安装方式将会重复很多遍.如果做成镜像,那么我们只需要make once ...

  2. Spring + Jedis集成Redis(集群redis数据库)

    前段时间说过单例redis数据库的方法,但是生成环境一般不会使用,基本上都是集群redis数据库,所以这里说说集群redis的代码. 1.pom.xml引入jar <!--Redis--> ...

  3. 超强、超详细Redis数据库入门教程

    这篇文章主要介绍了超强.超详细Redis入门教程,本文详细介绍了Redis数据库各个方面的知识,需要的朋友可以参考下 [本教程目录] 1.redis是什么2.redis的作者何许人也3.谁在使用red ...

  4. 深入浅出Redis02 使用Redis数据库(String类型)

    一 String类型 首先使用启动服务器进程 : redis-server.exe 1. Set 设置Key对应的值为String 类型的value. 例子:向 Redis数据库中插入一条数据类型为S ...

  5. Redis数据库的使用与介绍

    本周11-15号开始用Redis数据库在现有的平台基础上开发一个独立模块,这是一个边学习.边记录.边交流.边开发.边总结的过程.大部分随笔都是个人的“工作日志”,旨在记录自己学习过程中收集的一些资料, ...

  6. node.js应用Redis数据库

    node.js下使用Redis,首先: 1.有一台安装了Redis的服务器,当然,安装在本机也行 2.本机,也就是客户端,要装node.js 3.项目要安装nodejs_redis模块 注意第 3 点 ...

  7. Ubuntu 安装和配置redis数据库

    Ubuntu 14.04下安装和配置redis数据库 小编现在在写一个分布式爬虫,要用到这个数据库,所以分享一下小编是如何安装和配置的,希望对大家有帮助. 工具/原料   Ubuntu 系统电脑一台 ...

  8. Redis数据库?-Redis的Virtual Memory介绍(转)

    众所周知,Redis是一个内存数据库,和Memcached类似,所有数据存在内存中,当然,Redis有rdb和appendonlyfile两个落地文件,可以对断电停机等故障下的数据恢复做一些保证.但是 ...

  9. php redis数据库操作类

    <?php namespace iphp\db; use iphp\App; /** * redis操作类 * 说明,任何为false的串,存在redis中都是空串. * 只有在key不存在时, ...

  10. Windows下安装Redis数据库并实现C#访问

    1.Redis在Windows下的安装 目前Redis官方并不支持Redis的Windows版本,需要去GitHub下载. GitHub上的Redis分两种,一种是以命令行形式安装的,一种是以Wind ...

随机推荐

  1. 10条建议让你创建更好的jQuery插件

    在开发过很多 jQuery 插件以后,我慢慢的摸索出了一套开发jQuery插件比较标准的结构和模式.这样我就可以 copy & paste 大部分的代码结构,只要专注最主要的逻辑代码就行了.使 ...

  2. smarty模板做人员表信息删除,修改 里面的性别单选按钮民族下拉,另外登录进去可以显示姓名

    首先登录进去可以显示姓名 smarty模板做人员表信息删除,删除的时候有提示框确定删除吗. 修改 里面的性别单选按钮,要修改谁有默认选中,用了变量调节器 民族位置做下拉,用<{foreach}& ...

  3. Objective-C日记-之类别Category

    类别Category 1,概述 为现有类添加新的方法,这些新方法的Objective-C的术语为“类别”. 2,用法 a,声明类别 @interface NSString(NumberConvenie ...

  4. ACM 比大小

    比大小 时间限制:3000 ms  |  内存限制:65535 KB 难度:2   描述 给你两个很大的数,你能不能判断出他们两个数的大小呢? 比如123456789123456789要大于-1234 ...

  5. 安装SVN报无法访问windows installer服务。

    第一步:点击开始--运行,输入:cmd 第二步:输入regsvr32 msi.dll然后回车,会提示成功. 第三步:点击开始--运行,输入:services.msc按回车 第四部:调到页面后找到Win ...

  6. .Net程序员学用Oracle系列(21):分组查询(GROUP BY)

    1.GROUP BY 标准分组 1.1.GROUP BY 概述 1.2.WHERE 和 HAVING 的区别? 2.GROUP BY 扩展分组 2.1.ROLLUP 分组 2.2.CUBE 分组 2. ...

  7. markown编辑器截图粘贴预览,并将图片传至七牛云

    最近在做一个项目,需要实现类似QQ截图后,就是能够在富文本编辑器中粘贴截图并预览. 先看一下效果: 分析一下实现步骤: QQ截图后在编辑器中粘贴,肯定会有一个粘贴事件,即 paste 事件 在事件回调 ...

  8. 【iOS】7.4 定位服务->2.1.2 定位 - 官方框架CoreLocation: CLLocationManager(位置管理器)

    本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正. 本文相关目录: ================== 所属文集:[iOS]07 设备工具 === ...

  9. dreamweaver中的 map怎么调用?_制作热点图像区域

    我们浏览网页时,经常看到一些图片上会出现特别的超链接,即在一张图片上有多个局部区域和不同的网页链接,比如地图链接. 这就是映射图像(Image Map),它是指一幅根据链接对象不同而被人为划分为若干指 ...

  10. cssLoading效果

    http://files.cnblogs.com/files/xdoudou/loaders.css-master.zip