You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 09:07:25