黑马程序员Redis教程学习笔记(第9部分):Redis底层数据结构(ZipList)与慢查询优化

2026-04-02
4
-
- 分钟
|

黑马程序员Redis教程学习笔记(第9部分):Redis底层数据结构(ZipList)与慢查询优化

课程概述

本篇笔记涵盖黑马程序员Redis教程中关于Redis底层数据结构ZipList(压缩列表)和慢查询优化的重要内容。主要包括ZipList的数据结构设计原理、内存布局、扩容机制,以及慢查询的识别、配置和优化策略。

1. ZipList(压缩列表)数据结构

1.1 设计背景与目的

Redis中的Dict(字典)底层采用哈希表实现,通过数组+链表解决哈希冲突。但在这种结构中,大量使用指针会导致:

  1. 内存浪费严重:每个指针占用8个字节,大量指针造成内存开销
  2. 内存不连续:分配的内存空间分散,容易产生内存碎片
  3. 寻址效率低:需要通过指针跳转访问数据

为了解决这些问题,Redis设计了ZipList(压缩列表)这种数据结构,旨在节省内存并提供双端列表的特性。

1.2 ZipList结构详解

ZipList是一种特殊的数据结构,虽然被称为”列表”,但实际上并非传统意义上的链表。它具备双端列表的特性:

  • 从头部或尾部进行数据的插入和删除
  • 支持从头到尾或从尾到头的遍历
  • 操作时间复杂度性能良好

1.2.1 ZipList内存布局

[ zlbytes ][ zltail ][ zllen ][ entry1 ][ entry2 ]...[ entryN ][ zlend ]
  • zlbytes:记录整个压缩列表占用的字节数,用于快速定位整个列表的结束位置
  • zltail:记录最后一个entry相对于压缩列表起始地址的偏移量,用于快速定位尾节点
  • zllen:记录压缩列表中entry的个数,但最多只能存储16位(65535个)
  • entry:实际的数据节点,可以包含字符串或整数
  • zlend:结束标识,固定值0xFF,表示列表结束

1.2.2 Entry节点结构

每个entry(节点)包含三部分:

[ prevlen ][ encoding ][ content ]
  • prevlen:前一个节点的长度,用于反向遍历
  • encoding:编码属性,记录content的数据类型和长度
  • content:实际存储的数据

1.3 编码方式

ZipList支持两种数据类型的存储:

1.3.1 字符串编码

  • 00xxxxxx:前两位为00,后6位存储字符串长度(最大63字节)
  • 01xxxxxx xxxxxxxx:前两位为01,后14位存储字符串长度(最大16383字节)
  • 10xxxxxx xxxxxxxx xxxxxxxx xxxxxxxx:前两位为10,后30位存储长度(最大1GB),但需要额外4个字节存储长度值

1.3.2 整数编码

  • 11000000:16位有符号整数
  • 11010000:32位有符号整数
  • 11100000:64位有符号整数
  • 11110000:24位有符号整数
  • 11111110:8位有符号整数
  • 11111111:特殊标识,用于表示列表结束

1.4 内存布局与寻址

由于ZipList中每个entry的长度不固定,无法像数组那样通过索引直接计算地址。ZipList通过以下方式实现寻址:

  • 从前往后遍历:从起始地址开始,依次读取每个entry的长度,逐步向前移动指针
  • 从后往前遍历:利用zltail快速定位到尾节点,然后通过prevlen向前移动

寻址公式:

entry_addr = start_addr + offset
offset = sum_of_previous_entry_lengths

1.5 动态扩容机制

当插入新元素导致当前编码无法容纳时,ZipList会自动升级编码方式:

  1. 重新分配内存空间:根据新编码方式计算所需空间
  2. 倒序复制元素:从最后一个元素开始向前复制,避免数据覆盖
  3. 插入新元素:在正确位置插入新元素

升级示例:

初始状态:[5, 10, 20],使用INT16编码(每个元素2字节)
插入50000:超出INT16范围(32767),升级到INT32编码
升级后:[5, 10, 20, 50000],使用INT32编码(每个元素4字节)

2. 慢查询问题分析与优化

2.1 慢查询定义

慢查询是指执行时间超过指定阈值的Redis命令。需要注意的是,这里的执行时间指的是命令执行时间,而不是网络传输时间。

2.2 慢查询危害

由于Redis是单线程执行命令,慢查询会带来以下问题:

  1. 阻塞主线程:慢查询会占用主线程较长时间,导致其他命令排队等待
  2. 性能下降:后续请求的响应时间变长,影响整体QPS
  3. 连接超时:排队命令可能因等待时间过长而超时

2.3 慢查询配置

2.3.1 慢查询阈值配置

# 设置慢查询阈值(单位:微秒)
CONFIG SET slowlog-log-slower-than 1000

# 查看当前配置
CONFIG GET slowlog-log-slower-than

建议配置: - 默认值:10000微秒(10毫秒) - 推荐值:1000微秒(1毫秒)或更小 - 原因:Redis正常命令执行时间在几十微秒级别,超过1毫秒已属异常

2.3.2 慢查询日志长度配置

# 设置慢查询日志最大长度
CONFIG SET slowlog-max-len 128

# 查看当前配置
CONFIG GET slowlog-max-len

建议配置: - 默认值:128条 - 推荐值:根据实际情况调整,不宜过大以免占用过多内存

2.4 慢查询检测与分析

2.4.1 查看慢查询日志

# 查看所有慢查询
SLOWLOG GET

# 查看最近N条慢查询
SLOWLOG GET 10

# 查看慢查询日志长度
SLOWLOG LEN

# 清空慢查询日志
SLOWLOG RESET

2.4.2 慢查询日志字段说明

每条慢查询记录包含以下字段: - id:日志唯一标识 - timestamp:命令执行的时间戳 - duration:命令执行耗时(微秒) - command:执行的命令详情 - client:客户端IP和端口信息

2.5 常见慢查询原因及解决方案

2.5.1 危险命令使用

  • KEYS命令:全量遍历,时间复杂度O(N)
  • FLUSHDB/FLUSHALL:清空数据库
  • SMEMBERS:获取集合所有元素
  • ZRANGE/ZREVRANGE:获取有序集合范围元素(未使用LIMIT)

解决方案: - 使用SCAN命令替代KEYS进行遍历 - 使用LIMIT限制查询结果数量 - 避免使用危险命令

2.5.2 Big Key问题

  • 定义:占用内存过大或包含元素过多的Key
  • 危害:阻塞主线程,影响性能

检测方法

# 使用内置命令检测Big Key
redis-cli --bigkeys

# 手动检测
DEBUG OBJECT key_name

解决方案: - 将Big Key拆分为多个小Key - 选择合适的数据结构 - 设置合理的过期时间

2.5.3 复杂度较高的命令

  • 集合操作:SUNION、SINTER、SDIFF等
  • 排序操作:SORT命令
  • 模式匹配:KEYS、SCAN的复杂模式

3. 性能优化建议

3.1 数据结构选择优化

  1. 合理选择数据类型:根据业务场景选择合适的数据结构
  2. 避免Big Key:控制单个Key的大小和元素数量
  3. 使用Pipeline:批量操作时使用Pipeline减少网络往返

3.2 命令使用优化

  1. 避免全量操作:使用LIMIT限制结果集大小
  2. 使用合适命令:选择时间复杂度更低的命令
  3. 批量操作:使用MSET、MGET等批量命令

3.3 内存优化

  1. 设置合适的过期时间:避免内存持续增长
  2. 选择合适的淘汰策略:根据业务需求选择maxmemory-policy
  3. 监控内存使用:定期检查内存使用情况

4. 实践案例

4.1 ZipList应用场景

ZipList主要用于以下场景: - 小列表:元素数量少、元素大小小的列表 - 小哈希:字段数量少的小哈希结构 - 小集合:元素数量少的整数集合

当满足一定条件时,Redis会自动使用ZipList作为底层实现: - 列表:元素个数<512且元素长度<64字节 - 哈希:键值对个数<512且键值长度<64字节 - 集合:元素个数<512且全部为整数

4.2 慢查询优化实践

// Java代码示例:避免KEYS命令,使用SCAN
public List<String> scanKeys(Jedis jedis, String pattern) {
    List<String> keys = new ArrayList<>();
    String cursor = "0";
    ScanParams params = new ScanParams();
    params.match(pattern);
    params.count(1000);
    
    do {
        ScanResult<String> result = jedis.scan(cursor, params);
        keys.addAll(result.getResult());
        cursor = result.getStringCursor();
    } while (!"0".equals(cursor));
    
    return keys;
}

5. 监控与运维

5.1 慢查询监控

  1. 定期检查慢查询日志
  2. 设置告警阈值
  3. 分析慢查询模式

5.2 性能监控指标

  • 慢查询数量:监控慢查询发生频率
  • 平均执行时间:了解命令执行效率
  • 内存使用率:监控内存占用情况

总结

Redis的ZipList和慢查询优化是性能调优的重要内容:

  1. ZipList设计精巧:通过连续内存存储和动态编码实现了内存优化
  2. 慢查询需重视:由于Redis单线程特性,慢查询对性能影响显著
  3. 预防胜于治疗:通过合理的数据结构选择和命令使用避免慢查询
  4. 监控不可少:建立完善的慢查询监控机制及时发现问题

理解这些底层原理和优化策略,有助于在实际项目中更好地使用Redis,确保系统的高性能和稳定性。

评论交流

文章目录