双向驱动与反馈模型:构建实时准确的酒店报价缓存系统

原创 谢欢 2025-12-04 18:04 北京

文章概览

  • 概述:构建报价缓存的必然性

  • 核心架构:实时-离线链路分离与缓存设计

  • 数据安全与高可用设计

  • 总结与展望

作者介绍:

谢欢,2017年加入去哪儿网,目前在国内酒店报价团队担任资深开发工程师,负责国内酒店报价离线查询、实时定价、平台验价等研发工作。

一、概述:构建报价缓存的必然性

为什么需要报价缓存?

1. 性能瓶颈:代理商无法保证接口响应性能

酒店报价查询接口对响应速度要求极高(毫秒级)。平台除接入标准代理商报价外,还接入了大量的非标代理商报价。尽管核心代理商的接口性能优异,但其他众多第三方代理商的接口响应较慢,无法满足要求。如果报价接口需要先调用代理商接口获取到报价再走平台逻辑,无疑会拉长整个接口的响应时间,影响用户体验。

2. 流量冲击:代理商无法承受平台高QPS调用压力

平台面临的查询峰值流量极高,远超任何代理商接口的承受能力(核心代理商也设置了其自身的限流阈值)。如果直接实时调用,必然会导致请求被限流或服务不可用。

3. 计算复杂度:缓解接口计算压力

酒店报价接口给出的很多数据与用户请求无关,而这些数据完全可以在用户请求到来之前提前处理,如报价的预处理(包括异常房型过滤、调用房型聚合接口设置产品房型的物理房型信息等)、整合代理商维度报价到酒店维度报价、预精简等。当用户查询酒店报价时,首先从缓存中查询酒店下所有代理商的报价,再进行过滤、选货、定价、优惠计算包装以精简等逻辑,能极大简化接口计算逻辑,缓解接口压力。

4. 稳定性兜底:保障平台稳定性

即便代理商接口性能满足要求且能抗住平台调用压力,从系统稳定性角度考虑,也需要有一层数据缓存兜底,防止意外发生和紧急情况的处理。

综上,构建报价缓存系统并非一个可选项,而是高并发、多代理商的酒店预订平台在性能、稳定性和用户体验压力下的必然选择。它本质上是一个在多重外部约束下,通过架构设计来寻求最优解的工程实践。

二、核心架构:实时-离线链路分离与缓存设计

怎么做?解决方案是什么?

面对外部代理商接口性能不确定和承压能力有限的双重挑战,我们引入了酒店报价缓存作为“缓冲带”,将流程拆分为实时计算链路和离线查询链路。

实时计算链路:由实时计算服务构成,负责快速响应端上用户请求。它直接从缓存中获取数据,进行轻量级处理,保障毫秒级的响应速度。

离线查询链路:由离线查询系统构成,异步地与各代理商交互。它负责智能决策何时查询、执行查询并将代理商报价聚合成酒店维度数据写入缓存。

通过数据的预加载和暂存,天然能解决和平衡用户体验与外部依赖的性能不确定性,保障接口的响应性能。同时缓存作为平台与代理商之间的“缓冲带”,将平台内部的高并发查询请求转换为对代理商的低频、可控的异步数据查询,能从根本上解决了流量冲击问题。但是代理商报价随时可能变动,如何保证平台缓存报价数据的及时更新,为用户端提供实时、准确的酒店报价成为不得不考虑的关键问题。

1. 关键机制:保障数据实时性的双向更新策略

1.1 用户流量驱动

  • 利用用户查询酒店报价的实时流量触发对酒店下所有代理商报价的查询:用户查询酒店报价时,通过接口或消息队列的方式触发离线查询链路的更新逻辑,离线查询系统对酒店的代理商报价进行决策性查询。当代理商报价不满足要求时,系统发起查询报价处理,实现代理商报价的刷新。

  • 利用用户进订、下单的流量触发对代理商报价的查询:用户进订下单后,可能会导致代理商报价的变化,此时强制查询代理商报价能够及时的更新缓存数据。离线查询系统监听到用户的进订或下单消息后,发起对代理商报价的查询与更新。

流量削峰:查询决策

用户向驱动缓存更新的核心利用户实时查询报价流量触发对代理商报价的查询。但如果每次用户查询报价都发起代理商报价查询,则仍然无法解决平台高QPS对代理商造成压力的问题。查询决策是离线查询链路的“大脑”,它决定酒店下哪些代理商报价需要查询更新。查询决策的关键是引入报价新鲜概念,通过判断代理商报价是否新鲜,拦截大量用户实时请求流量,减少对代理商的调用压力。不过查询决策还会考虑代理商的一些特殊情况进行强制查询或强制不查询:如代理商当前不在线、不在服务时间范围内、超出代理商支持的查询天数、不支持昨日房等情况时,即便代理商报价不新鲜,也不会发起查询。对于代理商报价存在变价的情况,即便报价新鲜,也会发起查询。

代理商报价查询回来在一定时间范围内,则认为代理商报价未过期有效,即新鲜。

4个关键时间

  • cacheTime:为代理商设置的缓存过期时间

  • sentTime:向代理商发起报价查询的时间

  • receiveTime:代理商报价回数时间

  • storeTime:代理商报价存储时间

判断代理商报价是否新鲜具体逻辑:从收到代理商报价的时间即回数时间receiveTime开始,如果当前时间now() - receiveTime <= cacheTime,则认为代理商报价未过期。由于不同代理商的调价频率存在差异,因此会有不同的缓存时间配置。

基于代理商缓存时间判断代理商报价是否有效新鲜的查询决策,能有效减少近一半的实时用户查询报价流量直接打到代理商。尤其在国庆等流量高峰期间,观测数据显示:尽管用户查询流量大幅上涨,但系统发起的代理商查询流量涨幅远低于查询流量涨幅,发起代理商查询率不增反降,原因是用户流量上涨会使代理商报价更新较平时更频繁,报价变得更热,因此查询率会有所下降。

十一期间,用户流量大幅上涨

查询率不增反降

1.2 代理商变价驱动

代理商变价是保证缓存数据准确性最直接、最关键的驱动力。为最大化变价消息价值,我们主要进行了以下两个实战:

即时查询近日期代理商报价

由于绝大部分报价查询都是近日期查询,因此在接收到变价消息后采取对最近日期而非变价日期进行查询,这样能够最小代价快速刷新用户最敏感的缓存报价,将有限的查询资源投入到最可能出问题的数据上,大幅提升了缓存更新的效率和准确性,避免了对代理商的不必要请求。

查询决策:变价区间映射机制

为确保任何受变价影响的查询都能获得最新报价,仅仅刷新近期数据是不够的,我们引入了 变价区间映射机制。变价处理系统在收到变价消息后会对其进行存储,当用户查询酒店报价时,查询决策时会优先判断代理商报价是否有过变价记录,即用户查询入住-离店日期与代理商变价入离事件区间是否存在交集。如果存在交集,说明缓存数据大概率已经失效,无论缓存报价是否新鲜,系统都会强制发起对该代理商报价的查询。如果没有交集,则会回退到正常的代理商报价新鲜来进行查询决策。

1.3 实现效果

  • 缓存命中率:97% 

用户查询报价时,实时计算系统直接从缓存中获取酒店报价,缓存命中率为97%。对于3%的缓存无报价情况,其中90%是代理商无报价导致,还有少部分是离线系统会对代理商报价进行预精简等处理导致。

2. 用户体验:端侧协同的报价更新机制

以上利用用户流量和接收代理商变价消息更新报价缓存最终目的都是为了让用户端展示报价更准确,但实际中由于如接口超时的各种原因仍然无法保证缓存中的报价一定是最新的,那又应该怎么办呢?

2.1 反馈模型:前端利用酒店报价是否新鲜标识刷新

如果用户查询报价时刚好需要发起一些代理商报价查询,由于代理商接口返回有快有慢。如果某些代理商报价返回慢,不能在一定时间内返回报价,为保证用户查询报价接口的及时响应,实时计算系统只能将已有的代理商报价定价后返回给前端,同时将酒店报价的状态反馈(英文:feedback)给到前端。当离线查询系统认为当前酒店报价状态不够理想时,希望前端能感知到并主动调用后端接口发起二次报价查询,实现用户端报价的更新。那什么样的酒店报价状态认为是理想的呢?这也是离线查询系统的反馈模型的关键。

这里涉及到另一个报价新鲜的概念:酒店报价新鲜。每个酒店下有多个代理商,理想情况下当酒店下的所有代理商报价都是新鲜的,那整个酒店报价肯定新鲜,即满足缓存更新要求。但代理商在接口性能、数据稳定性及综合服务能力有差异,系统会筛选部分达标的代理商进入模型计算。计算酒店报价是否新鲜主要关注三个核心数据:发起查询且参与模型计算的代理商个数、已返回报价的代理商个数以及未返回报价的代理商中是否有核心代理商。当已返回报价的代理商大于一定阈值且未返回报价的代理商都是非核心代理商时,则认为酒店报价是新鲜的。

2.2 前端定时刷新机制

如果用户长时间在界面停留,由于代理商报价随时发生变动,可能会导致一开始用户看到的报价是准确的,但由于长时间的停留代理商报价发生变化导致用户看到了不准确的报价,影响用户体验,因此前端还会有定时调用报价接口进行刷新界面的机制。

三、数据安全与高可用设计

1. 合理的缓存过期时间

为缩短实时计算系统查询代理商报价的时间,离线查询系统在保存报价时将多个代理商维度报价聚合成一个酒店维度报价。对代理商维度的报价不设置过期时间。因为代理商报价过期不代表代理商报价一定发生了变化,如果超过了代理商报价的缓存时间,系统并不会主动清理代理商的报价。当代理商报价不能及时查询回来时,还能给用户展示之前的代理商报价。但对酒店维度的报价有过期时间设置,超过缓存时间整个酒店报价将被清除。对于酒店报价来说,正常情况下只有当入住日小于当前日期时,报价才算过期,但由于凌晨房的存在,酒店报价的过期时间会被优化为入住日+1天的凌晨6点。

2. 主备双写与分片

为保障报价缓存的安全性,在业务数据保存时采用主redis和备份redis双写处理,当主redis异常时,可直接切换到备份redis进行数据读取。同时,为减少实时计算系统查询缓存报价的时间,针对报价多、代理商多的酒店,在离线查询系统在缓存报价时采用分片处理,将报价拆分成多个分片进行存储,实时计算系统在查询所有分片报价后进行聚合,避免单个缓存实例查询时间过高的情况。

四、总结与展望

代理商报价是平台报价的基础,离线查询链路与缓存作为实时服务的“数据底座”,一方面保障了代理商报价的实时和准确,另一方面保障了实时计算系统接口的快速响应,直接决定了用户酒店预订体验的上限。报价缓存系统核心是在复杂、不可控的外部依赖下,通过架构设计构建稳定、高性能的系统,主要可概括为以下几点实践经验:

1. 架构解耦:以异步预处理与读写分离构建性能基石

将实时请求处理与耗时的数据准备进行解耦,通过异步的离线查询链路提前完成数据聚合、清洗和加工,使实时计算链路变得轻量、快速。将不可控的外部调用延迟和计算压力从用户请求的关键路径中移除,保证了最终用户的体验。

2. 数据准确性保障:多源触发与智能决策的更新策略

首先是设计了由“用户流量”和“外部变价通知”双驱动的更新机制,然后又基于报价数据新鲜和变价区间映射规则进行查询决策,避免无效请求代理商,实现了流量削峰和保护下游系统,最后通过反馈模型将数据状态(是否新鲜)同步给前端,引导二次查询,以一种协作的方式逼近端上报价的最终准确性,保障用户体验。

3. 构建系统韧性:数据安全与可用性的多级保障

在数据可靠性和服务连续性方面采用了主备双写,然后对大value数据进行分片存储保证查询性能,最后通过不同过期时间管理防止脏数据的长期留存,共同保障了系统韧性和数据的安全性。

展望一、深化高可用:从“冷备份”到“热演练”

目前缓存报价时虽然采用了主备双写的模式,但备份redis未接入主流程流量,风险来临时能否正常返回或扛住正常流量是个考验。这就好比“久未上战场的兵”,平时缺乏实战演练,真要到紧要关头,很容易“关键时刻掉链子,光说不练假把式”。后续可对备份redis进行小流量试水和定期演练,确保风险真正来临时能实现平稳切换,保障报价服务的稳定性和数据的可靠性。

1、小流量“试水”:备份redis不能只写不读,日常可接入主流程小流量“试水”,验证双写的准确性。

2、定期“演练”:定期主动进行故障转移演练。在可控时间内,将读流量切换至备份Redis,观察其表现,验证其承载能力和数据一致性,结束后再切回。

展望二、增加强制刷新缓存SOP

一方面报价缓存更新主要依赖用户流量和代理商变价通知,整个链路还缺少一套系统主动触发报价缓存更新的SOP;另一方面,虽然采用了主备双写redis确保数据安全,但对于数据脏写甚至丢失这种极端情况,系统也要能快速响应和处理。目前对酒店维度、城市维度以及代理商维度已经有相关的主动更新后门,但实际数据恢复过程中仍需要人工介入各个环节,数据恢复过程的自动化程度还有可提升空间。

阅读原文

跳转微信打开