← 返回文档中心

查价引擎

查价引擎

优先级: P0(核心交易模块)
开发周期: 3 周

目标

平台核心引擎,负责接收代理商查价请求,并发查询多个供应商,聚合结果后返回最优报价。要求高性能(P99 < 2s)、高可用(降级策略)、智能缓存。

三阶段查价架构

查价链路拆分为三个阶段,每个阶段有明确的职责边界和执行时机:

┌─────────────────────────────────────────────────────────────┐
│                      查价请求入口                            │
└──────────────────────┬──────────────────────────────────────┘
                       ▼
┌─────────────────────────────────────────────────────────────┐
│              Phase 1: 查价前 (Pre-Query)                     │
│  ─────────────────────────────────────────                   │
│  职责:过滤、拦截、准备,决定是否需要查价                      │
│                                                              │
│  1. Gateway 鉴权 + 限流                                       │
│  2. 风控检查(信用额度、流量异常)                              │
│  3. 熔断检查(供应商/代理商维度)                               │
│     └─ 熔断状态 → 直接跳过该供应商,不进入查价                  │
│  4. 可见性过滤(白名单/黑名单)                                 │
│  5. 缓存检查                                                  │
│     ├─ L1 本地缓存命中 → 直接返回(不走后续阶段)               │
│     └─ L2 Redis 聚合缓存命中 → 直接返回 + 回填 L1             │
│  6. 供应商映射解析                                            │
│     └─ 平台酒店ID → 对应可售卖的供应商酒店ID列表               │
│  7. 渠道配置加载(定价策略、可用供应商、请求配额)               │
│                                                              │
│  → 输出:有效供应商列表 + 查价参数                              │
│  → 阻断条件:熔断、信用不足、所有缓存命中                       │
└──────────────────────┬──────────────────────────────────────┘
                       ▼
┌─────────────────────────────────────────────────────────────┐
│              Phase 2: 查价 (Query)                            │
│  ─────────────────────────────────────────                   │
│  职责:并发查询供应商,获取原始报价数据                         │
│                                                              │
│  1. 并发查价(goroutine + errgroup)                          │
│     ├─ 直连供应商 → 调用 SA 实时查询 API(带超时 3s)          │
│     │   ├─ L2 原始报价缓存命中 → 直接使用(TTL 60s)           │
│     │   └─ 未命中 → 调用远程 API → 缓存结果                    │
│     │                                                        │
│     └─ 直采供应商 → 查本地库存(inventory_snapshots + Redis)  │
│         ├─ available_rooms IS NULL → 有价即有房               │
│         ├─ available_rooms > 0 → 返回本地定价                  │
│         └─ available_rooms = 0 → 不返回该选项                 │
│                                                              │
│  2. 降级策略                                                  │
│     ├─ 单个供应商超时 → 跳过,返回其他供应商结果               │
│     ├─ 单个供应商 5xx → 跳过,自动重试 1 次                    │
│     ├─ 所有供应商超时 → 返回 Redis 缓存(标记"可能非实时")     │
│     └─ Redis 不可用 → 退化纯实时查询,仅靠 L1 兜底             │
│                                                              │
│  → 输出:各供应商原始报价列表(不含加价/动态定价/促销)          │
└──────────────────────┬──────────────────────────────────────┘
                       ▼
┌─────────────────────────────────────────────────────────────┐
│              Phase 3: 查价后 (Post-Query)                     │
│  ─────────────────────────────────────────                   │
│  职责:价格计算、聚合、排序,返回最终结果                       │
│                                                              │
│  1. 结果聚合                                                  │
│     ├─ 去重(同一房型可能同时有直连和直采报价,都保留)         │
│     ├─ 排序(按价格、推荐度、供应商优先级)                     │
│     └─ 分页                                                  │
│  2. Markup 加价(Redis 缓存规则)                              │
│     └─ 最终价 = 采购价 × (1 + markup%) + 固定加价              │
│  3. 动态定价调整(Redis 缓存规则)                             │
│     └─ 按入住日期/提前天数/连住天数叠加规则                     │
│  4. 促销匹配(Redis 缓存促销)                                │
│     └─ 多促销不可叠加,取最优                                  │
│  5. 汇率换算(Redis 缓存汇率)                                │
│  6. 写入缓存                                                  │
│     ├─ L2 Redis: price:agg:* (TTL 5min)                      │
│     └─ L1 本地缓存 (TTL 30s)                                  │
│  7. 异步写查价日志(ClickHouse)                              │
│                                                              │
│  → 输出:最终报价列表(含加价、动态定价、促销、汇率)           │
└─────────────────────────────────────────────────────────────┘

设计要点 - Phase 1 的阻断操作(熔断、可见性、信用检查)在查价前完成,避免浪费供应商查询资源 - Phase 2 只做数据获取,不做价格计算,保证原始数据纯净 - Phase 3 的所有价格变换操作(加价/动态定价/促销/汇率)都是实时计算的,不缓存中间结果 - 缓存策略:原始报价(price:raw)缓存不含加价,聚合结果(price:agg)缓存含最终价 - 促销/加价规则变更时,只需清理 price:agg 缓存,price:raw 不受影响

阶段间数据流

Pre-Query 输出                Query 输出                     Post-Query 输出
─────────────                ─────────                     ───────────────
· 有效供应商列表              · 供应商原始报价列表             · 最终报价列表
· 查价参数                    · 各供应商响应状态               · 排序后结果
· 渠道配置                    · 缓存命中标记                   · 分页信息
· 熔断/过滤决策               · 降级标记                       · 查价元数据

各阶段对应的模块依赖

Phase 操作 依赖模块 说明
1 查价前 Gateway 鉴权 + 限流 API Gateway JWT/HMAC + 速率限制
风控检查 Risk Control 信用额度、流量异常
熔断检查 Sales Management 供应商/代理商熔断状态
可见性过滤 Sales Management 白名单/黑名单
缓存检查 Query Engine (L1/L2) 聚合缓存命中直接返回
供应商映射 Mapping 平台酒店 → 供应商酒店列表
渠道配置 Sales Management 定价策略、配额
2 查价 直连供应商查询 Supplier Adapter (SA) 实时 API 调用
直采供应商查询 Inventory 本地库存查询
原始报价缓存 Query Engine (L2 Redis) price:raw 缓存
降级策略 Query Engine 超时/错误处理
3 查价后 Markup 加价 Pricing 基础加价规则
动态定价 Pricing Advanced 动态定价引擎
促销匹配 Pricing Advanced 促销引擎
汇率换算 Pricing Advanced 汇率服务
结果聚合排序 Query Engine 去重/排序/分页
缓存写入 Query Engine (L1/L2) price:agg 缓存
查价日志 BI Analytics ClickHouse 异步写入

降级策略矩阵

场景 策略 用户感知
单个供应商超时 跳过该供应商,返回其他供应商结果 正常返回,部分供应商可能缺失
单个供应商 5xx 跳过该供应商,自动重试 1 次 同上
所有供应商超时 返回 Redis 缓存结果(即使过期),标记 "数据可能非实时" 降级返回
Redis 不可用 退化为纯实时查询,仅靠 L1 本地缓存兜底 稍慢但可用
系统过载 触发限流,返回 429 Too Many Requests 提示稍后重试

连接模式路由

查价引擎(Phase 2)根据供应商的 connection_mode 采用不同的查询策略:

connection_mode 查询方式 延迟 数据来源
direct_connect 调用供应商适配器(SA)实时查询 较高(200ms-3s) 供应商 API 实时返回
direct_procure 查本地 inventory_snapshots + Redis(通过 DirectProcureAdapter) 极低(<10ms) 本地数据库/Redis

注意:直采数据有有效期(sync_validity_minutes),过期数据不返回 详细设计见: 价格同步模块 supplier-sync.md

查价请求
  ├─ 遍历酒店关联的所有供应商
  │   ├─ direct_connect 供应商 → 调用 SA 实时查询 API(带超时)
  │   │   ├─ 返回有价格 → 视为有房(有价即有房,无库存数量)
  │   │   └─ 超时/错误 → 走降级策略(返回缓存或跳过)
  │   │
  │   └─ direct_procure 供应商 → 查询本地库存(inventory_snapshots + Redis)
  │       ├─ available_rooms IS NULL → 有价即有房,返回本地定价
  │       ├─ available_rooms > 0 → 返回本地定价(有明确库存数量)
  │       └─ available_rooms = 0 → 不返回该选项
  │
  ├─ 聚合结果:合并 direct_connect 和 direct_procure 的结果
  │   ├─ direct_connect 结果:available_rooms 可能为空(表示"有价即有房")
  │   ├─ direct_procure 结果:available_rooms 为具体数字
  │   └─ 去重:同一房型可能同时有直连和直采报价,都保留让用户选择
  │
  └─ 加价策略 → 排序 → 返回

注意:direct_connect 的缓存策略与 direct_procure 不同。direct_connect 缓存的是查询结果(价格),有效期短(5min);direct_procure 的库存数据由库存模块独立维护,查价引擎直接读取。

数据模型

-- 查价请求日志表(写入 ClickHouse 用于分析,PG 做短期热数据)
CREATE TABLE price_search_logs (
    id              BIGSERIAL PRIMARY KEY,
    trace_id        VARCHAR(64) NOT NULL,          -- 链路追踪ID
    agent_code      VARCHAR(32) NOT NULL,          -- 代理商编码
    channel_code    VARCHAR(32),                   -- 渠道编码
    hotel_id        BIGINT,                        -- 平台酒店ID
    check_in        DATE NOT NULL,
    check_out       DATE NOT NULL,
    adults          SMALLINT DEFAULT 2,
    children        SMALLINT DEFAULT 0,
    room_count      SMALLINT DEFAULT 1,
    currency        VARCHAR(3) DEFAULT 'CNY',
    supplier_codes  VARCHAR(255),                  -- 查询了哪些供应商
    result_count    INT DEFAULT 0,                 -- 返回结果数量
    min_price       DECIMAL(12, 2),                -- 最低价
    cache_hit       BOOLEAN DEFAULT false,         -- 是否命中缓存
    phase_blocked   VARCHAR(32),                   -- 被阻断的阶段(pre_query/query/post_query/null)
    response_ms     INT,                           -- 响应耗时
    error_code      VARCHAR(32),                   -- 错误码
    created_at      TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_search_logs_hotel_date ON price_search_logs (hotel_id, check_in, check_out);
CREATE INDEX idx_search_logs_agent ON price_search_logs (agent_code, created_at);

-- Redis 缓存 Key 设计
# 原始供应商报价缓存: price:raw:{supplier_code}:{supplier_hotel_id}:{check_in}:{check_out}
# TTL: 60s (直连) / 30s (直采)
# Value: 原始报价 JSON(不含 markup/动态定价/促销)

# 聚合结果缓存: price:agg:{hotel_id}:{check_in}:{check_out}:{adults}:{channel_code}
# TTL: 300s (5min)
# Value: JSON数组,每个元素包含供应商、房型、最终价格(含markup+动态定价+促销)、库存等

# 供应商映射缓存: mapping:suppliers:{hotel_id}
# TTL: 3600s (1h)
# Value: 该酒店可售卖的供应商列表(含 supplier_hotel_id、connection_mode)

# 供应商熔断状态: circuit:{supplier_code}
# TTL: 动态(open 状态 30s,half_open 60s)
# Value: 状态JSON(state/failure_count/last_failure)

# 代理商熔断状态: circuit:{agent_code}
# TTL: 动态
# Value: 状态JSON

# 代理商信用额度: credit:{agent_code}
# TTL: 300s (5min)
# Value: 信用额度JSON(available/frozen/limit)

# 渠道定价配置: channel:pricing:{channel_code}
# TTL: 600s (10min)
# Value: 渠道定价策略JSON

# 详细见系统架构文档 缓存架构章节的完整 Key 设计和 TTL 策略

接口设计

# 查价 API(核心接口)
POST   /api/v2/prices/search
# 请求体:
{
  "hotel_id": "HPT-001234",
  "check_in": "2025-03-01",
  "check_out": "2025-03-03",
  "adults": 2,
  "children": 0,
  "room_count": 1,
  "currency": "CNY",
  "guest_country": "CN",
  "rate_plan_codes": [],           # 可选: 指定价格计划
  "supplier_codes": [],            # 可选: 指定供应商
  "client_reference": "req-xxx"    # 代理商请求号
}

# 响应体:
{
  "request_id": "plat-xxx",
  "hotel_id": "HPT-001234",
  "hotel_name": "Shanghai Marriott Hotel",
  "check_in": "2025-03-01",
  "check_out": "2025-03-03",
  "nights": 2,
  "currency": "CNY",
  "options": [
    {
      "option_id": "OPT-001",
      "supplier_code": "HOTELBEDS",
      "room_type_code": "DBL",
      "room_type_name": "豪华大床房",
      "meal_plan": "BB",
      "rate_plan_code": "BAR",
      "gross_price": 1200.00,       # 售价(含佣金)
      "net_price": 1000.00,         # 净价
      "commission": 200.00,         # 佣金
      "currency": "CNY",
      "available_rooms": 5,
      "cancellation_policy": {
        "type": "non_refundable"
      },
      "supplier_ref": "HB-123456",  # 供应商侧报价引用号
      "valid_until": "2025-01-20T10:00:00Z"  # 报价有效期
    }
  ],
  "meta": {
    "total_options": 3,
    "search_time_ms": 850,
    "suppliers_queried": ["HOTELBEDS", "EXPEDIA", "BEDSONLINE"],
    "suppliers_skipped": ["AGODA"],   # Phase 1 阻断的供应商(熔断等)
    "cache_hit": false,
    "phase_trace": {                   # 三阶段耗时追踪
      "pre_query_ms": 5,
      "query_ms": 820,
      "post_query_ms": 25
    }
  }
}

# 批量查价 API(多酒店)
POST   /api/v2/prices/batch-search

# 报价验证 API(下单前验证报价是否仍有效)
POST   /api/v2/prices/verify

与其他模块的关系

关联模块 关系
API 网关 入口,鉴权和限流(Phase 1)
酒店基地 获取酒店信息用于返回
匹配管理 Phase 1 获取供应商映射关系
价格管理 Phase 3 加价策略、币种转换
进阶定价 Phase 3 动态定价调整、促销匹配、汇率换算
销售管理 Phase 1 熔断检查、可见性过滤、渠道配置
供应商适配器 Phase 2 direct_connect 供应商调用 SA 实时查询
库存管理 Phase 2 direct_procure 供应商查询本地库存
风控体系 Phase 1 信用额度检查、频率限制
订单管理 报价验证、下单入口
数据智能 查价日志用于分析

技术选型建议