分享一个缓存降级的技术实现方案

2021/06/21 design

这里分享一个退改规则服务属性提供接口的缓存降级技术实现方案,用户在搜索机票报价的时候,需要对多个来源的报价进行排序和取优,除了票面价,这其中还会参考很多因素,比如报价的退改规则等服务属性


背景

用户在我们平台搜索机票报价的时候,主流程是这样的

第一步,用户选定出发地+目的地+出发日期,点击搜索报价

比如下图查询条件为北京-成都-12月31日

image-20220512130456941

第二步,用户进入航班列表页,此页展示当天的多条航班,展示价格为每个航班的最优报价

比如下图:

  • 13:30出发某个套餐(航班)的最优报价为558元
  • 06:25出发的3U6874航班的最优报价为750元

image-20220512130553366

第三步,用户选定航班,进入航班详情页,此处展示各个代理商提供的报价,此页有一定的取优排序规则

同时此页可展示此条报价的退改信息

比如下图只截取了一条报价,第一条报价为xxx优选提供的报价,价格为1062元

image-20220512131031100

此处用户搜索报价流程完毕,后续进入下单流程

一处改进:让详细的退改签信息参与到竞价

image-20220512125649628

我们要比较一个报价的好坏,不只是通过票面价这一个维度

报价系统还会参考一些简单的服务属性:

  • 该报价有没有免费的行李

  • 该报价的退改规则能不能解析

  • 该报价的退改规则是不是按航司的默认规定

但以上都是非常基本的服务属性

那有没有可能让详细的退改签信息参与到竞价中来呢?

举个例子

image-20220512125536088

【报价A】票面价 9元, 不支持退改, 折算价9元

【报价B】票面价10元, 退改手续费2元, 折算价8元

只考虑票面价,报价A比报价B低,我们展示报价A

考虑票面价和退改规则,报价B优于报价A,我们展示报价B

现状分析

现有系统的航班搜索页报价系统提供高QPS的接口服务

在用户搜索报价流程完毕后,进入下单流程前,报价详情页退改签系统提供低QPS的接口服务

image-20220512125748110

如果我们想要实现上述的改进,就需要对我们的退改签系统进行大改造,让其支持高QPS调用

目标

业务目标

  1. 退改签规则免费行李额等服务属性参与竞价
  2. 参与竞价的以上服务属性需和航班详情页(OTA详情页)展示属性保持一致

技术目标

  1. 根据报价系统的预估,调用退改签系统的QPS初步设定为1~2 w/s
  2. 为了不影响报价流程,期望的响应时间为:
    • P50 < 30 ms
    • P90 < 100 ms
    • P99 < 200 ms
  3. 退改签系统底层强依赖外部HTTP接口,其最大支持QPS为 2 k/s

  4. 报价维度搜索、OTA页展示搜索、生单调用 这三种搜索方式应在同一个流程,但不能相互影响

什么是 P50P90P99

服务响应时间的衡量指标通常有以下几种:

  • 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,退改签系统为此提供三个接口:

  1. 报价竞价搜索接口:用户搜索航班报价时,报价系统调用退改签系统,得到每一条报价的退改信息,用于竞价参考。
  2. 用户触发搜素接口:用户在下单前,报价详情展示时,调用此接口,给用户展示退改信息。
  3. 用户生单调用接口:用户在下单的时候,调用此接口得到退改信息,并且留存(用户实际退改的时候参考此退改信息。

我们根据以上三个接口,将退改签系统分为三个集群

  1. 报价搜索集群(提供报价竞价搜索接口)
  2. 用户搜索集群(提供用户触发搜索接口)
  3. 用户生单集群(提供用户生单调用接口)

这样隔离降低相互的影响

比如报价搜索集群的QPS很高,出现性能等问题不会影响到用户搜索集群和用户生单集群(不影响用户实际体验

报价竞价搜索接口的数据流程

报价系统退改签系统,整个数据流有多个系统之间的分合

报价系统中,包装层提供包装好的价格包,而竞价层收集多个子系统的不同来源的报价进行报价整合

报价系统的子系统在每拿到一条子报价的时候,调用退改签系统获取退改信息

退改签系统合并层提供统一合并好的退改信息,其中最重要的ATPCO数据源来自子系统

image-20220516125726714

缓存降级方案【重点】

在以上数据流程的背景下,我们来设计缓存降级的方案

我们在报价系统做了一层缓存,在退改签系统做了三层缓存

image-20220516130313293

第一层缓存

报价系统由于是并发调用退改签系统,所以做了一层本地缓存,时间仅为1分钟,以拦截重复请求

第二层缓存

在数据合并流程中的缓存,我们采用Redis存储,在这时报价搜索条件的粒度是粗粒度,我们进行粗粒度的拦截,以保证拦截掉大多数无效的请求,此处缓存时长为半小时

PS:第一层我们尝试过使用本地缓存,提供的dubbo接口采用一致性哈希负载均衡方式,但由于搜索条件的客观不平衡原因(比如航线存在热门航线和冷门航线等),导致分布不均匀,故删去本地缓存

什么是dubbo接口的一致性哈希负载均衡

Dubbo 一致性Hash负载均衡实现剖析

第三层缓存

数据源的实时缓存,我们采用Redis存储,在这时,我们将搜索条件的粒度进行拆分,比如将航班时间提取出来,此处缓存时长为1小时

我们为什么拆分缓存的粒度?

因为航司的退改规则一般是连续的,比如同一条航班,第二天的退改规则大概率和第一天是一样的,当航司采用新的退改规则时,一般是某一个时间段均采用同样的退改规则,这一点很适合用于缓存的粒度拆分

根据业务数据的特性,此层缓存还有其它的粒度拆分,此处毋庸赘述。

第四层缓存

ATPCO数据源的持久化存储,我们采用HBase存储,在这步我们和第三层缓存拆分的粒度保持一致,此处缓存时长为xxx天

这一层主要是避免外部数据源出现问题,导致无数据可用。

效果展示

响应时间

在固定的机器数量的情况下,开量阶段,不同的QPS所统计到的平均相应时间

(集群可根据QPS的实际值来动态扩缩容)

image-20220522170024075

集群隔离

可以明显看到,在红圈标识的地方,QPS最高的第三个集群报价维度搜索集群出现了较大的波动,但此时只影响到了报价维度搜索集群的平均响应时间,另外两个集群没有明显的变化。

image-20220522170157699

降级缓存拦截【重点】

以下为退改签系统的三层缓存的拦截率展示

基本上达到了缓存降级的拦截效果,最终到底层HTTP接口的请求量降低到了2k以下

(图示数据的波动为新开请求量导致)

image-20220522170713424

遇到的问题

在此技术方案实施的过程中,我们也遇到了很多的问题

比如在第二层缓存(我们采用了粗细粒度),我们开了新的航线请求量,导致第二层拦截率大幅下降,此处需要分析我们的粗细粒度是否合理,根据业务和实际的请求数据分析,有没有继续改进的空间

底层HTTP的接口有一定的QPS限制,为了保护底层HTTP接口,我们做了每个系统的入口和出口限流处理,但在请求量上涨的时候,仍有大量的请求被丢弃,无法满足上层请求接口的需要。

image-20220522170955844

新的尝试——拦截无效请求

底层数据的特征分析

我们分析底层HTTP接口返回的数据的特征,发现有75%的请求返回了空结果,有2%的请求结果与上一次请求发生了变化。

虽然我们上面第一层缓存做了一个粗粒度的空结果缓存拦截,能保证大部分无效请求在一开始就被拦截掉

但此处的数据分析,我们还可以继续拦截大概3/4的无效请求

image-20220522171534161

计数排行榜的设计方案

由于请求量比较大,且为分布式,我们首先想到了Redis中的有序集合

我们在Redis中维护这样一个排行榜,每一次请求我们都会给该次请求打一个分值

这样在下一次请求到来时,我们根据这样的排行榜的分值,判断是不是“无效请求”,决定是否要过滤掉这个请求

image-20220522172314083

现实因素

Redis的有序集合通常适用于数据量较小的场景,如果我们使用它,会存在很严重的性能问题

image-20220522172248015

总结

权衡利弊的意识

image-20220522172503175

代码设计的必要

image-20220522172525747