缓存策略的全面应用
缓存策略的基本概念
缓存策略是提升应用性能的关键手段之一,通过减少重复计算和网络请求来优化响应速度。在Koa2中,缓存可以应用于多个层面,包括内存缓存、HTTP缓存和数据库查询缓存。不同的场景需要不同的缓存策略,合理选择能显著提升系统吞吐量。
内存缓存是最直接的实现方式,适合存储频繁访问且变化不大的数据。例如使用lru-cache
这类LRU算法实现的缓存库:
const LRU = require('lru-cache')
const cache = new LRU({
max: 500,
maxAge: 1000 * 60 * 5 // 5分钟
})
// 中间件示例
app.use(async (ctx, next) => {
const key = ctx.url
const cached = cache.get(key)
if (cached) {
ctx.body = cached
return
}
await next()
cache.set(key, ctx.body)
})
HTTP缓存头设置
HTTP协议提供了完善的缓存控制机制,通过响应头可以精确控制客户端和代理服务器的缓存行为。Koa2中可以通过中间件灵活设置这些头信息:
app.use(async (ctx, next) => {
await next()
if (ctx.status === 200 && ctx.method === 'GET') {
ctx.set('Cache-Control', 'public, max-age=3600')
ctx.set('ETag', generateETag(ctx.body))
}
})
强缓存与协商缓存的组合使用能有效减少网络传输。当资源未变更时,304 Not Modified响应可以节省大量带宽:
app.use(async (ctx, next) => {
const ifNoneMatch = ctx.headers['if-none-match']
const etag = generateETagForRequest(ctx)
if (ifNoneMatch === etag) {
ctx.status = 304
return
}
await next()
ctx.set('ETag', etag)
})
数据库查询缓存
ORM层缓存可以避免重复执行相同查询。Sequelize等ORM库提供了查询缓存功能:
const result = await Model.findAll({
where: { status: 'active' },
cache: true,
cacheKey: 'active_users'
})
对于复杂查询,可以手动实现缓存逻辑:
app.use(async (ctx, next) => {
const cacheKey = `query_${ctx.path}_${JSON.stringify(ctx.query)}`
const cached = await redis.get(cacheKey)
if (cached) {
ctx.body = JSON.parse(cached)
return
}
await next()
if (ctx.status === 200) {
await redis.setex(cacheKey, 300, JSON.stringify(ctx.body))
}
})
页面级缓存策略
全页面缓存适用于内容变化不频繁的页面。Koa2中间件可以实现简单的HTML缓存:
const pageCache = {}
app.use(async (ctx, next) => {
if (ctx.method !== 'GET' || !ctx.accepts('html')) {
return next()
}
const url = ctx.url
if (pageCache[url] && Date.now() - pageCache[url].timestamp < 60000) {
ctx.type = 'text/html'
ctx.body = pageCache[url].html
return
}
await next()
if (ctx.status === 200 && ctx.type === 'text/html') {
pageCache[url] = {
html: ctx.body,
timestamp: Date.now()
}
}
})
对于动态内容,可以考虑片段缓存(ESI)或组件级缓存方案。
缓存失效与更新策略
缓存失效是缓存系统中最具挑战性的部分。常见的策略包括:
- 定时过期:设置固定的缓存时间
- 主动清除:数据变更时立即清除相关缓存
- 版本控制:通过URL或参数版本号强制更新
Koa2中实现主动清除的例子:
// 数据更新操作后清除缓存
router.post('/articles/:id', async (ctx) => {
await updateArticle(ctx.params.id, ctx.request.body)
await redis.del(`article_${ctx.params.id}`)
await redis.del('article_list')
ctx.status = 204
})
对于依赖关系复杂的场景,可以使用发布-订阅模式通知缓存失效:
const redis = require('redis')
const sub = redis.createClient()
const pub = redis.createClient()
sub.on('message', (channel, key) => {
cache.del(key)
})
// 数据变更时发布消息
pub.publish('cache_invalidate', `user_${userId}`)
分布式缓存实践
在集群环境下,内存缓存需要共享存储。Redis是常见的分布式缓存解决方案:
const redis = require('redis')
const client = redis.createClient()
app.use(async (ctx, next) => {
const key = `view_${ctx.path}`
const cached = await client.get(key)
if (cached) {
ctx.body = JSON.parse(cached)
return
}
await next()
if (ctx.status === 200) {
await client.setex(key, 60, JSON.stringify(ctx.body))
}
})
缓存雪崩防护可以通过随机过期时间实现:
function getCacheTTL() {
const base = 3600 // 1小时
const random = Math.floor(Math.random() * 600) // 0-10分钟随机
return base + random
}
缓存性能监控
完善的监控能帮助优化缓存策略。可以收集以下指标:
- 缓存命中率
- 缓存加载时间
- 内存使用情况
Koa2中间件实现简单的监控:
const cacheStats = {
hits: 0,
misses: 0
}
app.use(async (ctx, next) => {
const start = Date.now()
const key = ctx.url
const cached = cache.get(key)
if (cached) {
cacheStats.hits++
ctx.body = cached
ctx.set('X-Cache', 'HIT')
return
}
cacheStats.misses++
await next()
if (ctx.status === 200) {
cache.set(key, ctx.body)
}
ctx.set('X-Cache-Time', `${Date.now() - start}ms`)
})
// 暴露统计端点
router.get('/cache-stats', (ctx) => {
ctx.body = cacheStats
})
高级缓存模式
对于特殊场景,可以考虑更高级的缓存模式:
- 写穿透:先更新缓存再更新数据库
- 读穿透:缓存未命中时从数据库加载
- 缓存预热:启动时加载热点数据
Koa2实现读穿透的例子:
app.use(async (ctx, next) => {
const key = `user_${ctx.params.id}`
let data = await redis.get(key)
if (!data) {
data = await db.query('SELECT * FROM users WHERE id = ?', [ctx.params.id])
if (data) {
await redis.setex(key, 3600, JSON.stringify(data))
}
} else {
data = JSON.parse(data)
}
ctx.body = data
})
缓存预热可以在应用启动时执行:
async function warmUpCache() {
const hotData = await db.query('SELECT * FROM products ORDER BY views DESC LIMIT 100')
await Promise.all(
hotData.map(item =>
redis.setex(`product_${item.id}`, 86400, JSON.stringify(item))
)
)
}
app.on('listening', warmUpCache)
缓存与安全考虑
缓存可能引入安全风险,需要注意:
- 敏感数据不应缓存
- 区分用户私有缓存
- 防止缓存投毒攻击
Koa2中处理用户私有缓存:
app.use(async (ctx, next) => {
const userToken = ctx.cookies.get('token')
const cacheKey = userToken ? `${userToken}_${ctx.url}` : ctx.url
const cached = cache.get(cacheKey)
if (cached) {
ctx.body = cached
return
}
await next()
if (ctx.status === 200) {
cache.set(cacheKey, ctx.body)
}
})
对于API响应,Vary头可以确保不同内容类型的正确缓存:
ctx.set('Vary', 'Accept-Encoding, Accept-Language')
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn