目 录CONTENT

文章目录

Redis五种数据类型

FatFish1
2025-06-09 / 0 评论 / 0 点赞 / 3 阅读 / 0 字 / 正在检测是否收录...

Redis数据类型和编码

redis有五种常用的数据结构,分别是:string、hash、list、set、zset有序集合

每种数据结构都有不同的内部编码,如下图:

补链接图p68

可以使用如下命令查看一个键实际的内部编码:

object encoding key

为什么每种数据结构要有不同的内部编码呢?

是为了不断演进数据结构的底层逻辑,一旦有大版本变化,不影响面向客户的命令和api,只需要改变底层编码即可

另外不同的编码可能有不同的优势,例如ziplist节省内存,但是如果元素多了,增删改查性能就会下降,所以元素达到一定数量就会内部自动转为linkedlist

字符串

字符串类型的key,其value可以是真正的字符串类型(可以是复杂的字符串例如jsonstring、xml等)、数字(整数、浮点),甚至是二进制,但是每个key对应的值最大不能超过512MB

字符串的命令有

设置值 - set操作

set key value [ex seconds] [px milliseconds] [nx|xx]

ex和px都是设置过期时间

nx是保证key不存在才能设置,xx则是键必须存在才能set成功并覆盖

这些参数在jedis中也经常用到,通常会使用Jedis#set(java.lang.String, java.lang.String, redis.clients.jedis.params.SetParams) 方法,在SetParam中增加nx参数,是使用redis加锁的一种常见模式

redis还提供了简化版的命令:setnx、setex,对应nx选项和ex选项

获取值 - get

get key

批量设置值 - mset

使用mset可以批量设置值,例如

mset a 1 b 2 c 3 d 4

批量设置值 - mget

mget a b c d

批量get操作比多次get操作的性能好,这是因为 n次get时间 = n次网络时间 + n次命令时间

而mget只需要一次网络时间即可,即1次mget n时间 = 1次网络时间 + n次命令时间

除了这种批量操作,pipeline也是解决网络问题的

其他命令

自增,值非整数返回错误,值是整数返回自增后的结果,key不存在,则执行set key 0,并返回1

incr key

除了incr命令,Redis提供了decr(自减)、incrby(自增指定数字)、 decrby(自减指定数字)、incrbyfloat(自增浮点数)

值追加,向value字符串尾部追加值

append key value

字符串长度

strlen key

设置并返回原值,和set一样会设置值,但是不同的是,它同时会返回键原来的值

getset key value

与jedis中的getset逻辑是一样的

设置指定位置的字符

setrange key offeset value

获取部分字符串

getrange key start end

内部编码

  • int:8个字节的长整型

  • embstr:39个字节及以下的字符串

  • raw:大于39个字节的字符串

这三个类型之间存在内存优化的排序

业务应用

  • 缓存:内存缓存和数据库底表之间的缓存,防止缓存击穿到库

  • 计数:通过自增做快速计数

  • 共享缓存:例如共享session缓存,对于分布式的服务器,用户登录在不同后端处理,使用内存缓存可能要求重复登录,可以用redis做共享缓存

  • 限速/流控:基于自增做一定时间内登录次数限制,缓存失效时间就是其限制登录时长

哈希

哈希类型就是k-v键值对,在redis里面本身就有key,因此实际上是value的结构为k-v键值对

在redis里面不再叫key,而是叫field-value

常用命令

  • 设置值:hset key field value ,成功返回1,不成功返回0

  • 获取值:hget key field ,存在返回value,不存在返回nil

  • 删除field:hdel key field1 [field2]

  • 计算field个数:hlen key

  • 批量设置或获取:hmget key field1 [field2...]hmset key field value [field2 value2]

  • 判断field是否存在:hexists key field

  • 获取所有field:hkeys key

  • 获取所有value:hvals key

  • 获取所有的field-value:hgetall key

  • 自增:hincrby key fieldhincrbyfloat key fieldhincrby和hincrbyfloat,就像incrby和incrbyfloat命令一样,但是它们的作用域是filed

  • 计算value的字符串长度:hstrlen key field

注意:hgetall的性能比较差,可能对串行的命令造成阻塞。开发时更建议使用hmget指定key,或使用渐进式的hscan

内部编码

  • ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries 配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64 字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的 结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。

  • hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使 用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而 hashtable的读写时间复杂度为O(1)

使用场景

  • 存储对象缓存,一个属性一个value

列表

列表(list)类型是用来存储多个有序的字符串,列表中的每个字符串 称为元素(element),一个列表最多可以存储232 -1个元素

补链接图p106

Redis列表特性包括:

  • 可以在两端做push和pop操作,分别为lpush、lpop、rpush、rpop

  • 元素有序,及可以通过下标index获取对应元素或对应范围的元素

  • 元素可以重复

基于第一个特性,Redis列表既可以担任栈的角色,也可以当队列来用,非常灵活

常用命令

操作类型

操作

添加

rpush、lpush、linsert指定位置插入

查询

lrange范围查找、lindex索引查找、llen获取长度

删除

lpop左弹出、rpop右弹出、lrem左删除、ltrim左修剪

修改

lset

阻塞操作

blpop、brpop

  • r、l代表从左还是从右,有些命令既可以左起也可以右起(例如pop、push),但有些就不行(例如lrange只能从左到右获取元素)

  • linsert用于在某个位置前/后插入:linsert key before|after pivot value

  • lrange可以进行范围查找:lrange key start end start和end表示方向和位数,从左到右是0到N,从右到左是-1到-N,lrange性能差,建议改用lscan

  • lpop和rpop只能弹出最左或最右元素,而lrem就比较强大了,可以用于指定值删除:lrem key count value 其中key代表这个列表在redis中对应的key,count代表顺序,大于0即从左到右删除count个元素,小于0则是从右到左删除count个元素,value可以指定删除元素的值,只有等于value的才会被删除

  • ltrim用于范围修剪:ltrim key start end 删除列表key中自左起从start到end的元素

  • blpop和brpop是lpop和rpop的阻塞版本:blpop/brpop key [key2 ...] timeout 其中timeout代表阻塞时长,如果是0则一直阻塞,例如brpop listtest 3 阻塞3s,而brpop listtest 0 如果列表为空则一直阻塞住,除非向列表中添加了数据

    • 需要注意的是,如果多列表弹出,则谁先能弹出就立即先返回,例如brpop list:1 list:2 list:3 0 如果先向list:2添加了元素,则brpop立即返回弹出list:2中的元素

    • 如果多个客户端执行阻塞弹出,都被阻塞了,则最先执行的客户端能拿到返回

内部编码

  • ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置 (默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时 (默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使 用。

  • linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用 linkedlist作为列表的内部实现。

  • quicklist:结合了前两者的优势

使用场景

  • 消息队列:使用lpush+brpop实现阻塞的消息队列,从而达成生产/消费模式,减少内存队列带来的内存压力

  • 文章列表:分页展示文章列表

  • lpush+lpop=Stack(栈)

  • lpush+rpop=Queue(队列)

  • lpsh+ltrim=Capped Collection(有限集合)

  • lpush+brpop=Message Queue(消息队列)

集合

集合(set)类型也是用来保存多个的字符串元素,一个集合最多可以存储232 -1个元 素,其特性包括:

  • 元素不允许重复

  • 元素无序,不能通过index下标取数据

  • 支持多个集合取交并差集

常用命令

  • 向集合添加元素:sadd key element [element ...] 返回结果为添加的个数

  • 删除元素:srem key element [element ...] 返回删除个数

  • 计算个数:scard key

  • 判断元素是否存在:sismember key element 如果给定元素element在集合内返回1,反之返回0

  • 随机捞取指定个数元素:srandmember key [count] [count]是可选参数,如果不写默认为1

  • 从集合中随机弹出元素:spop key 集合的pop不像列表是从左或者从右弹一个,因为集合无序,因此只能随机弹一个;此外,srandmemeber不删除元素,而spop会删除元素

  • 获取所有元素:smembers key smember性能查,不建议用,建议改用sscan

  • 求集合交集:sinter key1 key2 [key3 ...] 如果要保存则加storesinterstore destination key1 key2 [key3 ...]

  • 求集合并集:sunion key1 key2 [key3 ...] 如果要保存则加storesunionstore destination key1 key2 [key3 ...]

  • 求集合差集:sdiff key1 key2 [key3 ...] 如果要保存则加storesdiffstore destination key1 key2 [key3 ...]

内部编码

  • intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。

  • hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。

使用场景

  • 最典型的场景就是用户标签,集合中的数据是无序的,很方便作为标签进行保存

  • 随机数生成、抽奖

有序集合

有序集合是一种排序的集合,它的每个元素都有一个对应的score作为排序依据,提供获取score、元素范围查询、计算排名等能力,它的特点包括:

  • 成员不能重复,但score可以重复

  • 有序的

  • 不能通过下标index查询元素

有序集合与列表的排序区别是,列表是通过插入删除顺序给定下标进行排序,而有序集合是通过score,是真正意义上的排序

常用命令

  • 添加成员:zadd key score member [score2 member2 ...] 这里zadd和set差不多,支持的参数也包括nx、xx,此外还支持ch参数可以返回操作后有序集合元素和分数变化个数,incr参数对score做增加

  • 计算成员个数:zcard key

  • 计算某个成员分数:zscore key member

  • 计算成员排名:zrank key memberzrevrank key member zrank是分数从低到高排名,zrevrank则是从高到低排名

  • 删除成员:zrem key member [member ...]

  • 增加成员分数:zincrby key incrementValue member

  • 返回指定排名范围的成员:zrange key start end [withscores]zrevrange key start end [withscores] 显然带re的是高到低排序,而withscores选项作用是带分数一起返回

  • 返回指定分数范围的成员:zrangebyscore key min max [withscores] [limit offset count]zrevrangebyscore key max min [withscores] [limit offset count]

    • 带rev的是从高到低排

    • withscores也是带分数返回

    • limit offset count则是限制起始位置和个数

    • min、max支持开区间和闭区间,且可以用-inf和+inf表示无限小和无限大,例如zrangebyscore user (200 +inf withscores

  • 返回指定分数范围成员个数:zcount key min max

  • 删除指定排名内的升序元素:zremrangebyrank key start end

  • 删除指定分数范围的成员:zremrangebyscore key min max

  • 取交集并保存:zinterstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]

    • numkeys表示需要做交集计算键的个数,因为这个方法参数太多了,指定个数,方便识别后面几个是key

    • weights weight[weight...]:每个键的权重,在做交集计算时,每个键中的每个member会将自己分数乘以这个权重,每个键的权重默认是1

    • aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、 min(最小值)、max(最大值)做汇总,默认值是sum

  • 取并集并保存:zunionstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]

内部编码

  • ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplistentries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配 置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。

  • skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作 为内部实现,因为此时ziplist的读写效率会下降。

使用场景

  • 排行榜:视频网站打榜,对点赞投币实时排行,用数据库会比较慢。对于其中的点赞、取消赞等实时操作,做定期刷新

Scan遍历命令与五种类型

scan用于代替keys等许多批量获取键的方法,提高性能

scan采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题,每次scan命令的时间复杂度是 O(1)

即scan将批量查询优化成了一次只扫描一点,从而避免阻塞其他命令,多次scan最终获得一个keys的结果,scan通过一个游标cursor标定扫描到的位置

用法:scan cursor [match pattern] [count number]

  • cursor是必需参数,实际上cursor是一个游标,第一次遍历从0开始,每 次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。

  • match pattern是可选参数,它的作用的是做模式的匹配,这点和keys的 模式匹配很像

  • count number是可选参数,它的作用是表明每次要遍历的键个数,默认 值是10,此参数可以适当增大

对于不同类型的redis value,scan分别衍生出了hscan代替hgetall、sscan代替smembers、zscan代替zrange

但由于用多次scan代替一次批量,已经将一个原子操作变成了一组操作,即整体是非原子性的,就可能产生并发问题,即其他线程向其中set,就会导致cursor发生改变,另一个线程在循环scan,可能导致新增的键可能没有遍历到,或遍历出了重复的键等情况

这时,就需要通过一些并发能力限制scan同时发生的写入操作

0

评论区