一文读懂Redis五种数据类型及应用场景
1。内容大纲
Redis提供了丰富的数据类型,常见的有五种:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。
随着Redis版本的更新,后面又支持了四种数据类型:BitMap(2。2版新增)、HyperLogLog(2。8版新增)、GEO(3。2版新增)、Stream(5。0版新增)。
每种数据对象都各自的应用场景,你能说出它们各自的应用场景吗?这次我们就来学习Redis数据类型的使用以及应用场景。
2。String2。1介绍
String是最基本的keyvalue结构,key是唯一标识,value是具体的值,value其实不仅是字符串,也可以是数字(整数或浮点数),value最多可以容纳的数据长度是512M。
2。2内部实现
String类型的底层的数据结构实现主要是int和SDS(简单动态字符串)。
SDS和我们认识的C字符串不太一样,之所以没有使用C语言的字符串表示,因为SDS相比于C的原生字符串:SDS不仅可以保存文本数据,还可以保存二进制数据。因为SDS使用len属性的值而不是空字符来判断字符串是否结束,并且SDS的所有API都会以处理二进制的方式来处理SDS存放在buf〔〕数组里的数据。所以SDS不光能存放文本数据,而且能保存图片、音频、视频、压缩文件这样的二进制数据。SDS获取字符串长度的时间复杂度是O(1)。因为C语言的字符串并不记录自身长度,所以获取长度的复杂度为O(n);而SDS结构里用len属性记录了字符串长度,所以复杂度为O(1)。Redis的SDSAPI是安全的,拼接字符串不会造成缓冲区溢出。因为SDS在拼接字符串之前会检查SDS空间是否满足要求,如果空间不够会自动扩容,所以不会导致缓冲区溢出的问题。
字符串对象的内部编码(encoding)有3种:int、raw和embstr。
如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void转换成long),并将字符串对象的编码设置为int。
如果字符串对象保存的是一个字符串,并且这个字符申的长度小于等于32字节(redis2。版本),那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串,并将对象的编码设置为embstr,embstr编码是专门用于保存短字符串的一种优化编码方式:
如果字符串对象保存的是一个字符串,并且这个字符串的长度大于32字节(redis2。版本),那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串,并将对象的编码设置为raw:
注意,embstr编码和raw编码的边界在redis不同版本中是不一样的:redis2。是32字节redis3。04。0是39字节redis5。0是44字节
可以看到embstr和raw编码都会使用SDS来保存值,但不同之处在于embstr会通过一次内存分配函数来分配一块连续的内存空间来保存redisObject和SDS,而raw编码会通过调用两次内存分配函数来分别分配两块空间来保存redisObject和SDS。Redis这样做会有很多好处:embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次;释放embstr编码的字符串对象同样只需要调用一次内存释放函数;因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面可以更好的利用CPU缓存提升性能。
但是embstr也有缺点的:如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,所以embstr编码的字符串对象实际上是只读的,redis没有为embstr编码的字符串对象编写任何相应的修改程序。当我们对embstr编码的字符串对象执行任何修改命令(例如append)时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令。2。3常用指令
普通字符串的基本操作:设置keyvalue类型的值SETnamelinOK根据key获得对应的valueGETnamelin判断某个key是否存在EXISTSname(integer)1返回key所储存的字符串值的长度STRLENname(integer)3删除某个key对应的值DELname(integer)1
批量设置:批量设置keyvalue类型的值MSETkey1value1key2value2OK批量获取多个key对应的valueMGETkey1key21)value12)value2
计数器(字符串的内容为整数的时候可以使用):设置keyvalue类型的值SETnumber0OK将key中储存的数字值增一INCRnumber(integer)1将key中存储的数字值加10INCRBYnumber10(integer)11将key中储存的数字值减一DECRnumber(integer)10将key中存储的数字值键10DECRBYnumber10(integer)0
过期(默认为永不过期):设置key在60秒后过期(该方法是针对已经存在的key设置过期时间)EXPIREname60(integer)1查看数据还有多久过期TTLname(integer)51设置keyvalue类型的值,并设置该key的过期时间为60秒SETkeyvalueEX60OKSETEXkey60valueOK
不存在就插入:不存在就插入(notexists)SETNXkeyvalue(integer)12。4应用场景2。4。1缓存对象
使用String来缓存对象有两种方式:直接缓存整个对象的JSON,命令例子:SETuser:1{name:xiaolin,age:18}。采用将key进行分离为user:ID:属性,采用MSET存储,用MGET获取各属性值,命令例子:MSETuser:1:namexiaolinuser:1:age18user:2:namexiaomeiuser:2:age20。2。4。2常规计数
因为Redis处理命令是单线程,所以执行命令的过程是原子的。因此String数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。
比如计算文章的阅读量:初始化文章的阅读量SETaritcle:readcount:10010OK阅读量1INCRaritcle:readcount:1001(integer)1阅读量1INCRaritcle:readcount:1001(integer)2阅读量1INCRaritcle:readcount:1001(integer)3获取对应文章的阅读量GETaritcle:readcount:100132。4。3分布式锁
SET命令有个NX参数可以实现key不存在才插入,可以用它来实现分布式锁:如果key不存在,则显示插入成功,可以用来表示加锁成功;如果key存在,则会显示插入失败,可以用来表示加锁失败。
一般而言,还会对分布式锁加上过期时间,分布式锁的命令如下:SETlockkeyuniquevalueNXPX10000lockkey就是key键;uniquevalue是客户端生成的唯一的标识;NX代表只在lockkey不存在时,才对lockkey进行设置操作;PX10000表示设置lockkey的过期时间为10s,这是为了避免客户端发生异常而无法释放锁。
而解锁的过程就是将lockkey键删除,但不能乱删,要保证执行操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的uniquevalue是否为加锁客户端,是的话,才将lockkey键删除。
可以看到,解锁是有两个操作,这时就需要Lua脚本来保证解锁的原子性,因为Redis在执行Lua脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。释放锁时,先比较uniquevalue是否相等,避免锁的误释放ifredis。call(get,KEYS〔1〕)ARGV〔1〕thenreturnredis。call(del,KEYS〔1〕)elsereturn0end
这样一来,就通过使用SET命令和Lua脚本在Redis单节点上完成了分布式锁的加锁和解锁。2。4。4共享Session信息
通常我们在开发后台管理系统时,会使用Session来保存用户的会话(登录)状态,这些Session信息会被保存在服务器端,但这只适用于单系统应用,如果是分布式系统此模式将不再适用。
例如用户一的Session信息被存储在服务器一,但第二次访问时用户一被分配到服务器二,这个时候服务器并没有用户一的Session信息,就会出现需要重复登录的问题,问题在于分布式系统每次会把请求随机分配到不同的服务器。
分布式系统单独存储Session流程图:
因此,我们需要借助Redis对这些Session信息进行统一的存储和管理,这样无论请求发送到那台服务器,服务器都会去同一个Redis获取相关的Session信息,这样就解决了分布式系统下Session存储的问题。
分布式系统使用同一个Redis存储Session流程图:
3。List3。1介绍
List列表是简单的字符串列表,按照插入顺序排序,可以从头部或尾部向List列表添加元素。
列表的最大长度为2321,也即每个列表支持超过40亿个元素。3。2内部实现
List类型的底层数据结构是由双向链表或压缩列表实现的:如果列表的元素个数小于512个(默认值,可由listmaxziplistentries配置),列表每个元素的值都小于64字节(默认值,可由listmaxziplistvalue配置),Redis会使用压缩列表作为List类型的底层数据结构;如果列表的元素不满足上面的条件,Redis会使用双向链表作为List类型的底层数据结构;
但是在Redis3。2版本之后,List数据类型底层数据结构就只由quicklist实现了,替代了双向链表和压缩列表。3。3常用命令
将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面LPUSHkeyvalue〔value。。。〕将一个或多个值value插入到key列表的表尾(最右边)RPUSHkeyvalue〔value。。。〕移除并返回key列表的头元素LPOPkey移除并返回key列表的尾元素RPOPkey返回列表key中指定区间内的元素,区间以偏移量start和stop指定,从0开始LRANGEkeystartstop从key列表表头弹出一个元素,没有就阻塞timeout秒,如果timeout0则一直阻塞BLPOPkey〔key。。。〕timeout从key列表表尾弹出一个元素,没有就阻塞timeout秒,如果timeout0则一直阻塞BRPOPkey〔key。。。〕timeout3。4应用场景3。4。1消息队列
消息队列在存取消息时,必须要满足三个需求,分别是消息保序、处理重复的消息和保证消息可靠性。
Redis的List和Stream两种数据类型,就可以满足消息队列的这三个需求。我们先来了解下基于List的消息队列实现方法,后面在介绍Stream数据类型时候,在详细说说Stream。
1、如何满足消息保序需求?
List本身就是按先进先出的顺序对数据进行存取的,所以,如果使用List作为消息队列保存消息的话,就已经能满足消息保序的需求了。
List可以使用LPUSHRPOP(或者反过来,RPUSHLPOP)命令实现消息队列。
生产者使用LPUSHkeyvalue〔value。。。〕将消息插入到队列的头部,如果key不存在则会创建一个空的队列再插入消息。消费者使用RPOPkey依次读取队列的消息,先进先出。
不过,在消费者读取数据时,有一个潜在的性能风险点。
在生产者往List中写入数据时,List并不会主动地通知消费者有新消息写入,如果消费者想要及时处理消息,就需要在程序中不停地调用RPOP命令(比如使用一个while(1)循环)。如果有新消息写入,RPOP命令就会返回结果,否则,RPOP命令返回空值,再继续循环。
所以,即使没有新消息写入List,消费者也要不停地调用RPOP命令,这就会导致消费者程序的CPU一直消耗在执行RPOP命令上,带来不必要的性能损失。
为了解决这个问题,Redis提供了BRPOP命令。BRPOP命令也称为阻塞式读取,客户端在没有读到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据。和消费者程序自己不停地调用RPOP命令相比,这种方式能节省CPU开销。
2、如何处理重复的消息?
消费者要实现重复消息的判断,需要2个方面的要求:每个消息都有一个全局的ID。消费者要记录已经处理过的消息的ID。当收到一条消息后,消费者程序就可以对比收到的消息ID和记录的已处理过的消息ID,来判断当前收到的消息有没有经过处理。如果已经处理过,那么,消费者程序就不再进行处理了。
但是List并不会为每个消息生成ID号,所以我们需要自行为每个消息生成一个全局唯一ID,生成之后,我们在用LPUSH命令把消息插入List时,需要在消息中包含这个全局唯一ID。
例如,我们执行以下命令,就把一条全局ID为111000102、库存量为99的消息插入了消息队列:LPUSHmq111000102:stock:99(integer)1
3、如何保证消息可靠性?
当消费者程序从List中读取一条消息后,List就不会再留存这条消息了。所以,如果消费者程序在处理消息的过程出现了故障或宕机,就会导致消息没有处理完成,那么,消费者程序再次启动后,就没法再次从List中读取消息了。
为了留存消息,List类型提供了BRPOPLPUSH命令,这个命令的作用是让消费者程序从一个List中读取消息,同时,Redis会把这个消息再插入到另一个List(可以叫作备份List)留存。
这样一来,如果消费者程序读了消息但没能正常处理,等它重启后,就可以从备份List中重新读取消息并进行处理了。
好了,到这里可以知道基于List类型的消息队列,满足消息队列的三大需求(消息保序、处理重复的消息和保证消息可靠性)。消息保序:使用LPUSHRPOP;阻塞读取:使用BRPOP;重复消息处理:生产者自行实现全局唯一ID;消息的可靠性:使用BRPOPLPUSH
List作为消息队列有什么缺陷?
List不支持多个消费者消费同一条消息,因为一旦消费者拉取一条消息后,这条消息就从List中删除了,无法被其它消费者再次消费。
要实现一条消息可以被多个消费者消费,那么就要将多个消费者组成一个消费组,使得多个消费者可以消费同一条消息,但是List类型并不支持消费组的实现。
这就要说起Redis从5。0版本开始提供的Stream数据类型了,Stream同样能够满足消息队列的三大需求,而且它还支持消费组形式的消息读取。4。Hash4。1介绍
Hash是一个键值对(keyvalue)集合,其中value的形式如:value〔{field1,value1},。。。{fieldN,valueN}〕。Hash特别适合用于存储对象。
Hash与String对象的区别如下图所示:
4。2内部实现
Hash类型的底层数据结构是由压缩列表或哈希表实现的:如果哈希类型元素个数小于512个(默认值,可由hashmaxziplistentries配置),所有值小于64字节(默认值,可由hashmaxziplistvalue配置)的话,Redis会使用压缩列表作为Hash类型的底层数据结构;如果哈希类型元素不满足上面条件,Redis会使用哈希表作为Hash类型的底层数据结构。
在Redis7。0中,压缩列表数据结构已经废弃了,交由listpack数据结构来实现了。4。3常用命令存储一个哈希表key的键值HSETkeyfieldvalue获取哈希表key对应的field键值HGETkeyfield在一个哈希表key中存储多个键值对HMSETkeyfieldvalue〔fieldvalue。。。〕批量获取哈希表key中多个field键值HMGETkeyfield〔field。。。〕删除哈希表key中的field键值HDELkeyfield〔field。。。〕返回哈希表key中field的数量HLENkey返回哈希表key中所有的键值HGETALLkey为哈希表key中field键的值加上增量nHINCRBYkeyfieldn4。4。应用场景4。4。1缓存对象
Hash类型的(key,field,value)的结构与对象的(对象id,属性,值)的结构相似,也可以用来存储对象。
我们以用户信息为例,它在关系型数据库中的结构是这样的:
我们可以使用如下命令,将用户对象的信息存储到Hash类型:存储一个哈希表uid:1的键值HMSETuid:1nameTomage152存储一个哈希表uid:2的键值HMSETuid:2nameJerryage132获取哈希表用户id为1中所有的键值HGETALLuid:11)name2)Tom3)age4)15
RedisHash存储其结构如下图:
在介绍String类型的应用场景时有所介绍,StringJson也是存储对象的一种方式,那么存储对象时,到底用Stringjson还是用Hash呢?
一般对象用StringJson存储,对象中某些频繁变化的属性可以考虑抽出来用Hash类型存储。4。4。2购物车
以用户id为key,商品id为field,商品数量为value,恰好构成了购物车的3个要素,如下图所示。
涉及的命令如下:添加商品:HSETcart:{用户id}{商品id}1添加数量:HINCRBYcart:{用户id}{商品id}1商品总数:HLENcart:{用户id}删除商品:HDELcart:{用户id}{商品id}获取购物车所有商品:HGETALLcart:{用户id}
当前仅仅是将商品ID存储到了Redis中,在回显商品具体信息的时候,还需要拿着商品id查询一次数据库,获取完整的商品的信息。5。Set5。1介绍
Set类型是一个无序并唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储。
一个集合最多可以存储2321个元素。概念和数学中个的集合基本类似,可以交集,并集,差集等等,所以Set类型除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集。
Set类型和List类型的区别如下:List可以存储重复元素,Set只能存储非重复元素;List是按照元素的先后顺序存储元素的,而Set则是无序方式存储元素的。5。2内部实现
Set类型的底层数据结构是由哈希表或整数集合实现的:如果集合中的元素都是整数且元素个数小于512(默认值,setmaxintsetentries配置)个,Redis会使用整数集合作为Set类型的底层数据结构;如果集合中的元素不满足上面条件,则Redis使用哈希表作为Set类型的底层数据结构。5。3常用命令
Set常用操作:往集合key中存入元素,元素存在则忽略,若key不存在则新建SADDkeymember〔member。。。〕从集合key中删除元素SREMkeymember〔member。。。〕获取集合key中所有元素SMEMBERSkey获取集合key中的元素个数SCARDkey判断member元素是否存在于集合key中SISMEMBERkeymember从集合key中随机选出count个元素,元素不从key中删除SRANDMEMBERkey〔count〕从集合key中随机选出count个元素,元素从key中删除SPOPkey〔count〕
Set运算操作:交集运算SINTERkey〔key。。。〕将交集结果存入新集合destination中SINTERSTOREdestinationkey〔key。。。〕并集运算SUNIONkey〔key。。。〕将并集结果存入新集合destination中SUNIONSTOREdestinationkey〔key。。。〕差集运算SDIFFkey〔key。。。〕将差集结果存入新集合destination中SDIFFSTOREdestinationkey〔key。。。〕5。4应用场景
集合的主要几个特性,无序、不可重复、支持并交差等操作。
因此Set类型比较适合用来数据去重和保障数据的唯一性,还可以用来统计多个集合的交集、错集和并集等,当我们存储的数据是无序并且需要去重的情况下,比较适合使用集合类型进行存储。
但是要提醒你一下,这里有一个潜在的风险。Set的差集、并集和交集的计算复杂度较高,在数据量较大的情况下,如果直接执行这些计算,会导致Redis实例阻塞。
在主从集群中,为了避免主库因为Set做聚合计算(交集、差集、并集)时导致主库被阻塞,我们可以选择一个从库完成聚合统计,或者把数据返回给客户端,由客户端来完成聚合统计。5。4。1点赞
Set类型可以保证一个用户只能点一个赞,这里举例子一个场景,key是文章id,value是用户id。
uid:1、uid:2、uid:3三个用户分别对article:1文章点赞了。uid:1用户对文章article:1点赞SADDarticle:1uid:1(integer)1uid:2用户对文章article:1点赞SADDarticle:1uid:2(integer)1uid:3用户对文章article:1点赞SADDarticle:1uid:3(integer)1
uid:1取消了对article:1文章点赞。SREMarticle:1uid:1(integer)1
获取article:1文章所有点赞用户:SMEMBERSarticle:11)uid:32)uid:2
获取article:1文章的点赞用户数量:SCARDarticle:1(integer)2
判断用户uid:1是否对文章article:1点赞了:SISMEMBERarticle:1uid:1(integer)0返回0说明没点赞,返回1则说明点赞了5。4。2共同关注
Set类型支持交集运算,所以可以用来计算共同关注的好友、公众号等。
key可以是用户id,value则是已关注的公众号的id。
uid:1用户关注公众号id为5、6、7、8、9,uid:2用户关注公众号id为7、8、9、10、11。uid:1用户关注公众号id为5、6、7、8、9SADDuid:156789(integer)5uid:2用户关注公众号id为7、8、9、10、11SADDuid:27891011(integer)5
uid:1和uid:2共同关注的公众号:获取共同关注SINTERuid:1uid:21)72)83)9
给uid:2推荐uid:1关注的公众号:SDIFFuid:1uid:21)52)6
验证某个公众号是否同时被uid:1或uid:2关注:SISMEMBERuid:15(integer)1返回0,说明关注了SISMEMBERuid:25(integer)0返回0,说明没关注5。4。3抽奖活动
存储某活动中中奖的用户名,Set类型因为有去重功能,可以保证同一个用户不会中奖两次。
key为抽奖活动名,value为员工名称,把所有员工名称放入抽奖箱:SADDluckyTomJerryJohnSeanMarryLindySaryMark(integer)5
如果允许重复中奖,可以使用SRANDMEMBER命令。抽取1个一等奖:SRANDMEMBERlucky11)Tom抽取2个二等奖:SRANDMEMBERlucky21)Mark2)Jerry抽取3个三等奖:SRANDMEMBERlucky31)Sary2)Tom3)Jerry
如果不允许重复中奖,可以使用SPOP命令。抽取一等奖1个SPOPlucky11)Sary抽取二等奖2个SPOPlucky21)Jerry2)Mark抽取三等奖3个SPOPlucky31)John2)Sean3)Lindy6。Zset6。1介绍
Zset类型(有序集合类型)相比于Set类型多了一个排序属性score(分值),对于有序集合ZSet来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。
有序集合保留了集合不能有重复成员的特性(分值可以重复),但不同的是,有序集合中的元素可以排序。
6。2内部实现
Zset类型的底层数据结构是由压缩列表或跳表实现的:如果有序集合的元素个数小于128个,并且每个元素的值小于64字节时,Redis会使用压缩列表作为Zset类型的底层数据结构;如果有序集合的元素不满足上面的条件,Redis会使用跳表作为Zset类型的底层数据结构;
在Redis7。0中,压缩列表数据结构已经废弃了,交由listpack数据结构来实现了。6。3常用命令
Zset常用操作:往有序集合key中加入带分值元素ZADDkeyscoremember〔〔scoremember〕。。。〕往有序集合key中删除元素ZREMkeymember〔member。。。〕返回有序集合key中元素member的分值ZSCOREkeymember返回有序集合key中元素个数ZCARDkey为有序集合key中元素member的分值加上incrementZINCRBYkeyincrementmember正序获取有序集合key从start下标到stop下标的元素ZRANGEkeystartstop〔WITHSCORES〕倒序获取有序集合key从start下标到stop下标的元素ZREVRANGEkeystartstop〔WITHSCORES〕返回有序集合中指定分数区间内的成员,分数由低到高排序。ZRANGEBYSCOREkeyminmax〔WITHSCORES〕〔LIMIToffsetcount〕返回指定成员区间内的成员,按字典正序排列,分数必须相同。ZRANGEBYLEXkeyminmax〔LIMIToffsetcount〕返回指定成员区间内的成员,按字典倒序排列,分数必须相同ZREVRANGEBYLEXkeymaxmin〔LIMIToffsetcount〕
Zset运算操作(相比于Set类型,ZSet类型没有支持差集运算):并集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积ZUNIONSTOREdestkeynumberkeyskey〔key。。。〕交集计算(相同元素分值相加),numberkeys一共多少个key,WEIGHTS每个key对应的分值乘积ZINTERSTOREdestkeynumberkeyskey〔key。。。〕6。4应用场景
Zset类型(SortedSet,有序集合)可以根据元素的权重来排序,我们可以自己来决定每个元素的权重值。比如说,我们可以根据元素插入SortedSet的时间确定权重值,先插入的元素权重小,后插入的元素权重大。
在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,可以优先考虑使用SortedSet。6。4。1排行榜
有序集合比较典型的使用场景就是排行榜。例如学生成绩的排名榜、游戏积分排行榜、视频播放排名、电商系统中商品的销量排名等。
我们以博文点赞排名为例,小林发表了五篇博文,分别获得赞为200、40、100、50、150。arcticle:1文章获得了200个赞ZADDuser:xiaolin:ranking200arcticle:1(integer)1arcticle:2文章获得了40个赞ZADDuser:xiaolin:ranking40arcticle:2(integer)1arcticle:3文章获得了100个赞ZADDuser:xiaolin:ranking100arcticle:3(integer)1arcticle:4文章获得了50个赞ZADDuser:xiaolin:ranking50arcticle:4(integer)1arcticle:5文章获得了150个赞ZADDuser:xiaolin:ranking150arcticle:5(integer)1
文章arcticle:4新增一个赞,可以使用ZINCRBY命令(为有序集合key中元素member的分值加上increment):ZINCRBYuser:xiaolin:ranking1arcticle:451
查看某篇文章的赞数,可以使用ZSCORE命令(返回有序集合key中元素个数):ZSCOREuser:xiaolin:rankingarcticle:450
获取小林文章赞数最多的3篇文章,可以使用ZREVRANGE命令(倒序获取有序集合key从start下标到stop下标的元素):WITHSCORES表示把score也显示出来ZREVRANGEuser:xiaolin:ranking02WITHSCORES1)arcticle:12)2003)arcticle:54)1505)arcticle:36)100
获取小林100赞到200赞的文章,可以使用ZRANGEBYSCORE命令(返回有序集合中指定分数区间内的成员,分数由低到高排序):ZRANGEBYSCOREuser:xiaolin:ranking100200WITHSCORES1)arcticle:32)1003)arcticle:54)1505)arcticle:16)2006。4。2电话、姓名排序
使用有序集合的ZRANGEBYLEX或ZREVRANGEBYLEX可以帮助我们实现电话号码或姓名的排序,我们以ZRANGEBYLEX(返回指定成员区间内的成员,按key正序排列,分数必须相同)为例。
注意:不要在分数不一致的SortSet集合中去使用ZRANGEBYLEX和ZREVRANGEBYLEX指令,因为获取的结果会不准确。
1、电话排序
我们可以将电话号码存储到SortSet中,然后根据需要来获取号段:ZADDphone013100111100013110114300013132110901(integer)3ZADDphone013200111100013210414300013252110901(integer)3ZADDphone013300111100013310414300013352110901(integer)3
获取所有号码:ZRANGEBYLEXphone1)131001111002)131101143003)131321109014)132001111005)132104143006)132521109017)133001111008)133104143009)13352110901
获取132号段的号码:ZRANGEBYLEXphone〔132(1331)132001111002)132104143003)13252110901
获取132、133号段的号码:ZRANGEBYLEXphone〔132(1341)132001111002)132104143003)132521109014)133001111005)133104143006)13352110901
2、姓名排序zaddnames0Toumas0Jake0Bluetuo0Gaodeng0Aimini0Aidehua(integer)6
获取所有人的名字:ZRANGEBYLEXnames1)Aidehua2)Aimini3)Bluetuo4)Gaodeng5)Jake6)Toumas
获取名字中大写字母A开头的所有人:ZRANGEBYLEXnames〔A(B1)Aidehua2)Aimini
获取名字中大写字母C到Z的所有人:ZRANGEBYLEXnames〔C〔Z1)Gaodeng2)Jake3)Toumas7。BitMap7。1介绍
Bitmap,即位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。BitMap通过最小的单位bit来进行01的设置,表示某个元素的值或者状态,时间复杂度为O(1)。
由于bit是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景。
7。2内部实现
Bitmap本身是用String类型作为底层数据结构实现的一种统计二值状态的数据类型。
String类型是会保存为二进制的字节数组,所以,Redis就把字节数组的每个bit位利用起来,用来表示一个元素的二值状态,你可以把Bitmap看作是一个bit数组。7。3常用命令
bitmap基本操作:设置值,其中value只能是0和1SETBITkeyoffsetvalue获取值GETBITkeyoffset获取指定范围内值为1的个数start和end以字节为单位BITCOUNTkeystartend
bitmap运算操作:BitMap间的运算operations位移操作符,枚举值AND与运算OR或运算XOR异或NOT取反result计算的结果,会存储在该key中key1keyn参与运算的key,可以有多个,空格分割,not运算只能一个key当BITOP处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作0。返回值是保存到destkey的字符串的长度(以字节byte为单位),和输入key中最长的字符串长度相等。BITOP〔operations〕〔result〕〔key1〕〔keyn〕返回指定key中第一次出现指定value(01)的位置BITPOS〔key〕〔value〕7。4应用场景
Bitmap类型非常适合二值状态统计的场景,这里的二值状态就是指集合元素的取值就只有0和1两种,在记录海量数据时,Bitmap能够有效地节省内存空间。7。4。1签到统计
在签到打卡的场景中,我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态。
签到统计时,每个用户一天的签到用1个bit位就能表示,一个月(假设是31天)的签到情况用31个bit位就可以,而一年的签到也只需要用365个bit位,根本不用太复杂的集合类型。
假设我们要统计ID100的用户在2022年6月份的签到情况,就可以按照下面的步骤进行操作。
第一步,执行下面的命令,记录该用户6月3号已签到。SETBITuid:sign:100:20220621
第二步,检查该用户6月3日是否签到。GETBITuid:sign:100:2022062
第三步,统计该用户在6月份的签到次数。BITCOUNTuid:sign:100:202206
这样,我们就知道该用户在6月份的签到情况了。
如何统计这个月首次打卡时间呢?
Redis提供了BITPOSkeybitValue〔start〕〔end〕指令,返回数据表示Bitmap中第一个值为bitValue的offset位置。
在默认情况下,命令将检测整个位图,用户可以通过可选的start参数和end参数指定要检测的范围。所以我们可以通过执行这条命令来获取userID100在2022年6月份首次打卡日期:BITPOSuid:sign:100:2022061
需要注意的是,因为offset从0开始的,所以我们需要将返回的value1。7。4。2判断用户登陆态
Bitmap提供了GETBIT、SETBIT操作,通过一个偏移值offset对bit数组的offset位置的bit位进行读写操作,需要注意的是offset从0开始。
只需要一个keyloginstatus表示存储用户登陆状态集合数据,将用户ID作为offset,在线就设置为1,下线设置0。通过GETBIT判断对应的用户是否在线。50000万用户只需要6MB的空间。
假如我们要判断ID10086的用户的登陆情况:
第一步,执行以下指令,表示用户已登录。SETBITloginstatus100861
第二步,检查该用户是否登陆,返回值1表示已登录。GETBITloginstatus10086
第三步,登出,将offset对应的value设置成0。SETBITloginstatus1008607。4。3连续签到用户总数
如何统计出这连续7天连续打卡用户总数呢?
我们把每天的日期作为Bitmap的key,userId作为offset,若是打卡则将offset位置的bit设置成1。
key对应的集合的每个bit位的数据则是一个用户在该日期的打卡记录。
一共有7个这样的Bitmap,如果我们能对这7个Bitmap的对应的bit位做与运算。同样的UserIDoffset都是一样的,当一个userID在7个Bitmap对应对应的offset位置的bit1就说明该用户7天连续打卡。
结果保存到一个新Bitmap中,我们再通过BITCOUNT统计bit1的个数便得到了连续打卡7天的用户总数了。
Redis提供了BITOPoperationdestkeykey〔key。。。〕这个指令用于对一个或者多个key的Bitmap进行位元操作。operation可以是and、OR、NOT、XOR。当BITOP处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作0。空的key也被看作是包含0的字符串序列。
假设要统计3天连续打卡的用户数,则是将三个bitmap进行AND操作,并将结果保存到destmap中,接着对destmap执行BITCOUNT统计,如下命令:与操作BITOPANDdestmapbitmap:01bitmap:02bitmap:03统计bit位1的个数BITCOUNTdestmap
即使一天产生一个亿的数据,Bitmap占用的内存也不大,大约占12MB的内存(108810241024),7天的Bitmap的内存开销约为84MB。同时我们最好给Bitmap设置过期时间,让Redis删除过期的打卡数据,节省内存。8。HyperLogLog8。1介绍
RedisHyperLogLog是Redis2。8。9版本新增的数据类型,是一种用于统计基数的数据集合类型,基数统计就是指统计一个集合中不重复的元素个数。但要注意,HyperLogLog是统计规则是基于概率完成的,不是非常准确,标准误算率是0。81。
所以,简单来说HyperLogLog提供不精确的去重计数。
HyperLogLog的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的内存空间总是固定的、并且是很小的。
在Redis里面,每个HyperLogLog键只需要花费12KB内存,就可以计算接近264个不同元素的基数,和元素越多就越耗费内存的Set和Hash类型相比,HyperLogLog就非常节省空间。
这什么概念?举个例子给大家对比一下。
用Java语言来说,一般long类型占用8字节,而1字节有8位,即:1byte8bit,即long数据类型最大可以表示的数是:2631。对应上面的264个数,假设此时有2631这么多个数,从02631,按照long以及1k1024字节的规则来计算内存总数,就是:((2631)81024)K,这是很庞大的一个数,存储空间远远超过12K,而HyperLogLog却可以用12K就能统计完。8。2内部实现
HyperLogLog的实现涉及到很多数学问题,太费脑子了,我也没有搞懂,如果你想了解一下,课下可以看看这个:HyperLogLog(opensnewwindow)。8。3常见命令
HyperLogLog命令很少,就三个。添加指定元素到HyperLogLog中PFADDkeyelement〔element。。。〕返回给定HyperLogLog的基数估算值。PFCOUNTkey〔key。。。〕将多个HyperLogLog合并为一个HyperLogLogPFMERGEdestkeysourcekey〔sourcekey。。。〕8。4应用场景8。4。1百万级网页UV计数
RedisHyperLogLog优势在于只需要花费12KB内存,就可以计算接近264个元素的基数,和元素越多就越耗费内存的Set和Hash类型相比,HyperLogLog就非常节省空间。
所以,非常适合统计百万级以上的网页UV的场景。
在统计UV时,你可以用PFADD命令(用于向HyperLogLog中添加新元素)把访问页面的每个用户都添加到HyperLogLog中。PFADDpage1:uvuser1user2user3user4user5
接下来,就可以用PFCOUNT命令直接获得page1的UV值了,这个命令的作用就是返回HyperLogLog的统计结果。PFCOUNTpage1:uv
不过,有一点需要你注意一下,HyperLogLog的统计规则是基于概率完成的,所以它给出的统计结果是有一定误差的,标准误算率是0。81。
这也就意味着,你使用HyperLogLog统计的UV是100万,但实际的UV可能是101万。虽然误差率不算大,但是,如果你需要精确统计结果的话,最好还是继续用Set或Hash类型。9。GEO
RedisGEO是Redis3。2版本新增的数据类型,主要用于存储地理位置信息,并对存储的信息进行操作。
在日常生活中,我们越来越依赖搜索附近的餐馆、在打车软件上叫车,这些都离不开基于位置信息服务(LocationBasedService,LBS)的应用。LBS应用访问的数据是和人或物关联的一组经纬度信息,而且要能查询相邻的经纬度范围,GEO就非常适合应用在LBS服务的场景中。9。1内部实现
GEO本身并没有设计新的底层数据结构,而是直接使用了SortedSet集合类型。
GEO类型使用GeoHash编码方法实现了经纬度到SortedSet中元素权重分数的转换,这其中的两个关键机制就是对二维地图做区间划分和对区间进行编码。一组经纬度落在某个区间后,就用区间的编码值来表示,并把编码值作为SortedSet元素的权重分数。
这样一来,我们就可以把经纬度保存到SortedSet中,利用SortedSet提供的按权重进行有序范围查找的特性,实现LBS服务中频繁使用的搜索附近的需求。9。2常用命令存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的key中。GEOADDkeylongitudelatitudemember〔longitudelatitudemember。。。〕从给定的key里返回所有指定名称(member)的位置(经度和纬度),不存在的返回nil。GEOPOSkeymember〔member。。。〕返回两个给定位置之间的距离。GEODISTkeymember1member2〔mkmftmi〕根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。GEORADIUSkeylongitudelatituderadiusmkmftmi〔WITHCOORD〕〔WITHDIST〕〔WITHHASH〕〔COUNTcount〕〔ASCDESC〕〔STOREkey〕〔STOREDISTkey〕9。3应用场景9。3。1滴滴叫车
这里以滴滴叫车的场景为例,介绍下具体如何使用GEO命令:GEOADD和GEORADIUS这两个命令。
假设车辆ID是33,经纬度位置是(116。034579,39。030452),我们可以用一个GEO集合保存所有车辆的经纬度,集合key是cars:locations。
执行下面的这个命令,就可以把ID号为33的车辆的当前经纬度位置存入GEO集合中:GEOADDcars:locations116。03457939。03045233
当用户想要寻找自己附近的网约车时,LBS应用就可以使用GEORADIUS命令。
例如,LBS应用执行下面的命令时,Redis会根据输入的用户的经纬度信息(116。054579,39。030452),查找以这个经纬度为中心的5公里内的车辆信息,并返回给LBS应用。GEORADIUScars:locations116。05457939。0304525kmASCCOUNT1010。Stream10。1介绍
RedisStream是Redis5。0版本新增加的数据类型,Redis专门为消息队列设计的数据类型。
在Redis5。0Stream没出来之前,消息队列的实现方式都有着各自的缺陷,例如:发布订阅模式,不能持久化也就无法可靠的保存消息,并且对于离线重连的客户端不能读取历史消息的缺陷;List实现消息队列的方式不能重复消费,一个消息消费完就会被删除,而且生产者需要自行实现全局唯一ID。
基于以上问题,Redis5。0便推出了Stream类型也是此版本最重要的功能,用于完美地实现消息队列,它支持消息的持久化、支持自动生成全局唯一ID、支持ack确认消息的模式、支持消费组模式等,让消息队列更加的稳定和可靠。10。2常见命令
Stream消息队列操作命令:XADD:插入消息,保证有序,可以自动生成全局唯一ID;XLEN:查询消息长度;XREAD:用于读取消息,可以按ID读取数据;XDEL:根据消息ID删除消息;DEL:删除整个Stream;XRANGE:读取区间消息XREADGROUP:按消费组形式读取消息;XPENDING和XACK:XPENDING命令可以用来查询每个消费组内所有消费者已读取、但尚未确认的消息;XACK命令用于向消息队列确认消息处理已完成;10。3应用场景10。3。1消息队列
生产者通过XADD命令插入一条消息:表示让Redis为插入的数据自动生成一个全局唯一的ID往名称为mymq的消息队列中插入一条消息,消息的键是name,值是xiaolinXADDmymqnamexiaolin16542549538080
插入成功后会返回全局唯一的ID:16542549538080。消息的全局唯一ID由两部分组成:第一部分1654254953808是数据插入时,以毫秒为单位计算的当前服务器时间;第二部分表示插入消息在当前毫秒内的消息序号,这是从0开始编号的。例如,16542549538080就表示在1654254953808毫秒内的第1条消息。
消费者通过XREAD命令从消息队列中读取消息时,可以指定一个消息ID,并从这个消息ID的下一条消息开始进行读取(注意是输入消息ID的下一条信息开始读取,不是查询输入ID的消息)。从ID号为16542549538070的消息开始,读取后续的所有消息(示例中一共1条)。XREADSTREAMSmymq165425495380701)1)mymq2)1)1)165425495380802)1)name2)xiaolin
如果想要实现阻塞读(当没有数据时,阻塞住),可以调用XRAED时设定BLOCK配置项,实现类似于BRPOP的阻塞读取操作。
比如,下面这命令,设置了BLOCK10000的配置项,10000的单位是毫秒,表明XREAD在读取最新消息时,如果没有消息到来,XREAD将阻塞10000毫秒(即10秒),然后再返回。命令最后的符号表示读取最新的消息XREADBLOCK10000STREAMSmymq(nil)(10。00s)
Stream的基础方法,使用xadd存入消息和xread循环阻塞读取消息的方式可以实现简易版的消息队列,交互流程如下图所示:
前面介绍的这些操作List也支持的,接下来看看Stream特有的功能。
Stream可以以使用XGROUP创建消费组,创建消费组之后,Stream可以使用XREADGROUP命令让消费组内的消费者读取消息。
创建两个消费组,这两个消费组消费的消息队列是mymq,都指定从第一条消息开始读取:创建一个名为group1的消费组,00表示从第一条消息开始读取。XGROUPCREATEmymqgroup100OK创建一个名为group2的消费组,00表示从第一条消息开始读取。XGROUPCREATEmymqgroup200OK
消费组group1内的消费者consumer1从mymq消息队列中读取所有消息的命令如下:命令最后的参数,表示从第一条尚未被消费的消息开始读取。XREADGROUPGROUPgroup1consumer1STREAMSmymq1)1)mymq2)1)1)165425495380802)1)name2)xiaolin
消息队列中的消息一旦被消费组里的一个消费者读取了,就不能再被该消费组内的其他消费者读取了,即同一个消费组里的消费者不能消费同一条消息。
比如说,我们执行完刚才的XREADGROUP命令后,再执行一次同样的命令,此时读到的就是空值了:XREADGROUPGROUPgroup1consumer1STREAMSmymq(nil)
但是,不同消费组的消费者可以消费同一条消息(但是有前提条件,创建消息组的时候,不同消费组指定了相同位置开始读取消息)。
比如说,刚才group1消费组里的consumer1消费者消费了一条id为16542549538080的消息,现在用group2消费组里的consumer1消费者消费消息:XREADGROUPGROUPgroup2consumer1STREAMSmymq1)1)mymq2)1)1)165425495380802)1)name2)xiaolin
因为我创建两组的消费组都是从第一条消息开始读取,所以可以看到第二组的消费者依然可以消费id为16542549538080的这一条消息。因此,不同的消费组的消费者可以消费同一条消息。
使用消费组的目的是让组内的多个消费者共同分担读取消息,所以,我们通常会让每个消费者读取部分消息,从而实现消息读取负载在多个消费者间是均衡分布的。
例如,我们执行下列命令,让group2中的consumer1、2、3各自读取一条消息。让group2中的consumer1从mymq消息队列中消费一条消息XREADGROUPGROUPgroup2consumer1COUNT1STREAMSmymq1)1)mymq2)1)1)165425495380802)1)name2)xiaolin让group2中的consumer2从mymq消息队列中消费一条消息XREADGROUPGROUPgroup2consumer2COUNT1STREAMSmymq1)1)mymq2)1)1)165425626558402)1)name2)xiaolincoding让group2中的consumer3从mymq消息队列中消费一条消息XREADGROUPGROUPgroup2consumer3COUNT1STREAMSmymq1)1)mymq2)1)1)165425627133702)1)name2)Tom
基于Stream实现的消息队列,如何保证消费者在发生故障或宕机再次重启后,仍然可以读取未处理完的消息?
Streams会自动使用内部队列(也称为PENDINGList)留存消费组里每个消费者读取的消息,直到消费者使用XACK命令通知Streams消息已经处理完成。
消费确认增加了消息的可靠性,一般在业务处理完成之后,需要执行XACK命令确认消息已经被消费完成,整个流程的执行如下图所示:
如果消费者没有成功处理消息,它就不会给Streams发送XACK命令,消息仍然会留存。此时,消费者可以在重启后,用XPENDING命令查看已读取、但尚未确认处理完成的消息。
例如,我们来查看一下group2中各个消费者已读取、但尚未确认的消息个数,命令如下:127。0。0。1:6379XPENDINGmymqgroup21)(integer)32)16542549538080表示group2中所有消费者读取的消息最小ID3)16542562713370表示group2中所有消费者读取的消息最大ID4)1)1)consumer12)12)1)consumer22)13)1)consumer32)1
如果想查看某个消费者具体读取了哪些数据,可以执行下面的命令:查看group2里consumer2已从mymq消息队列中读取了哪些消息XPENDINGmymqgroup210consumer21)1)165425626558402)consumer23)(integer)4107004)(integer)1
可以看到,consumer2已读取的消息的ID是16542562655840。
一旦消息16542562655840被consumer2处理了,consumer2就可以使用XACK命令通知Streams,然后这条消息就会被删除。XACKmymqgroup216542562655840(integer)1
当我们再使用XPENDING命令查看时,就可以看到,consumer2已经没有已读取、但尚未确认处理的消息了。XPENDINGmymqgroup210consumer2(emptyarray)
好了,基于Stream实现的消息队列就说到这里了,小结一下:消息保序:XADDXREAD阻塞读取:XREADblock重复消息处理:Stream在使用XADD命令,会自动生成全局唯一ID;消息可靠性:内部使用PENDINGList自动保存消息,使用XPENDING命令查看消费组已经读取但是未被确认的消息,消费者使用XACK确认消息;支持消费组形式消费数据
Redis基于Stream消息队列与专业的消息队列有哪些差距?
一个专业的消息队列,必须要做到两大块:消息不丢。消息可堆积。
1、RedisStream消息会丢失吗?
使用一个消息队列,其实就分为三大块:生产者、队列中间件、消费者,所以要保证消息就是保证三个环节都不能丢失数据。
RedisStream消息队列能不能保证三个环节都不丢失数据?Redis生产者会不会丢消息?生产者会不会丢消息,取决于生产者对于异常情况的处理是否合理。从消息被生产出来,然后提交给MQ的过程中,只要能正常收到(MQ中间件)的ack确认响应,就表示发送成功,所以只要处理好返回值和异常,如果返回异常则进行消息重发,那么这个阶段是不会出现消息丢失的。Redis消费者会不会丢消息?不会,因为Stream(MQ中间件)会自动使用内部队列(也称为PENDINGList)留存消费组里每个消费者读取的消息,但是未被确认的消息。消费者可以在重启后,用XPENDING命令查看已读取、但尚未确认处理完成的消息。等到消费者执行完业务逻辑后,再发送消费确认XACK命令,也能保证消息的不丢失。Redis消息中间件会不会丢消息?会,Redis在以下2个场景下,都会导致数据丢失:AOF持久化配置为每秒写盘,但这个写盘过程是异步的,Redis宕机时会存在数据丢失的可能主从复制也是异步的,主从切换时,也存在丢失数据的可能(opensnewwindow)。
可以看到,Redis在队列中间件环节无法保证消息不丢。像RabbitMQ或Kafka这类专业的队列中间件,在使用时是部署一个集群,生产者在发布消息时,队列中间件通常会写多个节点,也就是有多个副本,这样一来,即便其中一个节点挂了,也能保证集群的数据不丢失。
2、RedisStream消息可堆积吗?
Redis的数据都存储在内存中,这就意味着一旦发生消息积压,则会导致Redis的内存持续增长,如果超过机器内存上限,就会面临被OOM的风险。
所以Redis的Stream提供了可以指定队列最大长度的功能,就是为了避免这种情况发生。
当指定队列最大长度时,队列长度超过上限后,旧消息会被删除,只保留固定长度的新消息。这么来看,Stream在消息积压时,如果指定了最大长度,还是有可能丢失消息的。
但Kafka、RabbitMQ专业的消息队列它们的数据都是存储在磁盘上,当消息积压时,无非就是多占用一些磁盘空间。
因此,把Redis当作队列来使用时,会面临的2个问题:Redis本身可能会丢数据;面对消息挤压,内存资源会紧张;
所以,能不能将Redis作为消息队列来使用,关键看你的业务场景:如果你的业务场景足够简单,对于数据丢失不敏感,而且消息积压概率比较小的情况下,把Redis当作队列是完全可以的。如果你的业务有海量消息,消息积压的概率比较大,并且不能接受数据丢失,那么还是用专业的消息队列中间件吧。
补充:Redis发布订阅机制为什么不可以作为消息队列?
发布订阅机制存在以下缺点,都是跟丢失数据有关:发布订阅机制没有基于任何数据类型实现,所以不具备数据持久化的能力,也就是发布订阅机制的相关操作,不会写入到RDB和AOF中,当Redis宕机重启,发布订阅机制的数据也会全部丢失。发布订阅模式是发后既忘的工作模式,如果有订阅者离线重连之后不能消费之前的历史消息。当消费端有一定的消息积压时,也就是生产者发送的消息,消费者消费不过来时,如果超过32M或者是60s内持续保持在8M以上,消费端会被强行断开,这个参数是在配置文件中设置的,默认值是clientoutputbufferlimitpubsub32mb8mb60。
所以,发布订阅机制只适合即时通讯的场景,比如构建哨兵集群(opensnewwindow)的场景采用了发布订阅机制。11。总结
Redis常见的五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。
这五种数据类型都由多种数据结构实现的,主要是出于时间和空间的考虑,当数据量小的时候使用更简单的数据结构,有利于节省内存,提高性能。
这五种数据类型与底层数据结构对应关系图如下,左边是Redis3。0版本的,也就是《Redis设计与实现》这本书讲解的版本,现在看还是有点过时了,右边是现在Github最新的Redis代码的。
可以看到,Redis数据类型的底层数据结构随着版本的更新也有所不同,比如:在Redis3。0版本中List对象的底层数据结构由双向链表或压缩表列表实现,但是在3。2版本之后,List数据类型底层数据结构是由quicklist实现的;在最新的Redis代码中,压缩列表数据结构已经废弃了,交由listpack数据结构来实现了。
Redis五种数据类型的应用场景:String类型的应用场景:缓存对象、常规计数、分布式锁、共享session信息等。List类型的应用场景:消息队列(有两个问题:1。生产者需要自行实现全局唯一ID;2。不能以消费组形式消费数据)等。Hash类型:缓存对象、购物车等。Set类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。Zset类型:排序场景,比如排行榜、电话和姓名排序等。
Redis后续版本又支持四种数据类型,它们的应用场景如下:BitMap(2。2版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;HyperLogLog(2。8版新增):海量数据基数统计的场景,比如百万级网页UV计数等;GEO(3。2版新增):存储地理位置信息的场景,比如滴滴叫车;Stream(5。0版新增):消息队列,相比于基于List类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。
针对Redis是否适合做消息队列,关键看你的业务场景:如果你的业务场景足够简单,对于数据丢失不敏感,而且消息积压概率比较小的情况下,把Redis当作队列是完全可以的。如果你的业务有海量消息,消息积压的概率比较大,并且不能接受数据丢失,那么还是用专业的消息队列中间件吧。
参考资料:《Redis核心技术与实战》https:www。cnblogs。comhunternetp12742390。htmlhttps:www。cnblogs。comqdhxhzp15669348。htmlhttps:www。cnblogs。combbgsxcp14376109。htmlhttp:kaitokidd。com20210419canredisbeusedasaqueue
关于开展吉首城区第八轮全员核酸检测的通告吉首市广大市民鉴于当前疫情防控形势严峻复杂,根据省州疫情防控工作部署要求,吉首市新冠肺炎疫情防控工作指挥部决定于2022年10月31日在城区范围内开展第八轮全员核酸检测,现将有关事
朋友圈未必有朋友,但黑名单总有故人你也比往年更不爱发朋友圈了吗?你的朋友圈也设置成了仅三天可见了吗?你发一条朋友圈之前也会打了又删,删了又打,斟字酌句吗?朋友圈的烟火气逐渐减少,我们已经变成为生活奔波的大人了。前几
格局和经历几年前去丹东自驾,山路到了一个高点又转弯,脚下是滚滚的鸭绿江,层层薄雾,红日出东方,对面是朝鲜,我们停车兴奋的大喊大叫,人间仙境,好美啊,三胖啊一个放牛的老大爷默默的看着我们,抽口
罗德曼前妻揭露我们曾在球馆风流,他夜店通宵仍统治比赛乔丹纪录片最后之舞播出后,意外将NBA名将罗德曼带回了人们的视野。在纪录片中,罗德曼和美艳前妻卡门伊莱克特拉一同出镜,回忆起公牛王朝的光辉事迹。卡门还大谈特谈她与罗德曼在球馆的风流
中外科研团队设计纳米岛提供解决催化剂矛盾新思路新华社南京10月28日电(记者戴威何曦悦)人类的生产生活离不开化学反应,提高催化反应效率提升催化剂耐久性,是化学家不断追求的目标。记者从中国科学技术大学获悉,该校曾杰教授课题组与来
昔日销冠小鹏汽车销量低迷,为何只向内部动刀?作者闪电懒编辑Duke来源钛财经新能源汽车无疑是当下最火热的赛道。与此同时,资本与造车势力的不断涌入也让这条赛道变得越来越拥挤,即便是一些比较有竞争力的头部造车新势力也很难熬出头,
人类长大后就丧失了很多先天能力,先天更比后天强人类长大后就丧失了很多能力,先天更比后天强。人类在娘胎里而未出生时,具有很多奇异的能力。比如不需要吃喝,而吸收胎盘的营养成长不需要用鼻子呼吸,而是胎吸不需要通过眼睛看耳朵听就能感知
为何说亚马逊是人类禁区?这6大生物,一旦遇到,没几个人能逃掉亚马逊河是全球流域面积最广,拥有支流数量最多的河流,大到什么程度呢?是长江的7倍大,支流超过1。5万条,长度6440公里。而就是这么一个在地球上无比显眼的地方,却危机重重,被认为是
人类基因组中的古老病毒DNA可防止感染根据一项新的研究,人类基因组中的病毒DNA从古代感染中嵌入其中,作为抗病毒药物,保护人类细胞免受某些当今病毒的侵害。10月28日发表在科学杂志上的论文逆转录病毒起源的人类蛋白质的进
狭窄的街道,蜂拥的人群韩国梨泰院踩踏事故是如何发生的?韩国首尔梨泰院的街道狭窄而陡峭,曾经是首尔著名的红灯区,而且以前为了迎合附近的驻韩美军龙山基地,街区到处都是廉价啤酒山寨商品,还可以出租女伴。但在过去的20年里,梨泰院发生了翻天覆
双十一专题iPhoneX终于退役了,iPhone下一部选择谁?使用了这么久的iPhoneX,终于要收藏在箱子里了!那么能够取代iPhoneX,在今年双11可以考虑的iPhone手机是哪一部呢?我们今天就和大家一起推荐几款值得考虑的iPhone