首页
东邻西舍
本站信息
前来吐槽
统计
Search
1
openwrt系统上安装第三方插件
28,884 阅读
2
ubuntu下zerotier的基本使用教程
15,989 阅读
3
给小米R3G更换系统:从padavan刷成openwrt
14,993 阅读
4
openwrt使用第一步:设置上网拨号
12,964 阅读
5
openwrt无线中继功能:实现不插网线就能上网
11,531 阅读
学习点滴
后端
前端
Linux
踩坑实录
AI
折腾搞机
关于建站
只言片语
登录
Search
标签搜索
Linux
Java
建站
踩坑实录
折腾搞机
Ubuntu
MySQL
MyBatis
CSS
HTML
Spring
SQL
Nginx
OpenWrt
树莓派
路由器
Windows
Maven
只言片语
Win10
知识分子没文化
累计撰写
87
篇文章
累计收到
155
条评论
首页
栏目
学习点滴
后端
前端
Linux
踩坑实录
AI
折腾搞机
关于建站
只言片语
页面
东邻西舍
本站信息
前来吐槽
统计
搜索到
1
篇与
的结果
2024-03-07
Redis进阶学习笔记
目录: 一、Redis 的基础知识 1.1、什么是 Redis 1.2、为什么要有 Redis 二、内存管理与淘汰机制 2.1、内存过期策略 2.2、内存淘汰机制 三、持久化 3.1、RDB 持久化 3.2、AOF 持久化 3.3、RDB-AOF 混合持久化(Redis 4.0) 四、Redis集群方案 4.1、单机模式(Standalone) 4.2、主从复制(Replication) 4.3、哨兵模式(Redis Sentinel) 4.4、Redis Cluster 五、应用场景 5.1 数据库 5.2 系统缓存 5.3 消息队列 5.4 计数器和排行榜 5.5 分布式锁 六、缓存相关问题 6.1、缓存穿透 6.2、缓存击穿 6.3、缓存雪崩 Redis 官网:Redis - The Real-time Data Platform 一、Redis 的基础知识 1.1、什么是 Redis Redis(REmote DIctionary Server)是一个开源的、基于内存的、键值对(Key-Value)数据库,它支持多种数据结构,并提供原子操作、事务、Lua脚本、发布订阅等高级特性。 Redis 支持的基础数据类型有五种: 数据类型 介绍 底层实现 特点 String(字符串) 最基础、最常用的数据类型 SDS(简单动态字符串) 可以存储:字符串、整数、浮点数、二进制数据单个数据最大支持 512MB支持原子操作(如自增、自减) List(列表) 本质是一个双向链表 压缩列表 / 双向链表 有序、可重复支持从两端插入/弹出操作时间复杂度:头尾 O(1) Hash(哈希) 类似于 Map<String, String> 压缩列表 / 哈希表 存储键值对集合(field-value)适合存储对象支持单字段读写 Set(集合) 无序、不 哈希表 / 整数集合 自动去重支持集合运算(交集、并集、差集) ZSet(Sorted Set,有序集合) 在 Set 基础上增加了一个 score(分数) 用来排序 跳跃表 + 哈希表 元素唯一,但 score 可重复按 score 自动排序(底层是跳跃表 + 哈希表)查询效率高(范围查询) 还有几种基于基础数据类型的扩展类型: 数据类型 介绍 特点 Bitmap(位图) 基于 String 的按位操作 每一位是 0 或 1非常节省空间(适合大规模布尔状态) HyperLogLog 用于基数统计(去重计数)的概率算法 占用极小内存(≈12KB)有一定误差(约 0.81%) Geospatial(地理位置) 用于存储地理位置信息 基于 Sorted Set 实现支持距离计算、范围搜索 Stream(流,Redis 5+) 类似消息队列,但功能更强 支持消费组(Consumer Group)支持消息持久化可回溯消息 1.2、为什么要有 Redis 传统关系型数据库(如 MySQL)擅长复杂查询和事务,但磁盘 I/O 导致高并发下存在延迟高、吞吐低的问题,因此数据库的读写性能往往会成为瓶颈。 而加入 Redis 作为高性能缓存,将使用频率高的数据(热点数据)放在内存中,需要时可以直接通过请求 Redis 获取,只有当 Redis 获取不到时才会查询数据库,这种方式可以降低数据库的压力,同时还显著提升系统的响应速度和吞吐量。 Redis 的主要优势包括: 基于内存:所有数据存储在内存中,读写也基于内存操作,避免了磁盘 I/O,因此速度极快,这是最根本的原因; 单线程模型(核心I/O线程): 准确来讲,Redis 不是完全单线程,Redis 6.0 之后,网络I/O、持久化会使用多线程,但命令执行依然是单线程; 在进行读写操作时,单线程可以避免多线程的上下文切换和竞争(锁竞争、死锁)带来的额外开销,反而提升了处理速度,此时的瓶颈不在CPU,而在内存和网络; 并且由于单线程没有了复杂的同步机制,因此也不易出错; I/O多路复用机制:让单线程能同时监听多个 socket 连接,当某个连接有数据请求时,才去处理。类似于一个服务员同时服务很多桌客人,谁举手就去谁那。使用了epoll等高效机制; 高效的数据结构:底层针对不同场景设计了极致优化的数据结构。 二、内存管理与淘汰机制 Redis 是内存数据库,随着应用的运行,如果不清理,Redis 中的 Key 会不断增加,最终内存耗尽导致 OOM(Out Of Memery)。 针对于内存溢出问题,Redis 通过内存过期策略预防内存溢出,而当内存使用达到 maxmemory 上限导致新 Key 无法写入时,Redis 使用内存淘汰机制淘汰旧数据为新数据腾出空间。 2.1、内存过期策略 Redis 通过为 Key 设置过期时间(TTL)来管理内存中的数据生命周期。当 Key 的过期时间到达时,Redis 会自动删除该键及其对应的值,以避免旧数据的无限累计。 内存过期策略主要包括: 定时删除:为每个设置过期时间的 Key 维护一个定时器,当过期时间到达时立即删除。但这种方式会消耗大量的CPU资源,比较影响性能。 惰性删除:在访问 Key 时才检查其是否过期,如果过期则删除。这种方式不会额外消耗CPU资源,但可能导致内存中存在大量过期的 Key,占用内存空间。 需要说明的是,Redis 并不是在 Key 到期的一瞬间就立刻将其从内存中删除,而是采用了定时删除与惰性删除两种策略的组合: Redis 内部开了一个定时任务,默认每段时间(比如 100ms)运行一次; 从设置了过期时间(TTL)的 Key 中随机抽取 20 个数据进行检查; 如果这 20 个 Key 中过期的 Key 占比超过 25%(即超过 5 个),Redis 会认为当前内存中过期 Key 较多,不会等待下一个 100ms,而是立即重复上一个步骤继续抽查、继续清理,直到过期的 Key 占比低于 25%。 内存过期策略也有可能会导致一个问题,如果同一时刻大量数据过期或者 Redis 无法提供服务,客户端请求从 Redis 中获取不到已过期的数据就会查询数据库,从而使数据库请求激增,压力过大造成数据库服务器宕机,即缓存雪崩。 所以要避免大量数据在同一时刻过期,解决办法是在固定过期时间上增加一个随机偏移量,同时对于热点数据,不设置过期时间,使其达到“物理”上的永不过期,可以避免缓存击穿问题。 2.2、内存淘汰机制 当内存使用达到 maxmemory 上限,且新数据写入时,触发淘汰策略,根据配置的内存淘汰策略删除部分键值对以释放内存。 那么应该选择哪些数据淘汰,大致的思想有两种: LRU(Least Recently Used)是按照最近最少使用原则来筛选数据,即最不常用的数据会被筛选出来; LFU(Least Frequently Used)是 Redis4 新增的淘汰策略,它根据 key 的最近访问频率进行淘汰,即访问次数最少的数据会被筛选出来。 再结合淘汰数据的范围(全部 Key 或设置了过期时间的 Key),那么综合考虑下来内存淘汰策略就有八种: 策略 描述 适用场景 noeviction 默认策略。不淘汰,写请求直接返回错误(如SET失败) 数据绝不能丢的场景,如作为数据库 allkeys-random 从所有key中随机淘汰 缓存数据访问频率相近 allkeys-lru 从所有数据中淘汰最近最少使用(LRU)的key 通用缓存,希望保留最可能被再次访问的数据 allkeys-lfu(Redis4.0) 从所有数据中淘汰使用频率最低(LFU)的key 通用缓存,希望保留经常被访问的数据 volatile-ttl 从设置了过期时间的key中,淘汰将要过期的key 通用缓存,希望优先淘汰快过期的数据 volatile-random 从设置了过期时间的key中,随机淘汰key 缓存数据访问频率相近 volatile-lru 从设置了过期时间的数据中淘汰最近最少使用(LRU)的key 通用缓存,希望保留最可能被再次访问的数据 volatile-lfu(Redis4.0) 从设置了过期时间的数据中淘汰使用频率最低(LFU)的key 通用缓存,希望保留经常被访问的数据,比LRU更精准 结合内存过期策略来说,如果此时设置的淘汰策略是 allkeys-lru 或 allkeys-random,那么即使某个 Key 还没到过期时间,也可能被提前淘汰,使用 volatile-lru 或 volatile-ttl 策略,即只针对有过期时间的 Key 进行淘汰,就可以解决这个问题。 三、持久化 对于 Redis 来说,由于数据都在内存中,如果发生断电或进程崩溃时就必然面临数据丢失的风险,因此有了数据的持久化,以便在 Redis 重启后能恢复数据。 Redis 支持的持久化方式有三种:RDB 持久化、AOF 持久化和 RDB-AOF 混合持久化。 3.1、RDB 持久化 RDB(Redis Database)是 Redis 默认采用的持久化方式,它的原理是 Redis 在后台 fork(复制)一个子进程,子进程将该时间的内存数据生成快照文件写入到磁盘中,创建一个经过压缩的 “.rdb” 二进制文件,存储各个数据库的键值对数据等信息。 RDB 持久化的触发方式有两种: 手动触发:通过 SAVE 或 BGSAVE 命令触发 RDB 持久化操作,创建“.rdb”文件; 自动触发:通过配置选项,让服务器在满足指定条件时自动执行 BGSAVE 命令。 RDB 持久化的优缺点: 优点:RDB生成紧凑压缩的二进制文件,体积小,恢复数据的速度非常快; 缺点:RDB 持久化没办法做到实时,BGSAVE 每次运行都要执行 fork 操作创建子进程,属于重量级操作,不宜频繁执行。 3.2、AOF 持久化 AOF 持久化的原理是记录了每次写入命令到 “.aof” 格式的文本文件,类似MySQL的binlog,当 Redis 重启时再重新执行 AOF 文件中的命令来恢复数据到内存中。 AOF的工作流程包括:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load)。 需要控制命令同步到硬盘的频率,即多久将命令写入到 AOF 文件中一次,就是同步策略 (appendfsync): appendfsync always:每次写操作后立即同步到磁盘。最安全,但性能最差; appendfsync everysec:每秒同步一次。折中方案,默认推荐,最多丢1秒数据,性能和安全平衡最好; appendfsync no:由操作系统决定同步时机。最快,但最不安全,但可能丢大量数据。 AOF 重写(Rewrite)机制:为了解决文件越来越大的问题,Redis会自动(或手动 BGREWRITEAOF)在后台重写 AOF 文件。只保留“当前数据集的最小命令集合”,去掉冗余操作(比如多次 SET 同一个 Key,只留最后一次)。重写操作由子进程完成,不会阻塞主进程。 AOF 持久化的优缺点: 优点: 数据安全性高,最多丢失一秒的数据; 生成的 “.aof” 格式的日志文件可读,便于调试和修复。 缺点: 文件通常比RDB大,恢复数据慢(因为需要逐条重新执行命令); 对性能有一定影响(尤其是 always 策略)。 3.3、RDB-AOF 混合持久化(Redis 4.0) RDB 持久化和 AOF 持久化都有各自鲜明的优缺点,因此从 Redis4.0 开始引入集成二者优点的持久化方式:RDB-AOF 混合持久化,这种方式的原理是将内存数据先以 RDB 格式写入一个快照到 AOF 文件头部,后续再把写命令追加到快照后面,恢复时先恢复数据的快照,再执行后面的追加命令。 与 AOF 持久化类似,RDB-AOF 混合持久化也具备同步策略和重写机制。 优势:兼顾了 RDB 持久化的加载速度和 AOF 持久化的数据安全,是 Redis 生产环境的首选方案。 四、Redis集群方案 4.1、单机模式(Standalone) 一台服务器上运行一个Redis实例(redis-server)。数据全部放在一台机器的内存里,所有读写操作都经过这个实例,没有任何备份或扩展。 优点:部署简单,适合开发、测试、小型项目;且性能最好,因为没有网络复制开销 缺点:机器宕机、Redis进程崩溃,都会导致服务完全不可用;数据量超过单机内存或 QPS 过高,就扛不住。 4.2、主从复制(Replication) 一个主节点(Master)负责处理写请求,多个从节点(Replica / Slave)实时复制主节点(Master)的数据,只负责处理读请求。整个集群的数据流向是单向的:Master -> Slave。 优点:结构简单,配置容易;从节点分担主节点读压力,极大提升 QPS 缺点:所有写操作都依赖主节点,写性能上限受限于主节点,无法水平扩展写能力;主节点故障需要人工干预切换,无法自动故障转移;异步复制可能导致轻微数据不一致 4.3、哨兵模式(Redis Sentinel) 哨兵模式基于主从模式扩展,主要解决主从模式中主节点(Master)宕机无法自动恢复的问题,以实现高可用。 在主从架构的基础上,引入一组独立的 Sentinel(哨兵)节点。Sentinel 不处理业务读写,只负责心跳检测主从节点,当主节点故障(客观下线),哨兵通过 Raft 协议投票选出一个新的主节点,并通知客户端和其他从节点。 主观下线与客观下线:一个 Sentinel 认为节点挂了是主观下线,半数以上 Sentinel 认为挂了才是客观下线,避免网络误判。 哨兵节点的数量至少为 3 个,且为奇数个,以避免脑裂问题,总体架构推荐至少 3 Sentinel + 1 Master + 2+ Replica。 优点:保留了主从模式的优点,依然支持读写分离,从节点分担读压力;实现了故障自动检测和切换 缺点:本质上还是主从模式,写性能上限受限于主节点,依然无法水平扩展写能力;且主从切换过程中,Redis 会短暂不可用 4.4、Redis Cluster Redis Cluster 是 Redis 官方提供的分布式解决方案,自 Redis 3.0 引入,旨在通过分片(Sharding)技术将数据分布在多个主节点上,解决单机写瓶颈和内存容量瓶颈。 Redis Cluster 将整个数据集分成 16384 个哈希槽(Hash Slot)。每个键通过 CRC16(key) % 16384 计算后,决定落在哪个槽位。集群中存在多个主节点(Master),每个主节点负责一段连续的槽位区间(如 0-5000、5001-10000、10001-16383),并且可挂载一个或多个从节点(Replica)作为数据备份实现高可用。节点之间通过 Gossip 协议相互通信,自动发现新节点、传播故障信息、协调槽位迁移。客户端连接任意一个节点,若请求的数据不在当前节点,节点会返回正确的目标节点地址(MOVED 重定向),智能客户端根据新的目标节点地址自动跳转到正确的节点。 优点: 数据容量水平扩展:总数据量不再受限于单机内存,集群总容量随节点增加而线性增长 写性能水平扩展:写操作分散到多个主节点,增加节点即可线性提升写吞吐量,彻底突破单机写瓶颈 内置高可用:主节点故障自动转移,无需人工干预,比哨兵 Sentinel 更快 故障隔离更好:单个节点故障只影响部分数据,只影响它负责的槽位,其他数据仍可正常读写 缺点: 复杂度高:搭建、监控、运维更麻烦(需要处理 slot 迁移、热点 slot 等) 客户端要求高:必须用支持Cluster的客户端(Redisson、go-redis等),否则需要处理MOVED重定向 批量操作受限:不支持跨节点的事务、和多 Key 操作(除非 Key 带有相同的 {hashtag},强制将其分配到同一槽位) 资源消耗更高:至少6个节点起步 数据倾斜风险:热点key可能集中在某个节点,需要优化key设计 五、应用场景 5.1 数据库 Redis 可以作为主数据库使用,适用于对性能要求高、数据量相对较小的场景,如会话存储、实时排行榜等。 5.2 系统缓存 Redis 最常见的用途是作为缓存层,通过缓存热点数据,可以减少对数据库的查询次数,减轻数据库负担,显著提高应用访问速度。 5.3 消息队列 Redis 的列表和发布/订阅模式可以用于实现消息队列: 通过列表(List)的 LPUSH 和 RPOP 命令可以实现简单的消息队列功能 通过发布/订阅模式可以实现更复杂的消息分发和订阅功能 5.4 计数器和排行榜 Redis 的原子操作和有序集合(ZSet)非常适合实现计数器和排行榜功能,通过为每个元素关联一个分数,可以方便地实现排序和统计功能,如网站访问统计、游戏积分榜等。 5.5 分布式锁 Redis 的原子性操作可以用于实现分布式锁。通过 SETNX 命令可以尝试获取锁,如果获取成功则执行临界区代码;执行完毕后通过 DEL 命令释放锁,保证在分布式环境下的互斥访问。为了避免死锁,还可以为锁设置过期时间。 六、缓存相关问题 在高并发系统中,使用 Redis 做缓存时,最容易引发系统故障的三大“杀手”就是缓存穿透、缓存击穿和缓存雪崩。 6.1、缓存穿透 客户端请求的数据在 Redis 中不存在(缓存未命中),同时在数据库中也不存在。每次请求都会直接打到数据库。如果是恶意构造的大量无效请求(比如 userId = -1、随机长字符串、已删除的商品 ID),数据库瞬间就被打崩。 核心特点: 查询的是“根本不存在”的数据。 缓存和数据库都无法命中,请求每次都穿透。 极易被恶意攻击利用(也叫恶意穿透)。 常见场景: 接口未做参数校验,攻击者用不存在的 ID 疯狂刷接口。 查询已下架或从未存在过的商品 / 用户详情。 解决方式: 接口层参数校验(第一道防线):对请求参数做基础合法性检查(格式、长度等),提前过滤无效请求 缓存空对象(空值缓存)(最常用、简单有效):当数据库返回 null/空时,也在 Redis 中缓存一个空结果,并设置较短的过期时间(建议 30 秒 ~ 5 分钟,避免长期占用内存) 布隆过滤器(Bloom Filter):在 Redis 前加一层布隆过滤器,提前判断该 Key 是否可能存在,如果肯定不存在,直接返回 null,不查 Redis 和数据库,如果可能存在,再走正常缓存流程。 6.2、缓存击穿 某个高并发热点 Key 的缓存突然过期(或被删除),而此时有大量并发请求同时到达。所有请求发现缓存 miss,同时去查数据库,导致数据库瞬时压力暴增,甚至宕机。 核心特点: 问题发生在单个热点 Key 过期瞬间。 查询的是真实存在且非常热门的数据。 影响虽然是单个 Key,但并发量极大,破坏力强。 常见场景: 秒杀商品的库存/详情 Key 刚好过期。 热门新闻、明星话题、爆款商品的缓存到期。 大促期间某个热卖品的缓存失效。 解决方式(核心:只让一个请求去查数据库,其他等待或用旧数据): 缓存未命中时,先尝试获取分布式锁。只有拿到锁的线程才允许查询数据库,其他线程等待或短暂重试。 不依赖 Redis 的原生 expire,而是把“过期时间”字段存到 Value 里(例如 JSON 中加一个 expireTime)。用户请求时,如果发现逻辑已过期,后台异步线程去刷新数据,用户先返回旧数据 热点数据永不过期 + 后台刷新 对极重要热点 Key 不设过期时间,或设很长 TTL,由定时任务/消息队列后台定期刷新 6.3、缓存雪崩 大量缓存 Key 在同一时间段内集体过期(或 Redis 实例宕机),导致海量请求同时穿透到数据库,数据库压力骤增,甚至引发连锁反应导致系统雪崩。 核心特点: 影响的是大量 Key(而非单个)。 通常发生在同一时刻大量 Key 同时失效。 后果最严重,可能导致整个服务不可用。 常见场景: 所有 Key 统一设置相同过期时间(如全设 30 分钟)。 大促活动结束,大量活动缓存同时到期。 Redis 重启或故障(无持久化时,所有缓存瞬间丢失)。 解决方式: 最简单、最有效:设置过期时间时加上随机偏移,避免大量 Key 同时到期 缓存预热 + 永不过期策略:提前把热点数据加载到缓存;核心数据可永不过期,由后台异步更新 多级缓存:第一层用 Caffeine/Guava 本地缓存挡住大部分请求,Redis 作为二级,数据库作为最后防线 熔断降级 + 限流 + 快速失败:使用 Sentinel / Hystrix / Resilience4j 等框架:当数据库压力过大时,快速熔断,返回兜底数据(“系统繁忙,请稍后重试”或缓存的旧数据)
2024年03月07日
3,209 阅读
0 评论
3 点赞