Redis是什么

Redis 是一个高性能的开源内存数据库,全称为 Remote Dictionary Server,以键值对(Key-Value)的形式存储数据。它支持多种数据结构、丰富的功能以及高效的操作,因此被广泛应用于缓存、消息队列、排行榜、会话管理等场景。

Redis特性

  1. 基于内存

    • 数据存储在内存里,读写速度极快
    • 支持将数据周期性地持久化到磁盘(RDB与AOD)
  2. 支持多种数据

    • 支持简单的键值对(String),哈希表(Hash),列表(List),集合(Set),有序集合(Sorted-Set)等
  3. 高性能

    • 每秒支持百万级的读写请求(单线程处,避免了线程切换的开销)
  4. 多种功能支持

    • 持久化:支持RDB快照和AOF日志两种方式将内存数据保存到磁盘上
    • 事务:支持简单的事务机制
    • 发布/订阅:支持消息的的订阅发布
    • Lua脚本:支持脚本操作,减少服务端与服务器之间的通信次数
    • 分布式:支持主从复制,哨兵模式和集群模式,轻松扩展
  5. 灵活的数据过期策略

    • 支持为键设置过期时间(TTL)

Redis的核心数据结构

  1. String(字符串)

    • 最基本的类型,常用于存储单个对象,计数器等
  2. Hash(哈希表)

    • 存储键值对的集合,类似于Map或字典
  3. List(列表)

    • 有序集合,可以从两端操作,适用于队列、栈等场景
  4. Set(集合)

    • 无序集合,自动去重,适用于校验唯一性
  5. SortedSet(有序集合)

    • 带权重的集合,支持按权重排序,适用于排行榜等场景
  6. Bitmap和HyperLogLog

    • Bitmap:用于高效存储和操作二进制数据。
    • HyperLogLog:适用于基数统计,如统计独立访问用户数。

Redis的应用场景

  1. 缓存:提高访问数据速度,减轻数据库压力
  2. 会话存储:存储用户的登录态(用户登录信息),支持高效读写和过期机制
  3. 排行榜:使用有序集合实现动态更新的排行榜
  4. 消息队列:利用List和Pub/Sub实现异步任务处理
  5. 分布式锁:通过键值对和过期时间实现可靠的分布式锁(如 Redisson 实现)
  6. 计数器:高效地实现访问量、点赞数等实时计数(因为支持原子操作)
  7. 全局唯一ID生成:使用 INCR 和 HyperLogLog 实现唯一性标识。

Redis和Memcache的区别

  1. 数据类型的支持上
  2. 持久化
  3. 内存管理
  4. 分布式的支持
  5. 多线程支持
  6. 性能对比
  7. 功能拓展
  8. 使用场景
  9. 协议
特性 Redis Memcache
数据结构支持 数据结构丰富 仅支持键值对
持久化 支持RDB和AOF 不能持久化
内存管理 自定义管理,多种淘汰策略 将内存划分为固定大小的块,以减少内存碎片,数据项大小受限(最大1M)
分布式支持 支持原生的Redis Cluster 不支持,需要客户端实现
多线程支持 部分多线程,但实际底层实现还是单线程 原生多线程
性能 高效,适合复杂场景 基于多线程,更快,适合简单场景
高级功能 发布/订阅,Lua脚本,Stream等 功能简单仅支持缓存

Redis为什么那么快?单线程

  1. 基于内存操作:

    • 所有数据都存储在内存上,访问数据很快
    • 读写操作远高于磁盘存储,减少了I/O操作的延迟
    • 单线程避免了线程的上下文切换的开销,使得操作更加高效
  2. 高效的事件机制

    • Redis使用I/O多路复用,基于epoll和Select,实现了高效的非阻塞I/O
    • 单线程避免了锁竞争的问题,操作简单且稳定
    • Redis将事件处理串行化,确保不会出现多线程并发冲突的问题
  3. 高效的底层实现

    • Redis 的核心是用 C 语言 编写的,执行效率非常高。
    • 使用了精心设计的数据结构,如压缩列表(ZipList)、整数集合(IntSet),在节省内存的同时提高了操作效率。
  4. 避免锁竞争
  5. 优化的协议与内存管理

    • Redis 使用了一种高效的通信协议 RESP(Redis Serialization Protocol),解析速度快,减少了数据传输中的开销。
    • 内存管理采用了高效的机制(如内存池、对象共享等),大幅减少了频繁的内存分配和回收操作
  6. 操作简单且快速

    • Redis 是一个 内存数据库,不涉及复杂的关系查询,所有操作都围绕数据结构展开,命令简单且执行速度快。
  7. 适合单线程的场景
  8. 减少上下文切换

Redis字符串

Redis字符串的最大长度 512MB

  • Redis 中的字符串类型的值可以存储最多 512 MB(512 1024 1024)的数据。
  • 这意味着可以存储很长的文本、二进制数据(如图片、音频文件等)。

字符串底层实现

  • 短字符串:简单动态字符串(SDS)

    • SDS 是 Redis 自定义的字符串实现,支持动态扩容和长度记录
    • 小于 39 字节的字符串使用 SDS 表示
  • 长字符串:直接用裸内存管理

    • 当字符串超过一定长度时,Redis 会切换为裸内存的方式进行管理

Redis默认分多少个数据库 16

数据库切换

  1. 默认使用 0 号数据库

    • Redis 在启动时,客户端连接的默认数据库是编号为 0 的数据库。
  2. 切换数据库命令:SELECT

    • 使用 SELECT 命令可以切换到指定编号的数据库。例如:

不同数据库的特点

  1. 逻辑隔离

    • 每个数据库是相互独立的,数据互不干扰。
    • 在一个数据库中存储的键值对,不能直接被另一个数据库访问。
  2. 连接共享

    • 所有数据库共享一个 Redis 实例的网络连接和内存。
  3. 使用场景

    • 一些简单场景中,可以用不同的数据库来存储不同的业务数据。例如,0 号数据库存储缓存数据,1 号数据库存储会话信息等

注意事项

  1. 集群模式下不支持多个数据库

    • 在 Redis 的集群模式中,每个节点只支持一个数据库,所有的键都存储在同一个数据库中。
  2. Redis 单实例性能瓶颈

    • Redis 虽然支持多个数据库,但这些数据库共享同一实例的资源,因此多个数据库会竞争同一个实例的 CPU 和内存。

Redis持久化

RDB持久化

原理:

  • RDB 持久化会在指定的时间间隔内,将内存中的数据生成快照(Snapshot)并保存到磁盘上。
  • 快照文件是一个二进制文件,默认名为 dump.rdb。

优点:

  1. 紧凑文件:RDB 文件为二进制格式,体积小,便于传输和备份。
  2. 快速恢复:RDB 文件在数据恢复时加载速度较快。
  3. 性能影响小:RDB 是间隔性操作,对 Redis 的性能影响较小。

缺点:

  1. 数据丢失的风险:因为 RDB 是周期性保存,故在最近一次快照与宕机之间的数据可能会丢失
  2. 适用性有限:不适用于对数据一致性要求非常高的场景。

配置:
在 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 文件中的日志命令,恢复数据

优点

  1. 数据安全性高:通过 fsync 策略,可以将数据丢失的可能性降到最低。
  2. 可读性强:AOF 文件是可读的日志格式,便于分析或修复。

缺点

  1. 文件体积较大:AOF 文件比 RDB 文件大得多。
  2. 恢复速度比较慢:重放日志的过程较耗时。
  3. 性能影响比较大:频繁的磁盘写操作可能影响 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事务的基本操作

事务的关键命令

  1. MUTLI:开启一个事务
  2. EXEC:提交事务,执行事务的所有命令
  3. DISCARD:取消事物,丢弃事务中的所有命令
  4. WATCH:监视一个或多个键,如果事务执行前这些键被其他客户端修改,事务会中止。
  5. UNWATCH:取消监视

事务的执行步骤

  1. 开始事务(MULTI)
  2. 输入需要执行的命令,这些命令放入事务队列
  3. 提交事物(EXEC),Redis 会顺序执行队列中的命令

事务的特点

  1. 无隔离级别

    • Redis 事务不支持传统关系型数据库的隔离级别。
    • 在事务执行前后,其他客户端可以继续操作键值。
  2. 事务中的命令不会部分回滚

    • 如果事务中的某条命令出错(如命令语法错误),错误会被记录,但事务中其他命令仍会执行。
    • Redis 不支持事务的部分回滚,如果一个命令失败,之前的命令不会回滚。
  3. 原子性

    • 每个事务内的命令会按顺序一次性执行,但不保证整体的原子性。

Redis事务的应用场景

  1. 简单的批量操作

    • 多个命令需要一起执行,但不需要严格的回滚机制。
    • 如初始化多个键的值。
  2. 乐观锁

    • 配合 WATCH 实现分布式锁或竞态条件的保护。

Redis事务的局限性

  1. 单点失败

    • 如果事务中的一个命令出错,不会回滚整个事务。
  2. 无隔离级别

    • 事务执行过程中,其他客户端仍可以修改非监视的键。
  3. 不支持条件控制

    • Redis 的事务中不允许包含条件判断(如 IF/ELSE)。

Redis Key过期删除策略

  1. 定时删除

    • 在设置过期时间的同时,会创建一个计时器在过期时间来临时,立刻执行删除操作
  2. 惰性删除

    • 客户端访问 Key 时,Redis 检查其是否过期
    • 如果过期,如果过期,Redis 删除该 Key 并返回不存在。
    • 优点:降低系统资源占用,按需检查。
    • 缺点:可能存在过期 Key 占用内存的问题。
  3. 定期删除

    • Redis 定期扫描一部分 Key,删除过期的 Key
    • 默认每秒运行 10 次,每次随机抽取一定数量的 Key 进行检查。
    • 优点:及时清理一部分过期数据。
    • 缺点:无法保证所有过期 Key 都能被及时删除。

Redis Pipeline

Pipeline(管道)是一种优化技术,用于在客户端和服务器之间批量发送多个命令,从而减少网络通信开销,提高性能。通过 Pipeline,可以将多个 Redis 命令打包为一次请求发送到服务器,并一次性接收所有响应,避免每条命令的网络往返。

Pipeline 的工作原理

  1. 常规方式:

    * 每次客户端发送一个命令,等待服务器响应,然后再发送下一个命令。
    * 网络往返时间(RTT, Round-Trip Time)会成为性能瓶颈。
  2. 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优势

  1. 减少网络开销

    • 多个命令通过单次网络请求发送,避免每个命令的单独网络往返。
  2. 提高网络吞吐量

    • 适合批量操作场景,例如批量写入数据、批量读取等。
  3. 降低延迟

    • 减少 RTT 对性能的影响,适合高并发场景

Pipeline 使用注意事项

  1. 管道中的命令没有依赖关系:

    • Pipeline 中的命令会被顺序执行,但结果返回时可能未同步,因此不能依赖前一个命令的结果。
  2. 内存占用:

    • Pipeline 会在客户端缓存所有待发送的命令,若命令过多可能导致内存占用增加。
  3. 错误处理:

    • Pipeline 中某些命令可能会失败,但其他命令不受影响。需要客户端按返回的结果手动处理。

Redis内存溢出

Redis内存溢出的常见原因

  1. 数据量过大

    • 存储的数据超出了 Redis 分配的内存限制,导致无法继续存储新数据。
  2. 未设置内存上限

    • 默认情况下,Redis 不限制内存使用量,可能会占用整个物理内存。
  3. 键过期未正确清理

    • 大量带过期时间的键未被及时清理,导致内存占用增加
  4. 调用了慢查询或者大数据操作

    • 例如执行 keys *、hgetall 等操作,可能消耗大量内存。
  5. AOF或者ROB文件过大

    • 持久化文件可能导致磁盘空间不足,间接影响 Redis 的运行。
  6. 数据结构未优化

    • 使用了不合适的数据结构(如过大的列表、集合、哈希等)导致内存浪费。

如何解决 Redis 内存溢出

  1. 设置最大内存限制
    通过 maxmemory 配置,限制 Redis 使用的最大内存
  2. 选择合适的内存淘汰策略
    当达到内存上限时,Redis 会根据淘汰策略删除某些数据。常见策略包括:

    • noeviction:禁止写入新数据(默认)。
    • allkeys-lru:从所有键中移除最近最少使用的键。
    • volatile-lru:仅从设置了过期时间的键中移除最近最少使用的键
    • allkeys-random:随机移除任意键
    • volatile-random:随机移除有过期时间的键
    • volatile-ttl:移除即将过期的键。
  3. 优化数据结构

    • 避免写过大的键或者值
    • 减少冗余数据,例如合并重复存储的字符串或对象。
    • 对大数据进行分片处理,降低单个实例的内存压力。
  4. 定期清理数据
  5. 分布式存储
  6. AOF和RDB文件优化(降低写的频率)

内存溢出案例分析

场景 1:缓存穿透导致溢出

问题:大量不存在的 Key 请求,导致缓存无效而持续占用内存。
解决方法:使用布隆过滤器过滤无效请求。

场景 2:大键导致阻塞

问题:存储了一个包含百万条数据的列表,内存占用过大。
解决方法:分片存储或改用其他数据结构。

场景 3:长时间未清理的过期数据

问题:过期键占用大量内存,未被及时清理。
解决方法:启用 lazyfree-lazy-expire 配置或手动清理。

缓存预热

在系统上线前,提前将相关数据加载到缓存系统,避免用户先查库,再查询缓存

缓存雪崩

缓存雪崩是指一瞬间大量缓存过期或者失效,导致所有请求直接打到后端的数据库或者服务,造成系统压力过大,甚至出现崩溃的情况

  1. 大量缓存同时失效

    • 缓存数据的过期时间设置不合理,导致某个时间点大批量缓存同时过期
  2. 缓存服务不可用

    • Redis或者其他缓存服务出现宕机,所有请求涌入数据库
  3. 高并发场景

    • 大量请求击穿缓存,而数据库无法承受高并发的压力

后端雪崩的影响

  1. 后段服务压力骤增

    • 数据库、应用服务等后端系统可能因瞬间高并发请求超载。
  2. 服务不可用

    • 数据库连接数耗尽,导致服务无法响应新请求,甚至出现宕机
  3. 用户体验差

    • 响应时间变长或者直接返回错误,影响用户体验

解决缓存雪崩的方法

  1. 缓存时间分散化

    • 设置缓存过期时间时,添加一个随机值,避免同一时间大量缓存失效。
  2. 缓存预热

    • 在系统上线或重启前,提前将热点数据加载到缓存中,避免流量高峰时缓存未命中。
  3. 缓存服务高可用

    • 部署缓存服务的高可用集群,避免因单点故障导致服务不可用:

      • 使用 Redis 的主从架构或哨兵模式。
      • 部署 Redis 集群,分担负载,提升可用性。
  4. 限流降级
  5. 二级缓存

    • 在本地或中间件中增加一层缓存,缓解对数据库的直接压力:

      • 例如使用 Guava Cache 做本地缓存。
      • 缓存未命中时,再请求远程缓存或数据库。
  6. 分布式锁

    • 当缓存过期时,通过分布式锁控制数据重建的请求数量,避免高并发重建缓存。
  7. 异步加载数据

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高可用方案

  1. 主从复制 (Master-Slave Replication)

原理

  • 一个 Redis 主节点 (Master) 提供写操作。
  • 多个从节点 (Slave) 通过复制主节点数据提供读操作。
  • 如果主节点故障,需要手动切换一个从节点为主节点。

特点

  • 提高读性能(读写分离)。
  • 数据可靠性提升,但主节点宕机时服务不可用。
  • 手动故障切换增加复杂性。
  1. Sentinel(哨兵模式)

原理

  • 基于主从复制,增加了哨兵(Sentinel)进程。
  • 哨兵监控主节点状态,发现主节点故障后自动选举一个从节点为主节点。
  • 客户端连接的主节点信息由哨兵动态更新。

特点:

  • 自动化主从切换,避免人工干预。
  • 提供高可用保障,主节点宕机时服务可快速恢复。
  • Sentinel 本身是分布式的,可以部署多个,保证其自身的高可用性。
  • 优点:支持自动故障转移、部署简单。
  • 缺点:需要更多的资源运行哨兵,无法保证强一致性。
  1. Cluster模式(Redis 集群)

原理:

  • Redis 将数据分片到多个节点,每个节点存储不同的数据子集。
  • 集群中的每个节点可以既是主节点又是从节点。
  • 如果某个主节点宕机,对应的从节点自动升级为主节点。

特点:

  • 水平扩展能力强:支持分布式数据存储。
  • 高可用:单节点宕机不会影响整个集群。
  • 自动故障转移。
  • 数据通过一致性哈希分布,客户端需要支持 Cluster 协议。
  • 优点:支持大规模数据存储,具备较高的可用性。
  • 缺点:集群部署和管理复杂,客户端需支持 Cluster 模式。
  1. 双写主备(Active-Standby)

原理

  • 两个Redis主节点之间通过主从复制互为备份。
  • 客户端通过负载均衡切换到可用的主节点。

特点

  • 提高了写性能。
  • 容灾能力强,但需要解决双主节点数据冲突问题。

优缺点

  • 优点:容灾能力较好。
  • 缺点:数据冲突管理复杂,适合只写少量数据的场景。
  1. 持久化 + 主从复制/Cluster

爱跑步的猕猴桃
1 声望0 粉丝