当前位置: 首页 > 后端技术 > Java

谈谈Redis五种数据结构及真实应用场景

时间:2023-04-01 15:48:05 Java

说说Redis的五种数据结构及其真实应用场景(List)、Set(集合)、zset(有序集)你可能还知道它还有三种特殊的数据结构类型:Geospatial、Hyperloglog、Bitmap。但是如果你问你在实际项目中使用了哪些数据结构。是不是感觉大多只是使用String数据结构,即使缓存了一个对象,也只是通过JSONObject.toJSONString(object)转换为String存储。抓取的时候,把这个json串转成一个对象。那么既然redis提供了五种基本的数据结构,那肯定有具体的应用。接下来,我们将对五种基本数据类型在实际开发中的应用场景进行演示。一、String(字符串)1、简介String类型是Redis中最基本、最常用的数据类型,甚至被很多玩家当作Redis的唯一数据类型。String类型在Redis中是二进制安全的,也就是说String值只关心二进制字符串,不关心具体格式。您可以使用它以json格式或JPEG图像格式存储字符串。2、如果内部编码存储数字,则使用int(8字节长整型)类型编码;如果存储的是非数字、小于等于39字节的字符串,则为embstr编码;如果大于39字节,则为原始编码。抽空写一篇关于redis数据内部编码的文章。3、使用场景(1)存储一些配置数据在前后台分离开发中,数据库中存储了一些数据,但变化很少。例如,有一张国家地区表。前端发起请求后,如果后台每次都从关系型数据库中读取,会影响网站的整体性能。我们可以在第一次访问的时候把所有的region信息都存在redis字符串中,再次请求,直接从数据库中读取该region的json字符串,返回给前端。(2)缓存对象将对象转成json存储,如商品信息、用户信息等。(3)数据统计redisinteger可以用来记录网站访问量、某个文件的下载量、签到次数、视频访问量等(自增自减)(4)限制一个时间段内的请求次数。例如,登录用户请求短信验证码,验证码5分钟内有效。当用户第一次请求短信接口时,将用户id存入redis发送的短信字符串中,并设置过期时间为5分钟。当用户再次请求短信接口时,发现已经有用户发送短信的记录,则不再发送短信。(5)订单号(全局唯一)有时候需要生成一个全局唯一的值,可以通过redis生成。键命令:incrby(原子增量)。SETorder_no2001--假设订单号从2001开始,这里vlaue必须是int类型INCRBYorder_no1--加1,这次返回2002(6)分布式会话当我们使用nginx做负载均衡时,如果我们每个slaveserver存储的是自己的session,所以当服务器切换时,session信息会因为不共享而丢失,不得不考虑第三方应用来存储session。通过使用关系数据库或非关系数据库(如Redis)。关系型数据库的存储和读取性能远比不上Redis等非关系型数据库。4.常用命令--addsetmykey"test"--给key设置一个新值,并覆盖原来的值setexmykey10"hello"--设置指定Key的过期时间为10秒,你生存时间内可以获取valuemsetkey3"stephen"key4"liu"--批量设置key--deletedelmykey--删除已有的key--changeincrmykey--如果key不存在则增加1,创建一个key,初始值为0,增加最终结果为1decrbymykey5--value减5--checkexistsmykey--判断key是否存在,存在则返回1,否则返回0getmykey--获取Key对应的值getkey3key4--批量获取keysHash)1.简介Hash的数据结构可以简单理解为java中的Map。这种结构特别适合存储对象。上面的String类型确实可以存储对象,但是我们每次修改对象中的某个属性,都得把整个json串取出来修改属性,然后重新插入,而hash的接口特性允许我们修改只有对象的某个属性。hash数据类型在存储上述类型的数据时,具有比String类型更灵活、速度更快的优点。具体来说,使用String类型进行存储,必须对json格式的字符串进行转换解析,即使不需要转换,在内存开销方面,hash还是有优势的。2、如果内部编码hash类型的元素个数小于512,且所有值小于64字节,则使用ziplist编码,否则使用hashtable编码。4、使用场景(1)Redisson分布式锁Redisson在实现分布式锁时,使用的内部数据是hash而不是String。因为Redisson实现了可重入锁机制。所以当前的线程ID存储在哈希中。(2)购物车列表以用户id为key,商品id为字段,商品数量为value,刚好构成购物车的三个元素,如下图所示。这里涉及的命令如下hsetcart:{userid}{productid}1#添加产品hincrbycart:{userid}{productid}1#增加数量hlencart:{userid}#获取产品总数hdelcart:{Userid}{productid}#删除商品hgetallcart:{userid}获取购物车中的所有商品描述:目前Redis中只存储商品ID,回显时需要持有商品id产品的具体信息查询数据库一次。(3)缓存对象哈希类型(key,field,value)的结构类似于对象的结构(objectid,attribute,value),也可以用来存储对象。在介绍String类型的应用场景的时候介绍过String+json也是一种存储对象的方式,那么在存储对象的时候到底应该使用String+json还是hash呢?两种存储方式的比较如下表所示。string+jsonhash效率高,容量大,灵活性低,序列化低,简单复杂的一般对象用string+json存储,对象中一些频繁变化的属性可以提取出来用hash存储。3.常用命令--addhsetkeyfield1"s"--如果字段field1不存在,创建key及其关联的Hash,在Hash中,key为field1,value设置为s,如果字段field1存在,覆盖hmsetkeyfield1"hello"field2"world"--一次设置多个字段--删除hdelkeyfield1--删除key中名为field1的字段keydelkey--删除key--changehincrbykeyfield1----字段值加1--查看hgetkeyfield1--获取key值为key,field为field1的值hlenkey--获取key的字段数keyhmgetkeyfield1field2field3--一次获取多个字段hgetallkey--返回key的所有字段值和value值keyhkeyskey--获取key中所有字段的字段值hvalskey--获取键key中所有字段的值values3.List(列表)1.介绍List类型是按插入顺序排序的字符串链表,一个list最多可以存储2^32-1个元素.我们可以简单理解为相当于java中的LinkesdList。就像数据结构中的普通链表一样,我们可以向其头部(左)和尾部(右)添加新元素。插入时,如果key不存在,Redis会为key新建一个链表。反之,如果链表中的所有元素都被移除,则key也将从数据库中移除。2.内部编码如果列表中的元素个数小于512且列表中每个元素的值小于64字节(默认),使用ziplist编码,否则使用linkedlist编码。Redis3.2之后使用ziplist+链表的混合结构,称为quicklist(快速链表)。2、使用场景有些人会考虑用列表数据结构做一些朋友圈的点赞列表、评论列表、排行榜。也不是不可以,但是我个人认为这些功能用set或者zset比较合适,下面我会给出具体的例子。(1)消息队列lpop和rpush(或者反过来,lpush和rpop)可以实现队列的功能。但是如果是这样的话,你发现redis作为消息队列并不安全。不可重复食用。一旦被消耗,它将被删除。同时,作为消费者确认ACK也很麻烦。所以在实际开发中很少用到redis中的消息队列。因为已经有Kafka、NSQ、RabbitMQ等成熟的消息队列,而且功能也比较完善。4.常用命令--增加lpushmykeyab--如果key不存在,则创建key及其关联的List,依次插入a和b,如果List类型的key存在,则插入到值rpush中mykeyab--在链表的末尾先插入b,再插入a(lpushlistab,然后按照b、a、rpush的顺序读取--deletedelmykey--deleteexistingkey--changelsetmykey1e----从头开始,将索引为1的元素的值设置为新值e,如果索引越界,则返回错误信息--checklrangemykey0-1--获取链表中的所有元素,其中0表示第一个元素,-1表示最后一个元素lrangemykey02--从头开始,取索引为0,1的元素,2lpopmykey--获取头部元素,弹出头部元素,出栈4.set(集合)1.简介Redis中的set有点类似于Java中的HashSet,其内部的键值对是无序且唯一的。它的内部实现相当于一个特殊的字典,字典中的所有值都是NULL。当集合中的最后一个元素被移除时,该数据结构被自动删除并回收内存。2.编码如果集合中的元素都是整数且元素个数小于512,则使用intset编码,否则使用hashtable编码。应用场景既然set集合的特点是:无序且唯一,那么我们考虑在一些唯一的场景下使用它。(1)抽奖活动将中奖用户ID存储在某次活动中,由于具有去重功能,可以保证同一个用户不会中奖两次。sadduser12345--将所有员工(姓名或编号)放入抽奖箱srandmemberuser1--抽一等奖(员工可重复参与抽奖)spopuser1--抽一等奖(员工不可重复参与抽奖)srandmemberuser3--抽取3个二等奖smembersuser--查看当前抽奖箱参与的所有员工scarduser--查看当前抽奖箱参与抽奖的人数(2)点赞保证一个用户只有一个点赞。key可以是某篇文章,朋友圈中的一篇文章idsaddkeyuserId--like(/favorite)sremkeyuserId--cancellike(/favorite)smemberskey--getalllike(/favorite)usercardkey--获取点赞用户数sismemberkeyuserId--判断是否点赞(/favorite)(3)好友网络密钥可以是用户idsadduserId112345sadduserId245678--friendsofauser放id入集合sinteruserId1userId2--获取共同好友sdiffuserId1userId2--将user1的好友sismember推荐给user2userId15sismemberuserId25--验证用户是否同时被user1和user2关注4.常用命令--addsaddmysetabc--如果key不存在,则创建key及其关联集合,依次插入a、b、c。如果键存在,则将其插入到值中,如果a已经存在于myset中,则将插入两个新成员b和c。--deletespopmyset--删除最后的b,实际上b不是sremmysetadf之前插入的第一个或最后一个成员--如果f不存在,删除a和d,返回2--changemovemysetmyset2a--moveafrommysettomyset2,--checksismembermyseta--判断a是否已经存在,返回值1表示存在。smembersmyset--查看set中的内容scardmyset--获取Set集合中的元素个数srandmembermyset--随机返回一个成员5.zset(orderedset)1.Introduction有一个score(分数)关联有了它,Redis就会根据分数对集合中的成员从小到大进行排序。成员是唯一的,但分数可以重复。2.数据编码当有序集合中的元素个数小于128且每个元素的值小于64字节时,使用ziplist编码,否则使用skiplist(跳表)编码。3.既然应用场景是有序的,不能重复列表,那么可以做一些排行榜相关的场景。1)排行榜(商品销量、视频评分、用户游戏评分)热搜新闻。4、常用命令--addzaddkey2"two"3"three"--添加两个分数分别为2和3的成员--deletezremkeyonetwo--删除多个成员变量并返回删除的一个数量--changezincrbykey2one--memberone的分数加2,并返回成员更新后的分数(分数变化后,其index也会随之变化)--checkzrangekey0-1WITHSCORES--return所有成员和分数,不带WITHSCORES,只返回成员zrangekeystartstop--按照元素分数从小到大的顺序返回从start到stop的所有元素zscorekeythree--得到成员三的分数zrangebyscorekey12--获取score满足表达式1