如何用Redis高效缓存带日期的嵌套酒店房价数据?
酒店房价Redis缓存方案(TypeScript)
方案一:优化版Sorted Set(按房型分组)
虽然每个房型单独创建Sorted Set看似繁琐,但这是Redis中实现高效日期范围查询的经典方案,可通过统一命名规范降低管理成本:
- 键命名规则:
hotel:price:{roomTypeId},例如hotel:price:1001 - 每个Sorted Set的
member存储JSON序列化后的单日期价格对象(包含date和price数组),score使用日期对应的时间戳(如new Date(date).getTime()),借助ZRANGEBYSCORE快速完成日期范围筛选 - TypeScript操作示例:
import redis from 'redis'; const client = redis.createClient(); // 写入单个房型的批量日期价格数据 async function setRoomPrice(roomTypeId: string, priceData: { date: string, price: { price: number, currency: string }[] }[]) { const key = `hotel:price:${roomTypeId}`; const pipeline = client.multi(); priceData.forEach(item => { const score = new Date(item.date).getTime(); pipeline.zadd(key, score, JSON.stringify(item)); }); await pipeline.exec(); } // 多房型+日期范围查询 async function getPrice(roomTypeIds: string[], fromDate: string, toDate: string) { const fromTs = new Date(fromDate).getTime(); const toTs = new Date(toDate).getTime(); const results = await Promise.all( roomTypeIds.map(async (id) => { const key = `hotel:price:${id}`; const data = await client.zrangebyscore(key, fromTs, toTs); return { roomTypeId: id, prices: data.map(item => JSON.parse(item)) }; }) ); return results.filter(res => res.prices.length > 0); }
- 优势:查询效率极高,Redis对Sorted Set的范围查询复杂度为O(logN + M)(M为结果数量),适配高频日期查询场景;数据结构简单,维护成本低
- 劣势:键数量随房型数量增长,但Redis支持百万级键存储,只要内存充足即可忽略
方案二:Hash+全局Sorted Set索引
若不想为每个房型单独建集合,可采用Hash存储全量数据+全局Sorted Set做日期索引的组合:
- Hash键:
hotel:prices:all,字段格式为{roomTypeId}:{date}(如1001:2024-05-20),值为JSON序列化的price数组 - 全局Sorted Set键:
hotel:prices:index,member为{roomTypeId}:{date},score为日期时间戳 - TypeScript操作示例:
// 写入缓存 async function setRoomPrice(roomTypeId: string, priceData: { date: string, price: { price: number, currency: string }[] }[]) { const pipeline = client.multi(); priceData.forEach(item => { const field = `${roomTypeId}:${item.date}`; const score = new Date(item.date).getTime(); pipeline.hset('hotel:prices:all', field, JSON.stringify(item.price)); pipeline.zadd('hotel:prices:index', score, field); }); await pipeline.exec(); } // 查询缓存 async function getPrice(roomTypeIds: string[], fromDate: string, toDate: string) { const fromTs = new Date(fromDate).getTime(); const toTs = new Date(toDate).getTime(); // 先通过全局索引获取日期范围内的所有字段 const allMembers = await client.zrangebyscore('hotel:prices:index', fromTs, toTs); // 筛选目标房型的字段 const targetFields = allMembers.filter(member => roomTypeIds.includes(member.split(':')[0])); if (targetFields.length === 0) return []; // 批量查询Hash数据 const priceValues = await client.hmget('hotel:prices:all', targetFields); // 整理结果格式 return targetFields.map((field, idx) => { const [roomTypeId, date] = field.split(':'); return { roomTypeId, date, price: JSON.parse(priceValues[idx] || '[]') }; }).reduce((acc, cur) => { const existItem = acc.find(item => item.roomTypeId === cur.roomTypeId); if (existItem) { existItem.prices.push({ date: cur.date, price: cur.price }); } else { acc.push({ roomTypeId: cur.roomTypeId, prices: [{ date: cur.date, price: cur.price }] }); } return acc; }, [] as { roomTypeId: string, prices: { date: string, price: { price: number, currency: string }[] }[] }[]); }
- 优势:全局键数量少,仅2个;可通过全局索引快速定位日期范围
- 劣势:查询时需额外筛选房型,当日期范围大、房型数量多的时候,数据过滤和整理的成本会上升;Hash批量查询字段过多时性能会下降
方案三:Redis JSON类型(Redis 7.0+)
如果你的Redis版本为7.0及以上,可直接使用Redis原生JSON类型,支持JSON路径查询:
- 键命名规则:
hotel:price:{roomTypeId},值为与接口返回结构一致的JSON数组 - TypeScript操作示例(依赖
redis-json库):
import { RedisJSON } from 'redis-json'; const jsonClient = new RedisJSON(client); // 写入缓存 async function setRoomPrice(roomTypeId: string, priceData: { date: string, price: { price: number, currency: string }[] }[]) { const key = `hotel:price:${roomTypeId}`; await jsonClient.set(key, priceData); } // 查询缓存 async function getPrice(roomTypeIds: string[], fromDate: string, toDate: string) { const results = await Promise.all( roomTypeIds.map(async (id) => { const key = `hotel:price:${id}`; // 通过JSON路径筛选日期范围内的元素 const prices = await jsonClient.get(key, { path: `$[?(@.date >= "${fromDate}" && @.date <= "${toDate}")]` }); return { roomTypeId: id, prices: prices || [] }; }) ); return results.filter(res => res.prices.length > 0); }
- 优势:数据结构与业务模型完全匹配,无需额外序列化/反序列化处理;支持复杂JSON查询
- 劣势:JSON路径查询性能不如Sorted Set,数据量大时差距明显;依赖Redis 7.0+版本
方案选择建议
- 若查询频率高、日期范围查询是核心需求,优先选方案一,虽然键数量多,但Redis完全能承载,且查询效率最优
- 若房型数量极大,担心键数量过多,且查询的日期范围不会特别大,可考虑方案二
- 若已使用Redis 7.0+,且希望数据结构更贴近业务逻辑,可选方案三
内容的提问来源于stack exchange,提问作者Agnete
相关产品推荐
相关产品推荐

