Key的设计
首先聊一下Redis的Key的设计, 一般情况下可以遵循以下原则:
- 用: 分割
- 表名作为key的前缀
- 中间段是主键值
- 尾段是列名
举个例子: user:1:username, user:1:code
数据类型
//redis db
typedef struct redisDb{
int id; //数据库序号, 0-15
long avg_ttl; // 平均ttl
dict *dict; //所有的kv
dict *expires; // 存储key的过期时间
dict *blocking_keys;//blpop存储阻塞key和客户端对象
dict *ready_keys; //阻塞后push 响应阻塞客户端,存储阻塞后push的key和客户端对象
dict *watched_keys; // 存储watch监控的key和客户端对象
}redisDb;
//redisObject
typedef struct redisObject{
unsigned type:4; //表示对象类型 String list hash set zset
unsigned encoding:4;
void *ptr; //底层实现数据结构指针
int refcount;// 引用计数
unsigned lru:LRU_BITS;// LRU_BITS为24bit 记录最后一次被命令程序访问的时间, 高16位存储一个分钟级别的时间戳, 低8位最近被访问的次数
}rObj;
String
表达3种值的类型, 字符串,整数,浮点数, encoding是 int,raw, embstr
| 命令 | 描述 | |
|---|---|---|
| set | set key value | 赋值 |
| get | get key | 取值 |
| getset | getset key value | 取值& 赋值 |
| setnx | setnx key value | 当key不存在复制 |
| append | append key value | 尾部添加 |
| strlen | strlen key | 字符串长度 |
| incr | incr key | 递增数字 |
| incrby | incrby key increment | 增加指定整数 |
| decr | decr key | 递减数字 |
| decrby | decrby key decrement | 减少指定的整数 |
SDS 存储字符串和整型数据
struct sdshdr{
int len;// 数组中已经使用的字节的数量
int free;// 数组未使用的字节的数量
char buf[]; //用于保存字符串长度
}
优势:
- 在C字符串的基础上加入了free和len字段, 获取字符串长度, SDS是O(1), C字符串是O(n)
- SDS由于记录了长度, 在可能造成缓冲去溢出时自动重新分配内存,杜绝了缓冲区溢出
- 可以存放二进制数据,字符串长度len作为结束标识
场景:
存储字符串和整型数据, 存储key, AOF缓冲区和用户缓冲区
list
存储有序,可重复的元素,最多个数2^32 -1个(40亿), encoding 是quicklist
| 命令 | 描述 | |
|---|---|---|
| lpush | lpush key v1 v2 v3 | 从左侧插入列表 |
| lpop | lpop key | 从列表左侧取出 |
| rpush | rpush key v1 v2 v3 | 从右侧插入列表 |
| rpop | rpop key | 从列表右侧取出 |
| lpushx | lpushx key value | 将值插入列表头部 |
| rpushx | rpushx key value | 将值插入列表尾部 |
| blpop | blpop key timeout | 从列表左侧取出,列表为空时阻塞 阻塞时间单位为秒 |
| brpop | brpop key timeout | 从列表右侧取出,列表为空时阻塞 阻塞时间单位为秒 |
| llen | llen key | 获得列表中元素个数 |
| lindex | lindex key index | 获取列表中下标为index的元素 |
| lrange | lrange key start end | 返回列表指定区间的元素 |
| lrem | lrem key count value | 删除列表中与value相等的元素, count >0, 从左侧删除,count<0从后边删除, count=0, 删除所有值为value的元素 |
| lset | lset key index value | 列表index位置的元素设置成value |
| ltrim | ltrim key start end | 修剪, 只保留start到end的区间 |
| rpoplpush | rpoplpush key1 key2 | 从key1列表弹出并插入到key2列表左侧 |
| brpoplpush | brpoplpush key1 key2 | 从key1列表右侧弹出并插入到key2列表左侧,阻塞 |
| linsert | linsert key BEFORE/AFTER pivot value | 将value插入到列表, 位于值pivot之前或之后 |
压缩列表ziplist: 一系列特殊编码的连续内存块组成的顺序型数据结构,节省内存,是一个字节数组,包含多个节点,每个节点保存一个字节数组或一个整数。
- zlbytes: 压缩列表的字节长度
- zltail: 压缩列表尾元素相对于压缩列表起始地址的偏移量
- zlen: 压缩列表的元素个数
- entry1.. entryx: 压缩列表的各个节点
- zlend: 压缩列表的结尾, 占一个字节 恒为0xFF(255)
entryX元素编码结果:
- previous_entry_length: 前一个元素的长度
- encoding: 当前元素编码
- content: 内容
typedef struct zlentry{
unsigned int prevrawlensize;// previous_entry_length字段长度
unsigned int prevawlen;//previous_entry_length存储内容
unsigned int lensize;//encoding 长度
unsigned int len;// 数据内容长度
unsigned int headersize;// 当前元素首部长度
unsigned char encoding;//数据类型
unsigned char *p;// 元素首地址
} zlentry;
quickList 是 linkedList 和ziplist的结合
typedef struct quicklist{
quicklistNode *head;
quicklistNode *tail;
unsigned long count;// 所有数据项个数
unsigned int len; //ziplist个数
int fill :16; //ziplist大小限定, list-max-ziplist-size;
unsigned int compress :16;//节点压缩深度设置, list-compress-depth
}quicklist;
typedef struct quicklistNode{
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *zl;// 数据指针,没被压缩指向ziplist, 压缩后指向quicklistLZF;
unsigned int sz;//指向ziplist总长度
unsigned int count :16; //ziplist中数据项
unsigned int encoding :2;// 编码方式
unsigned int container :2;// 预留字段,1--NONE, 2-ziplist
unsigned int recompress :1;//解压标记
unsigned int attepted_compress;//测试
unsigned int extra :10; //扩展字段
}quicklistNode;
quicklist每个节点的实际存储为ziplist, 节省内存。 可以对ziplist进行压缩,算法为LZF,基本思想是数据与前面重复的记录重复位置及长度,不重复记录原始数据
typedef struct quicklistLZF{
unsigned int sz;//长度
char compressed[];//数组
}quicklistLZF;
Set
无序,唯一元素, 集合中最大的成员数 2^32-1, encoding是inset 和hashtable
| 命令 | 描述 | |
|---|---|---|
| sadd | sadd key mem1 mem2 | 添加新成员 |
| srem | srem key mem1 mem2 | 删除指定成员 |
| smembers | smembers key | 获得集合中所有元素 |
| spop | spop key | 返回一个随机元素,并且删除 |
| srandmember | srandmember key | 返回一个随机元素,不会删除元素 |
| scard | scard key | 获得集合中元素的数量 |
| sismember | sismember key member | 判断集合是否在集合里 |
| sinter | sinter key1 key2 key3 | 交集 |
| sdiff | sdiff key1 key2 key3 | 差集 |
| sunion | sunion key1 key2 key3 | 并集 |
intset : 有序的整数集合, 连续存储结构, redis集合类型都是整数且处在64位有符号整数范围内
typedef struct intset{
uint32_t encoding;
uint32_t length;
int8_t contents[];
}intset;
sortedset
元素本身无序不重复,每个元素关联一个分数,可根据分数排序,分数可重复, encoding是ziplist skiplist
| 命令 | 描述 | |
|---|---|---|
| zadd | zadd key score1 member1 score2 member2 | 添加新成员 |
| zrem | zrem key mem1 mem2 | 删除指定成员 |
| zcard | zcard key | 元素数量 |
| zcount | zcount key min max | score在[min, max]区间的元素数 |
| zincrby | zincrby key increment member | 在member分值上加increment |
| zscore | zscore key member | 获取member分值 |
| zrank | zrank key member | 获取member排名,从小到大 |
| zrevrank | zrevrank key member | 获得集合中member的排名, 从大到小 |
| zrange | zrange key start end | 获取集合中指定区间成员,递增 |
| zrevrange | zrevrange key start end | 获取集合中指定区间成员, 递减 |
跳跃表: 有序链表分层
typedef struct zskiplistNode{
sds ele;
double scrore;// 分值
struct zskiplistNode *backward;// 后退指针,指向当前节点最底层的前一个节点
struct zskiplistLevel{
struct zskiplistNode * forward;//本层下个节点
unsigned int span;// 本层下个节点到本节点的元素个数
}level[];
} zskiplistNode;
typedef struct zskiplist{
struct skiplistNode *header, *tail;
unsigned long length;
int level;
}zskiplist;
优势:
- 可以快速找到节点
- 在O(1)的时间复杂复杂度下,快速获取头节点,尾节点, 长度和高度;
hash
String类型的field和value的映射表,每个hash可以存储2^32-1键值对, encoding 是 hashtable和ziplist
| 命令名称 | 描述 | |
|---|---|---|
| hset | hset key field value | 赋值 |
| hmset | hmset key field1 value1 field2 value2 | 批量赋值 |
| hsetnx | hsetnx key field value | 赋值,存在不操作 |
| hexists | hexists key field | 查看是否存在 |
| hget | hget key field | 获取一个字段值 |
| hmget | hmget key field1 field2 | 获取多个字段值 |
| hgetall | hgetall key | 获取所有字段 |
| hdel | hdel key field1 field2 | 删除指定字段 |
| hincrby | hincrby key field increment | 指定字段自增increment |
| hlen | hlen key | 获得字段数量 |
-
数组
存储数据的容器,头指针+ 偏移量快速定位数据所在的内存地址
-
Hash函数
任意长度的输入通过散列算法转换成固定类型,固定长度的散列值。
-
数组下标= hash(key)%数组容量
-
Hash冲突 redis通过链表解决
typedef struct dictht{
dictEntry **table;
unsigned long size; //哈希表数组大小
unsigned long sizemask; // 映射位置掩码, size-1
unsigned long used; // 已有节点数目包含next 单链表数据
}dictht
hash表数组初始容量为4,每次扩容为2倍, 索引值为hash值&掩码值。
typedef struct dictEntry{
void *key;
union{
void *val;
uint64_t u64;
int64_t s64;
double d;
}v;
struct dictEntry *next;
}dictEntry;
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx;
int iterators;
}
typedef struct dictType{
//计算hash值的函数
unsigned int (*hashFuction)(const void *key);
// 复制键的函数
void *(*keyDup)(void *privdata, const void *key);
//复制值的函数
vood *(*valDup)(void *privdata, const void *obj);
//比较键的函数
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
//销毁键的函数
void (*keyDestructor)(void *privdata, void *key);
//销毁值的函数
void (*valDestructor)(void *privdata, void *obj);
}dictType;
扩容过程:
- 初始申请4个dictEntry, 后面申请为当前hash表的一倍
- rehashidx=0 表示进行rehash操作
- 新增加数据在hash表h[1]
- 修改,删除,查询在老hash表h[0], 新hash表h[1]{rehash中}
- h[0]数据重新计算索引后迁移到h[1],称为rehash
渐进式hash: 服务器忙, 一个节点hash,如果闲批量hash
bitmap
位操作
| 命令 | 描述 | |
|---|---|---|
| setbit | setbit key offset value | 设置key在offset处的bit |
| getbit | getbit key offset | 获取key在offset处的bit值 |
| bitcount | bitcount key | 获取key的bit位为1的个数 |
| bitpos | bitpos key value | 返回第一个被设置成bit值的索引值 |
| bitop | bit op and [or/xor/not] destkey key [key ...] | 对多个key进行逻辑运算后存入destkey中 |
geo
处理位置信息
| 命令 | 描述 | |
|---|---|---|
| geoadd | geoadd key 经度 纬度 成员名称1 经度 纬度 成员名称2 | 添加成员 |
| geohash | geohash key 成员名称1 成员名称2 | 返回标准的geohash串 |
| geopos | geopos key 成员名称1 成员名称2 | 返回成员经纬度 |
| geodist | geodist key 成员1 成员2 单位 | 成员间距离 |
| georadiusbymember | georadiusbymember key 成员 值 单位 count asc[desc] | 查找附近的人 |
stream
可持久化的消息队列, 底层使用了listPack和Rax树
缓存过期和淘汰策略
maxmemory 默认为0, 不限制, 作为DB使用的时候配合禁止驱逐的淘汰策略.
如果要限制,一般设置物理内存的3/4。此时必须设置maxmemory-policy
删除策略
删除策略有定时删除,惰性删除和主动删除三种方式, 目前采用惰性删除和主动删除的方式
-
定时删除
设置过期时间的同时,创建定时器, 定时器在键的过期时间来临时,立即执行对键的删除操作。
-
惰性删除
在key访问时,如果发现已经失效,就删除它
-
主动删除
配置 maxmemory-policy
主动删除策略
-
LRU
在数据集中随机挑选几个键值对,取出其中lru最大的键值对淘汰。
volatile-lru: 从设置已过期时间的数据集中挑选
allkeys-lru: 从数据集中挑选
-
LFU
最不经常使用,相应的也有 volatile-Lfu和allkeys-lfu
-
random
随机,相应的也有volatile-random和allkeys-random
-
ttl
volatile-ttl, 从过期时间的表里随机挑选几个键值对, 取出其中ttl最小的键值对淘汰
-
noenviction
禁止驱逐