概述
字符串
SDC(Simple Dynamic String,简单动态字符串)作为字符串表示
每个sds.h/sdshdr结构表示一个SDS值
struct sdshdr{
...
}
len
free
buff
优势
- 常数复杂度获取字符串长度
数据结构中有len属性用于保存字符串的长度 - 杜绝缓冲区溢出
在字符串进行扩展前,会先检查一下buff的长度是否足够,如果不够的话就会进行扩展,然后再执行添加字符的操作 - 减少内存分配次数
SDS通过两种策略来实现
3.1 空间预分配
如果对SDS进行修改之后,len属性小于1MB,那么程序分配和len同样大小的未使用空间,即len和free属性值相同;如果大于1MB,那么将分配1MB未使用空间
3.2 惰性空间释放
当SDS的API需要缩短保存的字符串时,内存重分配不会立即释放未使用的空间,而是将其作为free的数量 - 二进制安全
首先需要了解C,C的字符串必须符合某种编码,例如如果一开始读入空格将被识别为结尾
SDS以二进制形式存储,文本存进去是什么内容,拿出来就还是什么内容 兼容部分C字符串函数
SDS遵循C字符串以空字符结尾,这样就能重用/<string.h>的stracasecmp函数strcasecpm(sds->buff,"hello world!")
链表
数据结构
双向链表
typedef struct list{
...
}list
listNode * head;
复习一下结点的数据结构
typedef struct listNode{
struct ListNode * prev;
struct ListNode * next;
void * value;
}
listNode * tail;
unsigned long len;
//节点复制函数
void (dup)(void *ptr)特性
双端、无环、带表头和表尾指针、带链表长度计数器和多态
关于多态这里解释一下,以复制函数为例,使用的void*指针来保存节点值,所以可以保存各种不同类型的值用途
列表键、发布与订阅、慢查询、监视器等
列表键暂时不知
发布与订阅容易消息丢失,适用于要求不高的场景,可以从确保消息不丢失的问题从延伸
慢查询暂时不知
监视器暂时不知字典
哈希表节点与哈希表数据结构
键值节点结构
哈希表结构
字典结构
从上面的图里可以看到,字典中有哈希表结构,而哈希表中有key-value键值节点哈希算法
通过键来计算出哈希值和索引值(通过哈希值和哈希掩码计算出来),将包含新键值对的哈希表节点放在置顶索引上面
解决键冲突(哈希冲突)
链表法
rehash(重新散列)
说人话就是为了在字典里面的数据增加或者减少的时候将哈希表的长度控制在一定范围内,避免不够用或者过于浪费
当哈希表增加或者缩减到一定程度时就会触发rehash操作- 如果是增加,那么ht[1]的大小等于ht[0].used*2的2^n^
如果是减少,那么ht[1]的大小等于ht[0].used的2^n^ - 将ht[0]的所有键值都放到ht[1]中,这个过程会重新计算hash值和索引值
释放ht[0],然后将ht[1]设置为ht[0],并且在ht[1]新创建一个空白哈希表,为下一次的rehash做准备
渐进式rehash
先定义一个rehashindex变量,初始值为0,在执行新增、删除、查询时都会将对应ht[0]的redisindex索引处的key-value重新散列到ht[1],并且在渐进式rehash期间,新增的结点不会进入到ht[0]中,就保证了ht[0]最终会成为空表
跳跃表
几个重要的概念
- 前进指针
每个跳跃表结点都有指向下一个结点的指针 - 层
每个跳跃表结点都会有很多层,至于具体是干什么的现在还不太清楚 - 后退指针
最后一个跳跃表结点指向前一个结点 - 跨度
就是从头结点开始到目标结点经历的路径,有点想图的权 分值和成员
分值是一个double类型的浮点数,跳跃表中的所有的结点的分值按照从小大来排序
对象是一个指针,它指向一个字符串对象,而字符串对象中则保存一个SDS值
不太清楚一个跳跃表结点是不是只能存放一个对象,但是我猜测是这样的总结
- Redis的跳跃表实现是由zkiplist和zkiplistNode两个结构组成,其中zkiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于跳跃表节点
多个跳跃表节点的分值可以相同,但是对象必须唯一
整数集合
整数集合是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis
sadd numbers 1 3 5 7 9
上面的命令用的是sadd,说明用的集合set的基本类型(Redis对外提供的)升级
先来看数据结构
typedef strunct intset{ unit32_t encoding; }
上面的encoding属性决定当前数组的元素是用什么方式编码,如果当前是inset_enc_int16,此时再添加进一个64位编码的元素那么就会执行升级操作
整数集合只支持升级而不支持降级压缩列表
压缩列表ziplist是列表键(List???)和哈希键(Hash???)的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么是小整数值,要么是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现
在3.2和5之后引入了quicklist和listpack,所以废弃了对象
5种基本类型
就是入门篇里提到的5种类型对象
注:由于Redis里面都是键对象和值对象,所以这一章的角度都是从这两个对象出发
EVAL "for i=1, 128 do redis.call('ZADD', KEYS[1], i, i) end" 1 numbers
上面这段代码是往一个zset里面插入128个元素,从1开始
在开始内容之前需要先看一个数据结构typedef struct redisObject{ //类型 unsigned type:4; //编码 unsigned encoding:4; //指向底层实现数据结构的设计 void *ptr; //对象的空转时长 unsigned lru:22; //引用计数 int refcount; }
不同类型的编码方式
虽然Redis有5种基本数据类型,但是每种数据类型还是的底层还是有至少两种以上的编码方式(这里说的编码方式和底层用的数据结构不是一个概念)
这里就不得不提一下多态了,真的是随处可见,比如说所有的类型都能够使用命令DEL
有些命令是数据类型独有的,例如RPUSH、ZADD,这些命令在执行时,不同底层编码方式也都会执行这些指令。类型检查
服务器在执行某些命令前,会先检查给定键的类型能够执行指定的命令,其实就是检查值对象的类型
内存回收机制
对象上有个字段refcount,代表当前对象被引用次数,如果为0,则会从内存中删除掉
内存共享
0-9999整数值会预先存好,就像Java的字符串常量池一样
空转时间
越近访问的值空转时间会越少
单机数据库的实现
选择数据库
select 0(index)
客户端程序中有个数据结构,其中有个属性保存了当前客户端使用的数据库数据库键空间
数据库键空间是这一章的重点
关于键值变化时的操作过程
- 新增键
- 删除键
- 更新键
读写键的维护操作
服务器中键有命中和不命中次数属性,是一个统计指标
键有ideltime,用来指示键的闲置时间
键有过期时长,不知道和这个闲置时间的属性有什么关系
键有个属性代表是否被修改,书上说的是键是不是为dirty(脏键),每次修改这个值都会被加1,如果客户端使用WATCH命令对其进行了监听,那么客户端程序在执行事务程序时就会注意到。设置生存或者过期时间
RDB持久化
SAVE和BGSAVE
Mac下使用Homebrew安装的Redis的RDB文件位置
- 首先还是得找到redis.conf的位置
使用brew info redis命令可以查看到
/opt/homebrew/etc/redis.conf 在redis.conf找到dbfilename和dir属性的值来确定文件名称和路径
db.dump和/opt/homebrew/var/db/redisAOF持久化
AOF缓冲区
重写
AOF重写缓冲区
事件
首先来复习一下多路复用,没办法用的多有什么辙
几个关键的记忆点,bind()函数和accept()函数,socket
客户端connect()
已连接队列
多进程模型
多线程模型
IO多路复用模型select()和poll()
IO多路复用模型epoll()
事件机制,回调函数Redis中的IO多路复用
客户端
主要内容是服务器内部保存的redis-client结构,对其中的的属性进行讲解
服务端
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。