阿里妹导读
本篇文章主要是对方案性能优化2.0中,所做的缓存设计的过程、方案、结果做一个总结。
一、前言
二、优化2.0面临什么问题
2.1 1.0优化做了什么
tair使用优化
mget替代tair get方法使用,降低网络资源消耗
2.2 计费流程的核心问题--嵌套循环查询
以上只是简化流程,依然有很深的嵌套循环。
2.3 计费数据涉及模型简述
2.4 当前缓存模型
为什么缓存?要获取 当前报价配置,从DB查询获取,查询涉及表较多,不做缓存,那肯定顶不住。
优点:
目前Tair缓存模型设计,没有把最核心、查询量级最大的方案和报价进行缓存,没解决真正痛点;
在计费过程中仍需要根据每个方案+费用项构造相应的缓存key,需要费用项多多情况下,仍然需要多次查询tair;
实现简单,不用对数据做新的聚合设计,调mapper接口级别缓存。于前期临时、快速解决性能问题的本地缓存方案;
大面积、细粒度使用本地缓存,集群机器本地缓存数据还不一致,易造成客户体验割裂问题(测试有时候都搞不清是bug还是缓存)
粒度太细,计费流程与数据存储层的交互还是嵌套分散在深层次循环流程的内部,当缓存失效,依然会有大量DB查询(特别是循环嵌套最深的报价查询)
不太能支持水平扩容(尝试过,DB扛不住)
缓存数据无法预热,面对大流量场景,程序重启易出现成功率下跌(优化前每次发布基本都会发生)
三、新的缓存
3.1 为什么需要新的缓存设计
3.2 对询价计费流程的重新定义
减少DB访问:配置型数据通过方案中心本地缓存框架访问获取,数据量大的费率模型数据,从tair缓存获取。通过此方案,可以大幅减少DB访问。
减少网络开销:在费用计算前组装好计费所需的数据上下文,通过批量tair查询读取费率,避免在核心流程循环访问获取费率数据,减少RPC请求次数。
图3.2-3 报价缓存失效后的查询高峰
3.3 缓存模型
Tair缓存模型,这里指的是方案和报价的缓存。这里的Tair缓存模型的设计,需要满足两个关键点:可批量查和高速查。贴合业务场景来进行设计,提高匹配方案报价的效率以及命中率。
图3.3-1 Tair缓存模型设计
之前考虑的使用mget可能会受限于key分片问题导致查询缓存性能不稳定。对于方案/报价查询,改用prefixPut,prefixGets进行批量缓存存取,一个主key,多个子key的情况下,以获得更好的批量读取性能。通过prefixGets,可以高效批量获取一次查价中多个sku的方案的缓存数据 或者 报价的缓存数据。
prefixPut发生在查询不到报价、或者方案的时候,进行惰性加载
方案value:没啥特殊的,结构比较简单
图3.3-2 方案缓存模型value
报价value:结构类似方案,核心是多了报价信息result【JSON结构数据】
图3.3-3 报价缓存模型value
报价记录value结构
询价计费时依赖的配置型数据,分成3大类,按照sku、resource、spu做聚合缓存。
key以 类型+ 大类的主键构成:sku+sku_id, resource+resource_id, spu+spu_id
图3.3-4 本地缓存模型value
通过这样的聚合模型设计,询价过程中,通过用skuID可以在本地缓存中检索到任意想要的服务表达定义相关的数据。另外这里缓存的实时性和写逻辑控制,在后面展开。
聚合模型每个子属性更新,需要更新整个模型的数据,这里为什么考虑要做聚合,而不采用每个表的数据都单独一个key缓存的实现方式呢?
综上,相关的配置数据聚合管理的好处大于缺点。
四、缓存读写
4.1 本地缓存
本地缓存预热,程序启动时,根据程序内置逻辑定义的本地缓存Key集合,提前加载缓存到应用内存,保证提供服务时,缓存已经加载。
简单来说就是通过监听精卫,借助广播能力,通知集群更新本地缓存。这里的缓存是一直存在于堆内存,不会失效,只会广播刷新。每次刷新缓存,按照图3.3-4 本地缓存模型value描述的聚合模型,每次更新最小粒度为一个ConfigDTO。
4.2 tair缓存
查询没啥输的,按照 tair缓存模型设计 的key-value,进行prefixGets,prefixPut。需要注意的是,key设计的粒度、报价value大小限制。
这里需要做预热的场景,基本就只有新运力线上线了,一般日常还是没问题的。新运力上线,目前要求是分批灰度,等Tair缓存命中率上去了,继续开启灰度,这是比较保守的做法。
前面有提到,Tair缓存数据的实时性控制,是依靠version_id的实时性控制,方案或者报价的version_id通过本地缓存准实时更新,能够保证version_id的准实时性,从而保证每次查询Tair缓存数据的实时正确性。因为每次获取到的version_id是最新的,拼接出来的Key自然也是查询最新的缓存的Key
4.3 缓存读写总结
图4.3-1 缓存读写总结
方案报价型数据:量大,无法本地缓存,具备版本特性,可以长时间存储在tair。
五、缓存脏数据处理
5.1 本地缓存
尽管精卫很强大,但也不是100%保证没有意外,为避免脏数据产生,因此会采用定时任务刷新的方式来定时更新本地缓存。
5.2 tair缓存
前面的设计有提到,目前的方案/报价缓存子key,是带版本号的,只要版本号正确,就不存在缓存脏数据的问题,而版本号数据实时性,依赖于本方案中的本地缓存实现,二者相互结合,保证查询Tair缓存数据的正确性。另外使用版本号作为缓存key还可以对数据做较长时间的缓存,避免了频繁失效要重新查询报价数据。
六、单点资源瓶颈
6.1 Tair瓶颈
对整个应用集群来说,支撑更大的流量,绕不开单点资源瓶颈,水平扩容更加绕不开单点资源瓶颈。不巧,最近在接入更大的流量场景的时候,就遇到Tair瓶颈问题
图6.1-1 切流Tair出现瓶颈监控视图
图6.1-2 缓存穿透后DB的QPS视图
可以看到出现大量的Tair限流,解决处理方向有几种,简单说一下
很简单,如果是原本Tair的限流阈值很低,那么可以申请扩容。需要注意的是,申请扩容的容量评估,需要结合我们查询缓存方式来评估,鹰眼上看的仅仅是对Tair发起RPC请求的统计,服务端限流统计是按照真正的key个数统计的。例如使用到prefixGet,那么就按Skey个数统计。
如果扩容不能满足,那么就需要回到代码中,看看有没有什么不必要的Tair查询,进行优化。
针对热点Key做一层本地缓存,如果应用服务器的热点本地缓存中包含key,那么就不需要查询Tair了,可以直接返回结果,降低对Tair的压力。热点key的识别可依赖Tair内嵌的LocalCache功能,或者我们自己实现,动态配置热点Key。
使用RDB。对持久化有需求,并且缓存QPS确实很高,如果当前使用的是LDB,那么可以考虑使用RDB,LDB成本比较高,没那么多资源。RDB成本相对较低,可以有更多资源。
这次遇到Tair瓶颈,方案中心是先从简单的方向1、2入手。
首先申请扩容,一开始评估预计QPS,按照的鹰眼平台展示的来估,因为方案中心使用了prefixGet,因此估少了,扩容完成后发现还是限流。
无奈,但也没继续申请扩容,而是到业务、代码中,分析可以减少的查询Tair的点。
七、总结
7.1 数据
通过本地缓存配置型数据 + tair缓存方案报价型数据的组合,缓存命中的场景下,查价计费链路已经可以实现无DB查询。目前线上稳定支持水平扩容,按照压测数据预估,单机支持180QPS,单集群50台机器支撑9000+qps
结合新的缓存组合,代码路径实现调整如下:
获取N个运力线方案版本+报价版本:查询本地缓存
批量获取N个运力线方案:查询tair or DB
快速玩转 Llama2!阿里云机器学习 PAI 推出最佳实践