作者:京东物流 赵帅 姚再毅 王旭东 孟伟杰 孔祥东
1 前语
物流合约中心是京东物流合同管理的唯一进口。为商家提供合同的创建,盖章等能力,为不同事务条线提供合同的定制,归档,查询等功能。由于各个事务条线很多,为各个事务条线提供高可用查询能力是物流合约中心重中之重。一起计费体系在每个物流单结算时,都需求查询合约中心,确保商家签署的合同内容来确保计费的准确性。
2 事务场景
1.查询维度剖析
从事务调用的来源来看,合同的大部分是计费体系在每个物流单计费的时分,需求调用合约中心来判别,该商家是否签署合同。
从事务调用的入参来看,绝大部分是多个条件来查询合同,但根本都是查询某个商家,或经过商家的某个属性(例如事务账号)来查询合同。
从调用的成果来看,40%的查询是没有成果的,其间绝大部分是由于商家没有签署过合同,导致查询为空。其余的查询成果,每次回来的数量较少,一般一个商家只需3到5个合同。
2.调用量剖析
调用量 现在合同的调用量,大概是在每天2000W次。
一天的调用量统计:
调用时刻 每天高峰期为上班时刻,最高峰为4W/min。
一个月的调用量统计:
由上能够看出,合同每日的调用量比较均匀,首要集中在9点到12点和13点到18点,也便是上班时刻,全体调用量较高,根本不存在调用暴增的状况。
全体剖析来看,合约中心的查询,调用量较高,且较均匀,根本都是随机查询,也并不存在热门数据,其间无效查询占比较多,每次查询条件较多,回来数据量比不大。
3 计划规划
从全体事务场景剖析来看,咱们决定做三层防护来确保调用量的支撑,一起需求对数据一致性做好处理。第一层是布隆过滤器,来拦截绝大部分无效的恳求。第二层是redis缓存数据,来确保各种查询条件的查询尽量射中redis。第三层是直接查询数据库的兜底计划。一起再确保数据一致性的问题,咱们借助于广播mq来实现。
1.第一层防护
由于近一半的查询都是空,咱们首先这是缓存穿透的现象。
缓存穿透问题
缓存穿透(cache penetration)是用户拜访的数据既不在缓存傍边,也不在数据库中。出于容错的考虑,假如从底层数据库查询不到数据,则不写入缓存。这就导致每次恳求都会到底层数据库进行查询,缓存也失去了意义。当高并发或有人利用不存在的Key频繁进犯时,数据库的压力骤增,乃至溃散,这便是缓存穿透问题。
常规处理计划
缓存特定值
一般关于缓存穿透咱们比较常规的做法便是,将不存在的key 设置一个固定值,比如说NULL,&&等等,在查询回来这个值的时分,咱们应用就能够认为这是一个不存在的key,那咱们应用就能够决定是否持续等候,仍是持续拜访,仍是直接放弃,假如持续等候拜访的话,设置一个轮询时刻,再次恳求,假如取到的值不再是咱们预设的,那就代表现已有值了,然后防止了透传到数据库,然后把很多的相似恳求挡在了缓存之中。
缓存特定值并同步更新
特定值做了缓存,那就意味着需求更多的内存存储空间。当存储层数据变化了,缓存层与存储层的数据会不一致。有人会说,这个问题,给key 加上一个过期时刻不就能够了,确实,这样是最简略的,也能在必定程度上处理这两个问题,可是当并发比较高的时分(缓存并发),其实我是不主张运用缓存过期这个战略的,我更期望缓存一直存在;经过后台体系来更新缓存中的数据一致性的意图。
布隆过滤器
布隆过滤器的核心思想是这样的,它不保存实际的数据,而是在内存中树立一个定长的位图用0,1来标记对应数据是否存在体系;过程是将数据经过多个哈希函数计算出不同的哈希值,然后用哈希值对位图的长度进行取模,最后得到位图的下标位,然后在对应的下标位上进行标记;找数的时分也是相同,先经过多个哈希函数得到哈希值,然后哈希值与位图的长度进行取模得到多个下标。假如多个下标都被标记成1了,那么阐明数据存在于体系,不过只需有一个下标为0那么就阐明该数据必定不存在于体系中。
在这里先经过一个示例介绍一下布隆过滤器的场景:
以ID查询文章为例,假如咱们要知道数据库是否存在对应的文章,那么最简略的方法便是咱们把一切数据库存在的ID都保存到缓存去,这个时分当恳求过进入体系,先从这个缓存数据里判别体系是否存在对应的数据ID,假如不存在的话直接回来出去,防止恳求进入到数据库层,存在的话再从获取文章的信息。可是这个不是最好的方法,由于当文章的数量很多很多的时分,那缓存中就需求存很多的文档id而且只能持续增长,所以咱们得想一种方法来节省内存资源当又能是恳求都能射中缓存,这个便是布隆过滤器要做的。
咱们剖析布隆过滤器的优缺陷
长处
1.不需求存储数据,只用比特表明,因此在空间占用率上有巨大的优势 2.检索效率高,刺进和查询的时刻复杂度都为 O(K)(K 表明哈希函数的个数) 3.哈希函数之间相互独立,能够在硬件指令层次并行计算,因此效率较高。
缺陷
1.存在不确定的要素,无法判别一个元素是否必定存在,所以不适合要求 100% 准确率的场景 2.只能刺进和查询元素,不能删去元素。
布隆过滤器剖析:面临长处,完全符合咱们的诉求,针对缺陷1,会有极少的数据穿透对体系来说并无压力。针对缺陷2,合同的数据,原本便是不行删去的。假如合同过期,咱们能够查出单个商家的一切合同,从合同的结束时刻来判别合同是否有用,并不需求取删去布隆过滤器里的元素。
考虑到调用redis布隆过滤器,会走一次网络,而咱们的查询近一半都是无效查询,咱们决定运用本地布隆过滤器,这样就能够削减一次网络恳求。可是假如是本地布隆过滤器,在更新时,就需求对一切机器的本地布隆过滤器更新,咱们监听合同的状况来更新,经过mq的广播形式,来对布隆过滤器刺进元素,这样就做到了一切机器上的布隆过滤器统一元素刺进。
2.第二层防护
面临高并发,咱们首先想到的是缓存。
引进缓存,咱们就要考虑缓存穿透,缓存击穿,缓存雪崩的三大问题。
其间缓存穿透,咱们已再第一层防护中处理,这里只处理缓存击穿,缓存雪崩的问题。
缓存击穿(Cache Breakdown)缓存雪崩是指只很多热门key一起失效的状况,假如是单个热门key,在不断的扛着大并发,在这个key失效的瞬间,持续的大并发恳求就会击破缓存,直接恳求到数据库,好像蛮力击穿相同。这种状况便是缓存击穿。
常规处理计划
缓存失效涣散
这个问题其实比较好处理,便是在设置缓存的时效时刻的时分添加一个随机值,例如添加一个1-3分钟的随机,将失效时刻涣散开,下降集体失效的概率;把过期时刻控制在体系低流量的时刻段,比如凌晨三四点,避过流量的高峰期。
加锁
加锁,便是在查询恳求未射中缓存时,查询数据库操作前进行加锁,加锁后后面的恳求就会堵塞,防止了很多的恳求集中进入到数据库查询数据了。
永久不失效
咱们能够不设置过期时刻来确保缓存永远不会失效,然后经过后台的线程来守时把最新的数据同步到缓存里去
处理计划:运用分布式锁,针对同一个商家,只让一个线程构建缓存,其他线程等候构建缓存履行结束,从头从缓存中获取数据。
缓存雪崩(Cache Avalanche)当缓存中很多热门缓存采用了相同的实效时刻,就会导致缓存在某一个时刻一起实效,恳求全部转发到数据库,然后导致数据库压力骤增,乃至宕机。然后形成一系列的连锁反应,形成体系溃散等状况,这便是缓存雪崩。
处理计划:缓存雪崩的处理计划是将key的过期设置为固守时刻范围内的一个随机数,让key均匀的失效即可。
咱们考虑运用redis缓存,由于每次查询的条件都不相同,回来的成果数据又比较少,咱们考虑限制查询都必须有一个固定的查询条件,商家编码。假如查询条件中没有查商家编码,咱们能够经过商家名称,商家事务账号这些条件来反查查商家编码。
这样咱们就能够缓存单个商家编码的一切合同,然后再经过代码运用filter对其他查询条件做支撑,防止不同的查询条件都去缓存数据而引发的缓存数据更新,缓存数据筛选现已缓存数据一致等问题。
一起只缓存单个商家编码的一切合同,缓存的数据量也是可控,每个缓存的巨细也可控,根本不会出现redis大key的问题。
引进缓存,咱们就要考虑缓存数据一致性的问题。
有关缓存一致性问题,可自行百度,这个就不在叙说。
如图所示 关于商家编码维度的缓存数据,咱们经过监听合同的状况,运用mq广播来删去对应商家的缓存,然后防止出现缓存和数据一致性的相关问题。
3.第三层防护
第三层防护,自然是数据库,假如有查询经过了第一层和第二层,那咱们需求直接查询数据库来回来成果,一起,咱们对直接调用到数据库的线程进行监控。
为防止一些不知道的查询很多查询涌入,导致数据库调用确保的问题,尤其是大促时,咱们能够提前对数据库里的一切商家合同进行提前缓存。在缓存时,为防止缓存雪崩问题,咱们对将key的过期设置为固守时刻范围内的一个随机数,让key均匀的失效。
一起,为防止仍然存在意外的状况,有很多查询涌入。咱们经过ducc开关控制数据库的查询,如调用量太高导致无法支撑,则直接关闭数据库的调用,确保数据库不会直接宕机导致整个事务不行用。
4 总结
本文首要剖析了面临高并发调用的调用场景规划及的技能计划,在引进缓存的一起,也要考虑实际的调用入参及成果,面临添加的网络恳求,是否能够进一步削减。面临redis缓存,是否能够经过一些手法防止一切查询条件都需求缓存,带来的缓存爆炸,缓存筛选战略等问题,以及处理缓存与数据一致等一系列问题。
本计划是根据具体的查询事务场景规划具体的技能计划,针对不同的事务场景,对应的技能计划也是不相同的。