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类型的内部底层编码有两种:

根据存储内容的不同大小采取合适的编码,从而达到更好的效果

  1. ziplist(元素数量<128,所有元素长度小于64bytes)
  2. 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
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值