Redis 常见命令
启用 redis:redis-cli -h [ip] -p [6379] -a [password]
名词解释
原子操作(命令):可以理解为只有这条命令执行完毕才会进行下一个命令操作,有点阻塞的意思。
位图:这里的位图并不是图形图像中的“位图(像素图片)”,而是一种数据储存概念,即“位图数据结构”。
例如假设我们想记录用户本周 7 天内签到记录,可以使用字符串 “0101101”
:第 1 位表示第 1 天,0 表示未签到,1 表示已签到。
通用命令
keys 命令
计算 redis
所有的键:keys *
keys pu*
(找出以 pu 开头的所有 key 的集合)
keys pu?
(找出以 pu 开头,长度为 3 的 key 的集合)
keys pu[a-g]*
(找出以 pu 开头,后面为 a-g 范围内的字母,*结尾并不限制的 key 的集合)
key 相关命令
计算 key 的总数:dbsize
判断一个 key 是否存在:exists key
删除 key:del key1 key2
设置 key
在 5 秒以后过期:expire key 5
(对于字符串类型比较特别,如果调用了 set 方法重新赋值,那么原来的过期时间会自动删除)
查看 key
的剩余过期时间:ttl key
(如果返回-2 则表示 key 不存在,如果返回-1 则表示该 key 存在且永远不过期)
删除 key
的过期时间(key
不再会过期):persist key
查看 key
的类型:type key
(返回类型可能为:string
、hash
、list
、set
、zset
、none
)
Object 命令
1、查看某个 key
在 redis
内部中记录的编码类型:object encoding key
int
:整数
raw
:一般字符串
embstr
:在 2.8 版本里不存在、3.0 版本里一般字符串小于 39 字节字符串、3.2 版本里小于 44 字节的字符串
zipmap
:比较小的嘻哈表
hashtable
:任意哈希表
ziplist
:节约大小并且较小的列
linkedlist
:任意列表
intset
:值储存数字的小集合
hashtable
:任意集合
ziplist
:比较小的有序集合
skiplist
:任何有序集合
2、查看某个 key
空闲时间(没有被读取或写入):object idletime key
返回空闲秒数
3、查看引用所储存值的次数:object refcount key
暂时没明白含义
type 命令
查看某个 key
对外的类型:type key
返回值有:none
(key
不存在)、string
(字符串)、hash (哈希表)、list
(列表)、set
(集合)、zset
(有序集)
字符串(string)类型操作
设置(新增)key
不管 key
是否存在,都设置(新增)key
:set key value
key
不存在,才设置:setnx key value | set key value nx
key xx
存在,才设置:set key value xx
key
存在的情况下,设置值 value
和过期时间(假设 8 秒):set key value ex 8 xx
设置 key 的值并且设置过期时间的另外一种写法:setex key 8 value nx|xx
整型(整数)运算
自增 1:incr key
(若 key 不存在,自增后 key=1)
自减 1:decr key
(若 key 不存在,自减后 key=-1)
自增 n:incrby n
(若 key 不存在,增加后 key=n)
自减 n:decrby n
(若 key 不存在,自减呼 key=-n)
浮点数运算
自增浮点数(例如 3.5):incrbyfloat key 3.5
批量获取或设置
批量获取:mget key1 key2 key3
批量设置:mset key1 value1 key2 value2 key3 value3
获取和修改 value 相关
设置 key
新的 newvalue
,并且返回旧的 value
:getset key newvalue
追加 value
到旧的 value
尾部:append key value
(返回追加后的长度)
注意:这里是追加到尾部,并不是增加,假设 key 当前值为 38,append key 9
执行以后,key 的值为 389)
返回字符串的长度:strlen key
(注意使用 UTF-8 编码一个汉字为 3 个字节、使用 GB2312
一个汉字为 2 个字节)
获取某个范围的值(例如获取下标 0-5 范围内的字符):getrange key 0 5
注意:返回前 6 个字符,因为第 1 个字符下标为 0,下标为 5 其实是第 6 个字符。若取值范围超出则会忽略超出部分。
设置某个下标的值(例如设置下标 5 的值):setrange key 5 value
(将下标 5 的值改为 value
,并返回修改之后 key
的长度)
注意:如果设置下标超出范围,则会将中间空白的范围自动填充\x00。\x00 表示不可见的字符。
哈希(hash)类型操作
设置(新增)hash
设置 key
的 value
:hset key value
获得 key
的 value
:hget key
删除 key
的 value
:hedel key
当 key
的 field
不存在时,添加并设置:hsetnx key field value
注意:哈希值操作中,不存在 hset key field value nx|xx
这个操作
批量设置或获取
设置 key
的多对 field
和 value
:hmset key field1 value1 field2 value2
获得 key
的多个 value
:hmget key field1 field2
返回 key
的所有 field
和 value
:hgetall key
返回 key
的所有 valus
:hvals key
返回 key
的所有 field
:hkeys key
判断子项(field)是否存在
判断 key
的 field
是否存在:hexists key field``
返回 key
拥有的 field
数量:hlen key
子项(field)自增
让 field
自增(若不存在则创建):hincrby k field n
让 field
浮点数自增:hincrbyfloat key field float
列表(list)类型操作
设置(新增)
当新增时 list
并不存在,则默认新增该 list
获取
返回列表总长度:llen listkey
获取索引对应的值:lindex listkey index
获取某范围内索引对应的值:lrange listkey n m
(若 0 -1,则表示从 0 到最后一个)
设置某个索引对应的值:lset listkey index newValue
删除
删除 count
个列表中某个值:lrem listkey count value
注意:
若 count=0
,则遍历列表,删除所有 value
若 count>0
,则从左开始遍历,删除最多 count
个符合的 value
若 count<0
,则从右开始遍历,删除最多 count
个符合的 value
增加或弹出
从右侧(结尾)插入值:rpush listkey value1 value2
从左侧(开头)插入值:lpush listkey value1 value2
从右侧弹出值:rpop listkey
从左侧弹出值:lpop listkey
在指定的值 前 | 后 插入新的值:linsert listkey before|after value newValue
(会遍历整个列,发现符合 value 就插入一次)
按照索引范围修剪(保留)列表:ltrim listkey start end
lpop 阻塞(等待有可用值)版本:blpop listkey timeout
(timeout
为阻塞超时时间,若 timeout=0
表示永远不阻塞)
rpop 阻塞(等待有可用值)版本:brpop listkey timeout
(timeout
为阻塞超时时间,若 timeout=0
表示永远不阻塞)
实用口诀
lpush + lpop = stack
(栈 先进后出) 永远在列表最前面插入和弹出
lpush + rpop = queue
(队列 先进先出) 在最前面插入,最后面弹出
lpush + ltrim = capped collection
在最前面插入,修剪队列 -> 控制列的数量
lpush + brpop = message queue
在最前面插入,最后面阻塞弹出 -> 实现消息队列
集合(set)类型操作
注意:集合中没有 get set
这些函数、集合里的内容不允许有重复。
新增或删除
向集合 key
中添加集合内容 element:sadd key element
(若 element
存在则添加失败)
删除集合 key
中的某个元素:srem key element
获取
获取集合中元素的个数:scard key
判断某元素是否在集合内容中:sismember key value
随机获取集合中的 count 个元素:srandmember key count
随机弹出(删除)集合中 1 个元素:spop key
获取集合全部元素:smembers key
两个集合间的操作
获取重合的集合内容(交集):sinter follow1 follow2
获取差异的集合内容(差集):sdiff follow1 follow2
获取全部的集合内容(合集):sunion follow1 follow2
有序集合(set)类型操作
概念
有序集合的“值”的构成:score
分数值(可重复) + element
值(不可重复)
注意
无论 string
、hash
、list
、set
,他们赋值通常都是 field + value
,但是有序集合的顺序和他们相反:score + element。
新增
添加有序集合的对值:zadd key score1 element1 score2 element2
(score
可重复,element
不可重复)
注意:该执行复杂程度是 O(logN)
删除
删除有序集合的值:zrem key element1 element2 ...
获取相关
获取有序集合内容总个数:zcard key
获取元素的分数:zscore key element
按照 score 来自增 score
增加或减少某元素的分数:zincrby key count element
(element
是唯一不可重复的)
按照 score 来获取排名
获取某 element
在集合中的排名(根据 score
的值升序排列,即从小到大):zrank key element
某范围内(按照 score 或按照索引)的相关操作,默认均为按 score 升序
获取某个范围内集合的值:zrange key start end withscores
(0 第一个,-1 最后一个)
注意:该执行复杂程度是 O(log(n)+m)
,其中 n 为有序集合中元素总个数,m 为 start
到 end
之间元素的总个数
获取指定分数范围内的所有值:zrangebyscore key minScore maxScore
获取指定分数范围内值的个数:zcount key minScore maxScore
删除指点排名内的升序元素:zremrangebyrank key start end
删除指定分数内的升序元素:zremrangebyscore key minScore maxScore
降序相关操作
降序 rev 开头,从高到低
zrevrank
、zrevrange
、zrevrangebyscore
两个有序集合相关操作
两个有序集合交集排名:zinterstore
两个有序集合合集排名:zunionstore
缓存更新策略
1、LRU/LFU/FIFO 算法剔除:
例如 maxmemory-policy
最大内存策略配置
优点:维护成本低、缺点:数据一致性最差
2、超时剔除:
例如给 key
设置 expire
过期时间
优点:维护成本低、缺点:数据一致性较差
3、主动更新:
开发控制生命周期
优点:数据一致强、缺点:维护成本高
实际项目中的策略:
1、若数据一致性要求不高,采用最大内存和淘汰策略。
2、若对数据一致性要求高,采用超时剔除和主动更新结合,还要加上最大内存和淘汰策略。
问:选择了主动更新为啥还要加上超时剔除?
答:因为万一主动更新失败,让超时剔除作为保底。当然最大内存和淘汰策略是更加基础的保底。
缓存穿透问题
名词解释:缓存穿透
通常一个合理的访问流程是:客户端请求 -> Redis
缓存层 -> 若没有缓存 -> 请求真实数据库 -> 反馈客户端同时将数据写入缓存层
假设客户端的绝大多数请求在缓存层里找不到,被迫需要去请求真实数据库,相当于每次跳过了缓存层,把这种现象称为“缓存穿透”。
造成原因
1、业务代码自身有问题:从数据库拿到的数据,其实没写入缓存层
2、遭遇恶意攻击、爬虫:大量意想不到的请求
如何发现?
1、使用 Redis
后,检查业务性能提高了多少,是否符合预期。
2、设定相关指标:总调度数、缓存层命中数、存储层(数据库)命中数
可以通过 redis
后台(如果有),查看上述统计,观察并优化缓存命中率。
Redis 实用技巧
缓存空对象
假设客户端请求的数据本身不存在(或短期内不存在),业务代码请求储存层(数据库)之后(并没有拿到任何结果),可以给 Redis 内写入一个空对象,并设置过期时间。
这样短期内客户端再请求这条不存在的数据时,可以从缓存层拿到(尽管是空对象),从而暂时性减少存储层的压力。
缺点:数据短期不一致
互斥锁(分布式锁)
假设瞬间大量客户端同时请求一条相同的数据,例如第 1、第 2、第 3...第 n 客户端,业务代码在处理第 1 个客户端请求时,给这条数据请求加上一个锁 lock,之后的第 2、第 3...第 n 客户端的请求会“挂起并等待中”。当第 1 个客户端请求经过真实存储层(数据库)获取并写入缓存层后,将数据请求解锁 unlock,再依次批量将请求结果反馈给第 2...第 n 个客户端。
缺点:若第 1 个客户端的请求一直未顺利执行完毕,则会造成后面第 2...第 n 客户端一直处于“卡顿”中,存在死锁风险。
解决方法:添加锁时增加一个过期时间,比如 3 秒,这样无论第 1 个客户端的流程是否顺利执行完毕,3 秒后都会解锁。
问题又来了,假设第 1 个客户端的执行流程没走完就进行了解锁(自动到期),这时第 2 个客户端也开始执行该请求,也会增加一把锁,那将来解锁环节是解的哪把锁(是第 1 个客户还是第 2 个客户的锁)?
解决方法:相对更加靠谱一些,就是在上锁时增加一个随机数,解锁时对比这个随机数来区分就是是哪把锁。
热点 key 永不过期
对于热点 key
,需要一直存在缓存层中。如果给 key 没有设置过期时间,那么会造成这个 key
的值一直不更新,数据不一致的情况。
如果给 key
设置过期时间,key 会在过期时间结束时被删除掉,直到下一次重新请求并写入缓存中。
如何保证缓存层里永远有一份 key
呢?
实现方法是:当有客户端请求这个 key
时,业务代码去判断 key 的过期剩余时间。假设剩余过期时间小于 8 秒,先把缓存层中 key
的值反馈给客户端,
同时偷偷的进行一次存储层(数据库)数据请求,并将结果更新到缓存层中(依然设定有过期时间)。
缺点:逻辑过期时间增加代码维护成本。
布隆过滤器
1970 年伯顿·布隆提出,2020 年我依然听不懂的一个原理。
Redis 键值设计原则
key 名设计
1、可读性和可管理性:业务名(或数据库名) + : + 表名 + : + id
,例如:mykoa:user:1
可管理性其中包含将来批量删除以 XX 开头的缓存数据。
2、简洁性:保证语义的前提下,控制 key 的长度。例如 user:{uid}:friends:messages:{mid}
简化为 u:{uid}:fr:m:{mid}
注意:建议在 redis3.0
中 key
不要超过 39
字节、3.2
以上版本不要超过 44
字节
3、不要包含特殊字符:例如空格、换行、单引号以及其他转义字符
value 值设计
1、value
不要过于大
string
类型控制在 10k 以内、其他类型(hash/list/set/zset
)元素个数不要超过 5000
2、根据需求,采用合理的数据结构
3、设置声明周期(通过给 key
设置过期时间)
注意:过期时间不宜集中,容易造成缓存穿透和雪崩
Redis 命令技巧
1、若数据量过大,不建议使用 hgetall
、lrange
、smembers
、zrange
、sinter
。可以使用 hscan
、sscan
、zscan
代替。
注意:上述讲的是获取而不是设置,推荐使用 hmse
t 这样的批量设置命令
2、禁止线上使用 keys
、flushall
、flushdb
等(可通过 redis
的 rename
机制禁掉命令)。若线上真的需要,可使用 scan
相关代替。
3、合理使用 select
(按数字进行 redis
多数据库划分)
注意:无论如何 redis
终究是一个单线程,所以无论怎么划分依然是会整体相互影响。
4、不建议过多使用 redis
事务功能。redis
不支持回滚,如果事务操作出错,无法回滚(恢复到上一个正常的状态)。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于