Redis
本文最后更新于:2023年12月5日 晚上
缓存技术
缓存 cache
buffer 和 cache
- buffer:缓冲,写缓冲,先写到缓冲中,再输出到网络或者写入磁盘
- cache:缓存
缓存保存位置及分层结构
互联网领域,缓存为王
- 用户层:浏览器 DNS 缓存,应用程序 DNS 缓存,操作系统 DNS 缓存客户端
- 代理层:CDN,反向代理缓存
- Web 层:解释器 Opcache,Web 服务器缓存
- 应用层:页面静态化
- 数据层:分布式缓存,数据库
- 系统层:操作系统 cache
- 物理层:磁盘 cache, Raid Cache
cache 的特性
自动过期、强制过期、命中率
CDN
利用 302 实现转发请求重定向至最优服务器集群
中国网络较为复杂,依赖 DNS 就近解析的调度,仍然会存在部分请求调度失效、调度生效慢等问题。腾讯云利用在全国部署的 302 重定向服务器集群,能够为每一个请求实时决策最优的服务器资源,精准解决小运营商的调度问题,提升用户访问质量, 能最快地把用户引导到最优的服务器节点上,避开性能差或者异常的节点。
CDN 分层缓存
Redis 部署和使用
Redis 基础
注意事项:谨慎甚至禁止使用某些命令,例如 keys
redis 对比 memcached
- 支持数据的持久化:可以将内存中的数据保持在磁盘中,重启 redis 服务或者服务器之后可以从备份文件中恢复数据到内存继续使用
- 支持更多的数据类型:支持 string(字符串)、hash(哈希数据)、list(列表)、set(集合)、zset(有序集合)
- 支持数据的备份:可以实现类似于数据的 master-slave 模式的数据备份,另外也支持使用快照+AOF
- 支持更大的 value 数据:memcache 单个 key value 最大只支持 1MB,而 redis 最大支持 512MB(生产不建议超过 2M,性能受影响)
- 在 Redis6 版本前,Redis 是单线程,而 memcached 是多线程,所以单机情况下没有 memcached 并发高,性能更好,但 redis 支持分布式集群以实现更高的并发,单 Redis 实例可以实现数万并发
- 支持集群横向扩展:基于 redis cluster 的横向扩展,可以实现分布式集群,大幅提升性能和数据安全性
- 都是基于 C 语言开发
redis 典型应用场景
- Session 共享:常见于 web 集群中的 Tomcat 或者 PHP 中多 web 服务器 session 共享
- 缓存:数据查询、电商网站商品信息、新闻内容
- 计数器:访问排行榜、商品浏览数等和次数相关的数值统计场景
- 微博/微信社交场合:共同好友,粉丝数,关注,点赞评论等
- 消息队列:ELK 的日志缓存、部分业务的订阅发布系统
- 地理位置: 基于 GEO(地理信息定位),实现摇一摇,附近的人,外卖等功能
Redis 安装及连接
编译安装
见脚本,注意 redis 不建议设置密码
解决启动时的三个警告提示
tcp-backlog
vm.overcommit_memory
transparent hugepage
Redis 配置和优化
主要配置项
动态修改配置
config 命令用于查看当前 redis 配置、以及不重启 redis 服务实现动态更改 redis 配置等
注意:不是所有配置都可以动态修改,且此方式无法持久保存
获取当前配置
设置连接密码
更改最大内存
慢查询
持久化
目前 redis 支持两种不同方式的数据持久化保存机制,分别是 RDB 和 AOF
RDB 模式
基于时间的快照,其默认只保留当前最新的一次快照,特点是执行速度比较快,缺点是可能会丢失从上次快照到当前时间点之间未做快照的数据
- save: 同步,会阻赛其它命令,不推荐使用
- bgsave: 异步后台执行,不影响其它命令的执行
- 自动: 制定规则,自动执行
bgsave 过程:Redis 从 master 主进程先 fork 出一个子进程,使用写时复制(copy-on-write)机制,子进程将内存的数据保存为一个临时文件,比如:tmp-.rdb,当数据保存完成之后再将上一次保存的 RDB 文件替换掉,然后关闭子进程,这样可以保证每一次做 RDB 快照保存的数据都是完整的
因为直接替换 RDB 文件的时候,可能会出现突然断电等问题,而导致 RDB 文件还没有保存完整就因为突然关机停止保存,而导致数据丢失的情况.后续可以手动将每次生成的 RDB 文件进行备份,这样可以最大化保存历史数据
RDB 模式的优缺点
优点:
- RDB 快照保存了某个时间点的数据,可以通过脚本执行 redis 指令 bgsave(非阻塞,后台执行)或者 save(会阻塞写操作,不推荐)命令自定义时间点备份,可以保留多个备份,当出现问题可以恢复到不同时间点的版本,很适合备份,并且此文件格式也支持有不少第三方工具可以进行后续的数据分析
比如: 可以在最近的 24 小时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个 ROB 文件。这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。 - RDB 可以最大化 Redis 的性能,父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后
这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘工/0 操作。 - RDB 在大量数据,比如几个 G 的数据,恢复的速度比 AOF 的快
缺点:
- 不能实时保存数据,可能会丢失自上一次执行 RDB 备份到当前的内存数据
如果你需要尽量避免在服务器故障时丢失数据,那么 RDB 不适合你。虽然 Redis 允许你设置不同的保存点(save point)来控制保存 RDB 文件的频率,但是,因为 ROB 文件需要保存整个数据集的状态,所以它并不是一个轻松的操作。因此你可能会至少 5 分钟才保存一次 RDB 文件。在这种情况下,一旦发生故障停机,你就可能会丢失好几分钟的数据 - 当数据量非常大的时候,从父进程 fork 子进程进行保存至 RDB 文件时需要一点时间,可能是毫秒或者秒,取决于磁盘 IO 性能
在数据集比较庞大时,fork()可能会非常耗时,造成服务器在一定时间内停止处理客户端﹔如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒或更久。虽然 AOF 重写也需要进行 fork(),但无论 AOF 重写的执行间隔有多长,数据的持久性都不会有任何损失
范例: 手动备份 RDB 文件的脚本
AOF 模式
按照操作顺序依次将操作追加到指定的日志文件末尾
AOF 和 RDB 一样使用了写时复制机制,AOF 默认为每秒钟 fsync 一次,即将执行的命令保存到 AOF 文件当中,这样即使 redis 服务器发生故障的话最多只丢失 1 秒钟之内的数据,也可以设置不同的 fsync 策略 always,即设置每次执行命令的时候执行 fsync,fsync 会在后台执行线程,所以主线程可以继续处理用户的正常请求而不受到写入 AOF 文件的 I/O 影响
同时启用 RDB 和 AOF,进行恢复时,默认 AOF 文件优先级高于 RDB 文件,即会使用 AOF 文件进行恢复
注意:AOF 模式默认是关闭的,第一次开启 AOF 后,并重启服务,因为 AOF 的优先级高于 RDB,而 AOF 默认没有文件存在,从而导致所有数据丢失
AOF rewrite 重写
将一些重复的,可以合并的,过期的数据重新写入一个新的 AOF 文件,从而节约 AOF 备份占用的硬盘空间,也能加速恢复过程,可以手动执行 bgrewriteaof 触发 AOF 或定义自动 rewrite 策略
AOF 文件重写由 Redis 自行触发,我们可以使用 bgrewriteaof 命令手动触发
AOF 模式的优缺点
优点:
- 数据安全性相对较高,根据所使用的 fsync 策略(fsync 是同步内存中 redis 所有已经修改的文件到存储设备),默认是 appendfsync everysec,即每秒执行一次 fsync,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据(fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)
- 由于该机制对日志文件的写入操作采用的是 append 模式,因此在写入过程中不需要 seek,即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在 Redis 下一次启动之前,可以通过 redis-check-aof 工具来解决数据一致性的问题
- AOF 文件体积变得过大时,可以自动在后台对 AOF 进行重写,重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,append 模式不断的将修改数据追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作
- AOF 包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,也可以通过该文件完成数据的重建
AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 协议的格式保存,因此 AOF 文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。导出(export)AOF 文件也非常简单,举个例子:如果你不小心执行了 FLUSHALL 命令,但只要 AOF 文件未被重写,那么只要停止服务器,移除 AOF 文件末尾的 FLUSHAL 命令,并重启 Redis,就可以将数据集恢复到 FLUSHALL 执行之前的状态
缺点:
- 即使有些操作是重复的也会全部记录,AOF 的文件大小要大于 RDB 格式的文件
- AOF 在恢复大数据集时的速度比 RDB 的恢复速度要慢
- 根据 fsync 策略不同,AOF 速度可能会慢于 RDB
- bug 出现的可能性更多
RDB 和 AOF 的选择
如果主要充当缓存功能,或者可以承受数分钟数据的丢失, 通常生产环境一般只需启用 RDB 即可,此也是默认值
如果数据需要持久保存,一点不能丢失,可以选择同时开启 RDB 和 AOF,一般不建议只开启 AOF
Redis 常用命令
- info:显示当前节点 redis 运行状态信息
- select:切换数据库,相当于在 MySQL 的 USE DBNAME 指令
- keys:查看当前库下的所有 key,慎用!可以考虑禁用
- bgsave:手动在后台执行 RDB 持久化操作
- dbsize:返回当前库下的所有 key 数量
- flushdb:强制清空当前库中的所有 key,慎用!可以考虑禁用
- flushall:强制清空当前 redis 服务器所有数据库中的所有 key,即删除所有数据,慎用!可以考虑禁用
- shutdown:安全关闭 redis
- 停止所有客户端
- 如果有至少一个保存点在等待,执行 SAVE 命令
- 如果 AOF 选项被打开,更新 AOF 文件
- 关闭 redis 服务器(server)
Redis 数据类型
- 字符串 string
- 列表 list
- 集合 set
- 有序集合 sorted set
- 哈希 hash
消息队列
消息队列主要分为两种,这两种模式 Redis 都支持
- 生产者/消费者模式
- 发布者/订阅者模式
生产者消费者模式
list
发布者模式
在发布者订阅者模式下,发布者将消息发布到指定的 channel 里面,凡是监听该 channel 的消费者都会收到同样的
- Publisher:发布者
- Subscriber:订阅者
- Channel:频道
Redis 高可用和集群
Redis 主从复制
实现
确保主从的配置文件(redis.conf)中 bind 设置为 0.0.0.0
从节点执行:
replicaof <masterip> <masterport>
,例如REPLICAOF 10.0.0.72 6379
或者直接修改配置文件,如果使用命令配置,建议也修改配置文件,否则重启会失效
INFO replication
查看主从复制信息,REPLICAOF no one
取消主从复制
如果设置了replica-read-only yes
,则从节点只读,不能写
requirepass 和 masterauth:
- requirepass:针对客户端,对登录权限做限制,redis 每个节点的 requirepass 可以是独立的,不同的
- masterauth:master 设置,在 slave 节点数据库同步的时候用到
故障恢复
从节点故障:因为从节点只负责读,当从节点故障,修改代码,连接其他从节点即可
主节点故障:需要提升一个从节点为主节点,然后把其他从节点重新指向新的主节点
提升主节点:
其他从节点重新指向新的主节点
优化
主从复制分为全量同步和增量同步,全量复制一般发生在 Slave 首次初始化阶段,这时 Slave 需要将 Master 上的所有数据都复制一份
避免全量复制:
- 第一次全量复制不可避免,后续的全量复制可以利用小主节点(内存小),业务低峰时进行全量
- 节点运行 ID 不匹配:主节点重启会导致 RUNID 变化,可能会触发全量复制,可以利用故障转移,例如哨兵或集群,而从节点重启动,避免导致全量复制
- 复制积压缓冲区不足:当主节点生成的新数据大于缓冲区大小,从节点恢复和主节点连接后,会导致全量复制。解决方法可以将 repl-backlog-size 调大
避免全量复制:
- 当主节点重启,多从节点复制
优化配置:
常见故障
- master 密码不对
- Redis 版本不一致
- 没有设置 bind 地址或者 密码,导致无法远程连接
- 配置不一致,例如:
- 主从节点的 maxmemory 不一致,主节点内存大于从节点内存,主从复制可能丢失数据
- rename-command 不一致,如在主节点定义了 flushdb,从节点没定义,结果执行 flushdb,不同步
Redis 哨兵
- sentinel 实际上是一个特殊的 redis 服务器,很多 redis 命令不支持,默认监听在 26379/tcp 端口
- 哨兵可以不与 redis 部署在一起,但是一般都部署在一起
- Sentinel 节点个数应该为大于等于 3 且最好为奇数
- Sentinel 进程监控 redis 集群中 Master 主服务器工作的状态,在 Master 主服务器发生故障时,可以实现 Master 和 Slave 服务器的切换,保证系统的高可用
启动服务后,redis 会对配置文件做一些修改
实现
范例:master:10.0.0.71、slave:10.0.0.72 和 10.0.0.73
修改配置文件
启动 redis 和 redis-sentinel 服务
测试
手动下线主节点,让 10.0.0.72 提升为主节点
应用程序连接
客户端不直接连接 redis,而是连接 sentinel,不过 sentinel 不是代理,以 python 为例:
Redis Cluster
Sentinel 机制可以解决 master 和 slave 角色的自动切换问题,但单个 Master 的性能瓶颈问题无法解决,类似于 MySQL 中的 MHA 功能,redis 3.0 之前版本中,生产环境一般使用哨兵模式
redis 3.0 版本之后推出了无中心架构的 redis cluster 机制,在无中心的 redis 集群当中,其每个节点保存当前节点数据和整个集群状态,每个节点都和其他所有节点连接
Redis Cluster 特点:
- 所有 Redis 节点使用(PING 机制)互联
- 集群中某个节点的是否失效,是由整个集群中超过半数的节点监测都失效,才能算真正的失效
- 客户端不需要代理即可直接连接 redis,应用程序中需要配置有全部的 redis 服务器 IP
- redis cluster 把所有的 redis node 平均映射到 0-16383 个槽位(slot)上,读写需要到指定的 redisnode 上进行操作,因此有多少个 redis node 相当于 redis 并发扩展了多少倍,每个 redis node 承担 16384/N 个槽位
- Redis cluster 预先分配 16384 个(slot)槽位,当需要在 redis 集群中写入一个 key -value 的时候,会使用 CRC16(key) mod 16384 之后的值,决定将 key 写入值哪一个槽位从而决定写入哪一个 Redis 节点上,从而有效解决单机瓶颈
Redis Cluster 架构
当主节点出现故障,其从节点会自动提升
实现
6 个节点 10.0.0.[71-76],其中 10.0.0.[71-73]是主节点、10.0.0.[74-76]是从节点
修改 redis.conf 相关配置
启动 redis 服务
创建集群
在每个节点执行
info replication
可以查看此节点的主从信息在任意节点执行
cluster nodes
可以查看所有节点的 ID 及连接信息在任意节点执行
cluster info
可以查看整个集群的状态信息在任意节点执行
redis-cli --cluster info 10.0.0.71:6379
可以查看所有主节点的信息在任意节点执行
redis-cli --cluster check 10.0.0.71:6379
可以查看所有节点的信息
管理
动态扩容
目前集群中有 6 个节点 10.0.0.[71-76],三主三从,现在要将 10.0.0.77 添加到集群中
添加节点:在任意一个节点上执行
新节点是 master,且没有分配槽位
给新 master 节点分配槽位
这样,新的 master 节点就分配了 4096 个槽位,由于这些槽位是其他三个 master 节点平均分配给新 master 节点的,所以避免不了槽位碎片化的问题
重新分配槽位后,之前节点的角色不变,但属关系可能发生改变,例如本来 10.0.0.71 的从节点是 10.0.0.74,扩容后它的从节点成了 10.0.0.75,而 10.0.0.74 成了 10.0.0.3 的从节点
这种扩容方法是在线的,不需要备份数据,如果数据量比较大的话,建议先备份
为新的 master 添加新的 slave 节点
新的 master 节点没有 slave 节点,需要添加一个(10.0.0.78)以保证高可用,任意节点执行以下命令:
或者先将新节点加入集群,再修改为 slave:
动态缩容
从节点:在任意节点上执行
redis-cli --cluster del-node host:port node_id
删除 slave 节点 10.0.0.75:
主节点:需要在删除之前先把槽位移动到其他 master 节点上,可以使用交互式和命式两种方式
删除 master 节点 10.0.0.71:
交互式:在任意节点上执行
命令式:在任意节点上执行
10.0.0.71 上的槽位都移走了之后,其 slave 节点(如果有)也会变成了其他 master 节点的 slave 节点
最后执行
redis-cli --cluster del-node host:port node_id
即可将 10.0.0.71 节点删除
导入现有数据至集群
在任意集群节点上执行:
- –cluster-replace:当导入的数据和已有的数据有重复的 key,–cluster-replace 表示替换为导入的数据
集群偏斜
redis cluster 多个节点运行一段时间后,可能会出现倾斜现象,某个节点数据偏多,内存消耗更大,或者接受用户请求访问更多,发生倾斜的原因可能如下:
- 不同槽对应的键值数量差异较大
- 包含 bigkey,建议少用
- 内存相关配置不一致
获取指定槽位中对应 key 值个数
执行自动的槽位重新平衡分布,但会影响客户端的访问,慎用
获取 bigkey,建议在 slave 节点运行
解散集群
systemctl stop redis.service
关闭所有的 redis 服务,然后删除 nodes-6379.conf 文件,修改 redis.conf:cluster-enabled no
redis cluster 的局限性
- 命令无法跨节点使用,所以 mset、mget、sunion 等操作支持不友好
- 客户端维护更复杂,客户端为了可以直接定位某个具体的 key 所在的节点,它就需要缓存槽位相关信息,这样才可以准确快速地定位到相应的节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整
- 不支持多个数据库,集群模式下只有一个 db0
- 主从复制只有一层,不支持级联复制
- 节点因为某些原因发生阻塞(阻塞时间大于 clutser-node-timeout)被判断下线,这种 failover 是没有必要的
以上缺点只有第一点是比较麻烦的