一、慢查询日志二、事务(Transactions)
2.1 回滚(roll back)2.2 使用 check-and-set 操作实现乐观锁 三、发布/订阅(Pub/Sub)四、位图(Bitmaps)五、HyperLogLogs六、GEO七、持久化(Persistence)
7.1 RDB(Redis Database)
7.1.1 优缺点7.1.2 使用方法 7.2 AOF(Append only File)
7.2.1 优缺点7.2.2 使用方法7.2.3 AOF 重写 7.3 如何选择持久化方式 八、主从复制
8.1 工作原理8.2 使用方法 九、哨兵(Sentinel)
9.1 使用方法9.2 所依赖的配置文件 十、集群(Cluster)
10.1 搭建方法10.2 集群操作 十一、缓存的使用和优化
11.1 双写一致性
11.1.1 先更新数据库,再更新缓存(极少使用)11.1.2 先删除缓存,再更新数据库(酌情使用)11.1.3 先更新数据库,再删除缓存(推荐使用) 11.2 缓存过期策略11.3 缓存穿透、击穿、雪崩
11.3.1 缓存穿透11.3.2 缓存击穿11.3.3 缓存雪崩
一、慢查询日志慢查询日志是为了记录执行时间超过给定时长的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。
优点:
使用默认的每秒 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)集群是在3.0之后加入的,在3.0到5.0版本中间,需要使用一个 ruby 脚本,5.0版本之后就不需要了。
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 缓存雪崩
大量的热点数据设置了相同的过期时间,导在缓存在同一时刻全部失效,造成瞬时数据库请求量大、压力骤增,引起雪崩,甚至导致数据库崩溃。
解决方法:
过期时间打散:给缓存的过期时间时加上一个随机值时间,使得每个数据的过期时间分散开来,不要集中在同一时刻失效。热点数据不过期:该方式和缓存击穿一样。加互斥锁:该方式和缓存击穿一样。



