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. Java集合学习笔记

      在Java中,我们经常听到Collections框架.Collection类以及Collections类.这三者名字相似,但是从概念上讲却是不同的.Collections框架泛指Java中用于存储 ...

  2. 有关html,css,js,less的使用规范

    写前端写久了,规则跟着开发的项目走,突然觉得是不是该总结总结前端的语言使用规则,看到下面这篇还不错,就直接链接过来了哦 http://zhibimo.com/read/Ashu/front-end-s ...

  3. burpsuite+sqlmap跨登录验证SQL注入

    (我操作的系统是kali linux) 1.利用burpsuite代理设置拦截浏览器请求(具体操作步骤可参考:http://www.cnblogs.com/hito/p/4495432.html) 2 ...

  4. Spark结构式流编程指南

    Spark结构式流编程指南 概览 Structured Streaming 是一个可拓展,容错的,基于Spark SQL执行引擎的流处理引擎.使用小量的静态数据模拟流处理.伴随流数据的到来,Spark ...

  5. linux vi hjkl由来

    很远原因来自历史 I was reading about vim the other day and found out why it used hjkl keys as arrow keys. Wh ...

  6. IOS动态自适应标签实现

    先上效果图 设计要求 1.标签的宽度是按内容自适应的 2.一行显示的标签个数是动态的,放得下就放,放不下就换行 3.默认选中第一个 4.至少选中一个标签 实现思路 首先我们从这个效果上来看,这个标签是 ...

  7. HTML阶段总结

    自学有两个多星期了,这段时间主要在学习HTML基础知识,万事开头难,刚开始根本没法上手,云里雾里的,没有清晰的思路和详细的学习计划.问了一些盆友,找了一些资料,找到了适合自己的学习方法,渐渐的进入了轨 ...

  8. 《用Python做HTTP接口测试》学习感悟

    机缘巧合之下,报名参加了阿奎老师发布在"好班长"的课程<用Python做HTTP接口测试>,报名费:15rmb,不到一杯咖啡钱,目前为止的状态:坚定不移的跟下去,自学+ ...

  9. 利用select实现伪并发的socket

    使用socket模块可以实现程序之间的通信,但是server在同一时刻只能和一个客户端进行通信,如果要实现一个server端可以和多个客户端进行通信可以使用 1.多线程 2.多进程 3.select ...

  10. while循环学习之统计流量

    /application/apache/logs/bbs-access_log日志文件中任意一行的格式如下,以空格为间隔第十列(2632)为此次请求内容的字节数大小 192.168.220.1 - - ...