吃住Redis
Redis入门指南
Redis设计与实现
Redis实战
基本类型
字符串类型 SDS (simple dynamic string)
还被用作缓冲区:AOF中的AOF缓冲区,客户端状态中的输入缓冲区
c字符串作为字符串字面量(string literal)用在一些无须对字符串值进行修改的地方,如打印日志.
Redis使用sdshdr类型变量来存储字符串,redisObject的ptr字段指向的是该变量的地址
struct sdshdr{ int len;//字符串长度,buf数组已使用字节数量 int free;//buf中剩余空间 char buf[];//存储字符串内容 字节数组 }
sds相较于c字符串的好处:
常数复杂度获取字符串长度
杜绝缓冲区溢出
减少修改字符串时带来的内存重分配次数
空间预分配: 对sds空间扩展时,会为sds分配额外的未使用空间
如果修改后sds长度小于1MB,则分配和len属性同样大小的未使用空间.
如: 修改后sds的len变成13字节,那么sds的buf长度会变成13+13+1=27字节
如果修改后sds长度大于1MB,则分配1MB空间.
如: 修改后sds的len变成30MB,那么sds的buf长度会变成30MB+1MB+1btyte=27字
惰性空间释放: 缩短sds保存的字符串时,使用free属性记录下来,等待将来使用.
二进制安全
链表
列表键的底层实现之一就是链表: 当一个列表键包含了数量比较多的元素,或者链表包含的元素都是比较长的字符串时,redis就会使用链表作为列表键的实现.
此外 发布订阅,慢查询,监视器等功能也用到了链表,redis服务器用链表保存多个客户端的状态信息,用链表来构建客户端输出缓冲区.
typedef struct listNode{ struct listNode *prev; struct listNode *next; void *value;}listNode;
typedef struct list{ listNode *head; listNode *tail; unsigned long len; void *(*dup)(void *ptr); //节点复制函数 void *(*free)(void *ptr);//节点释放函数 void *(*match)(void *ptr, void *key);//节点对比函数}list;
- Redis链表特性
- 双端: 获取某个节点的前置和后置节点复杂度都是O(1)
- 无环: 表头节点的prev和表尾节点的next都指向NULL
- 有表头表位指针.O(1)获取
- 长度计数器
- 多态,可以保存各种不同类型的值
字典
又称 关联数组,映射,符号表. 用于保存键值对, 一个键和一个值关联
字典用来表示数据库, 也是哈希键的底层实现之一: 当一个哈希键包含的键值对比较多,或者键值对中的元素都是比较长的字符串时,Redis就会使用字典作为哈希键的底层实现.
字典使用哈希表作为底层实现.
typedef struct dictht{ dictEntry **table;//哈希表数组 unsigned long size;//哈希表大小 unsigned long sizemask;//哈希表大小掩码,用于计算索引值,总是等于size-1 unsigned long used;//哈希表已有节点的数量} dictht;
sizemask和哈希值一起决定一个键应该被放到table数组的哪个索引上
typedef struct dictEntry{ void *key; union{ void *val; uint64_t u64; int64_t s64; } v; struct dictEntry *next;//可以将多个哈希值相同的键值连接在一起,解决键冲突问题} dictEntry;
键值可以是一个指针,一个uint64_t整数或一个int64_t整数
Redis的字典:
typedef struct dict { dictType *type; //类型特定函数 void *privdata; //私有数据 dictht ht[2]; //哈希表 int trehashidx; //rehash索引} dict;
Redis键 总是一个字符串对象string object
Redis键值都是用redisObject结构体保存的
typedef struct redisObject{ unsigned type:4; unsigned notused:2; unsigned encoding:4; unsigned lru:22; //lru time (relative to server.lrulock) int refcount; //该键值被引用数量 void *ptr;} robj;
type是键值数据类型,取值:
#define REDIS_STRING 0#define REDIS_LIST 1#define REDIS_SET 2#define REDIS_ZSET 3#define REDIS_HASH 4
encoding表示Redis键值的内部编码方式,取值
#define REDIS_ENCODING_RAW 0#define REDIS_ENCODING_INT 1#define REDIS_ENCODING_HT 2 //hash table#define REDIS_ENCODING_ZIPMAP 3#define REDIS_ENCODING_LINKEDLIST 4#define REDIS_ENCODING_ZIPLIST 5#define REDIS_ENCODING_INTSET 6#define REDIS_ENCODING_SKIPLIST 7#define REDIS_ENCODING_EMBSTR 8 //embeded sds string encoding
执行set key foobar时,存储键值占用的空间是: sizeof(redisObject) + sizeof(sdshdr) + strlen("foobar") = 30字节执行set key 123456时,占用空间是: sizeof(redisObject) = 16字节
Redis启动后会预先建立10000个 从0到9999这些数字的redisObject类型变量作为共享对象.
如果要设置的字符串键值在这范围内,则直接引用共享变量而不用再建立一个redisObject 存储键值占用0字节当配置文件参数maxmemory设置了Redis可用的最大空间大小时,Redis不会使用共享变量.
因为对于每一个键值都需要使用一个redisObject来记录其lru信息事物
脚本
持久化
复制 (replication)
master 读写slave 只读在从数据库的配置文件加入slaveof 主数据库地址 主数据库端口使用info replication 获取相关信息slaveof no one 使从数据库变成主数据库当一个从数据库启动后,会向主数据库发送sync命令,主数据库接收到sync命令后,开始在后台保存快照(RDB持久化),并将保存快照期间接收到的命令缓存起来,当快照完成时,Redis会将快照文件和所有缓存的命令发送给从数据库.从数据库收到后,会载入快照文件并执行收到的缓存的命令.