一、前言
携程金融从成立至今,整体架构经历了从0到1再到10的变化,其中有多个场景使用了缓存来提升服务质量。从系统层面看,使用缓存的目的无外乎缓解DB压力(主要是读压力),提升服务响应速度。引入缓存,就不可避免地引入了缓存与业务DB数据的一致性问题,而不同的业务场景,对数据一致性的要求也不同。本文将从以下两个场景介绍我们的一些缓存实践方案:
-
最终一致性分布式缓存场景
-
强一致性分布式缓存场景
注:我们DB用的是MySQL,缓存介质用的是携程高可用Redis服务,存储介质的选型及存储服务的高可用不是本文重点,后文也不再做特别说明。
二、最终一致性分布式缓存场景
2.1 场景描述
经过几年演进,携程金融形成了自顶向下的多层次系统架构,如业务层、平台层、基础服务层等,其中用户信息、产品信息、订单信息等基础数据由基础平台等底层系统产生,服务于所有的金融系统,对这部分基础数据我们引入了统一的缓存服务(系统名utag),缓存数据有三大特点:全量、准实时、永久有效,在数据实时性要求不高的场景下,业务系统可直接调用统一的缓存查询接口。
我们的典型使用场景有:风控流程、APP入口信息提示等,而对数据一致性要求高的场景依然需要走实时的业务接口查询。引入缓存前后系统架构对比如下:
统一缓存服务的构建给部门的整体系统架构带来了一些优势:
对业务系统:
-
响应速度提升:相比直接调用底层高流量的基础服务,调用缓存服务接口的系统响应时间大大减少(缓存查询接口P98为10毫秒)。
-
统一接口,降低接入成本:一部分业务场景下可以直接调用统一缓存服务查询接口,而不用再对接底层的多个子系统,极大地降低了各个业务线的接入成本。
-
统一缓存,省去各个服务单独维护缓存的成本。
对基础服务:
-
服务压力降低:基础平台的系统本身就属于高流量系统,可减少一大部分的查询流量,降低服务压力。
整体而言,缓存服务处于中间层,数据的写入方和数据查询方解耦,数据甚至可以在底层系统不感知的情况下写入(见下文),而数据使用方的查询也可在底层服务不可用或“堵塞”时候仍然保持可用(前提是缓存服务是可用的,而缓存服务的处理逻辑简单、统一且有多种手段保证,其可用性比单个子系统都高),整体上服务的稳定性得到了提升。
在构建此统一缓存服务时候,有三个关键目标:
-
数据准确性:DB中单条数据的更新一定要准确同步到缓存服务。
-
数据完整性:将对应DB表的全量数据进行缓存且永久有效,从而可以替代对应的DB查询。
-
系统可用性:我们多个产品线的多个核心服务都已经接入,utag的高可用性显的尤为关键。
接下来先说明统一缓存服务的整体方案,再逐一介绍此三个关键特性的设计实现方案。
2.2 整体方案
我们的系统在多地都有部署,故缓存服务也做了相应的异地多机房部署,一来可以让不同地区的服务调用本地区服务,无需跨越网络专线,二来也可以作为一种灾备方案,增加可用性。
对于缓存的写入,由于缓存服务是独立部署的,因此需要感知业务DB数据变更然后触发缓存的更新,本着“可以多次更新,但不能漏更新”的原则,我们设计了多种数据更新触发源:定时任务扫描,业务系统MQ、binlog变更MQ,相互之间作为互补来保证数据不会漏更新。
此外为了缓存更新流程的统一和与触发源的解耦,我们使用MQ来驱动多地多机房的缓存更新,在不同的触发源触发后,会查询最新的DB数据,然后发出一个缓存更新的MQ消息,不同地区机房的缓存系统同时监听该主题并各自进行缓存的更新。对于MQ我们使用携程开源消息中间件QMQ 和 Kafka,在公司内部QMQ和Kafka也做了异地机房的互通。
对于缓存的读取,utag系统提供dubbo协议的缓存查询接口,业务系统可调用本地区的接口,省去了网络专线的耗时(50ms延迟)。在utag内部查询redis数据,并反序列化为对应的业务model,再通过接口返回给业务方。
为了描述方便,以下异地多机房部署统一使用AB两地部署的概念进行说明。
整体框架如下图所示:
接下来介绍一下几个关键点的设计 。
2.3 数据准确性设计
不同的触发源,对缓存更新过程是一样的,整个更新步骤可抽象为4步:
-
step1:触发更新,查询DB中的新数据,并发送统一的MQ
-
step2:接收MQ,查询缓存中的老数据
-
step3:新老数据对比,判断是否需要更新
-
step4:若需要,则更新缓存
由于我们业务的大部分核心系统和所有的DB都在A地机房,所以触发源(如binlog的消费、业务MQ的接收、扫表任务的执行)都在A侧,触发更新后,第一步查询DB数据也只能在A侧查询(避免跨网络专线的数据库连接,影响性能)。查询到新数据后,发送更新缓存的MQ,两地机房的utag服务进行消费,之后进行统一的缓存更新流程。总体的缓存更新方案如下图所示:
由于有多个触发源,不同的触发源之间可能会对同一条数据的缓存更新请求出现并发,此外可能出现同一条数据在极短时间内(如1秒内)更新多次,无法区分数据更新顺序,因此需要做两方面的操作来确保数据更新的准确性。
(1)并发控制
若一条DB数据出现了多次更新,且刚好被不同的触发源触发,更新缓存时候若未加控制,可能出现数据更新错乱,如下图所示:
<