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 {
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 内部字符串没有使用 c 语言传统字符串表示而是用了自己定义的 SDS(simple dynamic string 动态字符串)类型, SDS 相关的定义在 src/sds.h src/sds.c 中,redis 一共定义了以下五种 SDS:
sds.h
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 低三位标识类型,高五位存储字符串长度 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t
前面12篇笔记对 redis 基本使用、配置、架构等做了简单的总结,内容没什么特别的都是 redis 基础知识。我是非常喜欢 redis 的,所以后面准备去学习 redis 的源码,并根据自己的理解对 redis 源码分篇章的讲下自己的理解。每篇文章会存在一个迭代的过程,因为我会根据自己的读取进度去同步记录笔记,所以我前期对一些内容的理解可能会存在偏差,所以随着我越往后读可能会矫正前面笔记的一些错误。
源码阅读分解
这里根据 redis 的数据结构及功能对 redis 源码进行分解,目录和书籍 << Redis 设计与实现 >> 的目录一致,中间可能会根据自己的理解
有时候我们需要在短时间内往 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支
管道
redis 是基于客户端-服务端模型以及请求/响应协议的TCP服务。也就是说每次请求都需要建立 TCP 连接并发送命令和接受返回数据。所以当我们有大量的命令需要执行的话那么就需要花费太多的时间在 TCP 连接建立上。
依次 TCP 连接之后可以执行多条命令这样就可以提升命令执行速度,减少不必要的消耗,redis 就是通过支持管道技术来实现的。
管道示例如下:
> $ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG
发布订阅
发布订阅实现了不同的客户端对不同的频道发送
事务
redis 中和事务相关的命令有四个:MULTI EXEC DISCARD WATCH,MULTI 用于开始一个事务,EXEC 用于提交事务,DISCARD WATCH 可以为事务提供乐观锁(后面会讲具体使用)。
事务的流程:客户端连接服务器,MULTI 命令开启一个事务,然后客户端可以向服务器发送任意多条命令,但是命令不会立即执行,直到我们发送EXEC命令,所有命令才会根据发送的顺序依次执行。当我们在执行EXEC之前如果想要停止事务可以通过发送DISCARD来终止事务。
redis 事务有如下特性:
事务是一个隔离操作,事务中的命令会按顺序执行,事务执行过程中不会被其他客户端打断。
redis 是内存性 key-value 数据库,所以 redis 的存储数据的大小受内存的限制,redis 默认是不会删除 key 的(过期的可以除外),当内存使用完的时候,新的写入操作就会返回错误。但是 redis 支持配置回收策略来实现数据过期,Redis 只支持 LRU 回收算法(memcache也是默认算法)。
maxmemory
maxmemory 用来配置 Redis 存储数据的内存大小限制,如果我们要使用 redis 实现 LRU 算法,并限制数据的多少可以通过这个配置来实现。
示例
maxmemory 1g # 限制 1G 内存大小
设置maxmemory为0代表没有内存限
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,一定会问到 Redis是多进程还是多线程?为什么单线程还可以有这么高的性能?
Redis为什么是单线程的
Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器的内存的大小和网络的带宽,而且单线程的性能已经非常高了,就没有必要使用多线程了,所以 Redis 是单进程单线程的。
提示:
如果我们运行的服务器是多核服务器,为了充分利用多核优势我们可以在单台服务器起多个 Redis 服务,或者架设 主从复制、哨兵模式、集群模式等多机方案。
Redis 服务运行时只是处理客户端请求是单进程单线程的,但是服务运行时会有其他进