黑马程序员Redis教程学习笔记(第9部分):Redis底层数据结构(ZipList)与慢查询优化
课程概述
本篇笔记涵盖黑马程序员Redis教程中关于Redis底层数据结构ZipList(压缩列表)和慢查询优化的重要内容。主要包括ZipList的数据结构设计原理、内存布局、扩容机制,以及慢查询的识别、配置和优化策略。
1. ZipList(压缩列表)数据结构
1.1 设计背景与目的
Redis中的Dict(字典)底层采用哈希表实现,通过数组+链表解决哈希冲突。但在这种结构中,大量使用指针会导致:
- 内存浪费严重:每个指针占用8个字节,大量指针造成内存开销
- 内存不连续:分配的内存空间分散,容易产生内存碎片
- 寻址效率低:需要通过指针跳转访问数据
为了解决这些问题,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_lengths1.5 动态扩容机制
当插入新元素导致当前编码无法容纳时,ZipList会自动升级编码方式:
- 重新分配内存空间:根据新编码方式计算所需空间
- 倒序复制元素:从最后一个元素开始向前复制,避免数据覆盖
- 插入新元素:在正确位置插入新元素
升级示例:
初始状态:[5, 10, 20],使用INT16编码(每个元素2字节)
插入50000:超出INT16范围(32767),升级到INT32编码
升级后:[5, 10, 20, 50000],使用INT32编码(每个元素4字节)2. 慢查询问题分析与优化
2.1 慢查询定义
慢查询是指执行时间超过指定阈值的Redis命令。需要注意的是,这里的执行时间指的是命令执行时间,而不是网络传输时间。
2.2 慢查询危害
由于Redis是单线程执行命令,慢查询会带来以下问题:
- 阻塞主线程:慢查询会占用主线程较长时间,导致其他命令排队等待
- 性能下降:后续请求的响应时间变长,影响整体QPS
- 连接超时:排队命令可能因等待时间过长而超时
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 RESET2.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 数据结构选择优化
- 合理选择数据类型:根据业务场景选择合适的数据结构
- 避免Big Key:控制单个Key的大小和元素数量
- 使用Pipeline:批量操作时使用Pipeline减少网络往返
3.2 命令使用优化
- 避免全量操作:使用LIMIT限制结果集大小
- 使用合适命令:选择时间复杂度更低的命令
- 批量操作:使用MSET、MGET等批量命令
3.3 内存优化
- 设置合适的过期时间:避免内存持续增长
- 选择合适的淘汰策略:根据业务需求选择maxmemory-policy
- 监控内存使用:定期检查内存使用情况
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 慢查询监控
- 定期检查慢查询日志
- 设置告警阈值
- 分析慢查询模式
5.2 性能监控指标
- 慢查询数量:监控慢查询发生频率
- 平均执行时间:了解命令执行效率
- 内存使用率:监控内存占用情况
总结
Redis的ZipList和慢查询优化是性能调优的重要内容:
- ZipList设计精巧:通过连续内存存储和动态编码实现了内存优化
- 慢查询需重视:由于Redis单线程特性,慢查询对性能影响显著
- 预防胜于治疗:通过合理的数据结构选择和命令使用避免慢查询
- 监控不可少:建立完善的慢查询监控机制及时发现问题
理解这些底层原理和优化策略,有助于在实际项目中更好地使用Redis,确保系统的高性能和稳定性。