Redis其它

1 slowlog慢查询日志

redis和mysql等数据库一样,提供了慢查询日志,简单说就是redis将查询执行时间超过设定值的命令记录下来。查询执行时间指的是不包括像客户端响应(talking)、发送回复等 IO 操作,而单单是执行一个查询命令所耗费的时间。另外,slowlog 保存在内存里面,读写速度非常快,因此你可以放心地使用它,不必担心因为开启 slowlog 而损害 Redis 的速度。

关于 Redis 慢查询的配置有两个,默认配置文件的相关部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
################################## SLOW LOG ###################################
# The following time is expressed in microseconds, so 1000000 is equivalent
# to one second. Note that a negative number disables the slow log, while
# a value of zero forces the logging of every command.
# 这个参数用来指定:超过多少微秒记录慢日志,10000微秒等于1秒
# 如果设置为0,所有命令都会记录
# 如果设置为负数,任何命令都不会记录
slowlog-log-slower-than 10000

# There is no limit to this length. Just be aware that it will consume memory.
# You can reclaim memory used by the slow log with SLOWLOG RESET.
# 日志是记录在内存中的,下面的参数用来限制日志的最大长度(条),默认最多保存128条
slowlog-max-len 128

除了在配置文件中设置,也可以在redis-cli中使用命令设置

1
2
config set slowlog-log-slower-than 10000
config set slowlog-max-len 128

要查看slowlog ,可以使用以下命令:

1
2
3
4
SLOWLOG RESET  # 清空slowlog
SLOWLOG LEN # 查看当前日志的数量
SLOWLOG GET # 打印所有 slowlog
SLOWLOG GET number # 则只打印指定数量的日志

2 事物与管道

2.1 事务与管道的区别

redis-cli命令里没有管道相关的命令,pipeline是客户端的行为,对于服务器来说是透明的,可以认为服务器无法区分客户端发送来的查询命令是以普通命令的形式还是以pipeline的形式发送到服务器的。管道的作用在于客户端一次性发送多个命令减少网络往返的传输时延,pipeline不是原子性的,中间可能会存在部分失败的情况,如果中间有命令出现错误,redis不会中断执行,而是直接执行下一条命令,然后将所有命令的执行结果(执行成果或者执行失败)放到列表中统一返回。

2.2 事务操作

事务可以一次执行多个命令,它是服务端的功能,具有原子性。MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务的基础。事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

要开启事务,使用命令multi,它总是返回OK,之后,客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当EXEC命令被调用时,所有队列中的命令才会被执行

1
2
3
4
5
6
set age 18
MULTI
incr age
incr age
incr age
EXEC

image-20211204201350290

通过调用DISCARD, 客户端可以清空事务队列, 并放弃执行事务。

事务在执行 EXEC 之前,入队的命令可能会出错,此时服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务:

1
2
3
4
5
6
7
set age 18
MULTI
INCR age
INCR a b c # 出错
INCR age
EXEC # 放弃执行
get age # 18

image-20211204202054486

如果是在EXEC执行时某条命令出错,事务队列中的其他命令仍然会继续执行 —— Redis 不会停止执行事务中的命令。

要注意的是,与mysql不同,Redis不支持回滚,而是继续执行余下的命令。鉴于没有任何机制能避免程序员自己造成的错误, 并且这类错误通常不会在生产环境中出现, 所以 Redis 选择了更简单、更快速的无回滚方式来处理事务。因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

2.3 使用watch实现乐观锁

WATCH命令可以为 Redis 事务提供 check-and-set(CAS)行为:

1
2
WATCH key [key ...]
# 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断

redis采用乐观锁是因为大多数情况下,不同的客户端会访问不同的键,碰撞的情况一般都很少,所以通常并不需要进行重试。

举例:

1
2
3
4
5
6
set age 18
WATCH age
MULTI
INCR age
INCR age
EXEc

如果在 WATCH 执行之后, EXEC 执行之前,如果有其他客户端修改了 age 的值,那么当前客户端的事务就会失败。另外,监视(WATCH)必须在开启事务(MULTI)之前。

3 发布与订阅

Redis 发布订阅 (pub/sub) 是一种消息通信模式,在这个实现中, 发送者(发送信息的客户端)不是将信息直接发送给特定的接收者(接收信息的客户端), 而是将信息发送给频道(channel), 然后由频道将信息转发给所有对这个频道感兴趣的订阅者。发送者无须知道任何关于订阅者的信息, 而订阅者也无须知道是那个客户端给它发送信息, 它只要关注自己感兴趣的频道即可。

3.1 发布消息

Redis采用PUBLISH命令发送消息,其返回值为接收到该消息的订阅者的数量,其中channel是指定的频道

1
PUBLISH channel message

image-20211204213830070

3.2 订阅频道

客户端可以订阅一个或多个频道

1
SUBSCRIBE channel [channel ...]

image-20211204214200574

还可以使用模式匹配订阅频道

1
PSUBSCRIBE pattern [pattern ...]

订阅一个或多个符合给定模式的频道。每个模式以 * 作为匹配符,比如 it* 匹配所有以 it 开头的频道( it.newsit.blogit.tweets 等等), news.* 匹配所有以 news. 开头的频道( news.itnews.global.today 等等),诸如此类。

4 HyperLogLog

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的,并且是很小的。基数即集合中包含的元素的“个数”,比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。这里不涉及底层实现,主要介绍使用方法。

4.1 存储到HyperLogLog结构中

将除了第一个参数以外的参数存储到以第一个参数为变量名的HyperLogLog结构中

1
PFADD key element [element ...]

1
PFADD hll a b c d e f g

4.2 获取基数

当参数为一个key时,返回存储在HyperLogLog结构体的该变量的近似基数,如果该变量不存在,则返回0

当参数为多个key时,返回这些HyperLogLog并集的近似基数,这个值是将所给定的所有key的HyperLoglog结构合并到一个临时的HyperLogLog结构中计算而得到的

HyperLogLog可以使用固定且很少的内存(每个HyperLogLog结构需要12K字节再加上key本身的几个字节)来存储集合的唯一元素

返回的可见集合基数并不是精确值, 而是一个带有 0.81% 标准错误(standard error)的近似值

1
PFCOUNT key [key ...]

1
2
PFCOUNT hll
>>> 7

4.3 合并HyperLogLog

将多个 HyperLogLog 合并为一个 HyperLogLog,合并后的 HyperLogLog 的基数接近于所有输入 HyperLogLog 的可见集合(observed set)的并集。合并得出的 HyperLogLog 会被储存在目标变量(第一个参数)里面, 如果该键并不存在, 那么命令在执行之前, 会先为该键创建一个空的。

1
PFMERGE destkey sourcekey [sourcekey ...]

5 Redis GEO

Redis GEO主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。使用GEO功能可以计算两地之间的距离、指定半径的地理信息集合等等。

5.1 添加地理信息坐标

将指定的地理空间位置(纬度、经度、名称)添加到指定的key中,采用有序集合存储,经度必须在纬度之前。

  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。
1
2
3
4
5
GEOADD key longitude latitude member [longitude latitude member ...]
# key 键
# longitude 经度
# latitude 纬度
# member 成员名称

以北京为例,如下表

省市 市县 拼音 东经 北纬
北京 海淀 haidian 116.298 39.959
北京 朝阳 chaoyang 116.443 39.922
北京 顺义 shunyi 116.655 40.13
北京 昌平 changping 116.231 40.221

添加位置信息

1
2
3
4
GEOADD bj 116.298 39.959 haidian
GEOADD bj 116.443 39.922 chaoyang
GEOADD bj 116.655 40.13 shunyi
GEOADD bj 116.231 40.221 changping

5.2 获取指定元素的位置

key里返回所有给定位置元素的位置(经度和纬度)。

1
GEOPOS key member [member ...]

5.3 获取元素之间距离

返回两个给定位置之间的距离。如果两个位置之间的其中一个不存在, 那么命令返回空值。

参数 unit 必须是以下单位的其中一个:

  • m 表示单位为米(如果没有指定,默认为米)
  • km 表示单位为千米
  • mi表示单位为英里
  • ft 表示单位为英尺
1
GEODIST key member1 member2 [unit]

比如,计算朝阳和海淀之间距离

1
2
GEODIST bj chaoyang haidian km
>>> "13.0323"

5.4 获取指定范围内的地理位置集合

以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。

1
2
3
4
5
6
7
8
9
10
11
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
# 在给定以下可选项时, 命令会返回额外的信息:
# WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
# WITHCOORD: 将位置元素的经度和维度也一并返回。
# WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。

# 用户可以指定被返回位置元素的排序方式:
# ASC: 根据中心的位置, 按照从近到远的方式返回位置元素。
# DESC: 根据中心的位置, 按照从远到近的方式返回位置元素。

# COUNT count 选项去获取前 N 个匹配元素

比如,以经纬度为116,40为中心,半径为40km以内,获取bj中符合条件的位置。

1
GEORADIUS bj 116 40 40 km

image-20211204230839843

除了指定经纬度,中心点还可以由给定的位置元素决定

1
GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]

比如,查找以朝阳为中心,半径20km以内的地区:

1
GEORADIUSBYMEMBER bj chaoyang 20 km

image-20211204231153647

6 持久化

Redis主要有两种持久化方式

  • RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
  • AOF持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。

6.1 RDB方式

在默认情况下, Redis将数据库快照保存在名字为dump.rdb的二进制文件中。

关于RDB有三种方式

第一种方式,同步存储

1
2
3
4
# 1 save命令
# SAVE 命令执行一个同步保存操作,将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘
# 一般来说,在生产环境很少执行 SAVE 操作,因为它会阻塞所有客户端
SAVE

1241012198-5b73cfc7710b8_fix732

第二种,异步存储

1
2
3
4
# 2 BGSAVE
# 在后台异步(Asynchronously)保存当前数据库的数据到磁盘。
# BGSAVE 命令执行之后立即返回 OK ,然后 Redis fork 出一个新子进程,原来的 Redis 进程(父进程)继续处理客户端请求,而子进程则负责将数据保存到磁盘,然后退出。
BGSAVE

602904611-5b73cfc740cfe

前两种方式的比较:

命令 save bgsave
IO类型 同步 异步
阻塞? 是(阻塞发生在fock(),通常非常快)
复杂度 O(n) O(n)
优点 不会消耗额外的内存 不阻塞客户端命令
缺点 阻塞客户端命令 需要fock子进程,消耗内存

第三种方式是通过配置文件,比如说, 以下设置会让Redis在满足60 秒内有至少有 1000 个键被改动这一条件时, 自动保存一次数据集。

默认配置文件的相关部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
################################ SNAPSHOTTING  ################################

# save <seconds> <changes>
# xxx秒内有xxx键被改动时,保存一次
# 如果没有指定,默认为以下配置
save 3600 1
save 300 100
save 60 10000

# 如果bgsave出现错误,是否停止写入
stop-writes-on-bgsave-error yes

# 采用压缩格式存储
rdbcompression yes

# 是否对rdb文件进行校验和检验
rdbchecksum yes

# 持久化文件的文件名
dbfilename dump.rdb

# 数据持久化文件存储目录(必须是目录而不能是文件)
dir ./

可以将rdb配置修改为

1
2
3
# save 900 1 
# save 300 10
# save 60 10000

6.2 AOF方式

AOF方式有三种策略:

fsync ,每秒钟一次 fsync ,或者每次执行写入命令时 fsync 。默认采用每秒钟一次 fsync ,并且Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据( fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)。

要打开AOF方式,需要在配置文件中修改,相关部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
############################## APPEND ONLY MODE ###############################

# 默认关闭aof,设置为yes开启
appendonly no
# 保存文件名称
appendfilename "appendonly.aof"

# 以下是三种保存策略,一般保持默认就可以,如果需要最安全,设置为always
# appendfsync always
appendfsync everysec
# appendfsync no

# 在后台执行(RDB的save|aof重写)时,暂时将appendfsync设为no
# 如果你有延迟问题可以将该项设置为“yes”。否则,从数据完整性的角度看,使用“no”更安全。
no-appendfsync-on-rewrite no

# 自动触发AOF重写的条件
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 指定当发生AOF文件末尾截断时,加载文件还是报错退出
# 当发生了末尾截断,Redis可以选择直接报错退出,或者继续执行并恢复尽量多的数据(默认选项)。
# yes :末尾被截断的 AOF 文件将会被加载,并打印日志通知用户。
# no :服务器将报错并拒绝启动。
aof-load-truncated yes

# 开启混合持久化,更快的AOF重写和启动时数据恢复
aof-use-rdb-preamble yes

6.3 AOF日志重写

因为 AOF 的运作方式是不断地将命令追加到文件的末尾, 所以随着写入命令的不断增加, AOF 文件的体积也会变得越来越大。举个例子, 如果你对一个计数器调用了 100 次 INCR , 那么仅仅是为了保存这个计数器的当前值, AOF 文件就需要使用 100 条记录(entry)。然而在实际上, 只使用一条 SET 命令已经足以保存计数器的当前值了, 其余 99 条记录实际上都是多余的。

为了处理这种情况, Redis 支持一种有趣的特性: 可以在不打断服务客户端的情况下, 对 AOF 文件进行重写(rebuild)。

可以在命令行使用命令触发重写

1
BGREWRITEAOF

执行该命令,异步执行一个 AOF(AppendOnly File)文件重写操作。重写会创建一个当前AOF文件的优化版本,这个文件包含重建当前数据集所需的最少命令。即使 bgrewriteaof 执行失败,也不会有任何数据丢失,因为旧的AOF文件在 bgrewriteaof 成功之前不会被修改。

Redis 2.2 需要自己手动执行 BGREWRITEAOF 命令; Redis 2.4以后则可以自动触发 AOF 重写,在配置文件中修改如下项:

1
2
auto-aof-rewrite-percentage 100  # 	触发AOF文件执行重写的增长率
auto-aof-rewrite-min-size 64mb # 触发AOF文件执行重写的最小尺寸

这样配置后,当AOF文件的体积大于64Mb,并且AOF文件的体积比上一次重写之久的体积大了至少一倍(100%)时,Redis将自动执行 bgrewriteaof 命令进行重写。

4211482760-5b73cfc6cfba8_fix732

6.4 RDB与AOF的比较

一般来说,你应该同时使用两种持久化功能。

如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。

RDB AOF
启动优先级
体积 略大一些
恢复速度 略慢
数据安全性 可能丢数据多一些 根据策略决定

6.5 总结

Redis 对于数据备份是非常友好的, 因为你可以在服务器运行的时候对 RDB 文件进行复制: RDB 文件一旦被创建, 就不会进行任何修改。 当服务器要创建一个新的 RDB 文件时, 它先将文件的内容保存在一个临时文件里面, 当临时文件写入完毕时, 程序才原子地用临时文件替换原来的 RDB 文件。

这也就是说, 无论何时, 复制 RDB 文件都是绝对安全的。

7 主从复制

Redis主从复制能使得从 Redis 服务器(下文称 slave)能精确得复制主 Redis 服务器(下文称 master)的内容,Redis支持一主一从、一主多从。Redis 使用异步复制,slave 和 master 之间异步地确认处理的数据量,并且,数据是单向的,即从master流向slave。

配置基本的 Redis 复制功能是很简单的,只需要将以下内容加进 slave 的配置文件:

1
2
3
4
slaveof 192.168.1.1 6379
slave-read-only yes
# 指定主服务的ip和端口
# 设置从库为只读

除此之外,也可以在从服务器的命令窗口使用如下命令,达到同样的效果

1
SLAVEOF host port

Redis复制功能的工作流程简单介绍如下:

  1. 首先,从库连接主库,并发送SYNC给主库。
  2. 主库收到后,开启一个后台保存进程,以便于生产一个 RDB 文件。与此同时,它开始缓冲所有从客户端接收到的新的写入命令。
  3. 当后台保存完成时, 主库将数据集文件传输给从库, 从库将之保存在磁盘上,然后加载文件到内存。
  4. 主库将所有所有缓冲的命令发给从库,此时便达到全同步

后续的主库更新时,只会将增量的数据发送给从库,而不是进行全同步(除非缓冲区积压过多,或者从库出现问题)。从工作流程中可以看到,正常情况下一个全同步要求在磁盘上创建一个 RDB 文件,然后将它从磁盘加载进内存,再发送给从库;Redis 2.8.18 是第一个支持无磁盘复制的版本。在此设置中,子进程直接发送 RDB 文件给 slave,无需使用磁盘作为中间储存介质。不过还是建议开启数据持久化保证数据安全。

最好使用 SHUTDOWN命令来执行从库的保存和退出操作。

要取消主从关系,使用如下命令

1
SLAVEOF no one

使用主从复制,可以使得整个redis系统实现读写分离。

8 Redis Sentinel哨兵

Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance),以实现主从数据库的高可用,该系统执行以下三个任务:

  • 监控: Sentinel 会不断地检查你的主服务器和从服务器是否运作正常
  • 提醒: 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知
  • 自动故障迁移: 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

Redis Sentinel 是一个分布式系统, 你可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。

虽然 Redis Sentinel 释出为一个单独的可执行文件 redis-sentinel , 但实际上它只是一个运行在特殊模式下的 Redis 服务器, 你可以在启动一个普通 Redis 服务器时通过给定 –sentinel 选项来启动 Redis Sentinel 。

哨兵的工作流程图大致如下:

1053081-20161230154322726-1942512342

当主库出现故障:

1053081-20161230154348742-1055330295

在从库中选择一个变为主库:

1053081-20161230154428320-1980223016

8.1 哨兵配置

Redis 源码中包含了一个名为sentinel.conf的文件, 这个文件是一个带有详细注释的Sentinel配置文件示例。以下是常用的配置项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# sentinel运行在哪个端口
port 26379

# 如果需要后台运行,设置为yes
daemonize no

# 工作目录
dir /tmp

# 指示 Sentinel 去监视一个名为 mymaster 的主服务器, 这个主服务器的 IP 地址为 127.0.0.1 , 端口号为 6379 , 而将这个主服务器判断为失效至少需要 2 个 Sentinel 同意
sentinel monitor mymaster 127.0.0.1 6379 2

# 指定 Sentinel 认为服务器已经断线所需的毫秒数。如果服务器在给定的毫秒数之内, 没有返回 Sentinel 发送的 PING 命令的回复, 或者返回一个错误, 那么 Sentinel 将这个服务器标记为主观下线
sentinel down-after-milliseconds mymaster 30000

# 指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步, 这个数字越小, 完成故障转移所需的时间就越长。
# 你可以通过将这个值设为 1 来保证每次只有一个从服务器处于不能处理命令请求的状态。
sentinel parallel-syncs mymaster 1

点击这里查看完整的哨兵配置文件。

通过配置文件启动哨兵:

1
redis-sentinel sentinel.conf

8.2 主观下线和客观下线

Redis 的 Sentinel 中关于下线(down)有两个不同的概念

哨兵进程在运行时,周期性地给所有的主从库发送 PING 命令,检测它们是否仍然在线运行。如果从库没有在规定时间内响应哨兵的 PING 命令,哨兵就会把它标记为“客观下线”;同样,如果检测的是主库,没有在规定时间内响应哨兵的 PING 命令,哨兵会先判定主库"主观下线",然后进行投票选举,判断主库为"客观下线"。只有主库有主观下线,从库没有。

如果 master-down-after-milliseconds 选项的值为 30000 毫秒(30 秒), 那么只要服务器能在每 29 秒之内返回至少一次有效回复, 这个服务器就仍然会被认为是处于正常状态的,不会被标记为主观下线。

为了减少误判带来不必要的开销,哨兵通常采取集群部署,多个哨兵分配在多个服务器上,避免单个哨兵因为自身网络状况不好,而误判主库下线的情况,降低误判率。另外,多个哨兵之间采取“少数服从多数”原则,半数以上的哨兵判断主库已经下线,才将其标记为客观下线。

8.3 故障转移

当哨兵们发现主服务器已经进入客观下线状态,便开始进行故障转移操作,选出一个从服务器,并将它升级为主服务器。这个选择过程主要是打分机制:先按照一定的筛选条件,把不符合条件的从库去掉。然后,我们再按照一定的规则,给剩下的从库逐个打分,将得分最高的从库选为新主库。

  • 一定的筛选条件
    • 检查所有从库的在线状态,把挂掉的从库去掉
    • 判断其它从库与原先主库的连接状态,如果之前一段时间内从库一直断连,超过设定的阈值,那么就认为这个从库的网络状况不能担任主库,将其去掉
  • 一定的规则打分,如果某一轮有从库得分最高,那么选择它为主库;否则进行下一轮筛选
    • 通过查看从库的slave-priority配置选项,选出优先级高的当选
    • 通过判断和旧主库同步程度来选择从库,谁的同步程度高,说明这个从库拥有最新的数据,选其作为主库
    • 通过ID选择主库,谁的ID最小,谁作为主库

至此,选出新的主库,而原先挂掉的主库上线后,将其作为从库。