发布时间:2025-06-24 17:58:04 作者:北方职教升学中心 阅读量:630
聚合查询等基本不可能,维护成本高.
上述这种数据的存储使用json字符串的方式也可以存储.
如果我们使用json字符串的话,只读取某一个字段的时候,需要先把json读取出来,之后解析为对象,之后对想要操作的指端进行操作,之后再重写为json字符串,之后再重新写回去.
如果使用hash的凡是来表示的时候,就可以直接使用字段中相应的值,此时就可以非常方便的修改和获取一个属性的值了.避免了对象和字符串之间来回解析的工作.
我们还可以使用原生字符串的形式表示,*使用Redis中的字符串类型,每个属性一个键值对.
setuser:1:name Jamessetuser:1:age 23setuser:1:city Beijing
这种表示方式,相当于把同一个数据给分散开表示了,这样的表现方式就是低内聚的表现方式.而使用hash来存储数据,就会把同一个对象的所有数据都保存在一起.这样的表现方式就是高内聚的.我们在编程的时候,一般使用的是高内聚的表现方式.
4. Redis中关于List的操作
4.1 概述
提到list我们会想到java中的List集合,这样的List就相当于数组或者是顺序表.
但是Redis中的List并非是一个简单的数组,更接近于双端队列(deque).因为Redis中的list支持头插,头删,尾插,尾删,还可以获取指定范围之内的元素和指定下表的元素(和python一样,支持负数下标).它可以充当栈和队列的角色.在实际开发中的应用场景非常广泛.
4.2 特点
- 列表中的元素是有序的
这里的有序,指的是list中的元素可以通过下标来访问.这里的下标可以是正数下表,可以是负数下标.和python一样,正数下标从0开始,负数下标从-1开始.正数表示的是第n+1个元素,负数表示的是倒数第n个元素. - 区分删除和获取的区别
这里之所以要区分,是因为删除和获取的返回值都是指定下标的值.容易混淆. - 列表中的元素允许重复
-
4.3 相关指令
- lpush
lpush key element [element...]
从list的左端插入指定的元素,(lpush中的l就是left的缩写). 可以一次插入多个元素,也可以一次插入一个元素.这里需要注意的是,并不是把这些所有的元素整体进行头插,而是按照顺序依次头插.比如我们要在一个list中插入1234,插入完成之后,list的头部是4321的顺序.如果key不存在,创建列表,如果已经创建了key,key对应的value必须是列表类型,否则报错. - lrange
lrange key start stop
查询列表中指定范围的所有元素.这个指令支持负数下标.其中start区间和stop区间都是闭区间.(这里的l和上面的l意义不一样,这里的l指的是list),如果想要查询list中所有元素的值,就需要使用lrange key 0 -1
这个指令.
127.0.0.1:6379>lpush key1 1234(integer)4127.0.0.1:6379>lrange key1 0-11)"4"2)"3"3)"2"4)"1"
谈到下标,我们往往就会关注下标越界的情况,在java中,要是数组或者线性表下表越界,就会抛出下表越界的异常.但是Redis中并没有采取上述的方案.Redis中的做法,==是直接尽可能的获取到给定区间的元素.==如果给定区间不合法,就会尽可能在不合法的区域中获取元素.此处对于下表的处理方式,类似与python中的列表切片.
127.0.0.1:6379>lrange key1 01001)"4"2)"3"3)"2"4)"1"
比如我们给出的范围是0~100,在这个区间内只有4321,那么Redis也只能获取4321这4个元素.
- lpushx
lpushx key element [element...]
在指定的list的左边插入元素,这个指令和lpush最大的区别就是,如果这个key不存在的时候,不会自动创建key,只可以在已经存在的key的list中头插元素.
127.0.0.1:6379>lpushx key1 4321(integer)8127.0.0.1:6379>lrange key1 0-11)"1"2)"2"3)"3"4)"4"5)"4"6)"3"7)"2"8)"1"127.0.0.1:6379>lpushx key2 4321(integer)0127.0.0.1:6379>get key2(nil)
我们从上面的代码中可以看出,如果key不存在的时候,会返回0,而且不会主动创建key.
- rpush
rpush key element [element...]
在list的右边插入指定的元素(这里的r就是right的缩写),可以一次插入多个元素,也可以一次插入一个元素.如果key不存在,创建列表,如果已经创建了key,key对应的value必须是列表类型,否则报错.
127.0.0.1:6379>rpush key 1234(integer)4127.0.0.1:6379>lrange key 0-11)"1"2)"2"3)"3"4)"4"
- rpushx
在指定的list的右边插入元素,这个指令和rpush最大的区别就是,如果这个key不存在的时候,不会自动创建key,只可以在已经存在的key的list中头插元素.
127.0.0.1:6379>rpushx key3 1234(integer)0127.0.0.1:6379>get key3(nil)127.0.0.1:6379>rpushx key 1234(integer)8127.0.0.1:6379>lrange key 0-11)"1"2)"2"3)"3"4)"4"5)"1"6)"2"7)"3"8)"4"
- lpop
lpop key
从list的最左边取出元素,即头删.
127.0.0.1:6379>lpop key"1"127.0.0.1:6379>lpop key"2"127.0.0.1:6379>lrange key 0-11)"3"2)"4"3)"1"4)"2"5)"3"6)"4"
- rpop
从list的右边取出元素,即尾删
127.0.0.1:6379>rpop key"4"127.0.0.1:6379>rpop key"3"127.0.0.1:6379>lrange key 0-11)"3"2)"4"3)"1"4)"2"
在Redis5的版本中,没有设置count参数,即一次可以pop元素的个数,但是从6.2开始,新增了一个count参数,可以指定pop元素的个数.比如
rpop key [count]
.
Redis中的list相当于一个双端队列,从两头插入/删除元素都非常高效,搭配使用rpush和lpop就相当于队列,搭配使用rpush和rpop就相当于栈.
- lindex
lindex key index
获取指定位置的元素.如果下标是非法的,返回的就是nil
127.0.0.1:6379>lrange key 0-11)"3"2)"4"3)"1"4)"2"127.0.0.1:6379>lindex key 2"1"127.0.0.1:6379>lindex key 100(nil)
- linsert
linsert key <before|after> pivot element
在list中指定元素的位置插入指定的元素.可以在指定元素的左边插入,也可以在指定元素的右边插入.在查找list中指定元素的时候,如果在list中找到了多个基准值,值插入第一个.返回值是插入之后得到的心得list长度.
127.0.0.1:6379>rpush key 44(integer)6127.0.0.1:6379>lrange key 0-11)"3"2)"4"3)"1"4)"2"5)"4"6)"4"127.0.0.1:6379>linsert key before 422(integer)7127.0.0.1:6379>lrange key 0-11)"3"2)"22"3)"4"4)"1"5)"2"6)"4"7)"4"127.0.0.1:6379>linsert key after 422(integer)8127.0.0.1:6379>lrange key 0-11)"3"2)"22"3)"4"4)"22"5)"1"6)"2"7)"4"8)"4"
- llen
llen key
获取list的长度
127.0.0.1:6379>lrange key 0-11)"3"2)"22"3)"4"4)"22"5)"1"6)"2"7)"4"8)"4"127.0.0.1:6379>llen key(integer)8
- lrem
lrem key count element
删除list中指定的值,其中count是要删除的个数,element是要删除的元素.
其中,count > 0的时候,从list的左边开始删除指定个数的元素,count = 0的时候,将指定的元素全部删除.当count < 0的时候,从list的右边开始删除指定个数的元素.
127.0.0.1:6379>lrem key 14(integer)1127.0.0.1:6379>lrange key 0-11)"3"2)"22"3)"22"4)"1"5)"2"6)"4"7)"4"127.0.0.1:6379>lrem key -24(integer)2127.0.0.1:6379>lrange key 0-11)"3"2)"22"3)"22"4)"1"5)"2"127.0.0.1:6379>lpush key 444(integer)8127.0.0.1:6379>lrem key 04(integer)3127.0.0.1:6379>lrange key 0-11)"3"2)"22"3)"22"4)"1"5)"2"
- ltrim
itrim key start stop
保留指定区间(闭区间)之内的元素,其他元素全部删除.
127.0.0.1:6379>lrange key 0-11)"3"2)"22"3)"22"4)"1"5)"2"127.0.0.1:6379>ltrim key 13OK127.0.0.1:6379>lrange key 0-11)"22"2)"22"3)"1"
- lset
lset key index element
将指定下标的元素修改为指定元素.
注意: 这里不像lrange那样下标可以越界,这里的下标如果越界了,会报错.
127.0.0.1:6379>lrange key 0-11)"22"2)"22"3)"1"127.0.0.1:6379>lset key 144OK127.0.0.1:6379>lset key 555(error)ERR index out of range127.0.0.1:6379>lrange key 0-11)"22"2)"44"3)"1"
4.4 阻塞版本指令
4.4.1 特点概述
- 阻塞指的就是: 当前线程的代码不会继续执行,会在满足一定条件之后唤醒.但是只有当前客户端表现为阻塞状态,不会影响到其他客户端的Redis指令的执行.
- 如果list中存在元素,blpop,brpop的效果和不加b(这里的b就是block的缩写)完全一样.如果list不存在元素,blpop,brpop就会阻塞等待,一直阻塞到list不为空或者是超时为止.
说到这里,我们可以回忆一下我们java中的BlockingQueue.java中的阻塞队列主要用于生产者消费者模型.它一方面主要用于生产者和消费者的削峰填谷,另一方面用于解除生产者和消费者的强耦合.
- Redis这里的list与阻塞相关的指令也是一样.只是这里的线程安全是通过单线程来实现的,阻塞只支持"队列为空"的情况,不支持"队列为满"的情况,和阻塞相关的指令只有pop相关的操作.
- 这里的阻塞等待也不是无休止的阻塞等待,我们可以自定义阻塞等待的时间.
- 命令中如果设置了多个键,会从头到尾遍历所有的键,一旦有个一键对应的列表中可以弹出元素,命令立即返回.也就是blpop和brpop都可以同时尝试获取多个key的列表元素,多个key对应多个list,哪个list中有元素,就返回哪个元素.
- 如果有多个客户端同时对一个键执行pop,则最先执行命令的客户端会得到弹出的元素.
4.4.2 指令
- blpop
blpop key [key...] timeout
指定要弹出元素的list对应的key,这个key可以有多个,可以只有一个,命令中如果设置了多个键,会从头到尾遍历所有的键,一旦有个一键对应的列表中可以弹出元素,命令立即返回.可以指定阻塞等待的时间,如果在等待时间之内list中新增了元素,则立即返回.如果超时,则返回nil.如果获取到元素,返回的值是一个二元组,分别是得到返回值的key(也就是告诉我们当前数据来自于哪个key)和list中弹出的值(数据是什么).
127.0.0.1:6379>lpush key2 100(integer)1127.0.0.1:6379>blpop key2 51)"key2"2)"100"//有元素立即弹出,无需等待127.0.0.1:6379>lpush key3 100(integer)1127.0.0.1:6379>lpush key4 100(integer)1127.0.0.1:6379>blpop key2 key3 key4 51)"key3"//弹出第一个获取到元素的key中的元素2)"100"127.0.0.1:6379>blpop key3 5(nil)(5.04s)//如果指定的key中没有元素,阻塞等待,最后返回nil//客户端1127.0.0.1:6379>blpop key3 1001)"key3"2)"1"(33.40s)//客户端2127.0.0.1:6379>lpush key3 1(integer)1//在一个客户端阻塞的期间,不影响其他客户端的命令执行//在等待期间,在客户端2中对key3插入元素,只要key3中有了元素,客户端1中阻塞等待的命令就会立即返回
- brpop
和blpop是同样的道理,只不过是尾删.
4.5 指令小结
5. list内部编码方式
在旧版本的Redis中,Redis中的编码方式分为了两种,一种是ziplist,一种是linkedlist.
- ziplist
这个ziplist的编码方式和hash中的ziplist的编码方式的限制条件是一样的,在list中的元素较少的时候(默认512个),或者是list中每个元素很短(默认不超过64字节).按照这种编码方式可以节省一定的空间,但是缺点就是读取的时候是比较慢的. - linkedlist
在list内部的元素个数和每个元素的字节长度超过阈值的时候,就会转换为linkedlist.
但是在新版本中的Redis中,list的编码方式并不是采用这样的方式,它采用的是quicklist的方式.
关于quicklist,它相当于结合了ziplist和linkedlist两者的优点.整体还是一个链表,但是每个链表的结点是一个压缩链表.每个压缩列表都不是很大,这些研所列表是通过链式结构连接起来的.
在之前配置文件中的list-max-ziplist-entries和list-max-ziplist-value这两个属性由于list底层编码方式的改变,现在都不再使用了.
6. list的应用场景
- 用list作为想数组这样的结构来存储数据
假如我们有这样两种数据,一种是班级,一种是学生.
在Redis中,我们存储学生信息的时候使用的是hash的方式来存储的,中存储的是学生的id,value中的hash中存储的是学生的各种不同的信息.存储班级信息的时候,key中存储的是class的id,value中存储的是该班级中有多少人.但是我们想要存储class中有哪些学生,我们就需要list存储,在key中存储的是class的id,在value中使用list存储的是学生的id.
由于Redis只能存储键值对,并不像MySQL中使用表存储数据,并提供了丰富的查询功能,所以在Redis中如何组织数据,就要根据实际的业务场景来决定.
- 用作消息队列
list中的阻塞命令可以为Redis作为消息队列提供很好的支持.和java一样,Redis作为消息队列的时候,一般也是用于生产者消费者模型.
这里我们假如生产者只有一个,那么生产者就会把信息交给Redis中的列表,其中列表就是消息队列,消费者有好多个.==这里消费者在获取信息的时候,是通过轮训的方式来获取信息.==谁先执行的drpop,谁就可以先获取到信息,此消费者获取完信息之后,假如还想要使用brpop获取信息,就会到达消费者优先级的末尾.比如消费者执行的顺序是123,在1消费者执行完成之后,1消费者想要再次获取消息队列中的元素,这时候就要排到消费者优先级的末尾,也就是按照231的方式执行的.消费者2也是同样的道理.
具体说明: 买包子
假如滑稽老铁有一天想吃包子,他要去包子摊买包子,轮到他的时候,他买了2个包子,刚一出队列,一想到自己还没有给女票带早饭,又想返回头去再买一个包子,这时候滑稽老铁一定是不可以插回原来的位置的,他只能到队尾再次进行排队.
- 分频道的消息队列
我们在Redis中,可能不止一个列表在作为消息队列,可能有多个消息队列,不同的消息队列中保存的是不同主题的消息.一个消费者可以从多个频道中获取信息.每一个消息队列都和上面我们提到的消息队列的特点是一样的.这样搞成多个频道的消息队列,可以在某种主题的数据发生问题的时候,不会对其他的频道造成影响.