1 Redis 简介
Redis 是一个key-value的存储系统,使用ANSI C语言编写,key的类型是字符串,value的类型有:string字符串类型,list列表类型,set集合类型,sortset有序集合类型,hash类型,bitmap类型,geo地图坐标类型,stream流类型。Reids的命令不区分大小写,key value 区分大小写
2 Redis 底层数据结构
2.1 RedisDB 结构
typedef struct redisDb {
int id; //id是数据库序号,为0-15(默认Redis有16个数据库)
long avg_ttl; //存储的数据库对象的平均ttl(time to live),用于统计
dict *dict; //存储数据库所有的key-value
dict *expires; //存储key的过期时间
dict *blocking_keys;//blpop 存储阻塞key和客户端对象
dict *ready_keys;//阻塞后push 响应阻塞客户端 存储阻塞后push的key和客户端对象
dict *watched_keys;//存储watch监控的的key和客户端对象
} redisDb;
2.2 RedisObject 结构
typedef struct redisObject {
unsigned type:4;//类型 五种对象类型 REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。
unsigned encoding:4;//编码 4表示位数
void *ptr;//指向底层实现数据结构的指针,指向具体数据
//...
int refcount;//引用计数
//...
unsigned lru:LRU_BITS; //LRU_BITS为24bit 记录最后一次被命令程序访问的时间
//高16位存储一个分钟数级别的时间戳,低8位存储访问计数(lfu : 最近访问次数)
//...
} robj;
## 获取对象类型与编码
type key 返回对象类型(String...)
object encoding key 获取编码
3 Redis 数据类型以及使用场景
- String类型(放置字符串,整数,浮点数)
常用命令:
set key value 赋值k-v
get key 获取key对应value
getset key value 取值并赋值
setnx key value 当value不存在时采用赋值 【分布式锁】
set key value NX PX 3000 原子操作,px 设置毫秒数
append key value 尾部追加值
strlen key 获取字符串长度
incr key 递增数字 【乐观锁】
incrby key increment 增加指定整数
decr key 递减数字
decrby key decrement 减少指定整数
对应encoding:int(int数据类型) embstr(长度小于44个字节) raw(长度大于44个字节)
2. list列表类型(存储有序,可重复元素)
lpush key v1 v2 v3 ... 从左侧插入列表
lpop key 从列表左侧取出
rpush key v1 v2 v3 ... 从右侧插入列表
rpop key 从列表右侧取出
lpushx key value 将值插入到列表头部
rpushx key value 将值插入到列表尾部
blpop key timeout 从列表左侧取出,当列表为空时阻塞,可以设置最大阻塞时间,单位为秒
llen key 获取列表元素个数
lindex key index 获取下标index的元素
lrange key start end 返回列表中指定区间的元素,区间通过start和end指定
lrem key count value 删除列表中与value相等的元素
lset key index value 将列表index位置的元素设置成value的值
ltrim key start end 对列表进行修剪,保留start和end区间
rpoplpush key1 key2 从key1列表右侧弹出并插入到key2列表左侧
brpoplpush key1 key2 从key1列表右侧弹出并插入到key2列表左侧,会阻塞
linsert key BEFORE/AFTER pivot value 将value插入到列表,且位于值pivot之前或之后
对应encoding:quicklist(快速列表)
3. set 集合类型(无序,唯一元素)
sadd key mem1 mem2 ... 集合添加成员
srem key mem1 mem2 .... 删除集合中指定成员
smembers key 获得集合中所有元素
spop key 返回集合中一个随机元素,并将该元素删除
srandmember key 返回集合中一个随机元素,不会删除该元素
scard key 获得集合中元素的数量
sismember key member 判断元素是否在集合内
sinter key1 key2 key3 求多集合的交集
sdiff key1 key2 key3 求多集合的差集
sunion key1 key2 key3 求多集合的并集
对应encoding:intset(元素都是整数并且都处在64位有符号整数范围内) dict(元素都是整数并且都处在64位有符号整数范围外)
4. sortedset 有序集合(分数排序,分数可重复)
zadd key score1 member1 score2 member2 ... 为有序集合添加新成员
zrem key mem1 mem2 ... 删除有序集合中指定成员
zcrad key 获得有序集合中的元素数量
zcount key min max 返回集合中score值在[min,max]区间的元素数量
zincrby key increment member 在集合的member分值上加increment
zscore key member 获得集合中member的分值
zrank key member 获得集合中member的排名(按分值从小到大)
zrevrank key member 获得集合中member的排名(按分值从大到小)
zrange key start end 获得集合中指定区间成员,按分数递增排序
zrevrange key start end 获得集合中指定区间成员,按分数递减排序
对应enconding:ziplist(元素的个数比较少,且元素都是小整数或短字符串时) skiplist+dict(元素的个数比较多或元素不是小整数或短字符串时)
5. hash类型( 散列表 )
hset key field value 赋值,不区别新增或修改
hmset field1 value1 field2 value2 批量赋值
hsetnx key field value 赋值,如果filed存在则不操作
hexists key filed 查看某个field是否存在
hget key filed 获取一个字段值
hmget key field1 field2 ... 获取多个字段值
hgetall key 获取所有
hdel key field1 field2... 删除指定字段
hincrby key field increment 指定字段自增increment
hlen key 获得字段数量
对应encoding:dict(元素的个数比较多或元素不是小整数或短字符串) ziplist(散列表元素的个数比较少,且元素都是小整数或短字符串时)
6. bitmap位图类型 (签到,统计活跃,用户在线状态)
setbit key offset value 设置key在offset处的bit值(只能是0或者1)。
getbit key offset 获得key在offset处的bit值
bitcount key 获得key的bit位为1的个数
bitpos key value 返回第一个被设置为bit值的索引值
bitop and[or/xor/not] destkey key [key …] 对多个key 进行逻辑运算后存入destkey中
7. geo地理位置类型 (记录地理位置,计算距离,查找附近的人,本质key value)
geoadd key 经度 纬度 成员名称1 经度1 纬度1 成员名称2 经度2 纬度 2 ... 添加地理坐标
geohash key 成员名称1 成员名称2... 返回标准的geohash串
geopos key 成员名称1 成员名称2... 返回成员经纬度
geodist key 成员1 成员2 单位 计算成员间距离
georadiusbymember key 成员 值单位 count 数 asc[desc] 根据成员查找附近的成员
8. stream 数据流类型
1xadd key id <*> field1 value1.... 将指定消息数据追加到指定队列(key)中,*表示最新生成的id(当前时间+序列号)
xread [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...] 从消息队列中读取,COUNT:读取条数,BLOCK:阻塞读(默认不阻塞)key:队列名称 id:消息id
xrange key start end [COUNT] 读取队列中给定ID范围的消息 COUNT:返回消息条数(消息id从小到大)
xrevrange key start end [COUNT] 读取队列中给定ID范围的消息 COUNT:返回消息条数(消息id从大到小)
xdel key id 删除队列的消息
xgroup create key groupname id 创建一个新的消费组
xgroup destory key groupname 删除指定消费组
xgroup delconsumer key groupname cname 删除指定消费组中的某个消费者
xgroup setid key id 修改指定消息的最大id
xreadgroup group groupname consumer COUNT streams key 从队列中的消费组中创建消费者并消费数据consumer不存在则创建)
对应encoding:主要使用了listpack(紧凑列表)和Rax树(基数树)
-
listpack表示一个字符串列表的序列化,listpack可用于存储字符串或整数。用于存储stream的消息内容。
-
Rax 是一个有序字典树 (基数树 Radix Tree),按照 key 的字典序排列,支持快速地定位、插入和删除操作。用于存储消息队列,在 Stream 里面消息 ID 的前缀是时间戳 + 序号,这样的消息可以理解为时间序列消息。使用 Rax 结构 进行存储就可以快速地根据消息 ID 定位到具体的消息,然后继续遍历指定消息 之后的所有消息。
9. HyperLogLog
PFADD KEY VALUE1 VALUE2 VALUE3
PFCOUNT KEY
PFMERGE destkey sourcekey [sourcekey ...]
Redis 的基数统计,这个结构可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP数、页面实时UV)、在线用户数等。但是它也有局限性,就是只能统计数量,而没办法去知道具体的内容是什么。
4 Redis 各个数据类型对应的数据结构
string:int(int数据类型) embstr(长度小于44个字节) raw(长度大于44个字节)
list:quicklist(快速列表)
hash:dict(元素的个数比较多或元素不是小整数或短字符串) ziplist(散列表元素的个数比较少,且元素都是小整数或短字符串时)
set:intset(元素都是整数并且都处在64位有符号整数范围内) dict(元素都是整数并且都处在64位有符号整数范围外)
zset:ziplist(元素的个数比较少,且元素都是小整数或短字符串时) skiplist+dict(元素的个数比较多或元素不是小整数或短字符串时)
4.1 字符串对象 SDS
## 作用 存储字符串和整型数据、存储key、AOF缓冲区和用户输入缓冲。
struct sdshdr{
//记录buf数组中已使用字节的数量
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
4.2 跳跃表
## 作用 有序集合(sorted-set)底层实现,效率高,实现简单
思想:将有序链表的部分节点分层,每一层都是一个有序链表
//跳跃表节点
typedef struct zskiplistNode {
sds ele; /* 存储字符串类型数据 redis3.0版本中使用robj类型表示, 但是在redis4.0.1中直接使用sds类型表示 */
double score;//存储排序的分值
struct zskiplistNode *backward;//后退指针,指向当前节点最底层的前一个节点 /* 层,柔性数组,随机生成1-64的值 */
struct zskiplistLevel {
struct zskiplistNode *forward; //指向本层下一个节点
unsigned int span;//本层下个节点到本节点的元素个数
} level[];
} zskiplistNode;
//链表
typedef struct zskiplist{
//表头节点和表尾节点
structz skiplistNode *header, *tail;
//表中节点的数量
unsigned long length;
//表中层数最大的节点的层数
int level;
} zskiplist;
## 插入时,以类似投硬币概率判断方式决定是否插入下一层
## 删除时,删除所有层中有该数据
## 查找时,按照层次查找
4.3 字典(散列表)
## 数组:用来存储数据的容器,采用头指针+偏移量的方式能够以O(1)的时间复杂度定位到数据所在的内存地址。
## Hash(散列),作用是把任意长度的输入通过散列算法转换成固定类型、固定长度的散列值。
## 数组下标=hash(key)%数组容量(hash值%数组容量得到的余数)
## 采用单链表在相同的下标位置处存储原始key和value
字典(dict) --> hash 表(dictht) --> hash表节点(dictEntry)
## dictht 哈希表
typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表数组的大小
unsigned long sizemask; // 用于映射位置的掩码,值永远等于(size-1)
unsigned long used; // 哈希表已有节点的数量,包含next单链表数据
} dictht;
## dictEntry hash表节点
typedef struct dictEntry {
void *key; // 键
union { // 值v的类型可以是以下4种类型
void *val;
uint64_t u64;
int64_t s64; double d;
} v;
struct dictEntry *next; // 指向下一个哈希表节点,形成单向链表 解决hash冲突
} dictEntry;
## dict 字典
typedef struct dict {
dictType *type; // 该字典对应的特定操作函数
void *privdata; // 上述类型函数对应的可选参数
dictht ht[2]; /* 两张哈希表,存储键值对数据,ht[0]为原生 哈希表, ht[1]为 rehash 哈希表 */
long rehashidx; /*rehash标识 当等于-1时表示没有在 rehash, 否则表示正在进行rehash操作,存储的值表示 hash表 ht[0]的rehash进行到哪个索引值 (数组下标)*/
int iterators; // 当前运行的迭代器数量
} dict;
## dicttype
typedef struct dictType {
// 计算哈希值的函数
unsigned int (*hashFunction)(const void *key); // 复制键的函数
void *(*keyDup)(void *privdata, const void *key); // 复制值的函数
void *(*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.4 压缩列表 ziplist
由一系列特殊编码的连续内存块组成的顺序型数据结构
-
sorted-set和hash元素个数少且是小整数或短字符串(直接使用)
-
list用快速链表(quicklist)数据结构存储,而快速链表是双向列表与压缩列表的组合。(间接使用)
ziplist结构体 typedef struct zlentry { unsigned int prevrawlensize; //previous_entry_length字段的长度 unsigned int prevrawlen; //previous_entry_length字段存储的内容 unsigned int lensize; //encoding字段的长度 unsigned int len; //数据内容长度 unsigned int headersize; //当前元素的首部长度,即previous_entry_length字段长 度与 encoding字段长度之和。 unsigned char encoding; //数据类型 unsigned char *p; //当前元素首地址 } zlentry;
4.5 整数集合intset
一个有序的(整数升序)、存储整数的连续存储结构
typedef struct intset{
//编码方式
uint32_t encoding;
//集合包含的元素数量
uint32_t length;
//保存元素的数组
int8_t contents[];
}intset;
4.6 快速列表
Redis底层重要的数据结构。是列表(3.2 之前采用双向链表和压缩列表实现,3.2以后quicklist)的底层实现。列表(List)的底层实现、发布与订阅、慢查询、监视器等功能quicklist是一个双向链表,链表中的每个节点时一个ziplist结构。quicklist中的每个节点ziplist都能够存储多个数据元素。
## 快速列表
typedef struct quicklist {
quicklistNode *head; // 指向quicklist的头部
quicklistNode *tail; // 指向quicklist的尾部
unsigned long count; // 列表中所有数据项的个数总和
unsigned int len; // quicklist节点的个数,即ziplist的个数
int fill : 16; // ziplist大小限定,由list-max-ziplist-size给定 (Redis设定)
unsigned int compress : 16; // 节点压缩深度设置,由list-compress-depth给定(Redis 设定)
} quicklist;
##
typedef struct quicklistNode {
struct quicklistNode *prev; // 指向上一个ziplist节点
struct quicklistNode *next; // 指向下一个ziplist节点
unsigned char *zl; // 数据指针,如果没有被压缩,就指向ziplist结构,反之指 向 quicklistLZF结构
unsigned int sz; // 表示指向ziplist结构的总长度(内存占用长度)
unsigned int count : 16; // 表示ziplist中的数据项个数
unsigned int encoding : 2; // 编码方式,1--ziplist,2--quicklistLZF
unsigned int container : 2; // 预留字段,存放数据的方式,1--NONE,2--ziplist
unsigned int recompress : 1; // 解压标记,当查看一个被压缩的数据时,需要暂时解压,标 记此参数为 1,之后再重新进行压缩
unsigned int attempted_compress : 1; // 测试相关
unsigned int extra : 10; // 扩展字段,暂时没用
} quicklistNode;
## 数据压缩
typedef struct quicklistLZF {
unsigned int sz; // LZF压缩后占用的字节数
char compressed[]; // 柔性数组,指向数据部分
} quicklistLZF;
4.7 流对象(消息、生产者、消费者和消费组构成)
主要使用了listpack(紧凑列表)和Rax树(基数树)
-
listpack表示一个字符串列表的序列化,listpack可用于存储字符串或整数。用于存储stream的消息内容。
-
Rax 是一个有序字典树 (基数树 Radix Tree),按照 key 的字典序排列,支持快速地定位、插入和删除操作。用于存储消息队列,在 Stream 里面消息 ID 的前缀是时间戳 + 序号,这样的消息可以理解为时间序列消息。使用 Rax 结构 进行存储就可以快速地根据消息 ID 定位到具体的消息,然后继续遍历指定消息 之后的所有消息。