文章目录
- 一、慢查询日志
- 二、事务(Transactions)
- 三、发布/订阅(Pub/Sub)
- 四、位图(Bitmaps)
- 五、HyperLogLogs
- 六、GEO
- 七、持久化(Persistence)
- 八、主从复制
- 九、哨兵(Sentinel)
- 十、集群(Cluster)
- 十一、缓存的使用和优化
一、慢查询日志
慢查询日志是为了记录执行时间超过给定时长的redis命令请求,让使用者更好地监视和找出在业务中一些慢redis操作,找到更好的优化方法。
在Redis中,关于慢查询有两个设置:
-
slowlog-max-len:
慢查询最大日志数,默认值为128。
设置为
0
时,没有数量限制;设置为<0
时,不记录任何命令。 -
slowlog-log-slower-than:
慢查询最大超时时间,单位为微秒,默认值为10000微秒。推荐设置为1000微秒。
注意!负数表示禁用慢速日志,而0则强制记录每个命令的日志。
获取慢查询记录的命令:
-
获取慢查询记录:
SLOWLOG GET [数量]
记录由4个部分构成:记录的标识id、发生的时间戳、命令耗时、执行的命令和参数。
-
获取慢查询记录的数量:
SLOWLOG LEN
-
清空慢查询日志:
SLOWLOG RESET
二、事务(Transactions)
事务的特征:
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。
- 事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
用法:
MULTI # 开启事务
# 若干条命令……
# 这些命令会被保存到队列中,不会执行
# 如果想清空队列中的命令,并放弃执行事务:DISCARD
EXEC # 执行队列中的操作
2.1 回滚(roll back)
Redis 不支持回滚,它在事务失败时不进行回滚,而是继续执行余下的命令。这样做的理由:
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的 key 上面。而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
2.2 使用 check-and-set 操作实现乐观锁
WATCH
命令用来监视一个 key 是否被修改。如果至少一个被监视的键在EXEC
执行之前被修改了, 那么整个事务都会被取消, EXEC
返回nil-reply
来表示事务已经失败。
三、发布/订阅(Pub/Sub)
Redis 发布订阅是一种消息通信模式:
-
发布者:发布者将消息发布到不同的频道(channels)。
-
订阅者:订阅者订阅一个或多个频道,并从中接收消息。
当一个发布者通过发布一条消息到A频道时,所有订阅了A频道的订阅者都会接收到这条消息,但是后来订阅的订阅者无法获取历史消息。
发布/订阅与key所在空间没有关系,它不会受任何级别的干扰,包括不同数据库编码。 发布在db 10,订阅可以在db 1。 如果你需要区分某些频道,可以通过在频道名称前面加上所在环境的名称(例如:测试环境,演示环境,线上环境等)。
常用命令:
-
发布消息:
PUBLISH channel message # 返回订阅者个数
-
指定频道名称订阅频道:
SUBSCRIBE channel [channel ……]
-
按照模式订阅匹配到的频道:
PSUBSCRIBE [pattern ……]
模式:
- ? :代表匹配任意一个字符。
- *:代表匹配任意多个字符。
- [ab]:代表匹配a或者b。
-
指定频道名称取消订阅频道:
UNSUBSCRIBE [channel ……] UNSUBSCRIBE # 取消所有频道
-
按照模式取消订阅匹配到的频道:
PUNSUBSCRIBE [pattern ……] PUNSUBSCRIBE # 取消所有频道
-
检查发布/订阅子系统的状态:
PUBSUB CHANNELS [pattern] # 列出至少有一个订阅者的频道 PUBSUB NUMSUB [channel ……] # 列出给定频道的订阅者数量 PUBSUB NUMPAT # 列出被订阅模式的数量
四、位图(Bitmaps)
位图不是实际的数据类型,而是在String类型上定义的一组面向位(bit)的操作。由于字符串的最大长度为512 MB,因此可以将其设置为 2 32 2^{32} 232个不同的位。
位图的最大优点之一是,在存储信息时,它们通常可以极大地节省空间。例如,在一个由增量用户id表示不同用户的系统中,仅使用512mb内存就可以记住40亿用户的单个位信息(例如,记录一个用户是否想要接收时事通讯)。
常用命令:
-
设置指定的位:
SETBIT key 索引 值
超出索引,会自动用0扩展位到相应位置,然后放入值。
-
获取指定的位:
GETBIT key 索引
超出索引范围会返回: (integer) 0。
-
统计字符串被设置为1的位数:
BITCOUNT key [start end]
注意:起始、结束位置是按照字节算的,也就是8个位为1个字节。
-
在字符串之间执行按位操作:
# 对一个或多个 key 求按位并,并将结果保存到 destkey BITOP AND destkey key1 [key2 ……] # 对一个或多个 key 求按位或,并将结果保存到 destkey BITOP OR destkey key1 [key2 ……] # 对一个或多个 key 求按位异或,并将结果保存到 destkey BITOP XOR destkey key1 [key2 ……] # 对指定的 key 求按位非,并将结果保存到 destkey BITOP NOT destkey key
五、HyperLogLogs
HyperLogLog (以下简称HLL)是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。因此,常用于独立用户统计。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2 64 2^{64} 264 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
常用命令:
-
将指定元素添加到 HLL:
pfadd key 元素 [元素 ……]
-
返回HLL在 key 处观察到的集合的近似基数:
PFCOUNT key [key ……]
-
将N个不同的HLL合并成一个单一的HLL:
PFMERGE destkey key [key ……]
六、GEO
GEO 主要用于存储地理位置信息(纬度、经度),这些信息存储在有序集合中,可以用来计算范围、距离等。
常用命令:
-
添加位置:
GEOADD key 经度 维度 名称 [经度 维度 名称 ……]
-
获取位置:
GEOPOS key 名称 [名称 ……] # 不存在则返回 nil
-
获取两个位置之间的距离:
GEODIST key 名字1 名字2 [m|km|ft|mi] # 名字后面的是距离单位
-
以给定坐标为中心,距离为半径,返回范围内所有的位置:
GEORADIUS key 经度 纬度 距离 m|km|ft|mi GEORADIUSBYMEMBER key 名字 距离 m|km|ft|mi # 用GEO中的名字指定中心位置
七、持久化(Persistence)
Redis 提供了两种级别的持久化方式,RDB 方式和 AOF 方式。
7.1 RDB(Redis Database)
RDB 持久化方式能够在指定的时间间隔后,对数据进行快照存储。例如,在24小时内每小时进行一次备份,并将这些文件保存30天。
7.1.1 优缺点
优点:
- RDB 文件是一个紧凑的单一文件,方便管理和传输。
- RDB 在保存 RDB 文件时,父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能。
- 与 AOF 相比,在恢复大的数据集的时候,RDB方式会更快一些。
缺点:
- 如果意外断电,丢失的数据相对较多。我们通常会每隔5分钟或者更久做一次备份,恰好这5分钟内停电,就会丢失这几分钟的数据。虽然可以把备份间隔设置到很短,但也不能太短,毕竟保存整个数据集是一个比较消耗资源的工作。
- RDB 需要经常 fork 子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致 redis 在几毫秒甚至 1 秒内失去响应。
7.1.2 使用方法
在默认情况下, Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中。通过下面3种触发机制,触发快照的拍摄:
-
save
命令:该命令是同步执行的,或造成 redis 的阻塞,所以很少用。 -
bgsave
命令:该命令是异步执行的,由 redis fork 一个子进程,再由子进程去执行快照的拍摄。 -
在配置文件中:
# 如果在3600秒(1小时)内至少有1个键改变,触发快照的拍摄 save 3600 1 # 如果在300秒(5分钟)内至少有100个键改变,触发快照的拍摄 save 300 100 # 如果在60秒(1分钟)内至少有10000个键改变,触发快照的拍摄 save 60 10000
可以写多个触发条件。
注意!新的 dump.rdb 会替换掉旧的 dump.rdb 文件。
7.2 AOF(Append Only File)
AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。AOF命令以 redis 协议追加保存每次写的操作到文件末尾。Redis 还能在后台对 AOF 文件进行重写,控制 AOF文件的体积不至于过大。
AOF 有3 fsync(同步内存中所有已修改的文件数据到储存设备)策略:
no
:无 fsync;everysec
:每秒一次 fsync(默认);always
:每次写入的时候 fsync。
7.2.1 优缺点
优点:
- 使用默认的每秒 fsync 策略,Redis 的性能依然很好,一旦出现故障,最多丢失1秒的数据。
- AOF 文件是一个只进行追加的日志文件,所以不需要写入的seek(文件的写指针)。即使由于某些原因(磁盘空间已满,写的过程中宕机等)未执行完整的写入命令,我们也可使用 redis-check-aof 工具修复这些问题。
- AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。
- AOF 文件有序地以 Redis 协议的格式保存了所有写入操作,非常易读,容易分析,导出也简单。
缺点:
- 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
- AOF 的速度可能会慢于 RDB ,这取决于 fsync 策略。
7.2.2 使用方法
在配置文件中:
appendonly
:设置为yes
,开启 AOF。appendfilename
:AOF 文件的名称,如appendonly.aof
。appendfsync
:fsync 策略,默认值为everysec
。dir
:存放路径。
7.2.3 AOF 重写
AOF 重写的本质,就是删除无效的、重复的命令。我们可以使用BGREWRITEAOF
命令主动调用重写机制。
与其相关的配置:
-
auto-aof-rewrite-percentage
:默认值为
100
,当前文件的大小比上一次重写后文件的大小大了100%,就触发重写 -
auto-aof-rewrite-min-size
:触发重写的最大文件尺寸,默认值为64mb
。 -
no-appendfsync-on-rewrite
:在BGSAVE
或BGREWRITEAOF
过程中(消占用磁盘,导致阻塞),是否进行操作的记录。当选项为:yes
:会有少量记录丢失,但没有阻塞。no
:不会丢失数据,但有阻塞。
7.3 如何选择持久化方式
一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 应该同时使用两种持久化功能。
如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失,那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。
八、主从复制
redis 可以通过主从模式搭建多台服务器为同一个应用服务,这么做的好处:
- 一个服务器出现故障时,其他服务器可以继续正常工作。
- 提高容量。
- 实现读写分离,提高性能。
- ……
主从模式中,有一个服务器称为 master 服务器(主服务器),而其它服务器称为 replica 服务器(从服务器,也叫 slave 服务器)。一个 master 可以有一个或多个 replica,但一个 replica 只能有一个 master。
8.1 工作原理
- 从服务器使用
REPLICAOF ip 端口
命令连接到主服务器,并发送SYNC
命令给主服务器; - 主服务器接收到
SYNC
命名后,开始执行BGSAVE
命令生成 RDB 快照文件; - 主服务器
BGSAVE
执行完后,向所有从服务器发送快照文件; - 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
- 之后,主服务器只要发生新的操作,都会以命令的方式发送给所有从服务器。
- 如果主从断开,从服务器的数据没有损坏。那么,重连之后从服务器发送
PSYNC
命令给主服务器。 - 主服务器收到命令,会将断连期间缺失的数据发送给从服务器,达到快速恢复的目的。
主从服务器之间的数据流是单向的,只能从 master 流向 replica。即 replica 只能从 master 获取数据。
强烈建议给 master 开启持久化!!!因为,如果 master 重启会导致 master 的数据变为空,如果一个 replica 试图与它同步,那么这个 replica 也会被清空。
8.2 使用方法
通过命令:
-
从 replica 连接到 master:
REPLICAOF master的ip地址 端口
-
断开连接:
REPLICAOF NO ONE
该命令不会丢弃 replica 的数据。
通过配置文件:
# 配置主从
replicaof master的ip地址 端口
# replica只读
replica-read-only yes或no
九、哨兵(Sentinel)
Redis 的哨兵系统是官方提供的高可用性(High Availability)解决方案,用于管理多个 Redis 服务器,它主要执行以下工作:
- 监控:不断地检查你的主服务器和从服务器是否运作正常。
- 提醒:当被监控的某个 Redis 服务器出现问题时, 哨兵可以通过 API 向管理员或者其他应用程序发送通知。
- 自动故障迁移:当一个主服务器不能正常工作时,哨兵会将失效主服务器的一个从服务器升级为新的主服务器; 当客户端试图连接失效的主服务器时, 哨兵也会向客户端返回新主服务器的地址, 使新主服务器代替失效的旧主服务器。
Redis 哨兵是一个分布式系统, 你可以在一个架构中运行多个哨兵进程,这些哨兵通过投票来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
注意事项:
- 为了系统足够健壮,至少需要3个哨兵进程。
- 哨兵必须运行在不同的物理机或虚拟机中,不能放在一起。
9.1 使用方法
启动命令:
redis-server /path/to/sentinel.conf --sentinel
默认情况下,Sentinels会监听到TCP端口26379的连接。
9.2 所依赖的配置文件
哨兵需要使用一个配置文件,因为系统将使用这个文件来保存当前状态,以便在重新启动时重新加载。如果没有提供,则哨兵会拒绝启动。
Redis 的源代码中包含了一个名为 Sentinel .conf 的文件,我们可以使用它进行配置,也可以自行创建配置,下面是一个最精简的配置:
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
sentinel monitor resque 192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5
上面的示例配置监视了两组 Redis 实例,每个实例由一个主服务器和一个未定义数量的从服务器组成。一组实例的名称为 mymaster ,另一组的名称为 resque。sentinel
配置的详细说明如下:
-
monitor 名称(随便写) 主服务器IP 端口 投票通过的数量
:最后的这个数量一般是哨兵数量的一半+1。 -
down-after-milliseconds 名称 时间(毫秒)
:如果服务器在给定的毫秒数之内, 没有回复哨兵发送的 PING 命令, 或者返回一个错误, 那么哨兵会主观地认为服务器已经下线。主观下线不会引起自动故障迁移,只有足够数量(投票通过的数量)哨兵都认为该服务器已经下线,这时才会引起自动故障迁移。
-
parallel-syncs
:指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步, 这个数字越小, 完成故障转移所需的时间就越长。
十、集群(Cluster)
Redis 集群是一个由多个主从节点群组成的分布式服务系统,它具有复制、高可用和分片特性,不需要哨兵也能完成自动故障迁移。
集群模式没有中心节点,可水平扩展。性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单。
10.1 搭建方法
集群搭建最少需要3个主从节点才能正常运行,从服务器用于主服务器的数据备份。
-
在每台机器上创建配置文件:
主服务器:
bind 192.168.146.199 port 7000 daemonize yes pidfile /usr/local/redis/run/redis_7000.pid logfile /usr/local/redis/log/redis_7000.log dir /usr/local/redis/data/7000 # 启用集群模式 cluster-enabled yes # 集群节点信息文件 cluster-config-file nodes.conf # 节点离线的超时时间,超时就认为离线了 cluster-node-timeout 5000 # 持久化 appendonly yes # 如果要设置密码需要增加如下配置: # 设置redis访问密码 requirepass 123456 # 设置集群节点间访问密码,跟上面一致 masterauth 123456
从服务器:
bind 192.168.146.199 port 7001 pidfile /usr/local/redis/run/redis_7001.pid logfile /usr/local/redis/log/redis_7001.log dir /usr/local/redis/data/7001 cluster-config-file nodes-7001.conf
相关的目录要提前创建好。
-
启动所有 redis 服务:
redis-server redis-7000.conf
-
搭建集群(这里使用的是6.0版本,不需要 ruby 脚本):
redis-cli -a 123456 --cluster create --cluster-replicas 1 ip:端口 ip:端口 ……
--cluster-replicas 1
:表示1个主服务器下挂1个从服务器;通过该方式创建的带有从节点的机器不能够自己手动指定主节点,redis集群会尽量把主从服务器分配在不同机器上。
每个主服务器都是平等的,都可以登录进行各种操作。而从服务器之间虽然也是平等的,但它更多的是作为备份而已。
10.2 集群操作
-
登录集群:
redis-cli -c -h ip地址 -p 端口 -a 123456
-
查看信息:
CLUSTER INFO
-
列出节点信息:
CLUSTER NODES
-
增加节点:
按照前面的方法配置好节点后启动,然后在集群中执行以下命令:
CLUSTER MEET IP地址 端口
-
删除节点:
CLUSTER FORGET 节点id
十一、缓存的使用和优化
11.1 双写一致性
数据有了变化后,在数据库和缓存之间,先更新哪一个,有以下几种方案:
11.1.1 先更新数据库,再更新缓存(极少使用)
存在问题:
-
产生脏数据:
比如,线程A、B按照顺序先后更新了数据库,却因为某种原因,在更新缓存时,B反而跑到了A前面,这就导致了B的最新数据被A的旧数据覆盖掉,产生了脏数据。
-
浪费系统资源:
我们主动写入缓存的数据不一定就是用户想要读取的,主动更新缓存反而浪费计算机资源。不如直接删除,用户需要什么就缓存什么,用户所请求的数据是最具代表性的。
11.1.2 先删除缓存,再更新数据库(酌情使用)
存在问题:
-
产生脏数据:
比如,A线程删除了缓存,还没来得及更新数据库。恰好B线程在此期间来读取数据,未能在缓存中获得,于是从数据库中读取了旧的数据,并将旧的数据写入缓存。于是产生了脏数据。
11.1.3 先更新数据库,再删除缓存(推荐使用)
存在问题:
-
读取脏数据:
比如,A线程更新完数据库后,没来得及删除缓存。恰好被B线程读取了缓存当中旧的数据。
但是,这种情况极少见,而且很快就会被A线程删掉缓存,不会出现前面两种情况下,缓存中一直存在脏数据的情况。
11.2 缓存过期策略
-
FIFO(First In First out):
先进先出算法,淘汰最先进来的数据,完全符合队列数据结构的特征。
-
LRU(Least recently used):
最近最少使用算法,淘汰距离上一次被使用过去时间最久的数据。该算法能保证数据最热,所以最常用。
配置:# 使用 LRU 算法,但仅限于设置了有效期的 maxmemory-policy volatile-lru
-
LFU(Least frequently used):
最近使用次数最少算法, 淘汰使用次数最少的数据。
11.3 缓存穿透、击穿、雪崩
11.3.1 缓存穿透
用户查询一个完全不存在的值时,缓存不会被命中,导致大量请求直接落到数据库上。数据库也没有数据,无法写入缓存,所以下一次请求同样也会落到数据库上,最终导致数据库压力过大。
解决方法:
- 接口校验:用户鉴权、数据合法性校验等,例如商品查询中,商品的ID是正整数,则可以直接对非正整数直接过滤等等。
- 缓存空值:将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来设置。
- 布隆过滤器:使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接返回空值,存在的 key 则再进一步查询缓存和数据库。
11.3.2 缓存击穿
某一个热点数据,在缓存过期的一瞬间有大量的请求进来,由于此时缓存过期了,所以请求最终都会落到数据库上,造成瞬时数据库请求量大、压力骤增,甚至可能崩溃。
解决方法:
- 加互斥锁:在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程阻塞,等到第一个线程将数据写入缓存后,从缓存得到数据。
- 热点数据不过期:直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。
11.3.3 缓存雪崩
大量的热点数据设置了相同的过期时间,导在缓存在同一时刻全部失效,造成瞬时数据库请求量大、压力骤增,引起雪崩,甚至导致数据库崩溃。
解决方法:
- 过期时间打散:给缓存的过期时间时加上一个随机值时间,使得每个数据的过期时间分散开来,不要集中在同一时刻失效。
- 热点数据不过期:该方式和缓存击穿一样。
- 加互斥锁:该方式和缓存击穿一样。