前言

当我们在Redis数据库中set一个KV的时候,这个KV保存在哪里?如果我们get的时候,又从哪里get出来。时间复杂度,空间复杂的等等,怎么优化等等一系列问题。

服务器中的数据库

Redis服务器将所有数据库信息都保存在redis.h##redisService结构体中。代码如下:

 struct redisServer {

     // ...

     /* General */

     // 配置文件的绝对路径
char *configfile; /* Absolute config file path, or NULL */ // 数据库
redisDb *db;

// 是否设置了密码
char *requirepass; /* Pass for AUTH command, or NULL */ // PID 文件
char *pidfile; /* PID file path */ // TCP 监听端口
int port; /* TCP listening port */ // ... }

列了几个,我认为比较重要的。其中最重要的,肯定是redisDb *db; 这个数据结构保存了我们所有的数据。

Redis 是一个键值对(key-value pair)数据库服务器, 服务器中的每个数据库都由一个 redis.h/redisDb 结构表示, 其中, redisDb 结构的 dict 字典保存了数据库中的所有键值对, 我们将这个字典称为键空间(key space)。代码如下:

/* Redis database representation. There are multiple databases identified
* by integers from 0 (the default database) up to the max configured
* database. The database number is the 'id' field in the structure. */
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 *dict; 他是一个字典,不太了解的小伙伴,可以看我前一篇的文章(https://www.cnblogs.com/wenbochang/p/11673590.html),对redis的字典有详细的介绍。

这个dict存储了我们真正的数据。

键空间和用户所见的数据库是直接对应的:

  • 键空间的键也就是数据库的键, 每个键都是一个字符串对象。
  • 键空间的值也就是数据库的值, 每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象在内的任意一种 Redis 对象。

因为数据库的键空间是一个字典, 所以所有针对数据库的操作 —— 比如添加一个键值对到数据库, 或者从数据库中删除一个键值对, 又或者在数据库中获取某个键值对, 等等, 实际上都是通过对键空间字典进行操作来实现的。那么复杂度显而易见基本就是O(1)级别了,这也是redis为什么能这么快的一个重要原因。

对键取值

对一个数据库键进行取值, 实际上就是在键空间中取出键所对应的值对象。代码如下:

 /*
* 返回字典中包含键 key 的节点
*
* 找到返回节点,找不到返回 NULL
*
* T = O(1)
*/
dictEntry *dictFind(dict *d, const void *key)
{
dictEntry *he;
unsigned int h, idx, table; // 字典(的哈希表)为空
if (d->ht[].size == ) return NULL; /* We don't have a table at all */ // 如果条件允许的话,进行单步 rehash
if (dictIsRehashing(d)) _dictRehashStep(d); // 计算键的哈希值
h = dictHashKey(d, key);
// 在字典的哈希表中查找这个键
// T = O(1)
for (table = ; table <= ; table++) { // 计算索引值
idx = h & d->ht[table].sizemask; // 遍历给定索引上的链表的所有节点,查找 key
he = d->ht[table].table[idx];
// T = O(1)
while(he) { if (dictCompareKeys(d, key, he->key))
return he; he = he->next;
} // 如果程序遍历完 0 号哈希表,仍然没找到指定的键的节点
// 那么程序会检查字典是否在进行 rehash ,
// 然后才决定是直接返回 NULL ,还是继续查找 1 号哈希表
if (!dictIsRehashing(d)) return NULL;
} // 进行到这里时,说明两个哈希表都没找到
return NULL;
}

看代码其实是很简单的。

  • 首先判断字典是否为空,如果为空,没有继续下去的必要了,直接return null
  • 第二步,如果在进行rehash,则先进行渐进式rehash。(不懂的,翻我上一篇博文)
  • 第三步,计算key的hash值。
  • 第四步,查找ht[0],ht[1]两张table表。其中如果是链表,则while循环查找即可。
  • 找到返回,没找到返回null。非常的简单清晰的逻辑。
  • 大致如下图:

对键增加,删除,更新类似于查找。我就不一一列出源码了。

后言

当使用 Redis 命令对数据库进行读写时, 服务器不仅会对键空间执行指定的读写操作, 还会执行一些额外的维护操作, 其中包括:

  • 在读取一个键之后(读操作和写操作都要对键进行读取), 服务器会根据键是否存在, 以此来更新服务器的键空间命中(hit)次数或键空间不命中(miss)次数, 这两个值可以在 INFO stats 命令的 keyspace_hits 属性和 keyspace_misses 属性中查看。
  • 在读取一个键之后, 服务器会更新键的 LRU (最后一次使用)时间, 这个值可以用于计算键的闲置时间, 使用命令 OBJECT idletime <key> 命令可以查看键 key 的闲置时间。
  • 如果服务器在读取一个键时, 发现该键已经过期, 那么服务器会先删除这个过期键, 然后才执行余下的其他操作。
  • 如果有客户端使用 WATCH 命令监视了某个键, 那么服务器在对被监视的键进行修改之后, 会将这个键标记为脏(dirty), 从而让事务程序注意到这个键已经被修改过。
  • 服务器每次修改一个键之后, 都会对脏(dirty)键计数器的值增一, 这个计数器会触发服务器的持久化以及复制操作执行。

redis系列之------数据库的更多相关文章

  1. redis系列之数据库与缓存数据一致性解决方案

    redis系列之数据库与缓存数据一致性解决方案 数据库与缓存读写模式策略 写完数据库后是否需要马上更新缓存还是直接删除缓存? (1).如果写数据库的值与更新到缓存值是一样的,不需要经过任何的计算,可以 ...

  2. Java Redis系列1 关系型数据库与非关系型数据库的优缺点及概念

    Java Redis系列1 关系型数据库与非关系型数据库的优缺点及概念 在学习redis之前我们先来学习两个概念,即什么是关系型数据库什么是非关系型数据库,二者的区别是什么,二者的关系又是什么? ** ...

  3. Redis系列之key操作命令与Redis中的事务详解(六)

    序言 本篇主要目的有二: 1.展示所有数据类型中key的所有操作命令,以供大家学习,查阅,更深入的挖掘redis潜力. 2.掌握redis中的事务,让你的数据完整性一致性拥有更优的保障. redis命 ...

  4. Redis系列(1)之安装

    Redis系列(1)之安装 由于项目的需要,最近需要研究下Redis.Redis是个很轻量级的NoSql内存数据库,它有多轻量级的呢,用C写的,源码只有3万行,空的数据库只占1M内存.它的功能很丰富, ...

  5. 用Redis作为Mysql数据库的缓存【转】

    用Redis作Mysql数据库缓存,必须解决2个问题.首先,应该确定用何种数据结构存储来自Mysql的数据:在确定数据结构之后,还要考虑用什么标识作为该数据结构的键. 直观上看,Mysql中的数据都是 ...

  6. Redis系列(二):Redis的数据类型及命令操作

    原文链接(转载请注明出处):Redis系列(二):Redis的数据类型及命令操作 Redis 中常用命令 Redis 官方的文档是英文版的,当然网上也有大量的中文翻译版,例如:Redis 命令参考.这 ...

  7. Redis系列(一):Redis的简介与安装

    原文链接(转载请注明出处):Redis系列(一):Redis的简介与安装 什么是 Redis Redis 是一个使用ANSI C 编写的开源.支持网络协议.基于内存.可选持久性的键值对数据库,它是一个 ...

  8. Redis系列--内存淘汰机制(含单机版内存优化建议)

    https://blog.csdn.net/Jack__Frost/article/details/72478400?locationNum=13&fps=1 每台redis的服务器的内存都是 ...

  9. redis系列--深入哨兵集群

    一.前言 在之前的系列文章中介绍了redis的入门.持久化以及复制功能,如果不了解请移步至redis系列进行阅读,当然我也是抱着学习的知识分享,如果有什么问题欢迎指正,也欢迎大家转载.而本次将介绍哨兵 ...

随机推荐

  1. 2018阿里前端 - 认真写下阿里的面筋,祝福大家收到满意的offer(前端向)

    作者:叮!阿里offer请查收!链接:https://www.nowcoder.com/discuss/102509来源:牛客网 首先表达一下对阿里面试官的感谢,以及大公司的气魄——没有因为不是科班出 ...

  2. explain的关键字段的意义

    mysql提供的explain工具可以输出一些有用的信息. 一下是相关的部分返回值的意义. select_type 表示SELECT的类型,常见的取值有: SIMPLE:简单表,不使用表连接或子查询 ...

  3. Elasticsearch在Java中的增删改查

    public class ElasticAPI { private static RestClient restClient; static { restClient=RestClient.build ...

  4. Graph and Chart Study

    1.选择主题 “Themes”,以"Graph"——“preset7” 为例: 2.选择“Canvas”——“GraphChart”预制体,1.Set data categorie ...

  5. kafka经典入门

    问题导读 1.Kafka独特设计在什么地方?2.Kafka如何搭建及创建topic.发送消息.消费消息?3.如何书写Kafka程序?4.数据传输的事务定义有哪三种?5.Kafka判断一个节点是否活着有 ...

  6. hive学习笔记之-数据类型

    数据类型 Hive基本的数据类型: Hive集合数据类型: 另外还有一个复合数据类型,可以综合上面的数据类型组合到一起. ·          union: UNIONTYPE<data_typ ...

  7. iOS面试题整理带答案

    iOS面试题整理带答案       找工作,面试是避免不了的! 而技术开发面试,问一些技术相关的问题是必须的,最新的技术可能人人都趋之若鹜,但有些原理和基础的也希望都有了解. 这里整理了一些iOS相关 ...

  8. Java 内存溢出分析

    原文地址:Java 内存溢出分析 博客地址:http://www.moonxy.com 一.前言 Java 的 JVM 的内存一般可分为 3 个区:堆(heap).栈(stack)和方法区(metho ...

  9. 查看 Linux 系统版本信息

    博客地址:http://www.moonxy.com 一.前言 Linux 下如何查看内核信息.发行版信息,系统位数.CPU 信息等等,Windows 下我们可以通过各种图形化软件来查看,但是对于 L ...

  10. mysql 存储过程 (ps:用法自己看 :)

    delimiter // drop procedure if exists operate_tables // create procedure operate_tables (in db_name ...