文章目录
- 1. rehash
- 1.1 redis的hash表的数据结构
- 2. AOF日志
- 2.1 简要介绍
- 2.2 AOF重写
- 3. RDB快照
- 3.1 执行过程
- 3.2 存在问题
- 解决方式
1. rehash
本文只介绍数据结构和结果图,如果要看文字描述过程,可以参考链接:rehash的详细过程
1.1 redis的hash表的数据结构
dictType类型:
type dictType struct {
hashFunction func(key interface{}) uint32
keyDup func(privdata interface{}, key interface{}) interface{}
valDup func(privdata interface{}, obj interface{}) interface{}
keyCompare func(privdata interface{}, key1 interface{}, key2 interface{}) int
keyDestructor func(privdata interface{}, key interface{})
valDestructor func(privdata interface{}, obj interface{})
}
//hashFunction:哈希函数,用于计算键的哈希值; MurmurHash方法
//keyDup:键复制函数,用于插入插入键值对
//valDup:值复制函数, 用于插入键值对
//keyCompare:键比较函数,用于比较两个键是否相等,用于查找键值对;
//keyDestructor:键销毁函数,用于释放键占用的内存;
//valDestructor: 值销毁函数,用于释放值占用的内存;
dict类型:
type dict struct {
ht [2]*dictht //两个hash表
rehashIndex int // 表示重新哈希的索引位置(如果等于 -1,则表示没有进行重新哈希)
iterators uint32 //当前正在迭代的迭代器数
randomSeed uint64 //是一个随机种子,用于在哈希函数中生成随机值
privdata interface{} //私有数据,保存着dictType结构中函数的 参数,保存哈希表的一些额外信息
type_ *dictType //指向了一个 dictType 结构体,用于实现哈希表的各项功能。
}
dictht类型:
//dict结构中ht[0]、ht[1]哈希表的数据结构
type dictht struct {
table []dictEntry 存放一个数组的地址,数组中存放哈希节点dictEntry的地址
size int 哈希表table的大小,初始大小为4
sizemask int 用于将hash值映射到table位置的索引,大小为(size-1)
used int //记录哈希表已有节点(键值对)的数量
}
dictEntry类型
type dictEntry struct {
key interface{}
v dictEntryValue
next *dictEntry
}
type dictEntryValue struct {
ptr interface{}
integer int64
uinteger uint64
_float float64
}
2. AOF日志
2.1 简要介绍
- 主要作用:redis是内存存储,如果系统崩溃可能会导致数据丢失,而AOF的作用就是用来redis崩溃后数据恢复的;
- 格式实例:AOF是直接将redis命令进行记录的,set testKey testValue这个命令:
- 该日志属于写后日志:在redis执行命令后在插入日志
- 优点:不会阻塞当前写操作;确保写入的命令是正确执行的;
- 缺点:redis执行成功后,数据写入AOF日志缓冲区没能及时同步,导致数据丢失,从而数据不一致;这个时候就需要选择合理的同步策略:
2.2 AOF重写
- 重写原因:作为文本记录的AOF日志,随着命令的增加,记录越来越多,从而导致了文件也越来越大,在数据恢复的时候会对系统产生较大的负担;
- 重写步骤:
- 主线程fork一个子线程来进行重写,用主线程如果文件过大,会阻塞主流程;
- 子线程对AOF日志进行重写,重写过程中新来的命令记录在AOF日志和重写AOF日志缓冲区中,避免数据不一致。重写完成后,将缓冲区中的数据加入到日志中,从而完成重写。
- 重写逻辑:即将多个命令合并成一个命令。通过这种方式,可以有效地减少AOF文件的大小,提高数据恢复的速度,并解决上述问题。因此,AOF重写确实是因为AOF日志中重复key的操作合并,以提高数据持久化的效率和效果。
//代码浅浅编写:
func aofRewrite(newAofFileName string) {
// 创建新的 AOF 文件
f := createFile(newAofFileName)
// 遍历数据库
for _, db := range redisServer.db {
// 忽略空数据库
if db.isEmpty() {
continue
}
// 写入 SELECT 命令,指定数据库号码
f.writeCommand("SELECT", db.id)
// 遍历数据库中的所有键
for _, key := range db {
// 忽略已过期的键
if key.isExpired() {
continue
}
// 根据键的类型对键进行重写
switch key.type {
case String:
rewriteString(key, f)
case List:
rewriteList(key, f)
case Hash:
rewriteHash(key, f)
case Set:
rewriteSet(key, f)
case SortedSet:
rewriteSortedSet(key, f)
}
// 如果键带有过期时间,那么过期时间也要被重写
if key.haveExpireTime() {
rewriteExpireTime(key, f)
}
}
}
// 写入完毕,关闭文件
f.close()
}
func rewriteString(key *Key, f *File) {
// 使用 GET 命令获取字符串的值
value := redis.Get(key).Val()
// 使用 SET 命令重写字符串键
f.writeCommand("SET", key, value)
}
func rewriteList(key *Key, f *File) {
// 使用 LRANG 命令获取列表键包含的所有元素
items := redis.LRange(key, 0, -1).Val()
// 使用 RPUSH 命令重写列表键
args := []interface{}{key}
for _, item := range items {
args = append(args, item)
}
f.writeCommand("RPUSH", args...)
}
func rewriteHash(key *Key, f *File) {
// 使用 HGETALL 命令获取哈希键包含的所有键值对
fieldVals := redis.HGetAll(key).Val()
// 使用 HMSET 命令重写哈希键
args := []interface{}{key}
for field, value := range fieldVals {
args = append(args, field, value)
}
f.writeCommand("HMSET", args...)
}
func rewriteSet(key *Key, f *File) {
// 使用 SMEMBERS 命令获取集合包含的所有元素
elems := redis.SMembers(key).Val()
// 使用 SADD 命令重写集合键
args := []interface{}{key}
for _, elem := range elems {
args = append(args, elem)
}
f.writeCommand("SADD", args...)
}
func rewriteSortedSet(key *Key, f *File) {
// 使用 ZRANG 命令获取有序集合包含的所有元素
memberScores := redis.ZRangeWithScores(key, 0, -1).Val()
// 使用 ZADD 命令重写有序集合键
args := []interface{}{key}
for _, memberScore := range memberScores {
args = append(args, memberScore.Score, memberScore.Member)
}
f.writeCommand("ZADD", args...)
}
func rewriteExpireTime(key *Key, f *File) {
// 获取毫秒精度的键过期时间戳
timestamp := getExpireTimeInUnixStamp(key)
// 使用 PEXPIREAT 命令重写键的过期时间
f.writeCommand("PEXPIREAT", key, timestamp)
}
3. RDB快照
3.1 执行过程
3.2 存在问题
如果快照间隔时间太短:
如果间隔时间过长,可能会导致数据大量丢失
解决方式
本文的目的只是做一个简单的记录,详细的可以参考链接:https://blog.csdn.net/m0_70325779/article/details/132409948