Redis学习总结(持续更新)
Redis
目前在学习redis,遇到的一些问题会放在这里,加深自己的印象。
1. Redis缓存相较于传统Session存储的特点
Session的存储方式:
通常,传统的Session是存储在应用服务器的内存中,比如Tomcat的Session管理器。用户登录后,服务器生成一个Session ID,通过Cookie返回给客户端,之后的请求都带着这个Session ID,服务器根据Session ID查找对应的Session会话数据。这种方式在单机环境下没问题,但在分布式或集群环境中会有问题,因为Session默认存储在单个服务器上,用户如果被负载均衡到其他服务器,就会找到不到Session,导致需要重新登录。
Redis的存储方式:
Redis作为缓存服务器,可以用来存储Session数据,这样所有应用服务器都可以访问同一个Redis实例,解决了分布式Session的问题。此外,Redis是内存数据库,读写速度很快,适合高并发场景。而且Redis支持持久化,数据不会因为服务器重启而丢失,还能设置过期时间自动清理数据,这些都是传统Session存储不具备的优点。
Redis缓存相较于传统Session存储具有以下显著优点,尤其适合现代分布式和高并发场景:
- 分布式支持:
- 传统Session:存储在单台服务器的内存中,用户请求必须路由到同一服务器(依赖负载均衡的
Sticky Session
),难以扩展为集群。 - Redis:集中存储所有Session数据,后续请求被负载均衡到服务器B。若Session存在Redis中,B可以直接读取,无需用户重新登录。
- 传统Session:存储在单台服务器的内存中,用户请求必须路由到同一服务器(依赖负载均衡的
- 高性能与低延迟:
- 传统Session:内存读写虽快,但受限于单机性能,高并发时可能成为瓶颈。
- Redis:基于内存的键值数据库,支持每秒数十万次操作,且通过多路复用和非阻塞I/O优化高并发访问。
- 持久化与可靠性:
- 传统Session:服务器重启或宕机会导致 Session 数据丢失(内存数据易失性)。
- Redis:支持RDB(快照)和AOF(追加日志)两种持久化机制,确保数据安全故障恢复后仍可保留Session。
- 自动过期与资源管理:
- 传统Session:需手动清理过期 Session,否则内存可能被无效数据占满。
- Redis:支持通过EXPIRE命令自动删除过期Session,避免内存泄漏,简化资源管理。
- 代码示例:
// 设置 Session 过期时间为 30 分钟
stringRedisTemplate.expire("session:token123", 30, TimeUnit.MINUTES);
- 扩展性与灵活性:
- 传统Session:存储内容受限(通常为序列化对象),难以存储复杂结构数据。
- Redis:支持多种数据结构(String、Hash、List、Set 等),可灵活存储用户状态、权限信息、临时缓存等。
- 代码示例:
// 更新用户昵称
stringRedisTemplate.opsForHash().put("user:token123", "nickname", "NewName");
- 高可用与容灾:
- 传统Session:单点故障风险高,服务器宕机导致服务不可用。
- Redis:支持主从复制、哨兵和集群模式,提供故障自动切换和数据冗余,保障高可用性。
- 跨平台与语言无关性:
- 传统Session:依赖特定语言或框架的实现(如 Java 的
HttpSession
),难以跨语言共享。 - Redis:通过统一协议(RESP)提供跨语言支持,不同服务(Java、Python、Go等)可共享同一份Session数据。
- 传统Session:依赖特定语言或框架的实现(如 Java 的
- 安全性增强:
- 传统Session:Session ID 通过 Cookie 传递,可能被劫持(如未启用 HTTPS)。
- Redis:可结合Token机制(如JWT)存储敏感信息,减少Cookie依赖,并通过加密传输提升安全性。
2. 缓存
2.1 什么是缓存
缓存(Cache)是一种临时存储数据的机制,用于加速对高频访问数据的读取。其核心思想是将计算结果或数据库查询结果保存在访问速度更快的存储介质(如内存)中,避免重复执行耗时操作(如复杂计算、数据库查询),从而提升系统性能。
2.2 为什么需要缓存
- 减少数据库压力:避免频繁查询数据库,降低其负载。
- 提升响应速度:内存访问速度远高于磁盘或网络请求(如数据库)。
- 应对高并发:通过缓存热点数据,支撑更多用户同时访问。
- 提高系统可用性:即使数据库短暂不可用,缓存仍可提供部分服务。
2.3 Redis缓存的优势
Redis是一种内存数据库,支持多种数据结构(String、Hash、List、Set等),常被用作高性能缓存解决方案。其优势包括:
- 超高性能:数据存储在内存中,读写速度极快。
- 丰富的数据结构:灵活支持多种数据格式,适应不同场景。
- 持久化:通过 RDB/AOF 机制,可持久化数据到磁盘。
- 分布式支持:天然适合集群化部署,支撑高可用和高并发。
2.4 缓存设计注意事项
- 缓存穿透:
- 问题:查询不存在的数据(如无效 ID),导致请求直接压到数据库。
- 解决:缓存空值 + 布隆过滤器(Bloom Filter)。
- 缓存雪崩:
- 问题:大量缓存同时过期,导致数据库瞬时压力激增。
- 解决:随机化过期时间(如基础 30 分钟 ± 随机 5 分钟)。
- 缓存击穿:
- 问题:热点 Key (就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了)过期瞬间,高并发请求直接访问数据库。
- 解决:互斥锁(如 Redis 的
SETNX
)或永不过期 + 后台异步更新。
3.缓存更新策略
在处理缓存更新策略时,主要目标是确保缓存数据与底层数据源(数据库)保持一致性,同时优化系统性能。以下是常见的缓存更新策略及其使用场景:
3.1 Cache-Aside(旁路缓存)
原理:
- 读操作:先查缓存,命中则返回;未命中则查数据库,并将结果写入缓存。
- 写操作:直接更新数据库,然后删除或更新缓存。
步骤:
- 读流程:
- 检查缓存是否存在数据。
- 缓存命中 → 返回数据。
- 缓存未命中 → 查询数据库 → 写入缓存 → 返回数据。
优点:
- 实现简单,缓存层与应用逻辑解耦。
- 避免写操作频繁触发缓存更新。
缺点:
- 短暂不一致:数据库更新后,缓存删除前可能有旧数据被读取。
- 需处理缓存穿透、雪崩等问题。
适用场景:读多写少,对一致性要求不苛刻的场景(如商品详情页)。
3.2 Read-Through(读穿透)
3.3 Write-Through(写穿透)
4. 缓存的三大问题
在使用缓存时,常见的三大问题包括缓存穿透、缓存击穿和缓存雪崩。这些问题可能导致数据库压力骤增甚至系统崩溃。
4.1 缓存穿透
定义:频繁查询数据库中不存在的数据,导致请求绕过缓存直接访问数据库。
原因:
- 恶意攻击:如用非法参数(如负数的用户ID)发起大量请求。
- 业务缺陷:代码逻辑未过滤无效查询,导致缓存和数据库均无数据。
解决思路:
- 布隆过滤器:在缓存层前加布隆过滤器,快速判断数据是否存在。
- 原理:利用位数组和哈希函数,判断元素是否“可能存在”或“一定不存在”。
- 实现:Redis 通过
RedisBloom
模块支持布隆过滤器。
- 缓存空值:对不存在的的数据缓存空值(null)并设置短过期时间(如5分钟),防止重复查询。
String key = "user:10000";
User user = cache.get(key);
if (user == null) {
user = db.getUser(10000);
if (user == null) {
cache.set(key, "NULL", 5, TimeUnit.MINUTES); // 缓存空值
} else {
cache.set(key, user, 30, TimeUnit.MINUTES);
}
}
注意事项:
- 布隆过滤器有误判率(可能误判存在),需权衡误判率与内存开销。
- 空值缓存需设置较短TTL,避免存储过多无效数据。
4.2 缓存击穿
定义:某个热点数据在缓存过期瞬间,大量并发请求直接穿透到数据库。
原因:热点数据过期后,高并发请求同时查询数据库重建缓存。
解决思路:
- 1. 逻辑过期:缓存不设置TTL,但在数据中存储过期时间,由异步线程主动刷新。
- 原理:逻辑过期的核心思路是在缓存数据中设置一个逻辑上的过期时间,而非实际的过期标识。当缓存数据在逻辑上过期后,并不会立即从缓存中删除,而是在后续查询时进行处理。
- 具体流程(结合图中线程情况说明):
- 查询缓存:当线程(图中线程1、线程3等)查询缓存时,首先判断缓存数据的逻辑过期时间。若发现逻辑时间已过期,进入下一步。
- 获取互斥锁:线程尝试获取互斥锁。只有获取到互斥锁的线程(如图线程1)才能进行后续操作,获取失败的线程(如图线程3)则直接返回过期数据。
- 重建缓存数据:获取到互斥锁的线程(线程1)开启新线程区查询数据库,重建缓存数据。这是因为主线程继续执行,先返回过期数据给调用方,避免长时间等待。新线程从数据库获取最新数据后,写入缓存,并重置逻辑过期时间。
- 释放锁:完成缓存数据重建和逻辑时间重置后,持有锁的线程(线程1)释放互斥锁。
- 后续查询:后续再有线程查询时,若缓存未过期则直接命中缓存获取数据;若逻辑过期,重复上述流程。
- 优点:
- 保证高可用性:在缓存数据重建过程中,未获取到锁的线程能快速返回过期数据,不会因等待重建而长时间阻塞,保证了系统的响应速度和可用性。
- 减少数据库压力:相较于所有请求在缓存未命中时都直接查询数据库,逻辑过期方案只有获取到互斥锁的线程才会去查询数据库重建缓存,降低里数据库的瞬时压力。
- 缺点:
- 数据一致性:在缓存逻辑过期到重建完成这段时间内,返回给用户的数据时过期的,可能导致数据短期不一致。
- 实现复杂度:需要额外维护逻辑过期时间,以及处理互斥锁相关逻辑,增加了代码实现的复杂度。
- 2. 互斥锁: