redis 7年前

从零学习redis(16)--- 源码阅读之链表

作者头像 刘宇帅
2466 0

redis 内部链表的实现比较简单,包括 3 个结构体:

src/adlist.h

typedef struct listNode {
  struct listNode *prev; // 前一个节点指针
  struct listNode * next; // 后一个节点指针
  void *value; // 节点值
} listNode;

typedef struct listIter {
  listNode *next; // 下一个节点
  int direction; // 遍历的方向(从头到尾或反向)
} listIter;

typedef struct list {
  listNode *head; // 链表的头结点指针
  listNode *tail; // 链表的尾节点指针
  void *(*dup)(void *ptr); // 复制节点值函数指针
  void (*free)(void *ptr); // 释放节点值函数指针
  int (*match)(void *ptr, void *key); // 比较节点值函数指针
  unsigned long len; // 节点长度
} list;

listNode 结构体表示单个节点的信息,从每个节点包含前一个节点的指针和后一个节点的指针我们可以知道 redis 的链表是双向链表,其中 value 字段是一个 void 指针是为了方便节点可以存放任何类型的值。

list 结构体表示一个链表,链表包含头尾节点的指针(其中 head 的 prev 为 NULL,tail 的 next 为 NULL,也就是说 redis 的链表是无环链表)、节点的长度、和 3 个用于处理节点值的函数。3 个指针函数同样是为了提升链表的可用性,上面说的节点的值 value 字段可以存放任何类型,而 dup、free、match 三个函数分别用于处理节点值的复制、释放、比较。

listIter 结构体用于表示链表的一个单向遍历链表,其中 direction 表示 方向。这个结构体是干什么的呢,因为 redis 的链表是双向链表,那么我们就可以从头到尾遍历数组或从尾到头遍历,而 listIter 就是为了方便这种遍历,redis 提供了函数 listIter listGetIterator(list list, int direction) 获得一个 listIter(其中 direction 指明遍历方向,direction等于0表示从头到尾或者表示从尾到头),然后我们就可以直接使用函数 listNode listNext(listIter iter) 获得下一个要遍历的节点。(去看下这两个的函数实现就很容易理解)

所有用于链表相关的函数声明如下(函数定义在src/adlist.c):

// micro 实现struct属性getter/setter
#define listLength(l) ((l)->len)
#define listFirst(l) ((l)->head)
#define listLast(l) ((l)->tail)
#define listPrevNode(n) ((n)->prev)
#define listNextNode(n) ((n)->next)
#define listNodeValue(n) ((n)->value)

#define listSetDupMethod(l,m) ((l)->dup = (m))
#define listSetFreeMethod(l,m) ((l)->free = (m))
#define listSetMatchMethod(l,m) ((l)->match = (m))

#define listGetDupMethod(l) ((l)->dup)
#define listGetFree(l) ((l)->free)
#define listGetMatchMethod(l) ((l)->match)

// 创建一个空list
list *listCreate(void);
// 删除并释放 list 上所有的 listNode
void listRelease(list *list);
// 删除并释放 list 上所有的 listNode
void listEmpty(list *list);
// 添加 value 到列表头部
list *listAddNodeHead(list *list, void *value);
// 添加 value 到列表尾部
list *listAddNodeTail(list *list, void *value);
// 把 value 添加到列表中某一个(old_node)的前面或后面(after=1 or 0)
list *listInsertNode(list *list, listNode *old_node, void *value, int after);
// 删除 list 上的特定 listNode
void listDelNode(list *list, listNode *node);
// 获得 list 上 listNode 遍历的单链表结构 (listIter),方向由 direction 指定
listIter *listGetIterator(list *list, int direction);
// 获得迭代器下一个元素
listNode *listNext(listIter *iter);
// 删除 listIter
void listReleaseIterator(listIter *iter);
// 复制 list
list *listDup(list *orig);
// 在列表中查找一个值,并返回值所在 listNode
listNode *listSearchKey(list *list, void *key);
listNode *listIndex(list *list, long index);
// 返回从头到尾迭代器
void listRewind(list *list, listIter *li);
// 返回从尾到头迭代器
void listRewindTail(list *list, listIter *li);
// 把 list 的最后一个节点转换成 list 的头节点
void listRotate(list *list);
// 把 list o 合并到 l 的后面,并清空 list o(不删除)
void listJoin(list *l, list *o);
作者头像

刘宇帅

非著名程序员,全栈开发工程师,长期专注系统开发与架构设计。

提示

功能待开通!


暂无评论~

相关文章

从零学习redis(8)--- 过期及过期策略

redis 的 string 类型是支持过期设置的,默认是永不过期的。 Redis 设置过期 redis 中设置设置 key 过期有3中方式 第一种在设置值的时候指定过期时间 Set 命令格式 SET key value [EX seconds] [PX milliseconds] [NX|XX] EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。 PX millisecond :设置键的过期时间为 millisecond 毫秒。 3. SET key value PX m

从零学习redis(6)--- 多机部署之集群模式

Redis 作为高效的缓存数据库,单机也能够表现出很好的性能。但是随着数据的增加、并发的增加,单机模式的 Redis 会受到单机性能、容量、稳定性的限制,即使 Redis 提供了稳定的持久化方案,但是单机服务器始终是部署在单个机器上,如果运行机器本身的服务器出现问题我们很难在短时间恢复服务。所以 Redis 提供了三种多机模式: 主从复制模式 哨兵模式 集群模式 redis集群模式 哨兵模式解决了主节点挂掉的问题,但是没有解决从节点挂掉的问题,而 redis 集群模式可以有效的解决这个问题。redis集群中的数据是和槽(slot)挂钩的,一共定义了16384个槽,所有的数据使用一致性哈希分

从零学习redis(5)--- 多机部署之哨兵模式

Redis 作为高效的缓存数据库,单机也能够表现出很好的性能。但是随着数据的增加、并发的增加,单机模式的 Redis 会受到单机性能、容量、稳定性的限制,即使 Redis 提供了稳定的持久化方案,但是单机服务器始终是部署在单个机器上,如果运行机器本身的服务器出现问题我们很难在短时间恢复服务。所以 Redis 提供了三种多机模式: 主从复制模式 哨兵模式 集群模式 哨兵模式 主从复制模式解决了数据备份和单机可能存在的性能问题,但是也引入了新的问题,一主多从,在使用过程中,如果主服务器挂掉那么整个 Redis 服务的写就挂掉了,如果一个从服务器挂掉那么调用方就无法正确的读数据了,只能通过修改调

从零学习redis(15)--- 源码阅读之内存分配

redis 中内存管理没有直接使用 C 语言提供的方法而是做了一个可管理已分配内存大小及添加了自己分配策略的实现,下面从 SDS 开始去了解 redis 的内存管理实现及策略。 SDS 内存管理函数定义如下: src/sdsalloc.h #include "zmalloc.h" #define s_malloc zmalloc #define s_realloc zrealloc #define s_free zfree s_malloc 内存申请、s_realloc 内存重新分配、s_free 内存释放,这里定义分别是 zmalloc.h 里三个函数的别名,而 zmalloc zrea

从零学习redis(12)--- 大量导入数据

有时候我们需要在短时间内往 redis 里插入大量的数据,我们如果单条单条的出入会浪费很多时间和服务器资源,我们可以使用管道来实现快速的插入。 netcat 我们把需要插入 redis 的数据创建为如下的一个命令集文件 set key1 val1 set key2 val2 ... set key3 val3 我们使用如下命令执行数据导入 > $ cat /tmp/test|nc localhost 6379 +OK +OK +OK pipe mode 使用 netcat 并不是一个可靠地方式,因为用netcat进行大规模插入时不能检查错误。从Redis 2.6开始redis-cli支