0%

Redis 事务详解(基于 6.0.10 版本)

Redis 事务是一组命令的集合,通过序列化执行机制保证命令的原子性执行(非传统 ACID 原子性),适用于需要批量执行命令且不被中断的场景。本文将深入解析 Redis 事务的特性、命令流程及实际应用。

事务的核心特性

Redis 事务基于单线程架构设计,与传统数据库事务(如 MySQL)有显著区别:

  1. 串行化执行
    事务中的所有命令会按顺序排队,在 EXEC 执行期间,Redis 不会插入其他客户端的命令,保证事务的独占性。
  2. 无隔离级别
    事务提交前,所有命令仅入队不执行,因此:
    • 事务内的查询无法看到事务内其他命令的修改(未执行)。
    • 事务外的查询也无法看到事务内的未执行命令,不存在 “脏读”“不可重复读” 等问题。
  3. 不保证原子性
    • 若事务内存在语法错误(如命令拼写错误),EXEC 会拒绝执行所有命令。
    • 若事务内存在运行时错误(如对字符串执行 INCR),Redis 会继续执行其他命令,仅返回该命令的错误,不支持回滚。

事务命令详解

命令 作用
MULTI 标记事务开始,后续命令进入队列(返回 QUEUED)。
EXEC 执行队列中的所有命令,返回结果列表(若事务被中断,返回 nil)。
DISCARD 取消事务,清空队列(需在 EXEC 前执行)。
WATCH key [key...] 监视键,若事务执行前键被修改,事务会被中断(乐观锁机制)。
UNWATCH 取消所有监视的键(EXECDISCARD 后会自动执行)。

事务执行流程

1. 正常执行

阅读全文 »

Redis 数据结构详解(基于 6.0.10 版本)

Redis 作为高性能键值数据库,支持多种丰富的数据结构,每种结构都有独特的底层实现和适用场景。本文从底层编码、核心命令到应用场景,全面解析 Redis 的 8 种核心数据结构:String、List、Set、Hash、Zset、Bitmaps、HyperLogLogs 和 GEO。

Redis 对象模型(redisObject)

Redis 中所有值都会被包装为 redisObject 结构体,通过 type 标识数据类型,encoding 指定底层存储方式,实现灵活的内存优化:

1
2
3
4
5
6
7
typedef struct redisObject {
unsigned type:4; // 数据类型(String/List/Set等)
unsigned encoding:4; // 编码方式(如int/ziplist/skiplist等)
unsigned lru:LRU_BITS; // 最后访问时间(用于LRU淘汰)
int refcount; // 引用计数(内存回收)
void *ptr; // 指向底层数据结构的指针
} robj;
  • type:通过 type key 命令查看,如 stringlist 等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // string
    #define OBJ_STRING 0 /* String object. */
    // list
    #define OBJ_LIST 1 /* List object. */
    // set
    #define OBJ_SET 2 /* Set object. */
    //zset
    #define OBJ_ZSET 3 /* Sorted set object. */
    //hash
    #define OBJ_HASH 4 /* Hash object. */
    // module
    #define OBJ_MODULE 5 /* Module object. */
    // stream
    #define OBJ_STREAM 6 /* Stream object. */
  • encoding:通过 object encoding key 命令查看,决定底层实现(如 String 的 int 编码 vs raw 编码)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 简单动态字符串  raw
    #define OBJ_ENCODING_RAW 0 /* Raw representation */
    // long类型的整数 int
    #define OBJ_ENCODING_INT 1 /* Encoded as integer */
    // 哈希表 hashtable
    #define OBJ_ENCODING_HT 2 /* Encoded as hash table */
    // 压缩 不再使用
    #define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
    // 双向链表 不再使用该结构
    #define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
    // 压缩列表 ziplist
    #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
    // 整数集合 intset
    #define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
    // 跳表 skiplist
    #define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
    // embstr编码的简单动态字符串 embstr
    #define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
    // 快表 quicklist
    #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
    // 流 stream
    #define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

核心数据结构详解

1. String(字符串)

特点:单键单值,可变字节数组,支持字符串、整数、二进制数据(最大 512MB)。
底层编码

  • int:存储 64 位整数(如 set num 123)。
  • embstr:存储短字符串(≤44 字节),连续内存分配(redisObject + SDS 同块内存)。
  • raw:存储长字符串(>44 字节),独立内存分配(redisObject 和 SDS 分开)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
127.0.0.1:6379> set test_int 1
OK
127.0.0.1:6379> object encoding test_int
"int"
127.0.0.1:6379> set test_raw zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
OK
127.0.0.1:6379> strlen test_raw
(integer) 44
127.0.0.1:6379> object encoding test_raw
"embstr"
127.0.0.1:6379> set test_raw zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
OK
127.0.0.1:6379> object encoding test_raw
"raw"
127.0.0.1:6379> strlen test_raw
(integer) 45

# 查看key的内部结构和编码等信息
>debug object k4
Value at:0x7ffc97c044f0 refcount:1 encoding:embstr serializedlength:3 lru:11351195 lru_seconds_idle:5363

embstr和raw两种编码的区别

embstr编码用于保存短字符串,与raw编码一样,都是使用redisObject结构和sdshdr结构来表示字符串对象,但是raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,两个对象在内存地址上是不连续的;而embstr编码则通过调用一次内存分配函数来分配一块连续的内存空间,空间中依次包含redisObject和sdshdr两个结构

底层实现:基于 SDS(简单动态字符串),记录长度(len)和容量(alloc),避免 C 字符串的缺陷(如 O (n) 长度计算):

1
2
3
4
5
6
struct sdshdr8 {  // 8位长度示例
uint8_t len; // 已使用长度
uint8_t alloc; // 总容量(不含头部和终止符)
unsigned char flags; // 类型标识
char buf[]; // 字符串数据
};

扩容:当字符串长度小于1M时,扩容是现有空间进行加倍;当字符串长度超过1M,扩容只会多扩1M的空间。且字符串最长为512M

核心命令

  • 设值:set key valuesetex key 3600 val(过期时间)、setnx key val(不存在时设值)。
  • 取值:get keymget key1 key2(批量获取)。
  • 修改:append key suffix(追加)、setrange key 0 "new"(替换部分字符)。
  • 计数:incr key(自增)、decrby key 5(减 5)。
阅读全文 »

Vim 编辑器完全指南:从入门到高效操作

Vim 是 Linux 系统中最强大的文本编辑器之一,以其高效的操作模式和丰富的功能著称。掌握 Vim 的核心在于理解其三种工作模式(命令模式、插入模式、末行模式)及模式间的切换逻辑。本文将系统讲解 Vim 的使用技巧,帮助你从入门到精通。

Vim 三种核心模式

Vim 的强大之处在于其模式化设计,不同模式下的操作逻辑不同,熟练切换是高效使用的基础:

模式 进入方式 主要功能 退出方式(进入命令模式)
命令模式 启动 Vim 后默认进入 光标移动、文本删除 / 复制 / 粘贴 无需退出(默认模式)
插入模式 命令模式下按 i/a/o 等键 输入文本 Esc
末行模式 命令模式下按 : 保存 / 退出、查找替换、配置等 Esc 键或执行命令后自动返回

命令模式:高效操作的核心

命令模式是 Vim 的 “指挥中心”,几乎所有非输入操作都在此完成。以下是常用操作分类:

1. 光标移动

(1)基础移动(单字符 / 行)
  • h:向左移动 1 字符
  • l:向右移动 1 字符
  • j:向下移动 1 行
  • k:向上移动 1 行
  • 0(数字 0):跳转到行首第一个字符
  • $:跳转到行尾最后一个字符
(2)快速移动(单词 / 段落)
  • w:向后移动 1 个单词(以空格 / 标点分隔)
  • b:向前移动 1 个单词
  • ^:跳转到行首第一个非空白字符
  • gg:跳转到文件首行
  • G(Shift+g):跳转到文件尾行
  • nG(如 5G):跳转到第 n 行
  • Ctrl+f:向下翻一页
  • Ctrl+b:向上翻一页
  • Ctrl+d:向下翻半页
  • Ctrl+u:向上翻半页
(3)屏幕定位
  • H:将光标移到当前屏幕顶部
  • M:将光标移到当前屏幕中间
  • L:将光标移到当前屏幕底部

2. 文本删除

阅读全文 »

Redis 主从复制详解(基于 6.0.10 版本)

Redis 主从复制(Master-Slave Replication)是实现高可用和读写分离的核心机制,通过将主节点(Master)的数据同步到从节点(Slave),避免单点故障并分担读压力。本文详细解析主从复制的原理、配置方式、常见模式及哨兵(Sentinel)自动故障转移机制。

主从复制核心原理

主从复制通过 PSYNC 命令 实现数据同步,支持全量同步和部分同步两种模式,兼顾初次复制和断线重连场景。

PSYNC 命令的两种模式

  • 全量同步(Full Resync):适用于初次复制(Slave 首次连接 Master)。
    流程:
    1. Slave 向 Master 发送 PSYNC ? -1(表示需要全量同步)。
    2. Master 生成 RDB 快照,发送给 Slave;同时缓存快照生成期间的写命令(复制积压缓冲区)。
    3. Slave 加载 RDB 快照,再执行缓存的写命令,最终与 Master 数据一致。
  • 部分同步(Partial Resync):适用于断线重连(Slave 短暂断开后重新连接)。
    流程:
    1. Slave 重连后,向 Master 发送 PSYNC <master_runid> <offset>(携带上次同步的 Master 标识和偏移量)。
    2. Master 验证master_runid(自身标识)和offset(同步偏移量):
      • 若有效(偏移量在复制积压缓冲区内),仅发送偏移量后的增量写命令。
      • 若无效(如 Master 重启或偏移量过期),触发全量同步。

核心概念

  • master_runid:Master 的唯一标识(每次重启会变化),用于 Slave 验证连接的是否为同一 Master。
  • 复制偏移量(Offset):Master 和 Slave 分别维护的计数器,记录已同步的字节数(确保数据一致性)。
  • 复制积压缓冲区:Master 端的环形缓冲区(默认 1MB),存储最近的写命令,用于部分同步。可通过 repl-backlog-size 调整大小(如 repl-backlog-size 10mb)。

主从复制配置

主从复制采用 “配从不配主” 原则:只需配置 Slave 指向 Master,Master 无需额外配置。

阅读全文 »

Redis 过期删除与内存淘汰机制详解(基于 6.0.10 版本)

Redis 允许为键设置过期时间,但如何高效处理过期键、避免内存溢出,是其性能优化的核心问题。不同于简单的定时删除,Redis 采用定期删除 + 惰性删除的混合策略处理过期键,并在内存不足时通过淘汰机制释放空间。本文详细解析这两种机制的原理、配置及实践建议。

过期键的删除策略

Redis 未采用 “每个过期键对应一个定时器” 的定时删除策略(避免 CPU 资源耗尽),而是结合定期删除惰性删除,在内存占用与 CPU 消耗之间取得平衡。

1. 惰性删除(Lazy Eviction)

  • 核心逻辑:键过期后不主动删除,仅在被访问时(如 gethget 等命令)才检查是否过期。若过期,则删除该键并返回空;若未过期,则正常返回值。
  • 优势:无需额外 CPU 资源监控过期键,仅在必要时执行删除,适合低频访问的过期键。
  • 劣势:若过期键长期未被访问,会占用内存(“内存泄漏” 风险),需配合定期删除弥补。

2. 定期删除(Periodic Eviction)

  • 核心逻辑:Redis 每隔一段时间(由hz配置控制,默认每秒 10 次)主动扫描部分过期键并删除,具体步骤:
    1. 从 “过期键字典”(专门存储设置了过期时间的键)中随机抽取 20 个键
    2. 删除这 20 个键中已过期的键。
    3. 若过期键占比超过 25%,重复步骤 1(继续抽取删除),直至占比低于 25% 或达到最大扫描时间(默认 25ms)。
  • 配置参数:
    • hz <num>:控制定期扫描的频率(默认 10,即每秒 10 次)。值越大,扫描越频繁,过期键删除越及时,但 CPU 消耗越高。
  • 优势:主动清理长期未访问的过期键,减少内存浪费。
  • 注意:为避免扫描耗时过长阻塞服务,单次扫描时间被限制在 25ms 内,因此无法保证所有过期键都被及时删除。

3. 主从结构中的过期键处理

在主从复制中,过期键的删除存在特殊逻辑:

  • 主服务器:采用上述 “定期 + 惰性” 策略正常删除过期键,并向从服务器发送 del 命令。
  • 从服务器:即使键已过期,也不主动删除,仅在收到主服务器的 del 命令后才删除。
  • 潜在问题:主从同步存在延迟时,从服务器可能返回已过期的键(主服务器已删除,但 del 命令未同步),导致短暂的数据不一致。

内存淘汰机制(Maxmemory Policy)

当 Redis 内存使用达到 maxmemory 配置的阈值时,会触发内存淘汰机制,主动删除部分键以释放空间。淘汰机制的核心是 “按规则筛选并删除键”,具体规则由 maxmemory-policy 配置。

阅读全文 »