Redis是什么
Redis 是一个高性能的开源内存数据库,全称为 Remote Dictionary Server,以键值对(Key-Value)的形式存储数据。它支持多种数据结构、丰富的功能以及高效的操作,因此被广泛应用于缓存、消息队列、排行榜、会话管理等场景。
Redis特性
基于内存
- 数据存储在内存里,读写速度极快
- 支持将数据周期性地持久化到磁盘(RDB与AOD)
支持多种数据
- 支持简单的键值对(String),哈希表(Hash),列表(List),集合(Set),有序集合(Sorted-Set)等
高性能
- 每秒支持百万级的读写请求(单线程处,避免了线程切换的开销)
多种功能支持
- 持久化:支持RDB快照和AOF日志两种方式将内存数据保存到磁盘上
- 事务:支持简单的事务机制
- 发布/订阅:支持消息的的订阅发布
- Lua脚本:支持脚本操作,减少服务端与服务器之间的通信次数
- 分布式:支持主从复制,哨兵模式和集群模式,轻松扩展
灵活的数据过期策略
- 支持为键设置过期时间(TTL)
Redis的核心数据结构
String(字符串)
- 最基本的类型,常用于存储单个对象,计数器等
Hash(哈希表)
- 存储键值对的集合,类似于Map或字典
List(列表)
- 有序集合,可以从两端操作,适用于队列、栈等场景
Set(集合)
- 无序集合,自动去重,适用于校验唯一性
SortedSet(有序集合)
- 带权重的集合,支持按权重排序,适用于排行榜等场景
Bitmap和HyperLogLog
- Bitmap:用于高效存储和操作二进制数据。
- HyperLogLog:适用于基数统计,如统计独立访问用户数。
Redis的应用场景
- 缓存:提高访问数据速度,减轻数据库压力
- 会话存储:存储用户的登录态(用户登录信息),支持高效读写和过期机制
- 排行榜:使用有序集合实现动态更新的排行榜
- 消息队列:利用List和Pub/Sub实现异步任务处理
- 分布式锁:通过键值对和过期时间实现可靠的分布式锁(如 Redisson 实现)
- 计数器:高效地实现访问量、点赞数等实时计数(因为支持原子操作)
- 全局唯一ID生成:使用 INCR 和 HyperLogLog 实现唯一性标识。
Redis和Memcache的区别
- 数据类型的支持上
- 持久化
- 内存管理
- 分布式的支持
- 多线程支持
- 性能对比
- 功能拓展
- 使用场景
- 协议
特性 | Redis | Memcache |
---|---|---|
数据结构支持 | 数据结构丰富 | 仅支持键值对 |
持久化 | 支持RDB和AOF | 不能持久化 |
内存管理 | 自定义管理,多种淘汰策略 | 将内存划分为固定大小的块,以减少内存碎片,数据项大小受限(最大1M) |
分布式支持 | 支持原生的Redis Cluster | 不支持,需要客户端实现 |
多线程支持 | 部分多线程,但实际底层实现还是单线程 | 原生多线程 |
性能 | 高效,适合复杂场景 | 基于多线程,更快,适合简单场景 |
高级功能 | 发布/订阅,Lua脚本,Stream等 | 功能简单仅支持缓存 |
Redis为什么那么快?单线程
基于内存操作:
- 所有数据都存储在内存上,访问数据很快
- 读写操作远高于磁盘存储,减少了I/O操作的延迟
- 单线程避免了线程的上下文切换的开销,使得操作更加高效
高效的事件机制
- Redis使用I/O多路复用,基于epoll和Select,实现了高效的非阻塞I/O
- 单线程避免了锁竞争的问题,操作简单且稳定
- Redis将事件处理串行化,确保不会出现多线程并发冲突的问题
高效的底层实现
- Redis 的核心是用 C 语言 编写的,执行效率非常高。
- 使用了精心设计的数据结构,如压缩列表(ZipList)、整数集合(IntSet),在节省内存的同时提高了操作效率。
- 避免锁竞争
优化的协议与内存管理
- Redis 使用了一种高效的通信协议 RESP(Redis Serialization Protocol),解析速度快,减少了数据传输中的开销。
- 内存管理采用了高效的机制(如内存池、对象共享等),大幅减少了频繁的内存分配和回收操作
操作简单且快速
- Redis 是一个 内存数据库,不涉及复杂的关系查询,所有操作都围绕数据结构展开,命令简单且执行速度快。
- 适合单线程的场景
- 减少上下文切换
Redis字符串
Redis字符串的最大长度 512MB
- Redis 中的字符串类型的值可以存储最多 512 MB(512 1024 1024)的数据。
- 这意味着可以存储很长的文本、二进制数据(如图片、音频文件等)。
字符串底层实现
短字符串:简单动态字符串(SDS)
- SDS 是 Redis 自定义的字符串实现,支持动态扩容和长度记录
- 小于 39 字节的字符串使用 SDS 表示
长字符串:直接用裸内存管理
- 当字符串超过一定长度时,Redis 会切换为裸内存的方式进行管理
Redis默认分多少个数据库 16
数据库切换
默认使用 0 号数据库
- Redis 在启动时,客户端连接的默认数据库是编号为 0 的数据库。
切换数据库命令:SELECT
- 使用 SELECT 命令可以切换到指定编号的数据库。例如:
不同数据库的特点
逻辑隔离
- 每个数据库是相互独立的,数据互不干扰。
- 在一个数据库中存储的键值对,不能直接被另一个数据库访问。
连接共享
- 所有数据库共享一个 Redis 实例的网络连接和内存。
使用场景
- 一些简单场景中,可以用不同的数据库来存储不同的业务数据。例如,0 号数据库存储缓存数据,1 号数据库存储会话信息等
注意事项
集群模式下不支持多个数据库
- 在 Redis 的集群模式中,每个节点只支持一个数据库,所有的键都存储在同一个数据库中。
Redis 单实例性能瓶颈
- Redis 虽然支持多个数据库,但这些数据库共享同一实例的资源,因此多个数据库会竞争同一个实例的 CPU 和内存。
Redis持久化
RDB持久化
原理:
- RDB 持久化会在指定的时间间隔内,将内存中的数据生成快照(Snapshot)并保存到磁盘上。
- 快照文件是一个二进制文件,默认名为 dump.rdb。
优点:
- 紧凑文件:RDB 文件为二进制格式,体积小,便于传输和备份。
- 快速恢复:RDB 文件在数据恢复时加载速度较快。
- 性能影响小:RDB 是间隔性操作,对 Redis 的性能影响较小。
缺点:
- 数据丢失的风险:因为 RDB 是周期性保存,故在最近一次快照与宕机之间的数据可能会丢失
- 适用性有限:不适用于对数据一致性要求非常高的场景。
配置:
在 redis.conf 中通过 save 指令设置触发条件。例如:
save 900 1 # 每 900 秒内有至少 1 次写操作触发快照
save 300 10 # 每 300 秒内有至少 10 次写操作触发快照
save 60 10000 # 每 60 秒内有至少 10000 次写操作触发快照
AOF持久化(追加日志持久化)
原理:
- AOF 将每次写操作(如 SET、LPUSH)以日志的形式追加到文件中,默认文件名为 appendonly.aof
- Redis 重启时通过重放 AOF 文件中的日志命令,恢复数据
优点
- 数据安全性高:通过 fsync 策略,可以将数据丢失的可能性降到最低。
- 可读性强:AOF 文件是可读的日志格式,便于分析或修复。
缺点
- 文件体积较大:AOF 文件比 RDB 文件大得多。
- 恢复速度比较慢:重放日志的过程较耗时。
- 性能影响比较大:频繁的磁盘写操作可能影响 Redis 的性能
配置:
在 redis.conf 中启用 AOF:
ppendonly yes
appendfsync always # 每次写操作都立即同步到磁盘,最安全但性能最低
appendfsync everysec # 每秒同步一次,性能和安全性折中(默认)
appendfsync no # 不强制同步,性能最高但可能丢失较多数据
RDB和AOF混合模式
在 AOF 文件重写时,将 RDB 快照与 AOF 增量日志结合存储,既保留了 RDB 快速恢复的优势,又保持了 AOF 的高数据完整性
优点
- 文件体积更小。
- 数据恢复速度快且数据丢失风险低。
持久化的注意事项
数据一致性和安全
- 对于对数据一致性要求高的场景,推荐使用 AOF。
- 如果追求性能并容忍少量数据丢失,可以仅使用 RDB。
持久化与性能的平衡
- RDB 和 AOF 都会对性能产生影响(磁盘 I/O),可以根据业务需求调整触发频率。
- Redis 也可以选择关闭持久化,将其作为纯内存数据库使用。
备份与恢复
- RDB 和 AOF 文件可用于定期备份。
- 恢复数据时,只需将 dump.rdb 或 appendonly.aof 文件拷贝到 Redis 数据目录并重启。
Redis事务
Redis事务通过命令的批量操作实现,支持事务的执行但并非完整意义上的事务模型(如没有隔离级别)。Redis事务的核心特点是一次性、顺序性、排他性地执行一组命令。
Redis事务的基本操作
事务的关键命令
- MUTLI:开启一个事务
- EXEC:提交事务,执行事务的所有命令
- DISCARD:取消事物,丢弃事务中的所有命令
- WATCH:监视一个或多个键,如果事务执行前这些键被其他客户端修改,事务会中止。
- UNWATCH:取消监视
事务的执行步骤
- 开始事务(MULTI)
- 输入需要执行的命令,这些命令放入事务队列
- 提交事物(EXEC),Redis 会顺序执行队列中的命令
事务的特点
无隔离级别
- Redis 事务不支持传统关系型数据库的隔离级别。
- 在事务执行前后,其他客户端可以继续操作键值。
事务中的命令不会部分回滚
- 如果事务中的某条命令出错(如命令语法错误),错误会被记录,但事务中其他命令仍会执行。
- Redis 不支持事务的部分回滚,如果一个命令失败,之前的命令不会回滚。
原子性
- 每个事务内的命令会按顺序一次性执行,但不保证整体的原子性。
Redis事务的应用场景
简单的批量操作
- 多个命令需要一起执行,但不需要严格的回滚机制。
- 如初始化多个键的值。
乐观锁
- 配合 WATCH 实现分布式锁或竞态条件的保护。
Redis事务的局限性
单点失败
- 如果事务中的一个命令出错,不会回滚整个事务。
无隔离级别
- 事务执行过程中,其他客户端仍可以修改非监视的键。
不支持条件控制
- Redis 的事务中不允许包含条件判断(如 IF/ELSE)。
Redis Key过期删除策略
定时删除
- 在设置过期时间的同时,会创建一个计时器在过期时间来临时,立刻执行删除操作
惰性删除
- 客户端访问 Key 时,Redis 检查其是否过期
- 如果过期,如果过期,Redis 删除该 Key 并返回不存在。
- 优点:降低系统资源占用,按需检查。
- 缺点:可能存在过期 Key 占用内存的问题。
定期删除
- Redis 定期扫描一部分 Key,删除过期的 Key
- 默认每秒运行 10 次,每次随机抽取一定数量的 Key 进行检查。
- 优点:及时清理一部分过期数据。
- 缺点:无法保证所有过期 Key 都能被及时删除。
Redis Pipeline
Pipeline(管道)是一种优化技术,用于在客户端和服务器之间批量发送多个命令,从而减少网络通信开销,提高性能。通过 Pipeline,可以将多个 Redis 命令打包为一次请求发送到服务器,并一次性接收所有响应,避免每条命令的网络往返。
Pipeline 的工作原理
常规方式:
* 每次客户端发送一个命令,等待服务器响应,然后再发送下一个命令。 * 网络往返时间(RTT, Round-Trip Time)会成为性能瓶颈。
Pipeline 方式:
* 客户端将多个命令打包发送到 Redis 服务器。 * Redis 批量执行这些命令,并将结果一次性返回给客户端。 * 减少了多次网络往返,提高了吞吐量。
Pipeline 示例
public class RedisPipelineExample {
public static void main(String[] args){
Jedis jedis = new Jedis("loaclhost", 6379);
Pipeline pipe = jedis.pipelined();
for (int i = 0; i < 10000; i++) {
pipe.set("key"+i, "value"+i);
}
pipe.sync();
jedis.close();
}
}
Pipeline优势
减少网络开销
- 多个命令通过单次网络请求发送,避免每个命令的单独网络往返。
提高网络吞吐量
- 适合批量操作场景,例如批量写入数据、批量读取等。
降低延迟
- 减少 RTT 对性能的影响,适合高并发场景
Pipeline 使用注意事项
管道中的命令没有依赖关系:
- Pipeline 中的命令会被顺序执行,但结果返回时可能未同步,因此不能依赖前一个命令的结果。
内存占用:
- Pipeline 会在客户端缓存所有待发送的命令,若命令过多可能导致内存占用增加。
错误处理:
- Pipeline 中某些命令可能会失败,但其他命令不受影响。需要客户端按返回的结果手动处理。
Redis内存溢出
Redis内存溢出的常见原因
数据量过大
- 存储的数据超出了 Redis 分配的内存限制,导致无法继续存储新数据。
未设置内存上限
- 默认情况下,Redis 不限制内存使用量,可能会占用整个物理内存。
键过期未正确清理
- 大量带过期时间的键未被及时清理,导致内存占用增加
调用了慢查询或者大数据操作
- 例如执行 keys *、hgetall 等操作,可能消耗大量内存。
AOF或者ROB文件过大
- 持久化文件可能导致磁盘空间不足,间接影响 Redis 的运行。
数据结构未优化
- 使用了不合适的数据结构(如过大的列表、集合、哈希等)导致内存浪费。
如何解决 Redis 内存溢出
- 设置最大内存限制
通过 maxmemory 配置,限制 Redis 使用的最大内存 选择合适的内存淘汰策略
当达到内存上限时,Redis 会根据淘汰策略删除某些数据。常见策略包括:- noeviction:禁止写入新数据(默认)。
- allkeys-lru:从所有键中移除最近最少使用的键。
- volatile-lru:仅从设置了过期时间的键中移除最近最少使用的键
- allkeys-random:随机移除任意键
- volatile-random:随机移除有过期时间的键
- volatile-ttl:移除即将过期的键。
优化数据结构
- 避免写过大的键或者值
- 减少冗余数据,例如合并重复存储的字符串或对象。
- 对大数据进行分片处理,降低单个实例的内存压力。
- 定期清理数据
- 分布式存储
- AOF和RDB文件优化(降低写的频率)
内存溢出案例分析
场景 1:缓存穿透导致溢出
问题:大量不存在的 Key 请求,导致缓存无效而持续占用内存。
解决方法:使用布隆过滤器过滤无效请求。
场景 2:大键导致阻塞
问题:存储了一个包含百万条数据的列表,内存占用过大。
解决方法:分片存储或改用其他数据结构。
场景 3:长时间未清理的过期数据
问题:过期键占用大量内存,未被及时清理。
解决方法:启用 lazyfree-lazy-expire 配置或手动清理。
缓存预热
在系统上线前,提前将相关数据加载到缓存系统,避免用户先查库,再查询缓存
缓存雪崩
缓存雪崩是指一瞬间大量缓存过期或者失效,导致所有请求直接打到后端的数据库或者服务,造成系统压力过大,甚至出现崩溃的情况
大量缓存同时失效
- 缓存数据的过期时间设置不合理,导致某个时间点大批量缓存同时过期
缓存服务不可用
- Redis或者其他缓存服务出现宕机,所有请求涌入数据库
高并发场景
- 大量请求击穿缓存,而数据库无法承受高并发的压力
后端雪崩的影响
后段服务压力骤增
- 数据库、应用服务等后端系统可能因瞬间高并发请求超载。
服务不可用
- 数据库连接数耗尽,导致服务无法响应新请求,甚至出现宕机
用户体验差
- 响应时间变长或者直接返回错误,影响用户体验
解决缓存雪崩的方法
缓存时间分散化
- 设置缓存过期时间时,添加一个随机值,避免同一时间大量缓存失效。
缓存预热
- 在系统上线或重启前,提前将热点数据加载到缓存中,避免流量高峰时缓存未命中。
缓存服务高可用
部署缓存服务的高可用集群,避免因单点故障导致服务不可用:
- 使用 Redis 的主从架构或哨兵模式。
- 部署 Redis 集群,分担负载,提升可用性。
- 限流降级
二级缓存
在本地或中间件中增加一层缓存,缓解对数据库的直接压力:
- 例如使用 Guava Cache 做本地缓存。
- 缓存未命中时,再请求远程缓存或数据库。
分布式锁
- 当缓存过期时,通过分布式锁控制数据重建的请求数量,避免高并发重建缓存。
- 异步加载数据
Redis锁实现
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 获取分布式锁
RLock lock = redisson.getLock("lockKey");
try {
// 尝试加锁,等待最多100ms,上锁后保持10秒
if (lock.tryLock(100, 10, TimeUnit.SECONDS)) {
// 执行业务逻辑
System.out.println("Lock acquired!");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 解锁
}
Redis高可用方案
- 主从复制 (Master-Slave Replication)
原理
- 一个 Redis 主节点 (Master) 提供写操作。
- 多个从节点 (Slave) 通过复制主节点数据提供读操作。
- 如果主节点故障,需要手动切换一个从节点为主节点。
特点
- 提高读性能(读写分离)。
- 数据可靠性提升,但主节点宕机时服务不可用。
- 手动故障切换增加复杂性。
- Sentinel(哨兵模式)
原理
- 基于主从复制,增加了哨兵(Sentinel)进程。
- 哨兵监控主节点状态,发现主节点故障后自动选举一个从节点为主节点。
- 客户端连接的主节点信息由哨兵动态更新。
特点:
- 自动化主从切换,避免人工干预。
- 提供高可用保障,主节点宕机时服务可快速恢复。
- Sentinel 本身是分布式的,可以部署多个,保证其自身的高可用性。
- 优点:支持自动故障转移、部署简单。
- 缺点:需要更多的资源运行哨兵,无法保证强一致性。
- Cluster模式(Redis 集群)
原理:
- Redis 将数据分片到多个节点,每个节点存储不同的数据子集。
- 集群中的每个节点可以既是主节点又是从节点。
- 如果某个主节点宕机,对应的从节点自动升级为主节点。
特点:
- 水平扩展能力强:支持分布式数据存储。
- 高可用:单节点宕机不会影响整个集群。
- 自动故障转移。
- 数据通过一致性哈希分布,客户端需要支持 Cluster 协议。
- 优点:支持大规模数据存储,具备较高的可用性。
- 缺点:集群部署和管理复杂,客户端需支持 Cluster 模式。
- 双写主备(Active-Standby)
原理
- 两个Redis主节点之间通过主从复制互为备份。
- 客户端通过负载均衡切换到可用的主节点。
特点
- 提高了写性能。
- 容灾能力强,但需要解决双主节点数据冲突问题。
优缺点
- 优点:容灾能力较好。
- 缺点:数据冲突管理复杂,适合只写少量数据的场景。
- 持久化 + 主从复制/Cluster
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。