这里分享一个退改规则服务属性提供接口的缓存降级技术实现方案,用户在搜索机票报价的时候,需要对多个来源的报价进行排序和取优,除了票面价,这其中还会参考很多因素,比如报价的退改规则等服务属性
背景
用户在我们平台搜索机票报价的时候,主流程是这样的
第一步,用户选定出发地+目的地+出发日期
,点击搜索报价
比如下图查询条件为北京-成都-12月31日
第二步,用户进入航班列表页
,此页展示当天的多条航班,展示价格为每个航班的最优报价
比如下图:
- 13:30出发某个套餐(航班)的最优报价为558元
- 06:25出发的3U6874航班的最优报价为750元
第三步,用户选定航班,进入航班详情页
,此处展示各个代理商提供的报价,此页有一定的取优排序规则
同时此页可展示此条报价的退改信息
比如下图只截取了一条报价,第一条报价为xxx优选
提供的报价,价格为1062元
此处用户搜索报价流程完毕,后续进入下单流程
一处改进:让详细的退改签信息参与到竞价
我们要比较一个报价的好坏,不只是通过票面价这一个维度
报价系统还会参考一些简单的服务属性:
-
该报价有没有免费的行李
-
该报价的退改规则能不能解析
-
该报价的退改规则是不是按航司的默认规定
但以上都是非常基本的服务属性
那有没有可能让详细的退改签信息参与到竞价中来呢?
举个例子
【报价A】票面价 9元, 不支持退改, 折算价9元
【报价B】票面价10元, 退改手续费2元, 折算价8元
只考虑票面价,报价A比报价B低,我们展示报价A
考虑票面价和退改规则,报价B优于报价A,我们展示报价B
现状分析
现有系统的航班搜索页
由报价系统
提供高QPS的接口服务
在用户搜索报价流程完毕后,进入下单流程前,报价详情页
由退改签系统
提供低QPS的接口服务
如果我们想要实现上述的改进,就需要对我们的退改签系统
进行大改造,让其支持高QPS调用
目标
业务目标
- 让
退改签规则
和免费行李额
等服务属性参与竞价 - 参与竞价的以上服务属性需和
航班详情页(OTA详情页)
展示属性保持一致
技术目标
- 根据报价系统的预估,调用退改签系统的QPS初步设定为1~2 w/s
- 为了不影响报价流程,期望的响应时间为:
- P50 < 30 ms
- P90 < 100 ms
- P99 < 200 ms
-
退改签系统底层强依赖外部HTTP接口,其最大支持QPS为 2 k/s
- 报价维度搜索、OTA页展示搜索、生单调用 这三种搜索方式应在同一个流程,但不能相互影响
什么是
P50
、P90
、P99
?服务响应时间的衡量指标通常有以下几种:
- Min(最小响应时间)
- Max(最大响应时间)
- Avg(平均响应时间)
但平均值并不能反映数据分布及极端异常值的问题,这时我们可以使用
百分位数值
。假设有100个请求,按照响应时间从小到大排列,位置为X的值,即为PX值。
P1就是响应时间最小的请求
P10就是排名第十的请求
P100就是响应时间最长的请求
P50: 即中位数值。100个请求按照响应时间从小到大排列,位置为50的值,即为P50值。如果响应时间的P50值为200ms,代表我们有半数的用户响应耗时在200ms之内,有半数的用户响应耗时大于200ms
P99:响应耗时从小到大排列,顺序处于99%位置的值即为P99值。
技术方案
根据以上提到的技术目标,我们制定相应的技术方案,就需要综合考虑,权衡各个方面的利弊。
集群隔离
业务目标1:参与竞价的以上服务属性需和航班详情页(OTA详情页)
展示属性保持一致
技术目标4:报价维度搜索、OTA页展示搜索、生单调用 这三种搜索方式应在同一个流程,但不能相互影响
根据业务目标1和技术目标4,退改签系统为此提供三个接口:
- 报价竞价搜索接口:用户搜索航班报价时,报价系统调用退改签系统,得到每一条报价的退改信息,用于竞价参考。
- 用户触发搜素接口:用户在下单前,报价详情展示时,调用此接口,给用户展示退改信息。
- 用户生单调用接口:用户在下单的时候,调用此接口得到退改信息,并且留存(用户实际退改的时候参考此退改信息。
我们根据以上三个接口,将退改签系统分为三个集群:
- 报价搜索集群(提供报价竞价搜索接口)
- 用户搜索集群(提供用户触发搜索接口)
- 用户生单集群(提供用户生单调用接口)
这样隔离降低相互的影响
比如报价搜索集群的QPS很高,出现性能等问题不会影响到用户搜索集群和用户生单集群(不影响用户实际体验)
报价竞价搜索接口的数据流程
从报价系统
到退改签系统
,整个数据流有多个系统之间的分合
在报价系统
中,包装层
提供包装好的价格包,而竞价层
收集多个子系统的不同来源的报价进行报价整合
报价系统的子系统
在每拿到一条子报价的时候,调用退改签系统获取退改信息
退改签系统
的合并层
提供统一合并好的退改信息,其中最重要的ATPCO数据源
来自子系统
缓存降级方案【重点】
在以上数据流程的背景下,我们来设计缓存降级的方案
我们在报价系统做了一层缓存,在退改签系统做了三层缓存
第一层缓存
报价系统由于是并发调用退改签系统,所以做了一层本地缓存,时间仅为1分钟
,以拦截重复请求
第二层缓存
在数据合并流程中的缓存,我们采用Redis
存储,在这时报价搜索条件的粒度是粗粒度
,我们进行粗粒度的拦截,以保证拦截掉大多数无效的请求,此处缓存时长为半小时
PS:第一层我们尝试过使用
本地缓存
,提供的dubbo接口采用一致性哈希负载均衡
方式,但由于搜索条件的客观不平衡原因(比如航线存在热门航线和冷门航线等),导致分布不均匀,故删去本地缓存
什么是dubbo接口的
一致性哈希负载均衡
?
第三层缓存
数据源的实时缓存,我们采用Redis
存储,在这时,我们将搜索条件的粒度进行拆分,比如将航班时间
提取出来,此处缓存时长为1小时
我们为什么拆分缓存的粒度?
因为航司的退改规则一般是连续的,比如同一条航班,第二天的退改规则大概率和第一天是一样的,当航司采用新的退改规则时,一般是某一个时间段均采用同样的退改规则,这一点很适合用于
缓存的粒度拆分
根据业务数据的特性,此层缓存还有其它的粒度拆分,此处毋庸赘述。
第四层缓存
ATPCO数据源的持久化存储,我们采用HBase
存储,在这步我们和第三层缓存拆分的粒度保持一致,此处缓存时长为xxx天
这一层主要是避免外部数据源出现问题,导致无数据可用。
效果展示
响应时间
在固定的机器数量的情况下,开量阶段,不同的QPS所统计到的平均相应时间
(集群可根据QPS的实际值来动态扩缩容)
集群隔离
可以明显看到,在红圈标识的地方,QPS最高的第三个集群报价维度搜索
集群出现了较大的波动,但此时只影响到了报价维度搜索
集群的平均响应时间,另外两个集群没有明显的变化。
降级缓存拦截【重点】
以下为退改签系统
的三层缓存的拦截率展示
基本上达到了缓存降级的拦截效果,最终到底层HTTP接口的请求量降低到了2k以下
(图示数据的波动为新开请求量导致)
遇到的问题
在此技术方案实施的过程中,我们也遇到了很多的问题
比如在第二层缓存(我们采用了粗细粒度),我们开了新的航线请求量,导致第二层拦截率大幅下降,此处需要分析我们的粗细粒度是否合理,根据业务和实际的请求数据分析,有没有继续改进的空间
底层HTTP的接口有一定的QPS限制,为了保护底层HTTP接口,我们做了每个系统的入口和出口限流处理,但在请求量上涨的时候,仍有大量的请求被丢弃,无法满足上层请求接口的需要。
新的尝试——拦截无效请求
底层数据的特征分析
我们分析底层HTTP接口返回的数据的特征,发现有75%的请求返回了空结果,有2%的请求结果与上一次请求发生了变化。
虽然我们上面第一层缓存做了一个粗粒度的空结果缓存拦截,能保证大部分无效请求在一开始就被拦截掉
但此处的数据分析,我们还可以继续拦截大概3/4的无效请求
计数排行榜的设计方案
由于请求量比较大,且为分布式,我们首先想到了Redis中的有序集合。
我们在Redis中维护这样一个排行榜,每一次请求我们都会给该次请求打一个分值
这样在下一次请求到来时,我们根据这样的排行榜的分值,判断是不是“无效请求”,决定是否要过滤掉这个请求
现实因素
Redis的有序集合通常适用于数据量较小的场景,如果我们使用它,会存在很严重的性能问题