# Redis的一些小功能
Redis提供的5种数据结构已经足够强大,但除此之外,Redis还提供了诸如慢查询分析、功能强大的Redis Shell、Pipeline、事务与Lua脚本、Bitmaps、HyperLogLog、发布订阅、GEO等附加功能,这些功能可以在某些场景发挥重要的作用。
# Redis慢查询分析
许多存储系统(例如MySQL)提供慢查询日志帮助开发和运维人员定位系统存在的慢操作。所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阀值,就将这条命令的相关信息(例如:发生时间,耗时,命令的详细信息)记录下来,Redis也提供了类似的功能。
一般来说,我们可以将Redis客户端执行一条命令·的过程拆分为以下四个阶段: 发送命令、命令排队、命令执行、返回结果。 对于这四个阶段,Redis只统计命令执行这个阶段所耗费的时间。
# Redis慢查询配置参数
对于慢查询,我们一般配置两个参数,一个预设阈值,另一个最大日志记录数。
- 预设阈值:配置这个选项就是设置最大超时时间(单位:微秒),当Redis执行命令耗费的时间超过这个预设阈值时,Redis就会记录这个慢查询,可以使用
slowlog-log-slower-than
参数设置超时时间。该参数默认值是10000。如果slowlog-log-slower-than=0
会记录所有的命令,slowlog-log-slowerthan<0
对于任何命令都不会进行记录。 - 最大日志记录数:实际上,Redis使用了一个列表来存储慢查询日志,
slowlog-max-len
参数设置的就是列表的最大长度。当一个新的命令满足慢查询条件时慢查询将被插入到这个列表中,当慢查询日志列表已处于其最大长度时,最早插入的一个命令将从列表中移出。
# 修改Redis配置参数
在Redis中有两种修改配置的方法,一种是修改配置文件,另一种是使用config set
命令动态修改。例如下面使用config set
命令将slowlog-log-slower-than
设置为20000微秒,slowlog-max-len
设置为1000。
config set slowlog-log-slower-than 20000
config set slowlog-max-len 1000
config rewrite
如果要Redis将配置持久化到本地配置文件,需要执行config rewrite命令。
虽然慢查询日志是存放在Redis内存列表中的,但是Redis并没有暴露这个列表的键,而是通过一组命令来实现对慢查询日志的访问和管理。下面介绍这几个命令。
# 获取慢查询日志
slowlog get [n]
下面操作返回当前Redis的慢查询,参数n可以指定条数。每个慢查询日志有4个属性组成,分别是慢查询日志的标识id、发生时间戳、命令耗时、执行命令和参数。
# 获取慢查询日志列表当前的长度
slowlog len
# 慢查询日志重置
slowlog reset
这个命令实际上是对列表进行清理工作,清理过后,若执行slowlog len
命令,日志长度为零。
# 慢查询最佳实践
slowlog-max-len
配置建议:线上建议调大慢查询列表,记录慢查询时Redis会对长命令做截断操作,并不会占用大量内存。增大慢查询列表可以减缓慢查询被剔除的可能,线上可设置为1000以上。slowlog-log-slower-than
配置建议:默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值。由于Redis采用单线程响应命令,对于高流量的场景,如果命令执行时间在1毫秒以上,那么Redis最多可支撑OPS不到1000。因此对于高OPS场景的Redis建议设置为1毫秒。
慢查询只记录命令执行时间,并不包括命令排队和网络传输时间。因此客户端执行命令的时间会大于命令实际执行时间。因为命令执行排队机制,慢查询会导致其他命令级联阻塞,因此当客户端出现请求超时,需要检查该时间点是否有对应的慢查询,从而分析出是否为慢查询导致的命令级联阻塞。
WARNING
由于慢查询日志是一个先进先出的队列,也就是说如果慢查询比较多的情况下,可能会丢失部分慢查询命令,为了防止这种情况发生,可以定期执行slow get
命令将慢查询日志持久化到其他存储中(例如MySQL),然后可以制作可视化界面进行查询。
Redis提供了redis-cli、redis-server、redis-benchmark等Shell工具。我们可以通过这些工具巧妙地解决问题。
# Redis官方提供的Shell工具
redis-cli除了-h
、-p
参数,还有其它很多有用的参数。,下面将对一些重要参数的含义以及使用场景进行说明。
# Shell工具之redis-cli
-r
参数重复执行命令
-r(repeat)选项代表将命令执行多次,例如下面操作将会执行三次ping命令。
redis-cli -r 3 ping
通过这个命令,将打印三次PONG
。
-i
参数定时执行一次命令
-i(interval)选项代表每隔几秒执行一次命令,但是-i选项必须和-r选项一起使用,下面的操作会每隔1秒执行一次ping命令,一共执行5次。
redis-cli -r 5 -i 1 ping
--stat
参数实时获取Redis的重要统计信息
--stat
选项可以实时获取Redis的重要统计信息,虽然info命令中的统计信息更全,但是能实时看到一些增量的数据(例如requests)对于Redis的运维还是有一定帮助的。
redis-cli --stat
执行命令,输出如下结果。
------- data ------ --------------------- load -------------------- - child -
keys mem clients blocked requests connections
0 834.12K 1 0 9 (+0) 4
0 834.12K 1 0 10 (+1) 4
0 834.12K 1 0 11 (+1) 4
0 834.12K 1 0 12 (+1) 4
0 834.12K 1 0 13 (+1) 4
0 834.12K 1 0 14 (+1) 4
0 834.12K 1 0 15 (+1) 4
0 834.12K 1 0 16 (+1) 4
0 834.12K 1 0 17 (+1) 4
0 834.12K 1 0 18 (+1) 4
0 834.12K 1 0 19 (+1) 4
0 834.12K 1 0 20 (+1) 4
0 834.12K 1 0 21 (+1) 4
0 834.12K 1 0 22 (+1) 4
0 834.12K 1 0 23 (+1) 4
0 834.12K 1 0 24 (+1) 4
0 834.12K 1 0 25 (+1) 4
0 834.12K 1 0 26 (+1) 4
0 834.12K 1 0 27 (+1) 4
0 834.12K 1 0 28 (+1) 4
------- data ------ --------------------- load -------------------- - child -
keys mem clients blocked requests connections
0 834.12K 1 0 29 (+1) 4
0 834.12K 1 0 30 (+1) 4
0 834.12K 1 0 31 (+1) 4
0 834.12K 1 0 32 (+1) 4
0 834.12K 1 0 33 (+1) 4
0 834.12K 1 0 34 (+1) 4
0 834.12K 1 0 35 (+1) 4
0 834.12K 1 0 36 (+1) 4
0 834.12K 1 0 37 (+1) 4
0 834.12K 1 0 38 (+1) 4
0 834.12K 1 0 39 (+1) 4
0 834.12K 1 0 40 (+1) 4
0 834.12K 1 0 41 (+1) 4
0 834.12K 1 0 42 (+1) 4
0 834.12K 1 0 43 (+1) 4
0 834.12K 1 0 44 (+1) 4
0 834.12K 1 0 45 (+1) 4
0 834.12K 1 0 46 (+1) 4
0 834.12K 1 0 47 (+1) 4
0 834.12K 1 0 48 (+1) 4
------- data ------ --------------------- load ------------
- --raw
和
--no-raw`命令返回结果是否格式化
--no-raw
选项是要求命令的返回结果必须是原始的格式,--raw
恰恰相反,返回格式化后的结果。
例如,我们设置一个中文的value。
redis-cli set hello "你好"
如果正常执行get
或者使用--no-raw
选项,那么返回的结果是二进制格式。
redis执行get命令,返回二进制结果\xe4\xbd\xa0\xe5\xa5\xbd
。
redis-cli get hello
redis执行命令时使用--no-raw
选项,同样返回二进制结果\xe4\xbd\xa0\xe5\xa5\xbd
。
redis-cli --no-raw get hello
如果在执行命令时使用--raw
选项,将会返回中文。
redis-cli --raw get hello
# Shell工具之redis-server
redis-server除了启动Redis外,还有一个--test-memory
选项。 通过redis-server --test-memory
命令,我们可以用来检测当前操作系统能否稳定地分配指定容量的内存给Redis,通过这种检测可以有效避免因为内存问题造成Redis崩溃。
例如下面操作检测当前操作系统能否提供1G的内存给Redis。
redis-server --test-memory 1024
整个内存检测的时间比较长。当输出passed this test时说明内存检测完毕,最后会提示--test-memory只是简单检测,如果有质疑可以使用更加专业的内存检测工具。
WARNING
通常无需每次开启Redis实例时都执行--test-memory选项,该功能更偏向于调试和测试。
# Shell工具之redis-benchmark
redis-benchmark可以为Redis做基准性能测试,它提供了很多选项帮助开发和运维人员测试Redis的相关性能。
-c
参数代表客户端的并发数量(默认是50)
-c
(clients)选项代表客户端的并发数量(默认是50)。
-n
参数代表客户端请求总量(默认是100000)
例如:若我们执行命令redis-benchmark -c 100 -n 20000
代表100各个客户端同时请求Redis,一共执行20000次。redis-benchmark会对各类数据结构的命令进行测试,并给出性能指标。
-t
参数对指定命令进行基准测试。
-t
参数可以对指定命令进行基准测试。当我们执行命令redis-benchmark -t get,set -q
。终端将输出
SET: 57937.43 requests per second
GET: 59952.04 requests per second
--csv
参数将结果按照csv格式输出
--csv
选项会将结果按照csv格式输出,便于后续处理,如导出到Excel等。
redis-benchmark -t get,set --csv
-k
代表客户端是否使用keepalive
-k选项代表客户端是否使用keepalive,1为使用,0为不使用,默认值为1。
# Redis的Pipeline机制
Redis提供了批量操作命令(例如mget、mset等),有效地节约RTT。但大部分命令是不支持批量操作的,而且
Redis的客户端和服务端可能部署在不同的机器上,由于网络延迟等因素,Redis执行命令的效率将大大下降。
# Redis使用Pipeline
TIP
Pipeline(流水线)机制能改善上面这类问题,它能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端。
redis-cli的--pipe
参数实际上就是使用Pipeline机制。但大部分开发人员更倾向于使用高级语言客户端中的Pipeline,目前大部分Redis客户端都支持Pipeline。
# 原生批量命令与Pipeline对比
- 原生批量命令是原子的,Pipeline是非原子的。
- ·原生批量命令是一个命令对应多个key,Pipeline支持多个命令。
- ·原生批量命令是Redis服务端支持实现的,而Pipeline需要服务端和客户端的共同实现。
# Pipeline最佳实践
Pipeline虽然好用,但是每次Pipeline组装的命令个数不能没有节制,否则一次组装Pipeline数据量过大,一方面会增加客户端的等待时间,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的Pipeline拆分成多次 较小的Pipeline来完成。
# Redis事务功能概述
Redis提供了简单的事务功能,将一组需要一起执行的命令放到multi
和exec
两个命令之间。multi
命令代表事务开始,exec
命令代表事务结束,它们之间的命令是原子顺序执行的。如果要停止事务的执行,可以使用discard
命令代替exec
命令即可。
# Redis事务基本用法
下面进行集合添加元素操作:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379> sadd user:b:fans user:a
QUEUED
可以看到sadd命令此时的返回结果是QUEUED,代表命令并没有真正执行,而是暂时保存在Redis中。如果此时另一个客户端执行sismember user:a:follow user:b
返回结果应该为0。
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 0
只有当exec
执行完成后,事务操作才算完成。
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
此时再次执行sismember user:a:follow user:b
命令,命令才不会返回0。
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 1
如果要停止事务的执行,可以使用discard
命令代替exec命令即可。
# Redis事务错误处理机制
若Redis事务发生错误,Redis事务对于不同错误处理的方式不一样。
- 命令错误:若错误属于命令错误,则整个事务将无法执行。
- 运行时错误:若错误属于运行时错误,例如语法正确,但本因使用集合的,使用了有序集合,则事务将继续执行。
可以看到Redis并不支持回滚功能,若错误属于运行时错误,则需要开发人员自己解决。
有些应用场景需要在事务之前,确保事务中的key没有被其他客户端修改过,才执行事务,否则不执行(类似乐观锁)。Redis提供了watch命令来解决这类问题。
Redis提供的事务比较简单,之所以说它简单,主要是因为它不支持事务中的回滚特性,同时无法实现命令之间的逻辑关系计算。
# Redis中的Bitmaps
现代计算机用二进制(位)作为信息的基础单位,1个字节等于8位,例如“big”字符串是由3个字节组成,但实际在计算机存储时将其用二进制表示,“big”分别对应的ASCII码分别是98、105、103,对应的二进制分别是 01100010、01101001和01100111。
许多开发语言都提供了操作位的功能,合理地使用位能够有效地提高内存使用率和开发效率。Redis提供了Bitmaps这个“数据结构”可以实现对位的操作。对于Bitmaps,我们需要注意两点:
- Bitmaps本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串的位进行操作。
- Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量 。
# Bitmaps设置值
setbit key offset value
# Bitmaps获取值
gitbit key offset
# 获取Bitmaps指定范围值为1的个数
bitcount [start][end]
其中,[start]和[end]代表的是起始和结束的字节数。
# Bitmaps间的运算
bitop op destkey key[key....]
bitop是一个复合操作,它可以做多个Bitmaps的and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在destkey中。
# 计算Bitmaps中第一个值为targetBit的偏移量
bitpos key targetBit [start] [end]
除此之外,bitops有两个选项[start]和[end],分别代表起始字节和结束字节。
# Redis中的HyperLogLog
HyperLogLog并不是一种新的数据结构(实际类型为字符串类型),而是一种基数算法,通过HyperLogLog可以利用极小的内存空间完成独立总数的统计,数据集可以是IP、Email、ID等。HyperLogLog提供了3个命令:
pfadd
、pfcount
、pfmerge
。
# 添加指定元素到 HyperLogLog 中
pfadd key element [element …]
pfadd用于向HyperLogLog添加元素,如果添加成功返回1。
# 计算独立用户数
pfcount key [key …]
pfcount用于计算一个或多个HyperLogLog的独立总数。
# 合并HyperLogLog元素
pfmerge destkey sourcekey [sourcekey ...]
pfmerge可以求出多个HyperLogLog的并集并赋值给destkey。
HyperLogLog内存占用量非常小,但是存在错误率,开发者在进行数据结构选型时只需要确认如下两条:
- 只为了计算独立总数,不需要获取单条数据。
- 可以容忍一定误差率,毕竟HyperLogLog在内存的占用量上有很大的优势。
# Redis发布-订阅模式
Redis提供了基于“发布/订阅”模式的消息机制,此种模式下,消息发布者和订阅者不进行直接通信,发布者客户端向指定的频道(channel)发布消息,订阅该频道的每个客户端都可以收到该消息。 Redis提供了若干命令支持该功能,在实际应用开发时,能够为此类问题提供实现方法。
Redis主要提供了发布消息、订阅频道、取消订阅以及按照模式订阅和取消订阅等命令。
# 发布消息命令
publish channel message
下面操作会向channel:sports频道发布一条消息“Tim won the championship”, 返回结果为订阅者个数 ,因为此时没有订阅,所以返回结果为0。
127.0.0.1:6379> publish channel:sports "Tim won the championship"
(integer) 0
# 订阅消息命令
subscribe channel [channel ...]
订阅者可以订阅一个或多个频道,下面操作为当前客户端订阅了channel:sports
频道。
subscribe channel:sports
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel:sports"
3) (integer) 1
此时另一个客户端发布一条消息:
publish channel:sports "James lost the championship"
当前订阅者客户端会收到如下消息:
subscribe channel:sports
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel:sports"
3) (integer) 1
1) "message"
2) "channel:sports"
3) "James lost the championship"
有关订阅命令有两点需要注意:
- 客户端在执行订阅命令之后进入了订阅状态,只能接收
subscribe
、psubscribe
、unsubscribe
、punsubscribe
的四个命令。 - 新开启的订阅客户端,无法收到该频道之前的消息 ,因为Redis不会对发布的消息进行持久化。
TIP
和很多专业的消息队列系统(例如Kafka、RocketMQ)相比,Redis的发布订阅略显粗糙。
# 取消订阅命令
unsubscribe [channel [channel ...]]
客户端可以通过unsubscribe命令取消对指定频道的订阅,取消成功后,不会再收到该频道的发布消息。
127.0.0.1:6379> unsubscribe channel:sports
1) "unsubscribe"
2) "channel:sports"
3) (integer) 0
# 按照模式订阅和取消订阅
psubscribe pattern [pattern...]
punsubscribe [pattern [pattern ...]]
除了subcribe
和unsubscribe
命令,Redis命令还支持glob风格的订阅命令psubscribe
和取消订阅命令punsubscribe
。
# 查询订阅命令-查看活跃的频道
pubsub channels [pattern]
所谓活跃的频道是指当前频道至少有一个订阅者,其中[pattern]是可以指定具体的模式:
127.0.0.1:6379> pubsub channels
1) "channel:sports"
# 查看订阅命令-查看频道订阅数
pubsub numsub [channel ...]
现在,我们来查看channel:sports
频道的订阅数。
127.0.0.1:6379> pubsub numsub channel:sports
1) "channel:sports"
2) (integer) 1
# 查看订阅命令-查看模式订阅数
pubsub numpat
由于我们没有psubscribe
命令,所以我们没有安装模式订阅的频道。
127.0.0.1:6379> pubsub numpat
(integer) 0
# Redis地理信息定位-GEO
Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位置信 息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能。
# 增加地理位置信息
geoadd key longitude latitude member [longitude latitude member ...]
longitude、latitude、member分别是该地理位置的经度、纬度、成员。下表展示了5个城市的经纬度。
城市 | 经度 | 纬度 | 成员 |
---|---|---|---|
北京 | 116.28 | 39.55 | beijing |
天津 | 117.12 | 39.08 | tianjin |
石家庄 | 114.29 | 38.02 | shijiazhuang |
唐山 | 118.01 | 39.38 | tangshan |
保定 | 115.29 | 38.51 | baoding |
127.0.0.1:6379> geoadd cities:locations 116.28 39.55 beijing
(integer) 1
返回结果代表添加成功的个数,如果cities:locations没有包含beijing,那么返回结果为1,如果已经存在则返回0。
# 更新地理位置信息
如果需要更新地理位置信息,仍然可以使用geoadd
命令,虽然返回结果为0。geoadd
命令可以同时添加多个地理位置信息。
127.0.0.1:6379> geoadd cities:locations 117.12 39.08 tianjin 114.29 38.02 shijiazhuang 118.01 39.38 tangshan 115.29 38.51 baoding
(integer) 4
# 获取地理位置信息
geopos key member [member ...]
下面操作会获取天津的经维度:
127.0.0.1:6379> geopos cities:locations tianjin
1) 1) "117.12000042200088501"
2) "39.0800000535766543"
# 获取两个地理位置的距离
geodist key member1 member2 [unit]
其中unit代表返回结果的单位,包含以下四种:
- m(meters)代表米。
- km(kilometers)代表公里。
- mi(miles)代表英里。
- ft(feet)代表尺。
下面命令可以计算天津到北京的距离,并以公里为单位:
127.0.0.1:6379> geodist cities:locations tianjin beijing km
"89.2061"
# 获取指定位置范围内的地理信息位置集合
获取指定位置范围内的地理信息位置集合的命令总共有两个:georadius
和georadiusbymember
命令。
georadius
命令
georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [COUNT count] [asc|desc] [store key] [storedist key]
georadiusbymember
命令
georadiusbymember key member radiusm|km|ft|mi [withcoord] [withdist]
[withhash] [COUNT count] [asc|desc] [store key] [storedist key]
georadius
和georadiusbymember
两个命令的作用是一样的,都是以一个地 理位置为中心算出指定半径内的其他地理信息位置,
不同的是georadius
命令的中心位置给出了具体的经纬度,georadiusbymember
只需给出成员即可。其中radiusm|km|ft|mi
是必需参数,指定了半径(带单位)。
这两个命令有很多可选参数:
withcoord:返回结果中包含经纬度。
withdist:返回结果中包含离中心节点位置的距离。
withhash:返回结果中包含geohash,有关geohash后面介绍。
COUNT count:指定返回结果的数量。
asc|desc:返回结果按照离中心节点的距离做升序或者降序。
store key:将返回结果的地理位置信息保存到指定键。
storedist key:将返回结果离中心节点的距离保存到指定键。
下面操作计算五座城市中,距离北京150公里以内的城市。
127.0.0.1:6379> georadiusbymember cities:locations beijing 150 km
1) "beijing"
2) "tianjin"
3) "tangshan"
4) "baoding"
# 获取地理位置的hash值
geohash key member [member ...]
Redis使用geohash
将二维经纬度转换为一维字符串,下面操作会返回beijing的geohash值。
127.0.0.1:6379> geohash cities:locations beijing
1) "wx48ypbe2q0"
geohash有如下特点:
·GEO的数据类型为zset,Redis将所有地理位置信息的geohash存放在zset 中。
127.0.0.1:6379> type cities:locations zset
字符串越长,表示的位置更精确。
·两个字符串越相似,它们之间的距离越近,Redis利用字符串前缀匹配 算法实现相关的命令。
geohash编码和经纬度是可以相互转换的。
# 删除地理位置信息
zrem key member
GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位置信息的删除。