redis --- 相关基础知识整理

目录

    • 一、基本
      • 1、数据结构
      • 2、有序集合的编码
        • [1. 压缩列表(Ziplist)](#1. 压缩列表(Ziplist))
        • [2. 跳跃列表(SkipList)](#2. 跳跃列表(SkipList))
        • [3. 动态转换机制](#3. 动态转换机制)
    • 二、应用场景
    • 三、持久化
      • [1、 RDB 持久化](#1、 RDB 持久化)
      • [2、 AOF 持久化](#2、 AOF 持久化)
      • [3、 混合持久化(RDB + AOF)](#3、 混合持久化(RDB + AOF))
      • [4、 RDB和AOF的对比](#4、 RDB和AOF的对比)
      • [5、 默认持久化方式](#5、 默认持久化方式)
    • 五、删除策略
      • [1、 过期键的删除策略](#1、 过期键的删除策略)
        • [1.1 定时删除](#1.1 定时删除)
        • [1.2 惰性删除](#1.2 惰性删除)
        • [1.3 定期删除](#1.3 定期删除)
    • 六、淘汰策略
    • 七、redis4.0
      • [1、 模块系统(Module System)](#1、 模块系统(Module System))
      • [2、 改进的复制机制](#2、 改进的复制机制)
      • [3、 新的淘汰策略](#3、 新的淘汰策略)
      • [4、 非阻塞删除命令](#4、 非阻塞删除命令)
      • [5、 混合持久化格式](#5、 混合持久化格式)
      • [6、 新命令](#6、 新命令)
      • [7、 其他改进](#7、 其他改进)
    • 八、事务
      • [1、 事务的基本命令](#1、 事务的基本命令)
      • [2、 事务的原子性](#2、 事务的原子性)
    • 九、主从
    • 十、相关问答
      • 1、缓存穿透、缓存击穿、缓存雪崩
        • [1.1 缓存穿透](#1.1 缓存穿透)
        • [1.2 缓存击穿](#1.2 缓存击穿)
        • [1.3 缓存雪崩](#1.3 缓存雪崩)
      • 2、双写一致
        • [2.1 延时双删](#2.1 延时双删)
        • [2.2 分布式锁](#2.2 分布式锁)
        • [2.3 异步通知](#2.3 异步通知)
        • [2.4 版本号方案](#2.4 版本号方案)
        • [2.5 读写锁](#2.5 读写锁)
        • 总结
      • 3、redis为什么快
        • [3.1 基于内存的存储](#3.1 基于内存的存储)
        • [3.2 单线程模型](#3.2 单线程模型)
        • [3.3 命令管道化(Pipeline)](#3.3 命令管道化(Pipeline))
        • [3.4 高性能的网络处理](#3.4 高性能的网络处理)
    • 十一、redis和mongdb的比较
    • 十二、配置
      • [1、 通用](#1、 通用)
      • [2、 快照](#2、 快照)
      • [3、 主从复制](#3、 主从复制)

(格式md直接粘贴过来有点乱 有空一定改 需要md的可以留言)

一、基本

1、数据结构

数据类型 可以存储的值 操作 应用场景
STRING 字符串、整数或者浮点数 对整个字符串或者字符串的其中一部分执行操作 对整数和浮点数执行自增或者自减操作
LIST 列表 从两端压入或者弹出元素 对单个或者多个元素进行修剪, 只保留一个范围内的元素 实现消息队列、任务队列、聊天记录等
SET 无序集合 添加、获取、移除单个元素 检查一个元素是否存在于集合中 计算交集、并集、差集 从集合里面随机获取元素 存储不重复的元素集合,如用户关注列表、标签集合等
HASH 包含键值对的无序散列表 添加、获取、移除单个键值对 获取所有键值对 检查某个键是否存在 存储对象的多个属性,如用户信息(用户名、密码、邮箱等)
ZSET 有序集合 添加、获取、删除元素 根据分值范围或者成员来获取元素 计算一个键的排名 实现排行榜、优先级队列、时间线等
Bitmap 位图 一种基于字符串的二进制数据结构,通过位操作实现高效的存储和查询 用户在线状态、用户签到、统计用户行为、布隆过滤器等
HyperLogLog 超日志 用于统计基数(集合中不重复元素的数量)的近似算法,占用空间小,精度高 统计独立访客数量、不重复的用户行为、广告点击去重
Streams 日志结构的数据类型,支持消息的持久化、消费组和消息确认等特性 实现分布式消息队列、事件溯源、日志存储等
Geo 借助zset 存储和操作地理空间数据的类型 实现"附近的人"或"附近的餐厅"功能。 计算两点之间的距离。 地理围栏和实时位置追踪

2、有序集合的编码

Redis 的有序集合(Sorted Set,ZSet)底层实现主要依赖两种数据结构:压缩列表(Ziplist)跳跃列表(SkipList)。具体使用哪种结构取决于有序集合的大小和元素的特性。

1. 压缩列表(Ziplist)

每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。

当有序集合满足以下条件时,Redis 使用压缩列表作为底层实现:

  • 元素数量不超过 128 个;

  • 每个元素的成员长度小于 64 字节。

    以上两个条件也可以通过Redis配置文件zset-max-ziplist-entries 选项和 zset-max-ziplist-value 进行修改。

    压缩列表是一种内存高效的数据结构,通过紧凑的内存布局存储元素和分数。每个元素的成员和分数分别存储在相邻的节点中。

2. 跳跃列表(SkipList)

当有序集合不满足压缩列表的条件时,Redis 使用跳跃列表作为底层实现。跳跃列表是一种基于链表的多级索引结构,通过随机化的方式构建多级索引,从而实现快速查找、插入和删除操作。

跳跃列表的主要特点包括:

  • 多级索引 :通过多级索引加速查询,平均时间复杂度为 O(log N) ,最坏情况下为 O(N)
  • 双向指针:每一层不仅有向前指针,还有向后指针,便于快速回溯。
  • 随机化级别生成:随机函数在插入新节点时被用来决定节点是否升级到更高层级,以平衡空间开销和访问速度。

跳跃列表的实现包括一个字典和一个跳跃表:

  • 字典 : 字典的键保存元素的值,字典的值则保存元素的分值,用于快速查找成员对应的分数,时间复杂度为 O(1)
  • 跳跃表 :跳跃表节点的 object 属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值,用于按分数排序并支持范围操作,如 ZRANGEZREVRANGE

跳跃表和b+树对比

相对于链表结构的跳跃表,B+树在每个节点上存储的数据量更多,所以通常会占用更多的存储空间。如果查询效率是首要考虑的因素,并且可以承担更高的空间占用,可以选择B+树

3. 动态转换机制

Redis 根据有序集合的规模和元素特性动态选择底层数据结构:

  • 当有序集合的元素数量或成员长度超出压缩列表的限制时,自动转换为跳跃列表。
  • 这种机制确保了在不同场景下都能高效地存储和操作有序集合。

二、应用场景

1、分布式锁

2、事件发布/订阅机制

3、缓存

4、实现任务队列或消息传递系统

5、会话管理,过期失效

三、持久化

1、 RDB 持久化

原理

RDB 是通过将 Redis 内存中的数据以二进制快照的形式保存到磁盘上的 dump.rdb 文件中。可以通过配置文件设置快照的触发条件,例如每 5 分钟内至少有 1000 个键被修改。RDB 支持手动触发(SAVEBGSAVE 命令)和自动触发。

优点

  • 文件紧凑:RDB 文件是经过压缩的二进制格式,占用磁盘空间小。
  • 恢复速度快:加载 RDB 文件恢复数据的速度比 AOF 更快。
  • 对性能影响小 :通过 BGSAVE 命令,子进程负责生成快照,主进程继续处理客户端请求。

缺点

  • 数据丢失风险:由于是定期快照,最后一次快照之后的数据可能会丢失。
  • fork 操作开销 :在数据量较大时,fork 子进程可能会导致性能下降。

适用场景

适用于对数据一致性要求不高,但对恢复速度和磁盘空间占用敏感的场景。

2、 AOF 持久化

原理

AOF 持久化通过将 Redis 的所有写操作追加到 AOF 文件末尾,记录所有修改操作的详细记录。Redis 启动时,通过重放 AOF 文件中的命令来恢复数据。

优点

  • 高数据可靠性:AOF 可以保证最后一次写操作之前的数据不会丢失。
  • 细粒度恢复:支持更细粒度的数据恢复,适合需要高可靠性的场景。
  • 可读性强:AOF 文件是文本格式,易于阅读和修改。

缺点

  • 文件较大:AOF 文件记录所有写操作,因此文件大小通常比 RDB 文件大。
  • 性能负担:每次写操作都需要写入磁盘,可能导致较高的 I/O 负载。
  • 恢复速度慢:从 AOF 文件恢复数据需要逐条执行命令,速度较慢。

适用场景

适用于对数据一致性要求高,且写操作较少的场景。

AOF配置

使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。有以下同步选项:

选项 同步频率
always 每个写命令都同步
everysec 每秒同步一次
no 让操作系统来决定何时同步
  • always 选项会严重减低服务器的性能;
  • everysec 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;
  • no 选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量。

​ 通过配置redis.conf中的appendonly yes就可以打开AOF功能。如果有写操作(如SET等),redis就会被追加到AOF文件的末尾。

​ 默认的AOF持久化策略是每秒钟fsync一次(fsync是指把缓存中的写指令记录到磁盘中),因为在这种情况下,redis仍然可以保持很好的处理性能,即使redis故障,也只会丢失最近1秒钟的数据。

​ 如果在追加日志时,恰好遇到磁盘空间满、inode满或断电等情况导致日志写入不完整,也没有关系,redis提供了redis-check-aof工具,可以用来进行日志修复。

​ 因为采用了追加方式,如果不做任何处理的话,AOF文件会变得越来越大,为此,redis提供了AOF文件重写(rewrite)机制,即当AOF文件的大小超过所设定的阈值时,redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。举个例子或许更形象,假如我们调用了100次INCR指令,在AOF文件中就要存储100条指令,但这明显是很低效的,完全可以把这100条指令合并成一条SET指令,这就是重写机制的原理。

​ 在进行AOF重写时,仍然是采用先写临时文件,全部完成后再替换的流程,所以断电、磁盘满等问题都不会影响AOF文件的可用性

​ 直接执行BGREWRITEAOF命令,那么redis会生成一个全新的AOF文件,其中便包括了可以恢复现有数据的最少的命令集。。

3、 混合持久化(RDB + AOF)

原理

Redis 4.0 引入了混合持久化机制,结合了 RDB 和 AOF 的优点。在 AOF 重写时,将 Redis 的数据以 RDB 格式写入 AOF 文件的开头,后续数据以 AOF 格式追加到文件末尾。

两种方式可以同时使用,在这种情况下,如果redis重启的话,则会优先采用AOF方式来进行数据恢复,这是因为AOF方式的数据恢复完整度更高。

优点

  • 快速启动:由于开头为 RDB 格式,Redis 启动速度更快。
  • 降低数据丢失风险:结合 AOF 的优点,减少了大量数据丢失的风险。

缺点

  • 文件格式复杂:混合持久化的 AOF 文件可读性较差,且不向下兼容。

适用场景

适用于对数据恢复速度和数据完整性都有较高要求的场景。

4、 RDB和AOF的对比

5、 默认持久化方式

  • Redis 3.x 及以下版本:默认不启用任何持久化方式,需要手动配置。
  • Redis 4.0 及以上版本:默认启用 AOF 持久化(混合模式)。
  • Redis 6.x 及以上版本:默认启用 AOF 持久化(混合模式),并优化了性能和可靠性。

五、删除策略

Redis 的删除策略主要分为过期键的删除策略和内存淘汰策略。以下是详细说明:

1、 过期键的删除策略

Redis 使用以下三种策略来处理过期键:

1.1 定时删除
  • 原理:在设置键的过期时间时,创建一个定时事件。当时间到达时,自动执行删除操作。
  • 优点:对内存友好,过期键会尽快被删除,释放内存。
  • 缺点:对 CPU 不友好,可能会占用较多 CPU 时间。
1.2 惰性删除
  • 原理:不主动删除过期键,仅在访问键时检查是否过期,如果过期则删除。
  • 优点:对 CPU 友好,只有在访问时才会检查和删除过期键。
  • 缺点:对内存不友好,过期键可能长时间占用内存。
1.3 定期删除
  • 原理:每隔一段时间随机检查一定数量的键,删除其中的过期键。默认每秒检查 10 次(100ms 一次),每次检查 20 个键。
  • 优点:平衡了 CPU 和内存的使用,既不会占用过多 CPU,也能及时清理过期键。
  • 缺点:可能无法立即删除所有过期键。

Redis 实际上采用了 惰性删除 + 定期删除 的组合策略,以在 CPU 使用和内存清理之间取得平衡

六、淘汰策略

在redis中提供了两种数据过期删除策略:

  • 第一种是情性删除,在设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。
  • 第二种是定期删除,就是说每隔一段时间,我们就对一些key进行检查,删除里面过期的key

Redis的过期删除策略:惰性删除 + 定期删除 两种策略进行配合使用。

1、淘汰策略

可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略。

Redis 具体有 6 种淘汰策略:

策略 描述
volatile-lru 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-lfu 从已设置过期时间的数据集中淘汰访问频率最低的键(Redis 4.0)
volatile-ttl 从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random 从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru 从所有数据集中挑选最近最少使用的数据淘汰
allkeys-lfu 从所有键中淘汰访问频率最低的键 (Redis 4.0)
allkeys-random 从所有数据集中任意选择数据进行淘汰
noeviction 禁止驱逐数据

作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法实际实现上并非针对所有 key,而是抽样一小部分并且从中选出被淘汰的 key。

Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通过统计访问频率,将访问频率最少的键值对淘汰。

2、配置方法

  • 配置文件设置 :在 redis.conf 文件中使用 maxmemory-policy <policy> 配置淘汰策略。
  • 运行时设置 :使用 CONFIG SET maxmemory-policy <policy> 动态更改策略。
  • 命令行启动 :通过 redis-server --maxmemory-policy <policy> 设置

七、redis4.0

Redis 4.0 的首个正式版本(4.0.0)于 2017 年 7 月 14 日发布

1、 模块系统(Module System)

Redis 4.0 引入了模块系统,允许用户通过编写代码扩展 Redis 的功能。模块系统通过高级 API 实现,与 Redis 核心完全分离,不会干扰 Redis 的正常运行。例如,可以通过 loadmodule 指令在 redis.conf 文件中加载模块。

2、 改进的复制机制

Redis 4.0 对复制机制进行了优化,引入了部分复制(Partial Replication)功能。在旧版本中,从节点重新连接主节点时需要重新同步整个数据集,而 Redis 4.0 支持在条件允许的情况下使用部分复制,从而减少数据传输量。

3、 新的淘汰策略

Redis 4.0 引入了基于最近最少使用(LFU)的淘汰策略,即 allkeys-lfuvolatile-lfu。这些策略通过记录键的访问频率来决定哪些键应该被优先淘汰。

4、 非阻塞删除命令

Redis 4.0 引入了 UNLINK 命令,这是一个异步版本的 DEL 命令,可以在后台线程中删除键,避免阻塞主线程。此外,FLUSHDBFLUSHALL 命令也支持 ASYNC 选项,允许在后台线程中执行数据库清空操作。

5、 混合持久化格式

Redis 4.0 提供了一种新的混合持久化格式,结合了 RDB 和 AOF 的优点。启用该功能后,AOF 重写文件会包含 RDB 格式的数据和 AOF 格式的增量数据,从而实现快速加载和数据完整性。

6、 新命令

Redis 4.0 引入了多个新命令,例如 MEMORY 命令用于检查内存使用情况,SWAPDB 命令用于交换两个数据库。

7、 其他改进

  • 兼容性增强:Redis 4.0 改进了对 NAT 和 Docker 的支持,允许在这些环境下更好地运行。
  • 性能优化:对现有缓存淘汰策略进行了优化,使其更高效、更准确。

八、事务

Redis 的事务功能允许将一组命令打包并一次性顺序执行,确保这些命令在执行过程中不会被其他客户端的命令打断。以下是 Redis 事务的主要特性及其使用方式:

1、 事务的基本命令

Redis 提供了以下命令来控制事务的执行:

  • MULTI:开始一个事务,将后续命令放入事务队列。
  • EXEC:执行事务中的所有命令,并返回结果。
  • DISCARD:放弃事务队列中的所有命令。
  • WATCH:监控一个或多个键,如果这些键在事务执行前被修改,则事务会自动取消。

2、 事务的原子性

Redis 事务保证了事务中的所有命令都能被顺序执行,而不会被其他客户端的命令打断。这意味着事务中的命令要么全部执行,要么全部不执行,确保了操作的原子性。

九、主从

Redis 的主从架构是一种常见的部署方式,用于实现数据的冗余备份、读写分离以及高可用性。

在主从架构中,从服务器通常被设置为只读模式,这样可以避免从服务器的数据被误修改。但是从服务器仍然可以接受CONFIG等指令,所以还是不应该将从服务器直接暴露到不安全的网络环境中。如果必须如此,那可以考虑给重要指令进行重命名,来避免命令被外人误执行。

一个从服务器只能有一个主服务器,并且不支持主主复制。

  1. 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令;
  2. 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;
  3. 主服务器每执行一次写命令,就向从服务器发送相同的写命令。

1、核心概念

在 Redis 的主从架构中:

  • 主节点(Master) :主节点是数据的写入点,负责处理所有写操作(如 SETHSET 等)以及读操作。主节点会将数据同步到从节点。
  • 从节点(Slave):从节点是主节点的副本,用于读操作,也可以作为数据备份。从节点可以配置为只读模式,以防止数据被意外修改。

从节点通过与主节点建立连接,实时复制主节点的数据。如果主节点发生故障,从节点可以接管主节点的角色(需要结合 Redis Sentinel 或 Cluster 实现自动故障转移)。

2、主从复制的过程

  1. 从节点启动后,会向主节点发送 SLAVEOF 命令,请求成为主节点的从节点,然后发出SYNC
  2. 当主服务器接到此命令后,就会调用BGSAVE指令来创建一个子进程专门进行数据持久化工作,也就是将主服务器的数据写入RDB文件中。在数据持久化期间,主服务器将执行的写指令都缓存在内存中。
  3. BGSAVE指令执行完成后,主服务器会将持久化好的RDB文件发送给从服务器
  4. 从服务器接到此文件后会将其存储到磁盘上,然后再将其读取到内存中。这个动作完成后,主服务器会将这段时间缓存的写指令再以redis协议的格式发送给从服务器。

另外,要说的一点是,即使有多个从服务器同时发来SYNC指令,主服务器也只会执行一次BGSAVE,然后把持久化好的RDB文件发给多个下游。在redis2.8版本之前,如果从服务器与主服务器因某些原因断开连接的话,都会进行一次主从之间的全量的数据同步;而在2.8版本之后,redis支持了效率更高的增量同步策略,这大大降低了连接断开的恢复成本。

主服务器会在内存中维护一个缓冲区,缓冲区中存储着将要发给从服务器的内容。从服务器在与主服务器出现网络瞬断之后,从服务器会尝试再次与主服务器连接,一旦连接成功,从服务器就会把=="希望同步的主服务器ID"和"希望请求的数据的偏移位置(replication offset)"==发送出去。主服务器接收到这样的同步请求后,首先会验证主服务器ID是否和自己的ID匹配,其次会检查"请求的偏移位置"是否存在于自己的缓冲区中,如果两者都满足的话,主服务器就会向从服务器发送增量内容。

增量同步功能,需要服务器端支持全新的PSYNC指令。这个指令,只有在redis-2.8之后才具有。

3、故障转移

Redis 提供了哨兵(Sentinel)和集群(Cluster)两种机制来实现自动故障转移:

  • 哨兵(Sentinel):监控主从架构,当主节点故障时,自动将其中一个从节点提升为主节点。
  • 集群(Cluster):支持自动故障转移和数据分片,适用于大规模分布式环境。
3.1 哨兵(Sentinel)

Redis-Sentinel [ˈsentɪnl] (哨兵模式)是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自懂切换。

3.2 集群(Cluster)

Redis Cluster [ˈklʌstə®] 是Redis的分布式解决方案,在Redis 3.0版本正式推出的,有效解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构达到负载均衡的目的。

3.3 分片

分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,这种方法在解决某些问题时可以获得线性级别的性能提升。

假设有 4 个 Redis 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,... ,有不同的方式来选择一个指定的键存储在哪个实例中。

  • 最简单的方式是范围分片,例如用户 id 从 0~1000 的存储到实例 R0 中,用户 id 从 1001~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。
  • 还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。

根据执行分片的位置,可以分为三种分片方式:

  • 客户端分片:客户端使用一致性哈希等算法决定键应当分布到哪个节点。
  • 代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。
  • 服务器分片:Redis Cluster。

十、相关问答

1、缓存穿透、缓存击穿、缓存雪崩

1.1 缓存穿透

缓存穿透 :查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库

解决方案一:缓存空数据,查询返回的数据为空,仍把这个空结果进行缓存。{key:1, value:null}

  • 优点:简单
  • 缺点:消耗内存,可能会发生不一致的问题

解决方案二:布隆过滤器

  • 优点:内存占用较少,没有多余key
  • 缺点:实现复杂,存在误判
1.2 缓存击穿

缓存击穿:给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮

  • 解决方案一:互斥锁

    左图,线程1没有查完db前,其他线程只能查redis,不能查db

  • 解决方案二:逻辑过期

    右图,不能保证数据绝对一致

1.3 缓存雪崩

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

解决方案

  • 给不同的Key的TTL添加随机值
  • 利用Redis集群提高服务的可用性 哨兵模式、集群模式
  • 给缓存业务添加降级限流策略 nginx或spring cloud gateway
  • 给业务添加多级缓存 Guava或Caffeine

2、双写一致

Redis 与数据库(如 MySQL)的双写一致性问题是指在同时更新 Redis 缓存和数据库时,由于网络延迟、并发操作或异常情况,导致两者数据不一致的情况。这种不一致可能引发脏读、幻读等问题,影响系统的正确性和用户体验。

双写一致性问题的典型场景

  1. 写数据库后忘记更新缓存:导致后续读请求从缓存中获取旧数据。
  2. 删除缓存后数据库更新失败:缓存长时间处于空缺状态,加重数据库压力。
  3. 并发环境下读写操作交错执行:可能导致缓存与数据库数据不一致。
  4. 主从复制延迟与缓存失效时间窗口冲突:从库未同步完成时,缓存重新加载旧数据。

解决方案

2.1 延时双删
  • 流程:先删除缓存,再更新数据库,然后延时一段时间(如 1 秒)再次删除缓存。延时时间需根据读操作耗时调整。
  • 优点:简单易实现。
  • 缺点:延时期间可能出现脏数据,且第二次删除缓存可能失败。
2.2 分布式锁
  • 流程:在更新数据库前获取分布式锁,删除缓存,提交数据库事务,最后释放锁。
  • 优点:强一致性,适用于对一致性要求高的场景。
  • 缺点:性能较低,分布式锁实现复杂。
2.3 异步通知
  • 流程:通过消息队列(如 Kafka)或 Canal(读取 MySQL binlog)异步通知缓存更新。
  • 优点:对业务代码侵入性低,适合高并发场景。
  • 缺点:存在消息丢失或延迟的风险。
2.4 版本号方案
  • 流程:在数据库中增加版本号字段,每次更新数据时版本号加一,缓存中也存储版本号,仅在版本号匹配时更新。
  • 优点:能有效解决并发更新问题。
  • 缺点:需要维护版本号,实现复杂。
2.5 读写锁
  • 流程:使用读写锁控制缓存和数据库的读写操作,确保同一时间只有一个线程可以写入。
  • 优点:强一致性,适合读多写少的场景。
  • 缺点:写操作时会阻塞其他线程。
总结

双写一致性问题的解决方案各有优缺点,选择时需根据业务需求权衡:

  • 强一致性要求:优先选择分布式锁或读写锁。
  • 允许最终一致性:延时双删或异步通知是较好的选择。
  • 高并发场景 :异步通知或 Canal + MQ 方案更适合。

3、redis为什么快

3.1 基于内存的存储

Redis 将所有数据存储在内存中,而不是磁盘上。内存访问速度比磁盘(即使是 SSD)快几个数量级,这使得 Redis 的读写操作能够以极高的速度执行。例如:

  • 内存读写速度:内存的读写速度通常在纳秒级别(10^-9 秒)。
  • 磁盘读写速度:即使是 SSD,读写速度也在毫秒级别(10^-3 秒),比内存慢得多。
3.2 单线程模型

Redis 采用单线程模型,避免了多线程环境下的线程切换和锁竞争开销。虽然单线程模型在高并发写入时可能会成为瓶颈,但 Redis 通过优化命令执行效率和网络 I/O,确保了在大多数场景下的高性能。例如:

  • 命令执行效率:Redis 的命令执行非常快,通常在微秒级别。
  • 网络 I/O 优化:Redis 使用非阻塞 I/O 和事件驱动模型,能够高效处理大量的并发连接。

Redis 4.0 的核心仍然是单线程处理客户端请求,包括网络 I/O、命令解析、执行和响应返回等操作。Redis 4.0 引入了多线程支持,主要用于处理一些耗时的操作,例如异步删除和异步清空数据库。

Redis6.0中新增了多线程的功能来提高IO的读写性能,它的主要实现思路是将主线程的IO读写任务拆分给一组独立的线程去执行,这样就可以使用多个socket的读写并行化了,但Redis的命令依旧是主线程串行执行的。

但是注意:Redis6.0是默认禁用多线程的,但可以通过配置文件redis.conf中的io-threads-do-reads 等于 true 来开启。但是还不够,除此之外我们还需要设置线程的数量才能正确地开启多线程的功能,同样是修改Redis的配置,例如设置 io-threads 4,表示开启4个线程。

3.3 命令管道化(Pipeline)

Redis 支持命令管道化,允许客户端将多个命令打包在一起发送给服务器,从而减少网络往返次数。这种方法可以显著提高性能,尤其是在高延迟网络环境下。例如:

  • 普通模式:每个命令都需要一次网络往返。
  • 管道化模式:多个命令一起发送,减少网络延迟的影响。
3.4 高性能的网络处理

Redis 使用高效的网络 I/O 模型(基于 epollkqueue),能够同时处理大量的并发连接。例如:

  • 事件驱动:Redis 使用事件驱动模型,通过回调函数处理网络事件,避免了阻塞操作。
  • 非阻塞 I/O:Redis 的网络操作是非阻塞的,能够快速响应客户端请求。

十一、redis和mongdb的比较

MongoDB和Redis都是NoSQL,采用结构型数据存储。二者在使用场景中,存在一定的区别,这也主要由于二者在内存映射的处理过程,持久化的处理方法不同。MongoDB建议集群部署,更多的考虑到集群方案,Redis更偏重于进程顺序写入,虽然支持集群,也仅限于主-从模式。

指标 MongoDB(v2.4.9) Redis(v2.4.17) 比较说明
实现语言 C++ C/C++ -
协议 BSON、自定义二进制 类Telnet -
性能 依赖内存,TPS较高 依赖内存,TPS非常高 Redis优于MongoDB
可操作性 丰富的数据表达、索引;最类似于关系数据库,支持丰富的查询语言 数据丰富,较少的IO MongoDB优于Redis
内存及存储 适合大数据量存储,依赖系统虚拟内存管理,采用镜像文件存储;内存占有率比较高,官方建议独立部署在64位系统(32位有最大2.5G文件限制,64位没有改限制) Redis2.0后增加虚拟内存特性,突破物理内存限制;数据可以设置时效性,类似于memcache 不同的应用角度看,各有优势
可用性 支持master-slave,replicaset(内部采用paxos选举算法,自动故障恢复),auto sharding机制,对客户端屏蔽了故障转移和切分机制 依赖客户端来实现分布式读写;主从复制时,每次从节点重新连接主节点都要依赖整个快照,无增量复制;不支持自动sharding,需要依赖程序设定一致hash机制 MongoDB优于Redis;单点问题上,MongoDB应用简单,相对用户透明,Redis比较复杂,需要客户端主动解决。(MongoDB 一般会使用replica sets和sharding功能结合,replica sets侧重高可用性及高可靠性,而sharding侧重于性能、易扩展)
可靠性 从1.8版本后,采用binlog方式(MySQL同样采用该方式)支持持久化,增加可靠性 依赖快照进行持久化;AOF增强可靠性;增强可靠性的同时,影响访问性能 MongoDB优于Redis
一致性 不支持事物,靠客户端自身保证 支持事物,比较弱,仅能保证事物中的操作按顺序执行 Redis优于MongoDB
数据分析 内置数据分析功能(mapreduce) 不支持 MongoDB优于Redis
应用场景 海量数据的访问效率提升 较小数据量的性能及运算 MongoDB优于Redis

十二、配置

redis配置文件被分成了几大块区域,一共100多行,它们分别是:

1.通用(general)

2.快照(snapshotting)

3.复制(replication)

4.安全(security)

5.限制(limits)

6.追加模式(append only mode)

7.LUA脚本(lua scripting)

8.慢日志(slow log)

9.事件通知(event notification)

1、 通用

默认情况下,redis并不是以daemon形式来运行的。通过daemonize配置项可以控制redis的运行形式,如果改为yes,那么redis就会以daemon形式运行:

daemonize no

当以daemon形式运行时,redis会生成一个pid文件,默认会生成在/var/run/redis.pid。当然,你可以通过pidfile来指定pid文件生成的位置,比如:

pidfile /path/to/redis.pid

默认情况下,redis会响应本机所有可用网卡的连接请求。当然,redis允许你通过bind配置项来指定要绑定的IP,比如:

bind 192.168.1.2 10.8.4.2

redis的默认服务端口是6379,你可以通过port配置项来修改。如果端口设置为0的话,redis便不会监听端口了。

port 6379

"如果redis不监听端口,还怎么与外界通信呢",其实redis还支持通过unix socket方式来接收请求。可以通过unixsocket配置项来指定unix socket文件的路径,并通过unixsocketperm来指定文件的权限。

unixsocket /tmp/redis.sock
unixsocketperm 755

当一个redis-client一直没有请求发向server端,那么server端有权主动关闭这个连接,可以通过timeout来设置"空闲超时时限",0表示永不关闭。

timeout 0

TCP连接保活策略,可以通过tcp-keepalive配置项来进行设置,单位为秒,假如设置为60秒,则server端会每60秒向连接空闲的客户端发起一次ACK请求,以检查客户端是否已经挂掉,对于无响应的客户端则会关闭其连接。所以关闭一个连接最长需要120秒的时间。如果设置为0,则不会进行保活检测。

tcp-keepalive 0

redis支持通过loglevel配置项设置日志等级,共分四级,即debug、verbose、notice、warning。

loglevel notice

redis也支持通过logfile配置项来设置日志文件的生成位置。如果设置为空字符串,则redis会将日志输出到标准输出。假如你在daemon情况下将日志设置为输出到标准输出,则日志会被写到/dev/null中。

logfile ""

如果希望日志打印到syslog中,也很容易,通过syslog-enabled来控制。另外,syslog-ident还可以让你指定syslog里的日志标志,比如:

syslog-ident redis

而且还支持指定syslog设备,值可以是USER或LOCAL0-LOCAL7。具体可以参考syslog服务本身的用法。

syslog-facility local0

对于redis来说,可以设置其数据库的总数量,假如你希望一个redis包含16个数据库,那么设置如下:

databases 16

这16个数据库的编号将是0到15。默认的数据库是编号为0的数据库。用户可以使用select 来选择相应的数据库。

2、 快照

主要涉及的是redis的RDB持久化相关的配置

save <seconds> <changes>

举例来说:

save 900 1 //表示每15分钟且至少有1个key改变,就触发一次持久化
save 300 10 //表示每5分钟且至少有10个key改变,就触发一次持久化

如果你想禁用RDB持久化的策略,只要不设置任何save指令就可以,或者给save传入一个空字符串参数也可以达到相同效果,就像这样:

save ""

如果用户开启了RDB快照功能,那么在redis持久化数据到磁盘时如果出现失败,默认情况下,redis会停止接受所有的写请求。这样做的好处在于可以让用户很明确的知道内存中的数据和磁盘上的数据已经存在不一致了。如果redis不顾这种不一致,一意孤行的继续接收写请求,就可能会引起一些灾难性的后果。

如果下一次RDB持久化成功,redis会自动恢复接受写请求。

当然,如果你不在乎这种数据不一致或者有其他的手段发现和控制这种不一致的话,你完全可以关闭这个功能,以便在快照写入失败时,也能确保redis继续接受新的写请求。配置项如下:

stop-writes-on-bgsave-error yes

对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大。

rdbcompression yes

在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果你希望获取到最大的性能提升,可以关闭此功能。

rdbchecksum yes

我们还可以设置快照文件的名称,默认是这样配置的:

dbfilename dump.rdb

最后,你还可以设置这个快照文件存放的路径。比如默认设置就是当前文件夹:

dir ./

3、 主从复制

通过slaveof配置项可以控制某一个redis作为另一个redis的从服务器,通过指定IP和端口来定位到主redis的位置。一般情况下,我们会建议用户为从redis设置一个不同频率的快照持久化的周期,或者为从redis配置一个不同的服务端口等等。

slaveof <masterip> <masterport>

如果主redis设置了验证密码的话(使用requirepass来设置),则在从redis的配置中要使用masterauth来设置校验密码,否则的话,主redis会拒绝从redis的访问请求。

masterauth <master-password>

当从redis失去了与主redis的连接,或者主从同步正在进行中时,redis该如何处理外部发来的访问请求呢?这里,从redis可以有两种选择:

第一种选择:如果slave-serve-stale-data设置为yes(默认),则从redis仍会继续响应客户端的读写请求。

第二种选择:如果slave-serve-stale-data设置为no,则从redis会对客户端的请求返回"SYNC with master in progress",当然也有例外,当客户端发来INFO请求和SLAVEOF请求,从redis还是会进行处理。

你可以控制一个从redis是否可以接受写请求。将数据直接写入从redis,一般只适用于那些生命周期非常短的数据,因为在主从同步时,这些临时数据就会被清理掉。自从redis2.6版本之后,默认从redis为只读。

slave-read-only yes

只读的从redis并不适合直接暴露给不可信的客户端。为了尽量降低风险,可以使用rename-command指令来将一些可能有破坏力的命令重命名,避免外部直接调用。比如:

rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52

从redis会周期性的向redis发出PING包。你可以通过repl_ping_slave_period指令来控制其周期。默认是10秒。

repl-ping-slave-period 10

部分参考链接:https://cloud.tencent.com/developer/article/2436822

应该还有很多,但是当年没记,忘了...

如有错误,千万指出,感谢!

相关推荐
m0_748250931 小时前
SQL Server Management Studio的使用
数据库·oracle·性能优化
车载诊断技术1 小时前
人工智能AI在汽车设计领域的应用探索
数据库·人工智能·网络协议·架构·汽车·是诊断功能配置的核心
没有十八岁1 小时前
云创智城YunCharge 新能源二轮、四轮充电解决方案(云快充、万马爱充、中电联、OCPP1.6J等多个私有单车、汽车充电协议)之新能源充电行业系统说明书
java·数据库·spring·汽车
pitt19971 小时前
Redis 高可用性:如何让你的缓存一直在线,稳定运行?
redis·redis集群·哨兵·redis主从·redis高可用
工一木子1 小时前
【缓存】缓存雪崩与缓存穿透:高并发系统的隐形杀手
缓存·高并发·缓存穿透·缓存雪崩
爱搞技术的猫猫2 小时前
微店商品详情API接口实战指南:从零实现商品数据自动化获取
大数据·linux·运维·数据库·自动化
大地爱2 小时前
如何使用Spring Boot框架整合Redis:超详细案例教程
spring boot·redis·后端
若云止水3 小时前
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_init_cycle 函数 - 详解(1)
数据库·nginx·ubuntu
WannaRunning4 小时前
MySQL中的共享锁和排他锁
数据库·mysql
lusklusklusk4 小时前
Sqlserver安全篇之_启用TLS即配置SQL Server 数据库引擎以加密连接
数据库·安全·sqlserver