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

  1. -r参数重复执行命令

-r(repeat)选项代表将命令执行多次,例如下面操作将会执行三次ping命令。

redis-cli -r 3 ping

通过这个命令,将打印三次PONG

  1. -i参数定时执行一次命令

-i(interval)选项代表每隔几秒执行一次命令,但是-i选项必须和-r选项一起使用,下面的操作会每隔1秒执行一次ping命令,一共执行5次。

redis-cli -r 5 -i 1 ping
  1. --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 ------------
  1. --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的相关性能。

  1. -c参数代表客户端的并发数量(默认是50)

-c(clients)选项代表客户端的并发数量(默认是50)。

  1. -n参数代表客户端请求总量(默认是100000)

例如:若我们执行命令redis-benchmark -c 100 -n 20000代表100各个客户端同时请求Redis,一共执行20000次。redis-benchmark会对各类数据结构的命令进行测试,并给出性能指标。

  1. -t参数对指定命令进行基准测试。

-t参数可以对指定命令进行基准测试。当我们执行命令redis-benchmark -t get,set -q。终端将输出

SET: 57937.43 requests per second
GET: 59952.04 requests per second
  1. --csv参数将结果按照csv格式输出

--csv选项会将结果按照csv格式输出,便于后续处理,如导出到Excel等。

redis-benchmark -t get,set --csv
  1. -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提供了简单的事务功能,将一组需要一起执行的命令放到multiexec两个命令之间。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个命令: pfaddpfcountpfmerge

# 添加指定元素到 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"

有关订阅命令有两点需要注意:

  • 客户端在执行订阅命令之后进入了订阅状态,只能接收subscribepsubscribeunsubscribepunsubscribe的四个命令。
  • 新开启的订阅客户端,无法收到该频道之前的消息 ,因为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 ...]]

除了subcribeunsubscribe命令,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"

# 获取指定位置范围内的地理信息位置集合

获取指定位置范围内的地理信息位置集合的命令总共有两个:georadiusgeoradiusbymember命令。

  • 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]

georadiusgeoradiusbymember两个命令的作用是一样的,都是以一个地 理位置为中心算出指定半径内的其他地理信息位置,

不同的是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命令实现对地理位置信息的删除。

LastUpdated: 1/9/2020, 9:38:02 AM