一、缓存穿透:当查询变成"无底洞"
想象一下这个场景:你的电商平台每秒要处理10万次商品查询请求,其中有个恶意用户一直在用根本不存在的商品ID发起请求。由于缓存里没有这个数据,每次请求都直接打到数据库,数据库很快就会被拖垮。这就是典型的缓存穿透问题。
在OpenResty中,我们可以用Lua脚本来实现布隆过滤器。下面是一个完整的实现示例(技术栈:OpenResty + Lua):
-- 初始化布隆过滤器
local bloom_filter = require "resty.bloomfilter"
local bf, err = bloom_filter.new(1000000, 0.001) -- 容量100万,错误率0.1%
-- 预热数据(实际应该从数据库加载)
local items = {"product_1001", "product_1002", "product_1003"}
for _, item in ipairs(items) do
bf:add(item)
end
-- 在access阶段检查
local function check_product_id()
local product_id = ngx.var.arg_product_id
if not product_id then
ngx.exit(ngx.HTTP_BAD_REQUEST)
end
-- 先检查布隆过滤器
if not bf:check(product_id) then
ngx.log(ngx.INFO, "Blocked non-existent product request: ", product_id)
ngx.exit(ngx.HTTP_NOT_FOUND)
end
end
这个方案有几个关键点需要注意:
- 布隆过滤器需要预热真实存在的键
- 错误率设置需要权衡内存和准确性
- 对于新增数据需要及时更新过滤器
二、缓存雪崩:当缓存集体"罢工"
缓存雪崩比穿透更可怕,它就像是缓存系统突然集体罢工。比如你的缓存设置了相同的过期时间,结果在同一时刻全部失效,导致所有请求直接涌向数据库。
在OpenResty中,我们可以采用多级缓存+随机过期时间的策略。看这个实现示例:
-- 多级缓存实现
local redis = require "resty.redis"
local shared_cache = ngx.shared.product_cache -- 这是nginx共享内存
local function get_product(product_id)
-- 第一层:本地共享内存
local product = shared_cache:get(product_id)
if product then
return product
end
-- 第二层:Redis缓存
local red = redis:new()
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis connect failed: ", err)
-- 继续尝试数据库查询
else
product = red:get(product_id)
if product and product ~= ngx.null then
-- 设置随机过期时间(基础300秒 + 0-300秒随机值)
local ttl = 300 + math.random(300)
shared_cache:set(product_id, product, ttl)
return product
end
end
-- 第三层:数据库查询
product = query_db(product_id)
if product then
-- 同时写入Redis和共享内存
local ttl = 300 + math.random(300)
red:set(product_id, product)
shared_cache:set(product_id, product, ttl)
end
return product
end
这个方案的精髓在于:
- 通过多级缓存分散压力
- 随机过期时间避免同时失效
- 即使某层缓存失效,系统仍可降级运行
三、热点Key问题:当某个商品突然爆红
双十一期间,某款手机突然爆红,每秒被查询10万次。即使有缓存,单个Key的极高并发也可能打爆缓存服务器。
OpenResty的Lua协程机制可以很好地解决这个问题:
local resty_lock = require "resty.lock"
local function get_hot_product(product_id)
local cache = ngx.shared.product_cache
local product = cache:get(product_id)
if product then
return product
end
-- 使用分布式锁防止缓存击穿
local lock = resty_lock:new("product_locks", {timeout=0.1, exptime=1})
local elapsed, err = lock:lock(product_id)
if not elapsed then
-- 获取锁失败,直接返回旧数据或默认值
local stale = cache:get_stale(product_id)
return stale or {default=true}
end
-- 临界区:只有一个请求能执行这里
product = query_db(product_id)
if product then
cache:set(product_id, product, 60) -- 正常缓存
else
cache:set(product_id, {default=true}, 5) -- 空值缓存
end
lock:unlock()
return product
end
这个方案的关键技术点:
- 使用轻量级锁避免并发重建缓存
- 支持返回陈旧数据保证可用性
- 对空结果也进行短期缓存
四、缓存更新策略:保证数据一致性
缓存最难的不是技术实现,而是保证数据一致性。我们来看看几种常见策略在OpenResty中的实现。
1. 主动更新策略示例:
-- 商品更新接口
local function update_product()
local args = ngx.req.get_post_args()
local product_id = args.product_id
-- 先更新数据库
local ok = update_db(product_id, args)
if not ok then
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
-- 再删除缓存
local red = redis:new()
red:connect("127.0.0.1", 6379)
red:del(product_id)
ngx.shared.product_cache:delete(product_id)
-- 异步更新其他节点缓存
ngx.timer.at(0, function()
notify_other_nodes(product_id)
end)
end
2. 延迟双删策略示例:
local function update_product_with_double_delete()
-- 第一次删除
delete_cache(product_id)
-- 更新数据库
update_db(product_id, data)
-- 延迟第二次删除
ngx.timer.at(1.0, function()
delete_cache(product_id)
end)
end
缓存更新的黄金法则:
- 先更数据库再删缓存
- 对于重要数据采用双删策略
- 考虑引入消息队列保证可靠性
五、实战经验与避坑指南
在实际项目中,我总结了这些血泪教训:
监控指标必须完善:
- 缓存命中率要分层次监控
- 慢查询需要设置阈值告警
- 内存使用率要实时关注
测试时的注意事项:
-- 压力测试时模拟缓存失效 local function mock_cache_failure() if ngx.var.arg_test_mode == "cache_failure" then ngx.shared.product_cache:flush_all() local red = redis:new() red:connect("127.0.0.1", 6379) red:flushall() end end灰度发布策略:
- 新缓存策略要先在小流量验证
- 支持快速回滚机制
- 要有降级开关
Key设计规范:
- 业务前缀:比如 "product:1001"
- 版本号:"v1:product:1001"
- 避免特殊字符
六、未来演进方向
随着业务发展,缓存架构也需要不断进化:
混合缓存策略:
- 本地缓存 + 分布式缓存 + 数据库
- 热点数据自动识别
智能缓存预热:
-- 基于历史数据预测预热 local function smart_preheat() local hot_items = predict_hot_items() for _, item in ipairs(hot_items) do preload_to_cache(item) end end弹性缓存架构:
- 根据负载自动调整缓存大小
- 动态TTL策略
缓存系统就像系统的免疫系统,需要持续优化和调整。希望这些实战经验能帮你少走弯路!
评论