前言
最近闲来无事,入手了一本钱文品老师的《Redis深度历险-核心原理与应用实践》一书,于是便偶尔翻几页学习一下Redis,毕竟用了好几年了,但是一些底层的核心内容一直没有系统的了解过,借此机会也让自己对Redis有个更深得了解吧。现在把第一张的内容进行总结并且记录一下,自己也在下面使用笔记本记录了一些要点,但是笔记本总是放着放着不知道拿哪里去了,搞了在不同的笔记本上东记一些西记一些,这里写个学习笔记,算是归纳整理吧,放在网上,也能跟朋友们交流下。
1、Redis的基础结构:
Redis常用的数据结构分为:- string(字符串)
- list(列表)
- hash(字典)
- set(集合)
- zset(有序集合)
- Redis特殊的数据结构:
- HyperLogLog
- BitMap(位图)
- 布隆过滤器
1、string的相关知识:
1.1、string的基本概念及实现
Redis的字符串时动态字符串,是可以修改的,Redis每次申请字符串空间的时候,是采用预分配冗余空间的方式进行分配的。
其内部字符串分配的空间内存,一定是高于储存的字符串的。如果说字符串小于1M那么每次申请到的内存是储存内存的一倍,
如果字符串大于1M时,那么每次申请都是以1M 进行扩容的。注意:字符串的最大空间(字符串的长度)是512M。
而字符串的底部实现则是以位图实现的,字符串是由多个字符组成的,而一个字符则是由八个位(bit)组成的,
所以一串字符串则是由好多个8位数组组成1.2、string的相关命令
string相当于键值对,支持简单的增删改查。命令一般有:
单个操作:
-
set key
-
get key
批量操作:
-
- mset key value key value key value
- mget key key key key
> set test 牛牛 OK > get test "牛牛" > mset test0 牛牛 test1 壮壮 test2 瞳瞳 OK > mget test test0 test1 test2 1) "牛牛" 2) "牛牛" 3) "壮壮" 4) "瞳瞳" > mget test0 test1 test2 1) "牛牛" 2) "壮壮" 3) "瞳瞳"
设置过期时间:
- expire key time
- setex key time value
> expire test 1 (integer) 1 > get test (nil) > get test1 "壮壮" > expire test1 1 (integer) 1 > get test1 (nil)
不存在就添加,但是存在就不改变(返回0/1来区分是否添加成功)
- setnx key value
> setnx test2 111 (integer) 0 > setnx test3 111 (integer) 1 > get test2 "瞳瞳" > get test4 (nil)
2、List(列表)的相关知识
Redis的列表相当于java中的LinkedList
(链表还是双向链表 //TODO:根据之前的Demo写一篇链表的博客—20220300),
这意味着Redis中List的插入和删除操作是非常快的,
其时间复杂度位O(1),但是索引的速度确实很慢,时间负责都为O(n)。
Redis的列表有多种用途,
当他从右边进并从左边出的时候,可以看做是队列。
然当他左边进左边出(或者右进右出)的时候可以就可以看成是栈,
而list也可以当作延时队列进行来使用。在一侧添加消息,并在另一侧对消息进行轮询弹出进行处理。2.1、list的相关命令
推进和弹出
- rpush key values
- rpop key
- lpush key values
- lpop key
> rpush listtest C# (integer) 1 > rpush listtest java (integer) 2 > rpush listtest php (integer) 3 > rpush listtest js (integer) 4 > rpop listtest "js" > lpop listtest "C#" > rpush tt C# java php (integer) 3 > rpush tt android node go python (integer) 7 //push 可添加多个元素
遍历定位
- lindex key index
- lindex用于对列表进行遍历定位,性能消耗随着index的数值增大而增加。
lindex listtest 0 "java" > lindex listtest 5 (nil) > lindex listtest -1 "php" > lindex listtest 1 "php" //之前的命令已将 C# 和js 弹出,所以此时列表里只存在两个元素, 在坐标中,-1代表着最后一个元素。也就是最右边的元素(列表是从左到右排列)
截取保留(修剪):该命令会保留区间内的所有元素,并且删除区间外的所有元素
返回列表元素:返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素 如果start是0 而end是-1 则返回正向所有元素
//返回列表元素
- lrange key start_index end_index
//截取保留
- ltrim key start_index ; end_index
> lrange tt 0 -1 1) "C#" 2) "java" 3) "php" 4) "android" 5) "node" 6) "go" 7) "python" > ltrim tt 2 5 OK > lrange tt 0 -1 1) "php" 2) "android" 3) "node" 4) "go" //可以发现从第三个元素到第六个元素还存在于列表中,其他的已经被裁剪掉了
2.2、注意:
Redis的列表并不是普通的链表,而是快速链表(quicklist),首先在元素较少的情况下,列表是使用一块连续的内存进行储存的,这个结构是ziplist(压缩列表)。它将所有的元素都彼此紧挨着一起储存。当数据量比较多的时候,则会改成quicklist进行储存,因为普通的链表需要的附加指针空间太大,会浪费时间,还会加重内存的碎片化,。所以Redis将链表和ziplist结合组成了quicklist,就是将多个ziplist使用双向指针进行串联,既能满足快速的插入删除,又不会出现太大的冗余。
3、hash(哈希表–字典)的相关知识
Redis的hash和C#的Dictionary<,>的结构是一毛一样的。
3.1、hash的命令:
设置和获取
- hset key hkey value
- hget key heky
获取key下的全部数据
- hgetall key
获取key下的二级键值对数量
- hlen
设置二级键值对数值计数
- hincrby
4、set(集合)的相关知识
Redis的集合其实就是相当于C#中的List<T>具体泛型是什么类型的,需要看具体插入的值(这里泛型仅作比如,具体的类型没啥用)。当集合的最后一个元素被移除后,结构就会被自动删除。内存则会被回收,该结构具有去重的功能,所以一边可用于储存中奖结果名单之类的作用,可以确保用户不会被抽中两次。
4.1、set(集合)的命令
添加和获取元素 <li>sadd key values </li> <li>smembers key</li> <br> 判断一个元素是否存在 <li>sismember key value</li> <br> 查询集合中的元素数量 <li>scard key</li> <br> 随机弹出一个值 <li>spop key</li>
5、zset(有序集合)的相关知识
Redis的有序集合其实和集合的区别就是它比集合多了个score(权重概念),而集合中的元素,可以根据score的变化,进行排序, 其内部实现用的是一种叫做“跳跃列表”的数据结构。同样的有序集合中最后一个元素被移除的时候数据结构就会被删除,内存就会被回收,有序集合通常用于储存 粉丝列表,用value来保存用户Id,用score来记录关注时间(时间戳),也可储存学生的成绩,value储存学生的id,score储存成绩,个人猜测:微信的步数列表、附近的人,应该是用这个进行储存的。
5.1、zset(有序集合)的命令
添加和获取元素 <li>zadd key score values </li> 按照权重正向排出区间内的元素 <li>zrange key start_index end_index</li> <br> 按照权重逆向排出区间内的元素 <li>zrevrange key start_index end_index</li> <br> 查询集合中的元素数量 <li>zcard key</li> <br> 获取指定value的权重 <li>zscore key value</li> <br> 获取指定value的排名 <li>zrank key value</li> <br> 获取权重区间内的元素 <li>zrangebyscore key start_score end_score</li> <br> 删除一个值 <li>zrem key</li>
Redis的数据结构类型及其通用规则
1、容器类型数据结构
在Redis中 list、set、hash、zset 这四种数据结构属于容器行数据结构,他们均有一下两条通用规则<br> <li>一、如果容器不存在就创建一个,再进行操作。</li> <li>二、如果最后一个元素被取出或者删除了,那么容器就会消失,内存回收释放。
Redis的过期时间
Redis的所有数据结构都可以设置过期时间,时间到了,Redis会自动删除相应的对象,但是过期时间是以对象为单位的,所以hash并不能对某个二级key设置过期时间,还有就是如果一个字符串设置了过期时间,那么如果再次调用set命令的话过期时间会消失。
位图的应用场景及使用方式
刚听说位图的时候,觉得这是一块很难懂的玩意,又是bit又是字符串的,但是当潜下心来了解的时候发现其实一点都不难,Redids的字符串,每个英文字母是一个字节,而一个字节是八个bit,而一个字符串就是一堆以八个bit为一组的数组,每个bit不是1就是0,也可以看作非真即假,这个时候,我们可以把这个玩意儿用于储存比如内日签到记录,或者每日登记之类的场景,因为是否签到,就是非真即假,签到了我们就存个1,没签到我们就存个0。这样一年的签到记录也就是个45个字的字符串。。。。
位图的命令也很简单- setbit key offset value
- getbit key offset
> set h h OK > getbit h 0 0 > getbit h 1 1 > getbit h 2 1 > getbit h 3 0 > getbit h 4 1 > getbit h 5 0 > getbit h 6 0 > getbit h 7 0 // 01101000 /* 使用py解析下h的二进制编码 >>> bin(ord('h')) '01101000' >>> */ 可以发现一毛一样,不过!! 正常的二进制是从高位到地位的, 也就是说py解析出来的八个数字。其实是 76543210以这样排列,但是Redis中位图则是从低到高进行排列的。 所以跟正常的二进制刚好是相反的01234567 接下来我们可以通过位图添加出一个字符 setbit t 0 0 0 > setbit t 1 1 0 > setbit t 2 1 0 > setbit t 3 0 0 > setbit t 4 1 0 > setbit t 5 0 0 > setbit t 6 0 0 > setbit t 7 0 0 > get t "h" > bitcount h 3 //上面这个命令则是统计这个Key的位图里有几个1,意味着位图如果使用到签到场景的话,可以统计整个记录里面签到多少次。如果指定了参数范围,则可以查看这个范围内的签到次数,但是注意,指定的范围,只能是8的倍数,而不能任意指定。
思考:
常用的数据存储中,我们针对一个完整的结构,是直接存储json还是分开存储属性?比如用户信息。我们是把整个用户信息进行存储还是以用户id为命名空间,下面存储各个信息的单独属性。