Redis源码-ZSet:Redis ZSet存储原理、Redis ZSet命令、 Redis ZSet两种存储底层编码ziplist/dict+skiplist、Redis ZSet应用场景
扩容的量级是原dictht的2的n次方,扩容之后,会把原表的数据重新分配到新表(rehash,重新计算hash),然后用ht[0]标记为当前使用的是哪个表,清空原先的dictht来释放空间。Redis中的Hash结构,在Redis中,所有的KV存储方式都是基于dict(外层Hash表,HashTable)这个结构去存存储的,那么如果value的编码类型也是。有序链表,根据分值去执行插入数据,会从前
Redis源码-ZSet:Redis ZSet存储原理、Redis ZSet命令、 Redis ZSet两种存储底层编码ziplist/dict+skiplist、Redis ZSet应用场景
Redis数据类型
Redis数据类型不等同与数据结构,数据结构是Redis该数据类型存储结构的存储原理,也是该数据类型的底层编码。
1.ZSet存储原理
列表list、集合set、有序集合zset差异
数据结构 | 是否允许重复元素 | 是否有序 | 有序实现方式 |
---|---|---|---|
列表list | 是 | 是 | 索引下标 |
集合set | 否 | 否 | - |
有序集合zset | 否 | 是 | 分值score |
2.1Redis-zset数据类型:操作命令
zset的命令都是前面+z开头的。
放入集合多个元素
# zadd key [NX|XX] [CH] [INCR] score member [score member ...]
# 放入集合多个元素,并给每个元素分配分值,返回集合大小
127.0.0.1:6379> zadd myzset 10 aaa 20 bbb 30 ccc 5 zzz 36 eee
(integer) 5
升序获取集合元素
# zrange key start stop [WITHSCORES]:升序获取有序集合元素
# WITHSCORES意思就是是否展示分值
127.0.0.1:6379> zrange myzset 0 -1
1) "zzz"
2) "aaa"
3) "bbb"
4) "ccc"
5) "eee"
127.0.0.1:6379> zrange myzset 0 -1 withscores
1) "zzz"
2) "5"
3) "aaa"
4) "10"
5) "bbb"
6) "20"
7) "ccc"
8) "30"
9) "eee"
10) "36"
降序获取集合元素
# zrevrange key start stop [WITHSCORES]:降序获取有序集合元素,rev就是reverse(逆转、颠倒)的缩写
# WITHSCORES意思就是是否展示分值
127.0.0.1:6379> zrevrange myzset 0 -1
1) "eee"
2) "ccc"
3) "bbb"
4) "aaa"
5) "zzz"
127.0.0.1:6379> zrevrange myzset 0 -1 withscores
1) "eee"
2) "36"
3) "ccc"
4) "30"
5) "bbb"
6) "20"
7) "aaa"
8) "10"
9) "zzz"
10) "5"
根据分值获取集合的元素
Redis Zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)次序排列。
具有相同分数值的成员按字典序来排列(该属性是有序集提供的,不需要额外的计算)。
默认情况下,区间的取值使用闭区间 (小于等于或大于等于),你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。
# zrangebyscore key min max [WITHSCORES] [LIMIT offset count]:随机获取集合的元素,count为随机获取的个数,默认1
# 返回所有符合条件 5 < score <= 30 的
127.0.0.1:6379> ZRANGEBYSCORE myzset (5 30
1) "aaa"
2) "bbb"
3) "ccc"
# 返回所有符合条件 5 < score < 30 的
127.0.0.1:6379> ZRANGEBYSCORE myzset (5 (30
1) "aaa"
2) "bbb"
# LIMIT offset count:分页偏移量,类似SQL中的limit
127.0.0.1:6379> ZRANGEBYSCORE myzset (5 (30 limit 0 1
1) "aaa"
如何指定范围区间
合法的 min 和 max 参数必须包含 ( 或者 [ , 其中 ( 表示开区间(指定的值不会被包含在范围之内), 而 [ 则表示闭区间(指定的值会被包含在范围之内)。
特殊值 + 和 - 在 min 参数以及 max 参数中具有特殊的意义, 其中 + 表示正无限, 而 - 表示负无限。 因此, 向一个所有成员的分值都相同的有序集合发送命令 ZRANGEBYLEX - + , 命令将返回有序集合中的所有元素。
删除集合的元素
# zrem key member [member ...]:删除集合的元素,返回删除成功的个数
127.0.0.1:6379> zrem myzset aaa bbb
(integer) 2
统计集合元素个数
# zcard key:统计集合元素个数
127.0.0.1:6379> zcard myzset
(integer) 3
根据分值区间统计集合元素个数
# zcount key min max:根据分值区间统计集合元素个数
127.0.0.1:6379> zcount myzset 30 70
(integer) 2
给集合的元素增加分值
# zincrby key increment member:给集合元素增加分值,返回变更后的分值
127.0.0.1:6379> zincrby myzset 7 ccc
"37"
获取集合的元素的排名
# zrank key member:获取集合的元素的排名
127.0.0.1:6379> zrank myzset ccc
(integer) 2
获取集合的元素的分值
# zscore key member:获取集合的元素的分值
127.0.0.1:6379> zscore myzset ccc
"37"
查看Redis内部的数据结构类型
当你插入Redis一个字符串数据时,会根据你插入的值,Redis再进行内部的数据转换。
可以先看本文目录:3.存储原理(底层编码)
可以用object encoding key名查看key的value再Redis的内部数据类型。
# 查看key在Redis的对内数据类型
127.0.0.1:6379> object encoding myzset
"ziplist"
3.存储原理(底层编码)
Redis源码:Redis源码怎么查看入门、Redis外部数据结构到Redis内部数据结构查看源码顺序、Redis源码查看顺序
Redis zset两种底层编码类型:ziplist、skiplist+dict
Redis zset类型的内部底层编码有两种:
根据存储内容的不同大小采取合适的编码,从而达到更好的效果
- ziplist(元素数量<128,所有元素长度小于64bytes)
- skiplist+dict
Redis zset底层编码类型①:ziplist
ziplist.c源码文件:
ziplist.c源码文件在解压后的src目录下
ziplist是什么:特殊编码的,由连续内存块组成的双向链表
ziplist是一个经过特殊编码的,由连续内存块组成的双向链表。
它不存储指向上一个链表节点和指向下一个链表节点的指针,而是存储上一个节点长度和当前节点长度。
ziplist结构:ziplist.c文件
看它的注释,这个代表了它的数据结构组成。
# ziplist数据结构组成
<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
<zlbytes> :占用内存字节数
<zltail> :列表末尾偏移量
<zllen> :节点数
<entry> <entry> ... <entry>
<zlend> :标记末端
ziplist的几种编码方式
ziplist.c文件的zlentry的源码截取
/* 并不代表实际存储方式 */
typedef struct zlentry {
unsigned int prevrawlensize; /* Bytes used to encode the previous entry len*/
/* 存储上一个链表节点的长度值所需要的字节数*/
unsigned int prevrawlen; /* Previous entry len. */
/* 上一个链表节点占用的长度 */
unsigned int lensize; /* Bytes used to encode this entry type/len.
For example strings have a 1, 2 or 5 bytes
header. Integers always use a single byte.*/
/* 存储当前链表节点长度数值所需要的字节数 */
unsigned int len; /* Bytes used to represent the actual entry.
For strings this is just the string length
while for integers it is 1, 2, 3, 4, 8 or
0 (for 4 bit immediate) depending on the
number range. */
/* 当前链表节点占用的长度 */
unsigned int headersize; /* prevrawlensize + lensize. */
/* 当前链表节点的头部大小(prevrawlensize + lensize),即非数据域的大小 */
unsigned char encoding; /* Set to ZIP_STR_* or ZIP_INT_* depending on
the entry encoding. However for 4 bits
immediate integers this can assume a range
of values and must be range-checked. */
/* 编码方式 */
unsigned char *p; /* Pointer to the very start of the entry, that
is, this points to prev-entry-len field. */
/* 压缩链表以字符串的形式保存,该指针指向当前节点起始位置 */
} zlentry;
Redis ZSet底层编码类型②:skiplist+dict
首先需要搞明白Redis外层的Hash表是什么。
Redis中的Hash结构,在Redis中,所有的KV存储方式都是基于dict(外层Hash表,HashTable)这个结构去存存储的,那么如果value的编码类型也是hashtable,应该怎么存储,其实就是hashtable的嵌套结构。
不理解dict结构、外层Hash、dict、dictht、dictEntry可以去看:
Redis源码:Redis源码怎么查看、Redis源码查看顺序、Redis外部数据结构到Redis内部数据结构查看源码顺序、Redis KV存储方式都是基于dict
dict相关
Redis的KV存储方式-dict、dictht、dictEntry关系图
在Redis中,所有的KV存储方式都是基于dict(外层Hash表,HashTable)这个结构去存储的。
外层的KV存储的HashTable(dict),结构如下
Redis 底层编码类型hashtable嵌套结构图
总体存储结构为:【dictEntry有序链表】→【放在dictEntry数组里面】→【dictEntry数组放在dictht(哈希表ht[0])里面】→【dictht又放在dict结构里面】
Hash的扩容
扩容怎么扩容的?
判断的条件:
扩容比例(源码dict.c中定义):
static unsigned int dict_force_resize_ratio = 5; /*
dictEntry数组后面的dictEntry链表,
所有节点的链表平均值超过一定程度(会计算dictEntry链表长度和dictEntry的长度的比例,这个值为5),
也就表示hash碰撞很严重,就会触发扩容。
*/
dictEntry数组后面的dictEntry链表,所有节点的链表平均值超过一定程度(会计算dictEntry链表长度和dictEntry的长度的比例,源码中这个值为5),也就表示hash碰撞很严重,就会触发扩容。
扩容的量级是原dictht的2的n次方,扩容之后,会把原表的数据重新分配到新表(rehash,重新计算hash),然后用ht[0]标记为当前使用的是哪个表,清空原先的dictht来释放空间。
skiplist相关
有序链表,根据分值去执行插入数据,会从前到后遍历一遍,才能找到合适的位置,时间复杂度O(n),与链表的长度有关,怎么优化一下查找速度?
次啊用skiplist(跳跃表、跳表):增加指针,level是随机的,每次有元素插入,会随机设置一个level的值,下次插入会优先比较level高的值,依次往下,以此优化查询速度
skiplist计算level的方法,源码t_zset.c文件
源码在t_zset.c文件下
int zslRandomLevel(void) {
int level = 1;
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
level += 1;
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
Redis为什么用skiplist,不用二分查找的其他方式,比如红黑树
单纯只是因为skiplist的实现更加的简洁
skiplist的实现:源码server.sh文件
typedef struct zskiplistNode {
sds ele; /* zset的元素 */
double score; /* 分值 */
struct zskiplistNode *backward; /* 后退指针 */
struct zskiplistLevel {
struct zskiplistNode *forward; /* 前进指针:对应level的下一个节点 */
unsigned long span; /* 从当前节点到下一个节点的跨度(跨度的节数) */
} level[]; /* 层级 */
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;/* 指向跳跃表的头节点和为节点 */
unsigned long length; /* 跳跃表的节点数 */
int level; /* 最大的层数 */
} zskiplist;
4.应用场景
动态的列表、有序的元素、可以排序(可倒序可正序)、分值可以变化
- 排行榜、热搜榜
新闻热搜榜
id为1001的新闻点击量+1:集合元素为n1001
# zincrby key increment member:给集合元素增加分值,返回变更后的分值
# hotnews:20220909-------key格式[hotnews:日期]
zincry hotnews:20220909 1 n1001
获取今天点击最多的15条:倒序,因为想要的顺序是从大到小
# zrevrange key start stop [WITHSCORES]:降序获取有序集合元素,rev就是reverse(逆转、颠倒)的缩写
# WITHSCORES意思就是是否展示分值
zrevange hotnews:20220909 0 15 WITHSCORES
Redis各数据结构命令、源码分析、应用场景
Redis源码-String:Redis String命令、Redis String存储原理、Redis String三种编码类型、Redis字符串SDS源码解析、Redis String应用场景
Redis源码-Hash:Redis String与Hash的区别、Redis Hash存储原理、Redis Hash命令、 Redis Hash存储底层编码、Redis Hash应用场景
Redis源码-List:Redis List存储原理、Redis List命令、 Redis List存储底层编码quicklist、Redis List应用场景
Redis源码-Set:Redis Set存储原理、Redis Set集合操作命令、Redis Set两种存储底层编码intset+hashtable、Redis Set应用场景
Redis源码-ZSet:Redis ZSet存储原理、Redis ZSet命令、 Redis ZSet两种存储底层编码ziplist/dict+skiplist、Redis ZSet应用场景
更多推荐
所有评论(0)