Redis 笔记之五:Redis小功能
慢查询
命令执行的典型过程
- 发送命令
- 命令排队
- 命令执行
- 返回结果
慢查询值统计 step3 的执行时间,即使没有慢查询,客户端也可能超时。
阈值参数
相关的阈值参数分别为:slowlog-log-slower-than
和slowlog-max-len
。
1 |
|
查询结果
1) 1) (integer) 7
2) (integer) 1570264725
3) (integer) 7
4) 1) “SLOWLOG”
2) “get”
3) “0”
5) “127.0.0.1:49802”
6) “”
慢日志的格式为:
1 慢日志id
2 日志发生的时间戳
3 命令耗时
4 命令详情
5 客户端地址
6 客户端的名称
参考:https://redis.io/commands/slowlog
最佳实践
1 调大 slowlog-log-slower-than 到1毫秒左右,可以保证 1000 的QPS(实际上在单台 mac pro上的 rps 可以达到接近10万)。
2 调大 slowlog-max-len,并定期把其中的数据取出来存入其他存储层。
3 如果发生客户端超时,注意对照相应的时间点,注意查看是不是存在慢查询导致级联失败。
Redis Shell
redis-cli
1 |
|
redis-server
1 |
|
redis-benchmark
1 |
|
pipeline
如上所述,Redis 命令执行流程是:
- 发送命令
- 命令排队
- 命令执行
- 返回结果
1+4 的耗时统称为RTT(Round Trip Time,往返时间)。
当我们把多个命令合并到一个 RTT 里的时候,可以使用 pipeline。
原生批量命令和 pipeline 的差异是:
- 原生批量命令是原子的,pipeline 是非原子的。
- 原生批量命令是一种操作针对多个 key,而 pipeline 是更高层的组合,一个流水线组合多个批量命令。
- 原生批量命令只靠 Redis 服务端即可实现,pipeline 需要服务端和客户端共同实现。
注意,pipeline 是一种批量发送命令的客户端工作模式,需要关注的是它只是对命令的组合:
1 |
|
事务
Redis 支持简单的事务(multi-exec)以及 lua 脚本。
简单事务(multi-exec)
一个基本的例子1
2
3
4
5
6
7
8multi
sadd user:a:follow user:b
sadd user:b:fans user:a
# 在提交以前,所有的命令都会被 queued 住,提交以后会批量返回批量执行结果
exec
# 在事务提交以后,其他 cli 才能读到最新的结果。被 queued 不算真的执行过
sismember user:b:fans user:a:follow
放弃提交(而不是回滚)的例子:1
2
3multi
incr num1
discard
被 queue 的命令因为被抛弃所以没有被执行。
除此之外,如果命令本身有语法错误,如把 set 写成了 sset,可以在 queue 的时候被检测出来,则事务整体都不会被执行。我们只能得到 EXECABORT 错误。
1 |
|
但是,如果命令本身有运行时错误,比如对错误类型的value 进行了错误的操作(对 list 执行了 zadd 操作),则已经执行成功的命令是不会被回滚的!
1 |
|
上面的操作本身会部分操作成功。可见 Redis 虽然声称这个特性是一个 transacion,但并不具备标准的数据库事务的原子性。
不会回滚是这种工作模式的局限。
乐观锁
在 Redis 中使用 watch 命令可以决定事务是执行还是回滚。一般而言,可以在 multi 命令之前使用 watch 命令监控某些键值对,然后使用 multi 命令开启事务,执行各类对数据结构进行操作的命令,这个时候这些命令就会进入队列。
当 Redis 使用 exec 命令执行事务的时候,它首先会去比对被 watch 命令所监控的键值对,如果没有发生变化,那么它会执行事务队列中的命令,提交事务;如果发生变化,那么它不会执行任何事务中的命令,操作结果就是 nil。
1 |
|
我们也可以使用事务来获取多重结果:1
2
3
4
5
6
7multi
get hello
get hello
exec
1
1
一点额外的结论
1 |
|
多个命令的写入在客户端就好像执行了一样,exec 才是把排队的命令结束。这也就意味着,这个事务的用法不是一个 func,而是一个类似普通事务的“起-止”边界模式。它的缺点是,它无法在执行出错的时候触发前面的执行结果的回滚。
Lua
Redis 提供 eval 和 evalsha 两种方法来调用 Lua 脚本。
lua 脚本拥有以下优点:
- 可以提供原子执行的能力,执行过程中不会插入其他命令。
- 可以提供自定义命令的能力。
- 可以提供命令复用的能力。
我们可以自由建模,然后打包一个组合脚本进行组合之间的运算-所以可以组合使用各种原子 API 来实现复杂计算。
eval
1 |
|
call 如果遇到错误,则脚本执行会返回错误。如果需要忽略错误执行,需要使用 pcall。
除了 redis.call 以外,还可以使用 redis.log 来把日志打印到 redis 的日志文件里,但要注意日志级别。
如果脚本比较长,可以考虑使用外部文件,配合 —eval 选项来执行。
Redis 的高版本里自带 lua debuger。
evalsha
这个功能可以实现 lua script 的复用,其基本流程为:
- 将 lua 脚本加载到服务器端,得到脚本的 sha1 指纹。
- evalsha 可以使用 sha1 指纹来复用脚本,避免重复发送脚本到服务器端的开销。
1 |
|
管理 lua 脚本
1 |
|
位图
详细的用法见位操作命令。
它的用例比较有意思,一个典型的用例是,统计一个大型社交网站的所有成员的具体登录信息。我们可以统计每天都产生了多少登录,最小的登录 id 是什么,最大的登录 id 是什么。但位图也不是万能的,如果位图很稀疏,则不如转为一个 list 或者 set 会更省内存-这需要做内存测试。
HyperLogLog
HyperLogLog 并不是新的数据结构,而是字符串与基数算法(cardinality algorithm)的结合。
1 |
|
HyperLogLog 本身极省内存,但数据量变大后,pfcount 会变得不准,最多有 0.81%的失误率。
1 |
|
HyperLogLog 具有以下特点:
- 不能取出存入数据。
- 计数不准,近似准确。
- 极省内存。
在现实之中,bitmap、HyperLogLog和传统的 set 可以视场景交替使用或者配合使用。比如 bitmap 标识哪些用户活跃,hyperloglog计数。
发布(publish)/订阅(subscribe)
在老版本的 Redis 里,开发者可以通过 list 这一数据结构来模拟消息队列中间件,但后来 Redis 提供了发布订阅功能。这一功能清晰地解耦了发布者和订阅者,两者不直接通信,发布者客户端)向指定的频道(channel)发布消息,订阅改频道的客户端都可以收到该消息。
1 |
|
值得注意的亮点分别是:
- 客户端在执行订阅命令后就进入订阅状态,只能接收 subscribe、psubscribe、unsubscribe 和 punscribe 四个命令。
- 新开启的客户端,无法收到该频道之前的消息,因为 Redis 不会对发布的消息进行持久化。—消息既无法堆积(accumulate),也无法回溯(backtrace)。
GEO(地理信息定位)
Redis 的 GEO 可以用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。
1 |
|
此外还可以对 geo 集合的成员求geohash。1
2# wx48ypbe2q
geohash cities:locations beijing
- geohash 的长度越长,精度越精确。
- 两个 geohash 越相似,距离越近。