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 field、hincrbyfloat 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列表既可以担任栈的角色,也可以当队列来用,非常灵活
常用命令
r、l代表从左还是从右,有些命令既可以左起也可以右起(例如pop、push),但有些就不行(例如lrange只能从左到右获取元素)
linsert用于在某个位置前/后插入:
linsert key before|after pivot valuelrange可以进行范围查找:
lrange key start endstart和end表示方向和位数,从左到右是0到N,从右到左是-1到-N,lrange性能差,建议改用lscanlpop和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 keysmember性能查,不建议用,建议改用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 member、zrevrank key memberzrank是分数从低到高排名,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同时发生的写入操作
评论区