目录
前面提到了Redis的五个基本的数据类型,在此基础有演变了不少类型。但是这篇要聊一下Redis的丰富的功能。
如下:
-
键过期
-
发布订阅
-
pipeline(管道)
-
事务和lua
-
Bitmaps (位图)
-
HyperLogLog
-
GEO
-
Stream
-
布隆过滤器
1. 键过期
Redis支持对键添加过期时间, 当超过过期时间后, 会自动删除键
常用在缓存、session共享、分布式锁等应用场景
设置过期时间:
expire key seconds //键在seconds秒后过期
expireat key timestamp //键在秒级时间戳timestamp后过期
可通过ttl和pttl查看剩余过期时间
ttl key //返回秒级剩余过期时间
pttl key //返回毫秒级剩余过期时间
有3种返回值:
-
大于等于0的整数: 键剩余的过期时间
-
-1: 键没有设置过期时间
-
-2: 键不存在
2. 发布订阅
Redis提供了基于“发布/订阅”模式的消息机制,此种模式下,消息发布者和订阅者不进行直接通信,发布者客户端向指定的频道(channel)发布消息, 订阅该频道的每个客户端都可以收到该消息 。
发布消息:
publish channel message
订阅消息:
订阅一个或多个消息
subscribe channel [channel ...]
缺点:
-
消息不会持久化
-
新的订阅之前的消息及重新订阅期间的消息无法接收。
3.pipeline(管道)
Redis客户端执行一条命令的过程分为:发送命令、命令排队、命令执行、返回结果。
整个过程的耗时时间称为RTT(往返时间)
虽然Redis提供了部分批量操作命令,有效节约了RTT,但是大部分命令还是不支持批量操作。
Redis提供了pipeline(管道)改善这个问题。pipeline(管道)能将一组命令进行组装通过一次RTT传输给Redis,再将这组命令的返回结果按顺序返回客户端。
原生批量命令与Pipeline对比
可以使用Pipeline模拟出批量操作的效果, 但是在使用时要注意它与原生批量命令的区别, 具体包含以下几点:
-
原生批量命令是原子的, Pipeline是非原子的。
-
原生批量命令是一个命令对应多个key, Pipeline支持多个命令。
-
原生批量命令是Redis服务端支持实现的, 而Pipeline需要服务端和客户端的共同实现。
注意:
每次Pipeline组装命令不能太多,避免一次组装Pipeline数据量过大,增加客户端等待时间;另一个是会造成网络阻塞。
4.事务和Lua
为了保证多条命令组合的原子性, Redis提供了简单的事务功能以及集成Lua脚本来解决这个问题。
4.1 事务
Redis提供了简单的事务功能, 将一组需要一起执行的命令放到multi和exec两个命令之间。 multi命令代表事务开始, exec命令代表事务结束, 它们之间的命令是原子顺序执行的。
//开启事务
127.0.0.1:6379> multi
OK
//集合中添加数据,返回QUEUED代表未真正执行,暂保存在redis中
127.0.0.1:6379> sadd user:a:a1 user:b
QUEUED
127.0.0.1:6379> sadd user:b:b1 user:a
QUEUED
另一个客户端中查看集合中成员是否存在,返回为0:不存在。数据为真正添加
127.0.0.1:6379> sismember user:a:a1 user:b
(integer) 0
停止事务:
127.0.0.1:6379> discard
OK
127.0.0.1:6379> sismember user:a:a1 user:b
(integer) 0
两个个客户端中查看集合中成员是否存在,都返回为0:不存在。
127.0.0.1:6379> sismember user:a:a1 user:b
(integer) 0
提交事务:
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
127.0.0.1:6379> sismember user:a:a1 user:b
(integer) 1
另一个客户端中查看集合中成员是否存在,返回为1:成员存在。
127.0.0.1:6379> sismember user:a:a1 user:b
(integer) 1
命令错误时,Redis的处理机制:
-
命令错误:语法错误,如:set写成了sett等,事务回退,命令未真正执行
-
运行时错误:语法正常,但命令使用不当,事务继续执行
4.2 Lua
Redis将Lua作为脚本语言可帮助开发者定制自己的Redis命令 。
Lua脚本功能带来如下三个好处:
-
Lua脚本在Redis中是原子执行的, 执行过程中间不会插入其他命令。
-
Lua脚本可以帮助开发和运维人员创造出自己定制的命令, 并可以将这些命令常驻在Redis内存中, 实现复用的效果。
-
Lua脚本可以将多条命令一次性打包, 有效地减少网络开销。
比如给一个列表中的所有元素加1,并保证原子执行,就可以通过Lua实现。
示例:列表中所有元素及元素的值
127.0.0.1:6379> lrange user:list 0 -1
1) "user:d:userId"
2) "user:c:userId"
3) "user:b:userId"
4) "user:a:userId"
127.0.0.1:6379> mget user:a:userId user:b:userId user:c:userId user:d:userId
1) "100"
2) "101"
3) "102"
4) "103"
使用Lua给一个列表中的所有元素加1脚本:lrange_inc.lua
local mylist = redis.call("lrange", KEYS[1], 0, -1)
local count = 0
for index,key in ipairs(mylist)
do
redis.call("incr",key)
count = count + 1
end
return count
执行脚本:
redis-cli.exe --eval lrange_inc.lua user:list
(integer) 4
查看执行后的结果:
127.0.0.1:6379> mget user:a:userId user:b:userId user:c:userId user:d:userId
1) "101"
2) "102"
3) "103"
4) "104"
可以看到已经执行成功。
5. Bitmaps (位图)
许多开发语言都提供了操作位的功能, 合理地使用位能够有效地提高内存使用率和开发效率。
Redis提供了Bitmaps可以实现对位的操作。
Bitmaps属于字符串类型的拓展,能对字符串进行位操作,并且有单独的操作命令。
set m1 a
setbit m1 1 0
setbit m1 2 1
get m1
/* 1 2 代表的a的二进制位的修改
a 对应的ASCII码是97,转换为二进制数据是01100001
b 对应的ASCII码是98,转换为二进制数据是01100010
因为bit非常节省空间(1 MB=8388608 bit),可以用来做大数据量的统计。
适用场景:用户的签到,在线用户统计,留存用户统计等
setbit onlineusers 11
setbit onlineusers 12
setbit onlineusers 13
支持按位与、按位或等等操作
BITOPANDdestkeykey[key...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。
BITOPORdestkeykey[key...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。
BITOPXORdestkeykey[key...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。
BITOPNOTdestkeykey ,对给定 key 求逻辑非,并将结果保存到 destkey 。
计算出3天内都在线的用户
BITOP "AND" "3_days_both_online_users" "day_1_online_users" "day_2_online_users" "day_3_online_users"
6. HyperLogLog
HyperLogLog也是字符串类型的拓展,是一种基数算法,通过HyperLogLog可以利用极小的内存空间完成独立总数的统计, 数据集可以是IP、 Email、 ID等。
HyperLogLog提供了3个命令:pfadd、 pfcount、 pfmerge。
HyperLogLog内存占用量非常小, 但是存在错误率, 开发者在进行数据结构选型时只需要确认如下两条即可:
-
只为了计算独立总数, 不需要获取单条数据。
-
可以容忍一定误差率, 毕竟HyperLogLog在内存的占用量上有很大的优势
127.0.0.1:6379> pfadd 2023_02_03:unique:ids "pf-1" "pf-2" "pf-3" "pf-4"
(integer) 1
127.0.0.1:6379> pfcount 2023_02_03:unique:ids
(integer) 4
127.0.0.1:6379> pfadd 2023_02_03:unique:ids "pf-1" "pf-2" "pf-3" "pf-5"
(integer) 1
127.0.0.1:6379> pfcount 2023_02_03:unique:ids
(integer) 5
计算02和03总数
127.0.0.1:6379> pfadd 2023_02_02:unique:ids "pf-1" "pf-2" "pf-3" "pf-6" "pf-7"
(integer) 1
127.0.0.1:6379> pfmerge 2023_02_02_03:unique:ids 2023_02_03:unique:ids 2023_02_02:unique:ids
OK
127.0.0.1:6379> pfcount 2023_02_02_03:unique:ids
(integer) 7
7. GEO
Redis3.2版本提供了GEO(地理信息定位) 功能, 支持存储地理位置信息用来实现诸如附近位置、 摇一摇这类依赖于地理位置信息的功能。
//添加坐标
127.0.0.1:6379> geoadd user:local 114.325916 30.619074 zhangsan
(integer) 1
127.0.0.1:6379> geoadd user:local 114.32615 30.619369 lisi
(integer) 1
127.0.0.1:6379> geoadd user:local 114.324335 30.620239 wanger
(integer) 1
127.0.0.1:6379> geoadd user:local 113.828022 30.271863 mazi
(integer) 1
//查询用户坐标
127.0.0.1:6379> geopos user:local lisi
1) 1) "114.326152503490448"
2) "30.61936950414612824"
127.0.0.1:6379> geopos user:local zhangsan
1) 1) "114.32591646909713745"
2) "30.619072941770483"
127.0.0.1:6379> geopos user:local mazi
1) 1) "113.82802337408065796"
2) "30.27186176792191219"
//计算两坐标之间的距离
127.0.0.1:6379> geodist user:local zhagnsan lisi
(nil)
127.0.0.1:6379> geodist user:local zhangsan lisi
"39.9810"
8. Stream
在Redis5.0之后,新增了一种数据结构Stream,是一个新的强大的支持多播的可持久化的消息队列 。
Stream中的每个消息都由以下两部分组成。
-
每个消息有唯一的消息ID,消息ID严格递增。
-
消息内容由多个field-value对组成 。
xadd Stream名称 *代表ID由Redis自动生成,name mystream1 age 18,消息内容的键值对
xadd mystream * name mystream1 age 18
生产者负责向消息队列中生产消息,消费者消费某个消息流。消费者可以归属某个消费组,也可以不归属任何消费组。当消费者不归属于任何消费组时,该消费者可以消费消息队列中的任何消息。
消费组是Stream的一个重要概念,具有以下特点。
-
每个消费组通过组名称唯一标识,每个消费组都可以消费该消息队列的全部消息,多个消费组之间相互独立。
-
每个消费组可以有多个消费者,消费者通过名称唯一标识,消费者之间的关系是竞争关系,也就是说一个消息只能由该组的一个成员消费。
-
组内成员消费消息后需要确认,每个消息组都有一个待确认消息队列(pending entry list,pel),用以维护该消费组已经消费但没有确认的消息。
-
消费组中的每个成员也有一个待确认消息队列,维护着该消费者已经消费尚未确认的消息。
相比发布/订阅来说,
-
Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。
-
Redis Stream 提供了一个定长 Stream 功能,避免消息堆积,造成缓冲区溢出。
具体可参考文章:Redis消息队列——Redis Stream
9. 布隆过滤器
布隆过滤器是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
具体原理可参考文章:Redis布隆过滤器