redis的一些重要的基础知识

文章目录

    • [1. rehash](#1. rehash)
      • [1.1 redis的hash表的数据结构](#1.1 redis的hash表的数据结构)
    • [2. AOF日志](#2. AOF日志)
      • [2.1 简要介绍](#2.1 简要介绍)
      • [2.2 AOF重写](#2.2 AOF重写)
    • [3. RDB快照](#3. RDB快照)
      • [3.1 执行过程](#3.1 执行过程)
      • [3.2 存在问题](#3.2 存在问题)
      • 解决方式

1. rehash

本文只介绍数据结构和结果图,如果要看文字描述过程,可以参考链接:rehash的详细过程

1.1 redis的hash表的数据结构

dictType类型:

go 复制代码
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类型:

go 复制代码
type dict struct {
    ht [2]*dictht //两个hash表
    rehashIndex int // 表示重新哈希的索引位置(如果等于 -1,则表示没有进行重新哈希)
    iterators uint32 //当前正在迭代的迭代器数
    randomSeed uint64 //是一个随机种子,用于在哈希函数中生成随机值
    privdata interface{} //私有数据,保存着dictType结构中函数的 参数,保存哈希表的一些额外信息
    type_ *dictType //指向了一个 dictType 结构体,用于实现哈希表的各项功能。
}

dictht类型:

go 复制代码
//dict结构中ht[0]、ht[1]哈希表的数据结构
type dictht struct {
    table []dictEntry 存放一个数组的地址,数组中存放哈希节点dictEntry的地址
    size int 哈希表table的大小,初始大小为4
    sizemask int 用于将hash值映射到table位置的索引,大小为(size-1)
    used int  //记录哈希表已有节点(键值对)的数量
}

dictEntry类型

go 复制代码
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的操作合并,以提高数据持久化的效率和效果‌。
go 复制代码
//代码浅浅编写:
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

相关推荐
lzb_kkk2 小时前
【Redis】redis5种数据类型(哈希)
开发语言·redis·算法·缓存·哈希算法
2401_858120262 小时前
探索Oracle数据库的多租户特性:架构、优势与实践
数据库·oracle·架构
pokemon..3 小时前
MySQL主从复制与读写分离
数据库·mysql
码农鑫哥的日常3 小时前
MySQL高可用配置及故障切换
数据库·mysql
longlongqin4 小时前
redis的 stream数据类型实现 消息队列?
数据库·redis·缓存
wrx繁星点点4 小时前
多个线程同时写入一个共享变量,会发生什么问题?如何解决?
java·开发语言·数据库
鲨鱼辣椒ii4 小时前
sql中索引查看是否生效
数据库·sql
leidata5 小时前
MySQL系列—10.Innodb行格式
数据库·mysql
阿维的博客日记5 小时前
聚簇索引和二级索引
数据库·聚簇索引·二级索引
kingandlog5 小时前
Redis网络模型、通信协议、内存回收
java·网络·redis