一、慢查询到底是什么鬼?
大家应该都有过这样的体验:明明是个简单的查询操作,却莫名其妙卡了好几秒。这种情况在Redis里就叫"慢查询"。Redis默认会把执行时间超过10毫秒的命令记录下来,这个阈值可以通过slowlog-log-slower-than参数调整。
举个例子,假设我们有个用户积分排行榜:
# 技术栈:Redis
# 获取排行榜前100名(正常情况)
ZREVRANGE user:scores 0 99
# 危险操作:获取全部用户(当用户量很大时)
ZREVRANGE user:scores 0 -1
第二个命令在用户量达到百万级别时,可能会变成慢查询。Redis是单线程的,这种慢查询会阻塞其他命令的执行,就像高速公路上的车祸现场,后面所有车都得等着。
二、如何揪出这些慢查询?
Redis提供了非常方便的慢查询日志功能。我们来看看具体怎么操作:
# 查看当前慢查询配置
CONFIG GET slowlog*
# 设置记录慢查询的阈值(单位微秒,这里设置为5毫秒)
CONFIG SET slowlog-log-slower-than 5000
# 设置最多保存多少条慢查询记录
CONFIG SET slowlog-max-len 1000
# 查看慢查询日志
SLOWLOG GET 10 # 获取最近10条慢查询
慢查询日志会显示这些信息:
- 唯一ID
- 命令执行时间戳
- 执行耗时(微秒)
- 命令和参数
我曾经遇到过一个案例:某个O(N)复杂度的命令在大数据量下执行缓慢,通过慢查询日志很快定位到了问题。
三、常见慢查询的优化套路
3.1 大Key问题
大Key是指包含大量元素的复合类型Key,比如:
- 包含百万字段的Hash
- 超长List/Sorted Set
# 危险的大Key操作
HGETALL huge:hash # 获取包含百万字段的Hash所有内容
# 优化方案1:分批获取
HSCAN huge:hash 0 COUNT 100
# 优化方案2:考虑拆分数据结构
# 把 huge:hash 拆分成 hash:part1, hash:part2...
3.2 复杂度过高的命令
有些命令的时间复杂度是O(N),数据量大时就会很慢:
# 危险操作
KEYS * # O(N)复杂度,会扫描所有key
# 优化方案:使用SCAN命令
SCAN 0 MATCH "user:*" COUNT 100
3.3 不合理的数据结构选择
# 错误示范:用List存储需要频繁查询中间元素的数据
LPUSH mylist item1 item2 item3... # 插入很快
LINDEX mylist 50000 # 但查询中间元素很慢
# 优化方案:改用Sorted Set
ZADD myset 1 item1 2 item2 3 item3...
ZRANGE myset 50000 50000 # 查询效率更高
四、进阶优化技巧
4.1 使用Lua脚本减少网络开销
-- 技术栈:Redis + Lua
-- 统计某个Hash中值大于100的字段数量
local count = 0
local fields = redis.call('HKEYS', KEYS[1])
for _, field in ipairs(fields) do
local value = redis.call('HGET', KEYS[1], field)
if tonumber(value) > 100 then
count = count + 1
end
end
return count
这个脚本避免了多次网络往返,但要注意:
- 脚本不要太复杂
- 执行时间不能太长
4.2 Pipeline批量操作
# 技术栈:Redis
# 普通操作(多次网络往返)
SET key1 value1
SET key2 value2
SET key3 value3
# 使用Pipeline(一次网络往返)
MULTI
SET key1 value1
SET key2 value2
SET key3 value3
EXEC
4.3 合理使用持久化策略
# 如果对性能要求极高,可以考虑关闭持久化
CONFIG SET save "" # 关闭RDB
CONFIG SET appendonly no # 关闭AOF
# 但这样会牺牲数据安全性,建议根据业务需求权衡
五、实战案例分析
最近优化过一个电商平台的购物车系统,原始方案是这样的:
# 每个用户的购物车是一个Hash
HSET cart:user1 item1 1 item2 2 item3 5...
# 获取购物车所有商品
HGETALL cart:user1 # 当商品数量很多时变慢
优化方案:
- 拆分购物车数据,热门商品单独存储
- 使用SCAN代替HGETALL
- 对购物车商品数量设置上限
优化后,购物车查询速度从平均200ms降到了5ms以下。
六、监控与预警
除了被动查看慢查询日志,我们还应该主动监控:
# 使用redis-cli监控命令延迟
redis-cli --latency -h 127.0.0.1 -p 6379
# 使用INFO命令获取统计信息
redis-cli INFO stats | grep total_commands_processed
建议设置监控告警,当慢查询数量突增时及时通知。
七、总结与建议
经过上面的分析,我们可以得出几个关键结论:
- 预防胜于治疗:设计阶段就要考虑性能问题
- 监控不能少:建立完善的监控体系
- 优化要循序渐进:不要一次性做太多改动
- 测试很重要:任何优化都要经过充分测试
最后记住:Redis优化没有银弹,要根据具体业务场景选择最合适的方案。有时候最简单的解决方案反而是最有效的,比如增加服务器内存或者升级硬件配置。
评论