6

Redis知识整合(一):常用数据结构&指令集

 2 years ago
source link: https://exceting.github.io/2021/05/18/Redis%E7%9F%A5%E8%AF%86%E6%95%B4%E5%90%88%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E5%B8%B8%E7%94%A8%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84&%E6%8C%87%E4%BB%A4%E9%9B%86/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Redis知识整合(一):常用数据结构&指令集

前言:Redis各版本重大更新

截至2021年5月,Redis的稳定版本已经更新到了6.2,所以这里我想整理一下redis自发布以来每一个大版本的重要更新,当然我不会全部都展示出来,只挑那些具有重要革新的功能说。

redis2.6

发行于2012年,相比旧版,它新支持了以下功能:

  • redis-server支持Lua脚本
  • 键的过期时间可以精确到毫秒
  • slave节点提供只读功能
  • 新指令:bitcountbittop(操作位图用的)
  • 基于浮点数的自增功能:incrbyfloathincrbyfloat
  • redis-cli支持用--eval参数实现Lua脚本

redis2.8

发行于2013年11月,新功能如下:

  • 支持部分主从复制,降低了由于网络问题导致频繁全量复制RDB时对系统造成的压力
  • 支持IPv6
  • 支持config rewrite指令可以将config set持久化到redis的配置文件中
  • 新指令:pubsub(给订阅发布功能用的)
  • redis哨兵v2

redis3.0

发行于2015年4月,新功能如下:

  • 支持redis集群(Redis Cluster),这是redis官方的集群方案,在这之前,有两种方案也可以实现redis的集群:CodisTwemproxy
  • LRU算法大幅提升
  • migrate连接缓存,大幅提升键的迁移速度
  • incrbitcount命令性能提升

redis3.2

  • 新增GEO功能
  • 对redis底层字符串(SDS)在速度和节省空间上都做了相关优化
  • 支持用upstartsystemd管理redis进程
  • 新的list编码类型:quicklist
  • 新增hstrlen指令、增强debugspop指令(支持的参数更多了)、增强cluster nodes指令(提速)
  • 新RDB格式(兼容旧版),提升了RDB文件的加载速度
  • Jemalloc更新至4.0.3版本

redis4.0

  • 提供模块功能,方便第三方拓展功能
  • PSYNC2.0:优化了之前版本中,主从节点切换必然引起全量复制的问题
  • 支持LFU,并对已有缓存过期算法进行了优化
  • 提供非阻塞式的delflushall/flushdb功能,解决了删除大key时可能会造成redis阻塞的问题
  • 提供RDB-AOF混合持久化格式,充分结合了RDB和AOF的优点
  • 新增memory指令,使内存监控更加全面
  • Redis Cluster兼容NAT和docker

一、五种数据结构

redis有五种数据结构,在操作它们时会使用到不同的api,在实际开发中,这些api也是离我们开发最近的一块内容,所以熟练掌握常用的api对我们非常有用。

字符串(string)

最基本的k-v格式,value是一个字符串,它的内部编码如下:

指令 解释 时间复杂度

set [k] [v] 最常用的质量,写入一对k-v O(1)

mset [k1] [v1] [k2] [v2] … 批量写入k-v O(k) k=键数

set [k] [v] nx 只有当key不存在时才会写入成功(新增) O(1)

setnx [k] [v] 作用等同于上面一条 O(1)

set [k] [v] xx 只有当key存在时才会写入成功(修改) O(1)

setxx [k] [v] 作用等同于上面一条 O(1)

get [k] 取value值 O(1)

del [k1] [k2] … 删除掉k-v数据,支持传多个key O(k) k=键数

incr [k] 自增 O(1)

decr [k] 自减 O(1)

incrby [k] [num] 自增指定的数字 O(1)

decrby [k] [num] 自减指定的数字 O(1)

incrbyfloat [k] [num] 自增指定的数字(针对浮点数自增) O(1)

append [k] [v] 往原有字符串尾部追加内容 O(1)

strlen [k] 获取value的长度(单位:字节) O(1)

setrange [k] [offset] [v] 字符替换,offset为被替换字符的位置 O(1)

getrange [k] [start] [end] 获取部分字符串,截取区间:[start, end] O(n) n=字符串长度

适用范围:做基本的分布式缓存、分布式锁、计数/限流、保存集群下的登录状态等。

列表(list)

顾名思义,一个key对应了一个列表,列表内保存着多个value,其内部结构和编码如下:

其中list-max-ziplist-entrieslist-max-ziplist-value都是redis的配置项。

指令 解释 时间复杂度

rpush [k] [v1] [v2] … 从列表的右边插入元素 O(k) k=插入元素数

lpush [k] [v1] [v2] … 从列表的左边插入元素 同上

linsert [k] before/after [vx] [vn] 向vx的前或后面插入vn O(n) n=vx与表头/尾的距离

lrange [k] [start] [end] 输出指定范围的列表,范围:[start, end],start/end为下标值 O(s+n) s=start偏移量;n=start~end的范围

lindex [k] [index] 获取指定下标的value O(n) n=index偏移量

llen [k] 获取列表长度 O(1)

lpop [k] 弹出左边第一个元素 O(1)

rpop [k] 弹出右边第一个元素 O(1)

blpop [k] [timeout] 阻塞式弹出左边第一个元素,timout非必须,当列表中没有元素时阻塞 O(1)

brpop [k] [timeout] 阻塞式弹出右边第一个元素,timout非必须,当列表中没有元素时阻塞 O(1)

lrem [num] [v] 删除v包括其前后的元素,这取决于num:num>0,从左至右删除最多num个元素;num<0,从右至左删除最多|count|个元素;num=0,全部删除 O(n) n=列表长度

ltrim [k] [start] [end] 只保留[start, end]区间的数据 O(n) n=start~end的长度

lset [k] [index] [v] 修改指定下标中的元素 O(n) n=index偏移量

诚如你所见,我们可以利用lpush和brpop来实现一个消息队列:

除此之外,我们可以利用它来保存一个信息id列表,然后用lrange指令实现列表的高效分页,拿到单页id集合后再去批量查询这些id对应的详细信息,最终聚合成完整信息输出给客户端,这样既节约资源,又便于我们设计和复用缓存结构。(ps:根据lrange的复杂度不难推断出在数据量庞大的情况下会影响其性能,这时可以尝试大key拆分成小key,比如可以利用CRC32(value) % key_num来做数据拆分,当然也可以利用redis3.2推出的quicklist内部编码实现,它对大key的性能一样很高)

集合(set)

跟list一样,一个key对应一堆value,但是set不允许有重复的value,同时它也是无序的,其内部结构和编码如下:

其中set-max-intset-entrie可配。

指令 解释 时间复杂度

sadd [k] [v1] [v2] … 添加元素 O(k) k=插入元素数

srem [k] [v1] [v2] … 删除元素 O(k) k=删除元素数

scard [k] 获取元素总数 O(1)

sismember [k] [v] v是否在集合k中 O(1)

srandmember [k] [num] 从集合k中随机返回num个元素 O(k) k=num

spop [k] [num] 从集合k中随机弹出num个元素 O(1)

smembers [k] 获取所有元素(结果无序) O(n) n=元素总数

sscan [k] 迭代出所有元素,类似下面scan的用法 同上

sinter [k1] [k2] … 求多个集合的交集 O(m*k) k=多个集合中最少元素数,m=集合数

sinterstore [dk] [k1] [k2] … 将算出的交集存入dk中 同上

suinon [k1] [k2] … 求多个集合的并集 O(k) k=多个集合的总元素数

suinonstore [dk] [k1] [k2] … 将算出的并集存入dk中 同上

sdiff [k1] [k2] … 求多个集合的差集 同上

sdiffstore [dk] [k1] [k2] … 将算出的差集存入dk中 同上

相比list,set并不能用来分页以及展示一个列表,因为它是无序的,但我们仍然可以为它找到合适的应用场景,比如可以用它来存储用户的喜好标签,便于利用取交集差集等功推荐同好;也可以存储某些权益信息,比如某大V粉丝集合,利用sismember指令可以很快得出一个用户是否是另一个用户的粉丝(得出按钮展示“已关注”还是“未关注”),这同样可以计算一个大V与另一个大V的粉丝重合度。

有序集合(zset)

相比set,zset可以进行灵活排序、分页等操作,同样它也不允许有重复元素,其内部结构和编码如下:

其中zset-max-ziplist-entrieszset-max-ziplist-value都是redis的配置项。

指令 解释 时间复杂度

zadd [k] [s1] [v1] [s2] [v2] … 添加成员 O(k*log(n)) k=插入个数,n=总个数

zscore [k] [v] 获取某成员分数 O(1)

zcard [k] 获取元素总数 O(1)

zcount [k] [min] [max] 获取score值在[min, max]区间的元素总数 O(log(n)) n=总个数

zincrby [k] [num] [v] 给v加num分 同上

zrank [k] [v] 返回v的正序排名 同上

zrevrank [k] [v] 返回v的倒序排名 同上

zrem [k] [v1] [v2] … 删除元素 O(k*log(n)) k=删除个数,n=总个数

zscan [k] 迭代出[k]里所有的元素,类似下面scan的用法 O(N) N=总个数

zrange [k] [start] [end] withsocres 按分数从小到大返回[start, end]区间内的数据(score+v都会返回) O(log(n)+k) k=获取数;n=总个数

zrevrange [k] [start] [end] withscores 按分数从大到小返回[start, end]区间内的数据(score+v都会返回) 同上

zrangebyscore [k] [min] [max] withscores 返回score值在[min, max]区间的数据(按从小到大排序) 同上

zrevrangebyscore [k] [max] [min] withscores 返回score值在[max, min]区间的数据(按从大到小排序) 同上

zremrangebyrank [k] [start] [end] 删除[start, end]名次区间内的数据 O(log(n)+k) k=删除数;n=总个数

zremrangebyscore [k] [min] [max] 删除分数在[min, max]区间内的数据 同上

zinterstore [dk] [num] [k1] [k2] … 将k1、k2等集合的交集存入dk中 O(n*k)+O(m*log(m)) n=成员数最小集合的成员数,k=参与取交集的集合数,m=结果集中的成员数

zunionstore [dk] [num] [k1] [k2] … 将k1、k2等集合的并集存入dk中 O(n)+O(m*log(m)) n=所有集合成员数之和,m=结果集中的成员数

作为一个支持正序、倒序、分页的集合,它特别适合做一些排行榜类的需求,这样可以很方便的获取名次、分数等关键信息;

除此之外也可以做一些含排序操作的列表页:比如b站的个人空间中up主的稿件列表,可以按照时间、播放量、收藏量来进行正序和倒序展示,同时还支持分页,那么我们就可以建设3个统计维度的有序集合(分别以投稿时间、播放量、收藏量做score),value保存稿件id,然后按照排序类型、页码查询出单页数据,再利用单页的id集合批量查询稿件详细信息,最终将这些信息聚合成接口返回给客户端即可。

哈希(hash)

hash类型的数据本身就是一个k-v结构,也就是一个redis key对应多个k-v,结构和内部编码如下:

其中hash-max-ziplist-entrieshash-max-ziplist-value都是redis的配置项。

指令 解释 时间复杂度

hset [k] [KEY] [VALUE] 为[k]添加一对[KEY]-[VALUE] O(1)

hget [k] [KEY] 获取[k]里[KEY]对应的value O(1)

hdel [k] [KEY1] [KEY2] … 删除[k]里的某些[KEY] O(k) k=被删KEY的个数

hlen [k] 获取[k]里[KEY]的数量 O(1)

hgetall [k] 获取[k]里所有的[KEY]-[VALUE] O(n) n=KEY数

hscan [k] 迭代的方式获取[k]里所有的[KEY]-[VALUE],用法跟后面的scan类似 O(n) n=单次迭代结果数

hmget [k] [KEY1] [KEY2] … 批量获取[k]里一些[KEY]的[VALUE] O(k) k=获取的KEY数

hmset [k] [KEY1] [VALUE1] [KEY2] [VALUE2] … 为[k]添加多个[KEY]-[VALUE] O(k) k=插入的KEY数

hexists [k] [KEY] 判断[k]内是否存在[KEY]键 O(1)

hkeys [k] 获取[k]内所有的[KEY] O(n) n=KEY数

hvals [k] 获取[k]内所有的[VALUE] 同上

hsetnx [k] [KEY] [VALUE] 当[VALUE]不存在时才设置成功 O(1)

hincrby [k] [KEY] [num] 对[k]内[KEY]的值自增num O(1)

hincrbyfloat [k] [KEY] [num] 对[k]内[KEY]的浮点值自增num O(1)

hstrlen [k] [KEY] 获取[k]内[KEY]对应[VALUE]的length O(1)

用处主要是用来结构化的存储一些数据,比如可以把一个对象各个属性和属性值当成hash结构缓存入redis。

二、通用指令

下面是一些通用的指令,不同于上面的指令,这些指令不会针对某些数据结构生效,而是全局有效。

指令 解释 时间复杂度

keys [xx]* 获取所有前缀为“xx”的key,若直接为keys *就是获取所有key,重操作 O(n) n=key总数

scan [cursor] [xx]* [count] 非阻塞式获取所有key,相比keysscan是利用[cursor]做游标进行迭代的,[count]则是返回的条数,默认10条,每次迭代结果中会返回下次迭代所需的[cursor],这样就可以一步步迭代出来所有的key了,它不是一个重操作,也就不会长阻塞redis线程 O(n) n=单次迭代结果数

dbsize 获取当前db内的key总数 O(1)

exists [k1] [k2] … 是否存在[k1]、[k2]等键 O(n) n=查看key的个数

del [k1] [k2] … 删除[k1]、[k2]等键 O(n) n=删除key的个数

expire [k] [time] 让[k]time秒后过期 O(1)

persist [k] 取消[k]的过期时间 O(1)

ttl [k] 返回[k]的剩余过期时间,若返回-1,说明没设过期时间,-2表示[k]不存在,>0表示剩余的时间,单位秒 O(1)

type [k] 查看[k]对应值的类型(string、set、zset、list、hash) O(1)

object encoding [k] 查看[k]的内部编码(int、embstr、raw、ziplist、hashtable、linkedlist、skiplist、intset) O(1)

rename [k1] [k2] 将[k1]重命名为[k2] O(1)

renamenx [k1] [k2] 当k2不存在时才能重命名成功 O(1)

randomkey 随机返回一个key O(1)

dump [k] 将[k]的值dump成RDB格式的数据 O(1)+O(M*N) M=k对应的对象数;N=对象平均大小,对于值很小的情况,复杂度趋近O(1)

restore [k] [ttl] [v] 跟dump结合使用,[v]=dump出的数据,适用于数据转移(比如将redis1中的[k]转移到redis2中);[ttl]为过期时间,若为0,则表示没有过期时间(这种迁移方式非原子性) 同上,但有序集合为O(N * M * log(N))

migrate [ip] [port] `key “”[db] [timeout]copyreplacekeys` [k1] [k2]… 数据迁移(这个命令实际上是在源实例中执行一次dump+del,在目标实例中执行一次restore),[ip]+[port]为迁移的目标机器,`key

select [db] 选择redis数据库,db为数据库下标(一般常为0)redis3中已弱化db功能,redis cluster更是不支持多db,默认0号db O(1)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK