struct sdshdr {
int len;
int free;
char buf[];
}
sds优点
struct listNode {
prev *listNode;
next *listNode;
value interface{}
}
struct linkedlist {
head *listNode
tail *listNode
len int64
dup func() // 节点值复制函数
free func() // 节点值释放函数
match func() // 节点值对比函数
}
//哈希表结构
struct dictht {
// 哈希表数组
table []*dictEntry
size int64 // 哈希表大小
sizemask int64 // 哈希表大小掩码,用于计算索引值,总是等于size-1
used int64 // 该哈希表已有节点数量
}
//哈希表节点结构
struct dictEntry {
key interface{}
v interface{}
next *dictEntry //使用链地址法解决键冲突问题
}
struct dictType {
hashFunction func //计算哈希值函数
keyDup func // 复制键函数
valDup func // 复制值函数
keyCompare func // 对比键的函数
valDestructor func // 销毁值的函数
}
// redis 字典数据结构
struct dict {
type *dictType // 类型特定函数
privdata interface{} // 私有数据
ht [2]dictht // 哈希表,字典只使用ht[0]哈希表,ht[1]只会在对ht[0]进行rehash时使用
rehashidx int //rehash索引时使用,标识rehash进度,未rehash时默认-1
}
字典插入一个新的键值对,根据key计算哈希值和索引值,根据索引值将新哈希表节点放在哈希表数组指定索引上:
// 使用字典哈希函数,计算key的哈希值
hash = dict.type.hashFunction(key)
//使用哈希表的sizemask和哈希值,计算索引值。
index = hash & dict.ht[x].sizemask
redis使用MurmurHash算法:https://github.com/aappleby/smhasher
为保证负载因子维持一定范围内,当哈希表键值对数量过多或过少,需要对哈希表大小进行相应扩容和缩容,通过rehash(重新散列)操作来完成
扩容条件:
// 负载因子=哈希表易保存节点数量/哈希表大小
load_factor=ht[0].used / ht[0].size
步骤:
渐进式rehash期间,删/查/改会在两个哈希表上进行;新增操作在ht[1]上
是一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,达到快速访问节点目的。支持平均O(logN),最坏O(N)复杂度查找,可替代AVL树: 跳跃表原理和代码实现
// 跳跃表节点
struct zskiplistNode {
// 层
level []zskiplistLevel
backward *zskiplistNode //后退指针
score float64 // 分值
robj interface{} //成员对象
}
// 层节点
struct zskiplistLevel {
forward *zskiplistNode // 前进指针
span uint // 跨度
}
// 跳跃表结构
struct zskiplist {
header *zskiplistNode // 表头节点
tail *zskiplistNode // 表尾节点
length int64 // 表中节点数量
level int // 表中层数最大的节点的层数
}
struct intset {
encoding uint32 //编码方式
length uint32 // 整数集合包含的元素数量
// 整数集合元素都是contents的数组项,按值从小到大排列,不包含重复项
// contents类型取决于encoding的值:
// INTSET_ENC_INT16
// INTSET_ENC_INT32
// INTSET_ENC_INT64
contents []encoding
}
新元素入整数集合前,若新元素类型比所有元素类型都要大时,整数集合需先升级,在添加
步骤:
整数集合不支持降级,对数组升级后,编码始终保持升级后状态
redis为节约内存实现了压缩列表,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表包含任意多个节点,每个节点可以保存一个字节数组或整数值。
// 压缩列表结构
struct zip1ist {
zlbytes uint32 // 记录整个压缩列表占用的内存字节数
zltail uint32 // 记录压缩列表表尾节点距离压缩列表的起始地址有多少字节:根据该偏移量,可直接确定表尾节点地址
zllen uint16 // 记录压缩列表包含的节点数量
entryX []zip1istNode // 列表节点
zlend uint8 //特殊值0xFF,标记压缩列表的末尾
}
struct zip1istNode {
//记录了压缩列表中前一个节点的长度,该属性的长度可以是1字节或者5字节
// 压缩列表从尾到头遍历操作使用该属性实现
//前一节点长度 < 254字节,previous_entry_length=1字节
//前一节点长度 >= 254字节,previous_entry_length=5字节
previous_entry_length byte
// 记录了节点的content属性所保存数据的类型和长度
// 值的最高位是00、01、10,标识content属性类型是字节数组,数组长度由encoding编码除去最高两位后的其他位值
// 值的最高位是11,标识content属性类型是数字
encoding byte
// 保存节点的值,值的类型和长度由encoding决定
content []byte or int
}
连锁更新
//快速列表节点结构
struct quicklistNode {
prev *quicklistNode
next *quicklistNode
zi *ziplist // 指向压缩列表
size int32 // ziplist的字节总数
count int16 // ziplist的元素总数
encoding byte // 存储形式,表示ziplist是否压缩和使用的压缩算法,1未压缩;2使用LZF算法压缩
}
// 压缩列表结构-略
struct ziplist{}
// 表示一个被压缩后的ziplist
struct ziplist_compressed {
size int32 // 压缩后的ziplist大小
compressed_data []byte //存放压缩后ziplist字节数组
}
//快速列表结构
struct quicklist {
head *quicklistNode // 列表头节点
tail *quicklistNode // 尾节点
count int64 // 元素总数
nodes int // ziplist节点个数
compressDepth int // lzf压缩算法深度
}
链表的附加空间相对太高,prev和next占去16字节,另外每个节点内存都是单独分配,加剧内存碎片化。因此定义quicklist将链表和压缩列表结合起来。为进一步节约内存,还应用LZF算法对ziplist进行压缩存储。
list-max-ziplist-size
控制压缩深度由参数list-compress-depth
控制,默认为0
0: 所有节点都不压缩
1: quicklist两端第一个ziplist不进行压缩,支持快速push/pop
2: 两端各自2个ziplist不压缩,中间压缩
n: 两端各自n个ziplist不压缩,中间压缩
对象类型和对应的编码
struct redisObject {
type uint //对象类型
// 对象底层数据结构类型由encoding决定
// REDIS_ENCODING_INT long类型的整数
// REDIS_ENCODING_EMBSTR embstr编码的简单动态字符串
// REDIS_ENCODING_RAW 简单动态字符串
// REDIS_ENCODING_HT 字典
// ...
encoding uint
ptr *pointer // 指向底层实现数据结构的指针
refcount int // 引用计数
...
}
OBJECT ENCODING [keyName] 查看对象的编码类型
redis通过引用计数
实现内存回收机制
引用计数refcount还具有对象共享作用,让多个键共享同一个值对象。
struct redisServer {
// ...
db *redisDb // db数组,保存着服务器中所有数据库
dbnum int //服务器的数据库数量,默认为16
}
struct redisDb {
dict *dict // 数据库键空间,保存着数据库中的所有键值对
// ecxpires为过期字典,保存数据库中所有键的过期时间
// 过期字典的键为指针,指向键空间中某个键对象,值为long类型整数,为毫秒级时间戳
ecxpires *dict
}
select [dbIdx] 切换目标数据库
flushdb 清空当前数据库的键空间中所有的键值对
expire/pexpire 设置生存时间
expireat/pexpireat 设置过期时间,时间戳格式
persist 移除过期时间
redis过期键删除策略:
创建rdb
自动间隔性创建
通过save设置多个保存条件,只要有一个满足,服务器就会执行bgsave命令
// redis.conf 中可设置save
save 900 1 // 服务器在900s内,对数据库进行了 >= 1次修改
save 300 10 // 服务器在300s内,对数据库进行了 >= 10次修改
save 60 10000 // ...
// 就是【数据库】中提到的服务器数据结构呀
struct redisServer {
// ...
// db *redisDb // db数组,保存着服务器中所有数据库
// dbnum int //服务器的数据库数量,默认为16
saveparams *[]saveparam // 记录了保存条件数组
dirty int64 // 计数器,记录距离上次成功执行save/bgsave后服务器对数据库状态进行了多少次修改
lastsave int // 时间戳,记录服务器上一次成功执行save/bgsave的时间
}
// save选项设置的保存条件结构
struct saveparam {
seconds int // 秒数
changes int //修改数
}
服务器周期性函数默认100ms
执行一次,根据dirty、lastsave、saveparams检查save选项设置条件是否满足
载入rdb
rdb文件结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IccLSR5a-1643116099954)(./rdb_file_struct.jpg)]
RDB持久化之RDB文件结构
od -c dump.rdb # 可以打印输出文件内容
aof通过保存redis服务器执行的写命令来记录数据库状态
持久化实现
struct redisServer {
// ...
aof_buf sds // AOF缓冲区
}
文件的写入和同步行为有redis.conf中appendfsync
控制:
载入AOF文件
BGREWRITEAOF
命令异步执行AOF文件重写服务器当前状态和重写后AOF保存状态不一致?
服务器设置一个AOF重写缓冲区,重写期间,写命令会追加到AOF缓冲区(aof_buf)和重写缓冲区。当完成重写后,服务器主进程:
命令:SLAVEOF
缺陷:主从断线后重复制,sync效率过低
使用PSYNC
替代SYNC来执行复制时的同步操作
命令传播
阶段,从服务器默认每秒一次频率,向主发送命令REPLCONF ACK
参考:
Redis 的 Sentinel 文档
Redis哨兵模式(sentinel)学习总结及部署记录
Redis Sentinel机制与用法
哨兵是redis高可用的解决方案:由一个或多个哨兵实例组成的哨兵系统可以监视任一多个主服务器,以及这些主服务器属下所有从服务器。
主要有三个作用:
# 启动Sentinel
redis-sentinel /path/to/your/sentinel.conf
# or:
redis-server /path/to/your/sentinel.conf --sentinel
// sentinel.conf的配置demo
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
sentinel monitor resque 192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5
当一个snetinel初始化时,步骤如下:
// 一个sentinel实例结构
struct sentinelState {
current_epoch uint64 // 当前纪元,用于实现故障转移
// 保存了所有被这个sentinel监视的主服务器
// 字典的key是服务器名,值是一个sentinelRedisInstance结构的指针
masters map[string]*sentinelRedisInstance
//。。。。
}
// 实例结构,代表一个被sentinel监视的redis服务器实例,该实例可以是主服务器、从服务器,或另一个sentinel
struct sentinelRedisInstance {
// 标识,记录了实例的类型,以及当前状态
flags int
// 实例名字
// 主服务器的名字由用户在配置文件中设置
// 从服务器以及sentinel的名字由sentinel自动设置,格式为"ip:port"
name string
// 配置纪元,用于实现故障转移
config_epoch uint64
// 实例地址
addr *sentinelAddr
// sentinel down-after-millisenconds 设定值
// 实例无响应多少毫秒后会被判断为主观下限
down_after_period int64 // 一个毫秒值
// sentinel monitor 中quorum设定值
// 判断这个实例为客观下线所需的支持投票数量
quorum int
// sentinel parallel-syncs 选项的值
// 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
parallel_syncs int
// sentinel failover-timeout 选项值
// 刷新故障迁移状态的最大时限
failover_timeout int64 //一个毫秒值
//。。。。
}
struct sentinelAddr {
ip string
port int
}
sentinel以10秒的频率向主/从服务器发送INFO
命令,获取主/从服务器信息回复
sentinel以2秒的频率,通过redis发布订阅模式,sentinel与被监听服务器建立连接,获取所有被监听服务器信息,并更新自己的masters字典
主观下线
客观下线
选举领头sentinel
局部领头
,一旦设置成局部领头,在该配置纪元中不能更改领头
故障转移
选主步骤
深入学习Redis之Redis Cluster
cluster meet <ip> <port> // 指定节点握手
cluster addslots <slot> [slot ...] //将一个或多个槽指派给节点负责
计算key属于哪个槽 slot = CRC16(key) % 16383
重新分片
主从复制
故障检测
集群内节点间定期ping,过时认为节点故障
故障转移
从节点发现自己的主节点已下线,从节点对下线主节点进行故障转移:
slaveof no one
命令,成为新的主节点# 发布订阅常见命令
publish # 向频道中发布消息
subscribe # 订阅指定频道
unsubscribe # 退订指定频道
psubscribe # 订阅一个或多个模式,从而成为这些模式的订阅者
punsubscribe # 退订模式
字典
实现,字典key是频道名称,value是一个链表,链表里记录所有订阅该频道的client链表
实现,链表节点结构包含pattern和client属性,pattern记录被订阅的模式发布订阅的消息无法持久化,如果出现网络断开、服务器宕机等问题,消息就会丢弃
Redis Stream
相关概念
// 事务相关命令
multi // 开启一个事务
exec // 事务提交
discard // 取消事务,放弃执行事务块内所有命令
watch // 乐观锁,在exec执行前,监视任意数量键,并在exec执行时,检查被监视的键是否至少有一个已经被修改过了,是的话服务器拒绝执行事务
unwatch // 取消watch对key的监视
事务周期的三个阶段:
// 客户端结构体演示
struct redisClient {
//....
// 事务状态
mstate *multiState
}
// 事务状态包含一个事务队列
struct multiState {
// 事务队列,FIFO顺序
commands *[]multiCmd
count int
}
// 事务队列是一个multiCmd类型数组,每个multiCmd结构保存了已入对命令的相关信息
struct multiCmd {
argv *obj // 参数
argc int // 参数数量
cmd *redisCommand // 命令指针
}
sort <key> //对列表键、集合键、有序集合键的值进行排序,默认升序排序
sort <key> alpha // alpha选项客队包含字符串值的键进行排序
sort <key> asc/desc //指定顺序
sort <key> limit <offset> <count> //返回一部分元素
// -- 慢查询相关的服务器配置 --
slowlog-log-slower-then // 指定执行时间超过多少微秒的命令请求会被记录到日志上
slowlog-max-len // 指定服务器最多保存多少条慢查询日志
// -- 查看服务器保存的慢查询日志命令 --
SLOWLOG GET
链表
实现通过执行MONITOR
命令,客户端成为一个监视器,实时接收并打印服务器当前处理的命令的请求相关信息