Go语言工程实践入门(七)Redis应用 | 青训营

21 阅读6分钟

Redis(Remote Dictionary Server)是一种开源的高性能键值存储系统。

基本工作原理如下:

  1. 内存存储:Redis主要将数据保存在内存中,能够提供快速的读写性能。内存中的数据通过快速的读写操作来满足用户请求,而被持久化在磁盘上的数据则用于数据恢复和持久性
  2. 单线程模型:Redis采用单线程模型来处理客户端请求。所有的命令都是顺序执行的,每个命令都会被串行地处理。这种简单的模型减少了锁的竞争和线程切换的开销,从而提高了性能
  3. 数据结构支持:Redis支持多种数据结构,如字符串、哈希表、列表、集合和有序集合等。这些数据结构的支持使得Redis在处理各种数据类型时更加灵活和高效
  4. 持久化机制:Redis提供两种持久化机制,分别是快照(snapshotting)和日志(logging)。快照通过将数据库的状态保存到磁盘上的文件中来实现,而日志则记录了所有修改操作,通过重放这些操作来恢复数据。这样可以保证在系统故障或重启后数据的可靠性和一致性

Redis 实现的特殊数据结构

String 数据结构

在Redis内部实现中,string数据结构是最基本的数据类型之一。下面是Redis内部实现string数据结构的一些关键点:

image.png

  1. 数据结构:Redis的string数据结构使用简单动态字符串(Simple Dynamic String,SDS)来表示。SDS是一个灵活的、可自动扩容的字符串结构,它相比于传统的C语言字符串更加安全且性能更好
  2. 存储方式:Redis中的string数据结构通过SDS存储在内存中。SDS结构包含一个指向字符数组的指针、字符串的长度信息(len)和分配的空间大小(alloc)等字段,使得Redis可以快速地进行字符串的读取和写入操作
  3. 常用操作:Redis提供了一系列常用的操作命令来处理string类型的数据,比如GETSETAPPENDINCR等,可以用于获取、设置、追加和增减字符串的值
  4. 内存优化:为了减少内存占用,Redis对于较短的字符串会进行压缩存储。当字符串的长度小于一定阈值(默认为44字节)时,Redis会将SDS结构中的字符数组缩小到实际所需的长度
  5. 二进制安全:Redis的string数据结构是二进制安全的,即可以存储任意二进制数据,而不仅仅局限于文本,使得Redis可以用于存储图片、视频、序列化对象等多种数据类型
  6. 操作复杂度:对于大多数常见的操作,比如获取和设置字符串的值,Redis的string数据结构的操作复杂度为O(1),即常数时间复杂度,使得Redis在处理大量请求时能够保持高效的性能

List 数据结构

Redis内部实现的list数据结构是双向链表(doubly linked list)。在C语言中,Redis使用一个定义了节点结构和列表结构的头文件来实现这个数据结构。

节点结构(listNode)定义如下:

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

每个节点包含了一个指向前一个节点的指针(prev)、一个指向后一个节点的指针(next)以及一个指向存储值的指针(value)。

这个存储的值指针指向的可能是一个listpack,即为元素节点的打包,内含有多个元素

列表结构(list)定义如下:

typedef struct list {
    listNode *head;
    listNode *tail;
    unsigned long len;
    void *(*dup)(void *ptr);
    void (*free)(void *ptr);
    int (*match)(void *ptr, void *key);
} list;

列表结构包含了一个指向头节点的指针(head)、一个指向尾节点的指针(tail)、表示列表长度的无符号长整型变量(len),以及一些用于操作节点值的函数指针。其中,dup函数用于复制节点值、free函数用于释放节点值、match函数用于比较节点值。

通过使用双向链表,Redis可以高效地进行头部和尾部的插入、删除操作,同时也能够在常数时间内根据索引位置进行访问。这使得Redis的list类型支持快速的队列操作,例如在列表的两端进行元素的推入(push)和弹出(pop)操作。

Dict 数据结构

Redis 内部实现的哈希数据结构 dict 是通过开放地址法的哈希表实现的,每个 dict 对象包含一个哈希表,用于存储键值对。通过哈希函数计算索引值,并使用桶和节点的结构来处理插入、查找和更新操作。这种实现方式使得 Redis 能够高效地处理大量的键值对,具有较低的时间复杂度。

当需要执行rehash操作,即将ht[0]中的数据全部迁移到ht[1]中时,如果数据量较大,有上百万的键值对时,直接完整拷贝需要花费时间,导致用户请求阻塞,因此要采用渐进式rehash的方式:每次用户访问时,都迁移用户访问的该部分少量数据,将整个迁移过程,平摊到所有的访问请求过程中。

Zskiplist 数据结构

Redis 内部实现的有序集合(Sorted Set)数据结构 zskiplist(跳跃表)是一种基于链表的数据结构,用于存储有序的成员和对应的分值。

zskiplist 是由多层链表组成的,每一层都是一个有序的链表。每个节点包含了一个成员和一个分值,并且通过前进指针跨越指针连接到其他节点。最底层的链表包含了所有节点,而上层的链表是下层链表的一个子集通过跳跃指针快速跳过部分节点,减少查找的时间复杂度。

当执行查找操作时,Redis 会从最高层开始,比较节点的分值与目标分值,并根据节点的指针跳跃到下一层或下一个节点,直到找到目标节点或搜索结束。

通过使用跳跃指针和分层结构,zskiplist 能够较快地进行插入、删除和查找操作,并具有较低的时间复杂度。此外,zskiplist 中每个节点的层数是通过随机算法生成的,使得整个结构相对平衡,以提高性能。

需要注意的是,为了保证有序集合的唯一性,zskiplist 在插入和更新操作时会检查成员是否相等,并根据需要进行替换或更新。