路由缓存策略优化
路由缓存策略优化
路由缓存策略是提升Koa2应用性能的关键手段之一。合理的缓存机制能减少重复计算和数据库查询,显著降低服务器负载。Koa2中间件架构为缓存实现提供了灵活的空间,通过适当设计可以兼顾响应速度和资源消耗。
缓存策略基础原理
路由缓存的核心思想是将频繁访问且不常变动的数据存储在内存或外部缓存系统中。Koa2应用中常见的缓存层级包括:
- 完整响应缓存:存储整个HTTP响应
- 数据结果缓存:存储路由处理后的数据对象
- 片段缓存:存储模板渲染的局部片段
内存缓存适合中小规模应用,使用类似lru-cache的模块:
const LRU = require('lru-cache')
const cache = new LRU({
max: 500, // 最大缓存条目数
maxAge: 1000 * 60 * 5 // 5分钟有效期
})
动态路由缓存实现
动态路由需要特别处理缓存键的生成。考虑带参数的API路由:
router.get('/api/users/:id', async (ctx) => {
const cacheKey = `user_${ctx.params.id}`
const cached = cache.get(cacheKey)
if (cached) {
ctx.body = cached
return
}
const user = await User.findById(ctx.params.id)
cache.set(cacheKey, user)
ctx.body = user
})
对于复杂查询参数的路由,可采用参数序列化生成缓存键:
function generateCacheKey(ctx) {
return `${ctx.path}?${querystring.stringify(ctx.query)}`
}
缓存失效策略设计
常见的缓存失效机制包括:
- 定时过期:设置固定的缓存存活时间
- 主动清除:数据变更时立即清除相关缓存
- 条件验证:通过ETag或Last-Modified实现条件请求
示例实现主动清除:
router.put('/api/users/:id', async (ctx) => {
const userId = ctx.params.id
await User.updateById(userId, ctx.request.body)
// 清除相关缓存
cache.del(`user_${userId}`)
cache.del('user_list') // 同时清除列表缓存
ctx.body = { success: true }
})
多级缓存架构
构建多级缓存可进一步提升性能:
async function getWithCache(key, fetchFunc, ttl) {
// 第一层:内存缓存
let data = memoryCache.get(key)
if (data) return data
// 第二层:Redis缓存
data = await redis.get(key)
if (data) {
memoryCache.set(key, data, ttl)
return data
}
// 回源获取
data = await fetchFunc()
memoryCache.set(key, data, ttl)
await redis.setex(key, ttl * 2, data) // Redis缓存时间更长
return data
}
缓存性能监控
实现缓存命中率监控有助于优化策略:
const stats = {
hits: 0,
misses: 0
}
app.use(async (ctx, next) => {
await next()
if (ctx.state.fromCache) {
stats.hits++
} else {
stats.misses++
}
})
// 定时输出统计
setInterval(() => {
const total = stats.hits + stats.misses
const ratio = total > 0 ? (stats.hits / total * 100).toFixed(2) : 0
console.log(`缓存命中率: ${ratio}% (${stats.hits}/${total})`)
}, 60000)
特殊场景处理
分页查询缓存需要特殊处理:
router.get('/api/posts', async (ctx) => {
const { page = 1, size = 10 } = ctx.query
const cacheKey = `posts_${page}_${size}`
const posts = await getWithCache(cacheKey, async () => {
return Post.find()
.skip((page - 1) * size)
.limit(size)
}, 300000) // 5分钟缓存
ctx.body = posts
})
关联数据更新时的缓存清理:
router.post('/api/comments', async (ctx) => {
const comment = await Comment.create(ctx.request.body)
// 清除关联的文章缓存
cache.del(`post_${comment.postId}`)
cache.del(`post_${comment.postId}_comments`)
ctx.body = comment
})
缓存安全考虑
防范缓存击穿和雪崩:
async function safeGet(key, fetchFunc, ttl) {
const value = cache.get(key)
if (value !== undefined) return value
// 使用互斥锁防止缓存击穿
const lockKey = `${key}_lock`
if (cache.get(lockKey)) {
await new Promise(resolve => setTimeout(resolve, 100))
return safeGet(key, fetchFunc, ttl)
}
cache.set(lockKey, true, 1000) // 1秒锁
try {
const data = await fetchFunc()
cache.set(key, data, ttl)
return data
} finally {
cache.del(lockKey)
}
}
实战优化案例
电商商品详情页缓存方案:
router.get('/api/products/:id', async (ctx) => {
const productId = ctx.params.id
const cacheKey = `product_${productId}_v2` // 版本化缓存键
const product = await getWithCache(cacheKey, async () => {
const [baseInfo, skus, reviews] = await Promise.all([
Product.findById(productId),
Sku.find({ productId }),
Review.find({ productId }).limit(5)
])
return {
...baseInfo.toObject(),
skus,
reviews,
updatedAt: Date.now() // 添加时间戳
}
}, 1800000) // 30分钟缓存
// 客户端缓存验证
if (ctx.get('If-Modified-Since')) {
const lastModified = new Date(ctx.get('If-Modified-Since')).getTime()
if (product.updatedAt <= lastModified) {
ctx.status = 304
return
}
}
ctx.set('Last-Modified', new Date(product.updatedAt).toUTCString())
ctx.body = product
})
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:动态路由与通配符匹配
下一篇:自定义路由解析器开发