Redis相关命令详解及其原理
Redis相关概念
Redis 是 Remote Dictionary Service 的简称;即远程字典服务;
Redis 是内存数据库,KV 数据库,数据结构数据库;
Redis中value的数据结构
redis是key-value数据库,redis提供5种基本数据结构存储其中的value。
String
String 是一种二进制安全的数据结构(即每个String都有一个长度字段,通过长度字段来区分每个String类型的数据,而不是C语言中使用\0来分割每个string),可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。
String是动态字符串 raw,当字符串长度小于1M 时,加倍扩容;超过 1M 每次只多扩 1M;字符串最大长度为 512M
常用命令
命令 | 介绍 |
---|---|
SET key value | 设置指定 key 的值 |
SETNX key value | 只有在 key 不存在时设置 key 的值 |
GET key | 获取指定 key 的值 |
MSET key1 value1 key2 value2 … | 设置一个或多个指定 key 的值 |
MGET key1 key2 ... | 获取一个或多个指定 key 的值 |
STRLEN key | 返回 key 所储存的字符串值的长度 |
INCR key | 将 key 中储存的数字值增一 |
DECR key | 将 key 中储存的数字值减一 |
EXISTS key | 判断指定 key 是否存在 |
DEL key(通用) | 删除指定的 key |
EXPIRE key seconds(通用) | 给指定 key 设置过期时间 |
存储结构
字符串长度小于等于 20 且能转成整数,则使用 int 存储;
字符串长度小于等于 44,则使用 embstr 存储;
字符串长度大于 44,则使用 raw 存储;
应用
当一个对象极少修改时,可以用String类型来存储对象
SET role:10001 '{["name"]:"mark",["sex"]:"male",["age"]:30}'SET role:10002 '{["name"]:"darren",["sex"]:"male",["age"]:30}'# 极少修改,对象属性字段很少改变的时候
GET role:10001
# key 如何来设置
# 1. 有意义的字段 role 有多行
# 2. role:10001 redis 客户端 role:10001:recharge role:10001:activity:10001
因为String类型可以存储整数和浮点数类型的数据,因此可以用String来作为一个计数器或累加器。
# 统计阅读数 累计加1incr reads# 累计加100incrby reads 100
基于String的SETNX命令,可以用String类型来作为一个分布式锁。
# 加锁 加锁 和 解析 redis 实现是 非公平锁 ectd zk 用来实现公平锁
# 阻塞等待 阻塞连接的方式
# 介绍简单的原理: 事务
setnx lock 1 # 不存在才能设置 定义加锁行为 占用锁
setnx lock uuid # expire 30 过期
set lock uuid nx ex 30# 释放锁
del lockif (get(lock) == uuid)del(lock);
String底层是以二进制来存储的,因此String可以实现位运算,位运算可以用来实现签到功能。
# 猜测一下 string 是用的 int 类型 还是 string 类型
# 月签到功能 10001 用户id 202106 2021年6月份的签到 6月份的第1天
setbit sign:10001:202106 1 1# 计算 2021年6月份 的签到情况
bitcount sign:10001:202106# 获取 2021年6月份 第二天的签到情况 1 已签到 0 没有签到
getbit sign:10001:202106 2
List(列表)
Redis 的 List 的实现为一个 双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
常用命令
命令 | 介绍 |
---|---|
RPUSH key value1 value2 ... | 在指定列表的尾部(右边)添加一个或多个元素 |
LPUSH key value1 value2 ... | 在指定列表的头部(左边)添加一个或多个元素 |
LSET key index value | 将指定列表索引 index 位置的值设置为 value |
LPOP key | 移除并获取指定列表的第一个元素(最左边) |
RPOP key | 移除并获取指定列表的最后一个元素(最右边) |
LLEN key | 获取列表元素数量 |
LRANGE key start end LRANGE key 0 -1 | 获取列表 start 和 end 之间 的元素 获取列表 第0个到最后一个元素之间 的元素 |
LREM key count value | 因为list不具备去重功能,因此一个list可能存在多个相同的value,LREM从左开始删除count个value |
BRPOP key timeout | 它是 RPOP 的阻塞版本,因为这个命令会 在给定list无法弹出任何元素的时候阻塞连接 |
应用
list可以从左与右同时追加value和去除value,通过限制,可以基于lsit实现栈(左进)和队列
#栈
LPUSH + LPOP# 或者
RPUSH + RPOP
#队列LPUSH + RPOP# 或者
RPUSH + LPOP
阻塞队列
LPUSH + BRPOP# 或者
RPUSH + BLPOP
基于LRANGE/RRANGE可以用来实现显示固定窗口记录
# 在某些业务场景下,需要获取固定数量的记录;比如获取最近50条战绩;这些记录需要按照插入的先
后顺序返回;
lpush says '{["name"]:"零声教育【Mark老师】", ["text"]:"祝大家儿童节快乐!",
["picture"]:["url://image-20210601172741434.jpg", "url://image
20210601172741435.jpg"], timestamp = 1231231230}'lpush says '{["name"]:"零声教育【King老师】", ["text"]:"祝大家儿童节快乐!",
["picture"]:["url://image-20210601172742434.jpg", "url://image
20210601172741436.jpg"], timestamp = 1231231231}'lpush says '{["name"]:"零声教育【Darren老师】", ["text"]:"祝大家儿童节快乐!",
["picture"]:["url://image-20210601172743434.jpg", "url://image
20210601172741437.jpg"], timestamp = 1231231232}'lpush says '{["name"]:"零声教育【Mark老师】", ["text"]:"一切只为渴望更优秀的你",
["picture"]:["url://image-20210601172744434.jpg", "url://image
20210601172741438.jpg"], timestamp = 1231231233}'lpush says '{["name"]:"零声教育【Darren老师】", ["text"]:"hello 0Voice! hello
to better self", ["picture"]:["url://image-20210601172745439.jpg",
"url://image-20210601172741435.jpg"], timestamp = 1231231234}'lpush says '{["name"]:"零声教育【King老师】", ["text"]:"2021届学员真牛逼!",
["picture"]:["url://image-20210601172745434.jpg", "url://image
20210601172741440.jpg"], timestamp = 1231231235}'# 裁剪最近5条记录 战绩 近50条
ltrim says 0 4lrange says 0 -1
list适合描述插入有序的列表,如朋友圈列表就是插入有序的,先发布的在后面,后发布的在前面,再比如抖音的发布作品、朋友圈点赞列表、评论区id列表,也适合用lsit描述。
Hash
Redis 中的 Hash 是一个 String 类型的 field-value(键值对) 的映射表,特别适合用于存储对象,后续操作的时候,你可以直接修改这个对象中的某些字段的值。
Hash 类似于 JDK1.8 前的 HashMap
,内部实现也差不多(数组 + 链表)。不过,Redis 的 Hash 做了更多优化。
常用命令
命令 | 介绍 |
---|---|
HSET key field value | 设置指定哈希表中指定字段的值 |
HSETNX key field value | 只有指定字段不存在时设置指定字段的值 |
HMSET key field1 value1 field2 value2 ... | 同时将一个或多个 field-value (域-值)对设置到指定哈希表中 |
HGET key field | 获取指定哈希表中指定字段的值 |
HMGET key field1 field2 ... | 获取指定哈希表中一个或者多个指定字段的值 |
HGETALL key | 获取指定哈希表中所有的键值对 |
HEXISTS key field | 查看指定哈希表中指定的字段是否存在 |
HDEL key field1 field2 ... | 删除一个或多个哈希表字段 |
HLEN key | 获取指定哈希表中字段的数量 |
存储结构
节点数量大于 512(hash-max-ziplist-entries) 或所有字符串长度大于 64(hash-max-ziplist value),则使用 dict 实现;
节点数量小于等于 512 且有一个字符串长度小于 64,则使用 ziplist 实现;
应用
hash因为是k-v组织的映射表,所以适合用于描述东西的属性字段,如朋友圈的各种属性如点赞数、评论数,商品的各种属性如好评数、价格、购买数。因此可以用来存储易发生变化的对象。
hmset hash:10001 name mark age 18 sex male# 与 string 比较
set hash:10001 '{["name"]:"mark",["sex"]:"male",["age"]:18}'# 假设现在修改 mark的年龄为19岁
# hash:hset hash:10001 age 19# string: get hash:10001# 将得到的字符串调用json解密,取出字段,修改 age 值# 再调用json加密set hash:10001 '{["name"]:"mark",["sex"]:"male",["age"]:19}'
同时hash也适合缓存热点数据,减小关系数据库的压力。
Set
Redis 中的 Set 类型是一种无序集合,集合中的元素没有先后顺序但都唯一。当你需要存储一个列表数据,又不希望出现重复数据时,Set 是一个很好的选择,并且 Set 提供了判断某个元素是否在一个 Set 集合内的重要接口,这个也是 List 所不能提供的。
你可以基于 Set 轻易实现交集、并集、差集的操作,比如你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。这样的话,Set 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
常用命令
命令 | 介绍 |
---|---|
SADD key member1 member2 ... | 向指定集合添加一个或多个元素 |
SMEMBERS key | 获取指定集合中的所有元素 |
SCARD key | 获取指定集合的元素数量 |
SISMEMBER key member | 判断指定元素是否在指定集合中 |
SINTER key1 key2 ... | 获取给定所有集合的交集 |
SINTERSTORE destination key1 key2 ... | 将给定所有集合的交集存储在 destination 中 |
SUNION key1 key2 ... | 获取给定所有集合的并集 |
SUNIONSTORE destination key1 key2 ... | 将给定所有集合的并集存储在 destination 中 |
SDIFF key1 key2 ... | 获取给定所有集合的差集 |
SDIFFSTORE destination key1 key2 ... | 将给定所有集合的差集存储在 destination 中 |
SPOP key count | 随机移除并获取指定集合中一个或多个元素 |
SRANDMEMBER key count | 随机获取指定集合中指定数量的元素 |
存储结构
元素都为整数且节点数量小于等于 512(set-max-intset-entries),则使用整数数组存储;
元素当中有一个不是整数或者节点数量大于 512,则使用字典存储;
应用
set是无序且唯一的集合,支持交集、并集、差集操作。
基于set元素的唯一性,可以用来实现抽奖
# 添加抽奖用户sadd Award:1 10001 10002 10003 10004 10005 10006sadd Award:1 10009# 查看所有抽奖用户smembers Award:1# 抽取多名幸运用户srandmember Award:1 10# 如果抽取一等奖1名,二等奖2名,三等奖3名,该如何操作?
基于交集操作,可以用来实共同关注
sadd follow:A mark king darren mole vicosadd follow:C mark king darrensinter follow:A follow:C
基于差集操作,可以用来实现推荐好友
sadd follow:A mark king darren mole vicosadd follow:C mark king darren# C可能认识的人:sdiff follow:A follow:C
zset
zset 类似于 Set,但和 Set 相比,zset 增加了一个权重参数 score
,使得集合中的元素能够按 score
进行有序排列,还可以通过 score
的范围来获取元素的列表。
常用命令
命令 | 介绍 |
---|---|
ZADD key score1 member1 score2 member2 ... | 向指定有序集合添加一个或多个元素 |
ZCARD KEY | 获取指定有序集合的元素数量 |
ZSCORE key member | 获取指定有序集合中指定元素的 score 值 |
ZINTERSTORE destination numkeys key1 key2 ... | 将给定所有有序集合的交集存储在 destination 中,对相同元素对应的 score 值进行 SUM 聚合操作,numkeys 为集合数量 |
ZUNIONSTORE destination numkeys key1 key2 ... | 求并集,其它和 ZINTERSTORE 类似 |
ZDIFF destination numkeys key1 key2 ... | 求差集,其它和 ZINTERSTORE 类似 |
ZRANGE key start end | 获取指定有序集合 start 和 end 之间的元素(score 从低到高) |
ZREVRANGE key start end | 获取指定有序集合 start 和 end 之间的元素(score 从高到底) |
ZREVRANK key member | 获取指定有序集合中指定元素的排名(score 从大到小排序) |
存储结构
节点数量大于 128 或者有一个字符串长度大于 64,则使用跳表(skiplist);
节点数量小于等于 128(zset-max-ziplist-entries)且所有字符串长度小于等于 64(zset-max ziplist-value),则使用 ziplist 存储;
应用
基于元素的score实现排行榜
# 点击新闻:zincrby hot:20230612 1 10001zincrby hot:20230612 1 10002zincrby hot:20230612 1 10003zincrby hot:20230612 1 10004zincrby hot:20230612 1 10005zincrby hot:20230612 1 10006zincrby hot:20230612 1 10007zincrby hot:20230612 1 10008zincrby hot:20230612 1 10009zincrby hot:20230612 1 10010# 获取排行榜:zrevrange hot:20230612 0 9 withscores
延时队列,将消息序列化成一个字符串作为 zset 的 member;这个消息的到期处理时间作为 score,然后用 多个线程轮询 zset 获取到期的任务进行处理。
def delay(msg):msg.id = str(uuid.uuid4()) #保证 member 唯一value = json.dumps(msg)retry_ts = time.time() + 5 # 5s后重试redis.zadd("delay-queue", retry_ts, value)# 使用连接池
def loop():while True:values = redis.zrangebyscore("delay-queue", 0, time.time(), start=0, num=1)if not values:time.sleep(1)continuevalue = values[0]success = redis.zrem("delay-queue", value)if success:msg = json.loads(value)handle_msg(msg)# 缺点:loop 是多线程竞争,两个线程都从zrangebyscore获取到数据,但是zrem一个成功一个失
败,
# 优化:为了避免多余的操作,可以使用lua脚本原子执行这两个命令
# 解决:漏斗限流
分布式定时器:生产者将定时任务 hash 到不同的 redis 实体中,为每一个 redis 实体分配一个 dispatcher 进程, 用来定时获取 redis 中超时事件并发布到不同的消费者中;
0voice · GitHub