前言
携程酒店订单系统的存储设计从1999年收录第一单以来,已经完成了从单一SQLServer数据库到多IDC容灾、完成分库分表等多个阶段,在见证了大量业务奇迹的同时,也开始逐渐暴露出老骥伏枥的心有余而力不足之态。基于更高稳定性与高效成本控制而设计的订单存储系统,已经是携程在疫情后恢复业务的必然诉求。
目前携程酒店订单系统正面临着在业务高增长的同时信息读写管理能力受制于数据库自身性能与稳定性的窘境。综合分析,一则为携程服役了十多年的SQLServer服务器集群的磁盘容量设计,已经跟不上时下新增订单量的空间诉求;二则在系统能力提升上造成了各大业务系统巨大的底层瓶颈与风险,同时又相比业界主流已基于MySQL架构设计存储系统而言,我们的订单存储系统仍基于SQLServer构建也整体推高了运营成本。
为了支撑未来每日千万级订单的业务增长目标,同时满足高可用、高性能、高可扩展的高效成本控制期望,我们为酒店部门的订单DB所有访问开发并落地了一套稳定且可靠的统一中间件封装方案,对现状收敛并提供了全局统一的热点缓存系统,彻底解决了当下订单上层应用与数据库间直连的方案缺陷。
新系统由中间件服务统一实现了对上层应用提供数据链服务,并达成了为现有依赖订单库的应用以及其他直接或间接的数据应用无感的实现存储底层由SQLServer向MySQL技术架构迁移的目标。
一、架构综述
通过对现有系统瓶颈的分析,我们发现核心缺陷集中在订单数据缓存分散导致数据各端不一致,各订单应用则与数据库直连又造成可扩展性差。通过实践我们编写中间件抽象并统一了数据访问层,以及基于数据库部署架构镜像构建了订单缓存统一管理热点数据,解决了各端差异。
图1.1 存储系统架构图
二、应用场景
2.1 新单秒级各端同步
从订单的提交到各端可见的速度为存储服务的核心指标之一,我们对数据链的主要环节进行了优化,覆盖了新单同步、消息实时推送、查询索引构建以及数据平台离线归档等主要环节,使大系统内数据到达速度在3秒以内,即用户刚下完单即可跳转我携列表可见。
当新用户创单时,同步服务作为数据链入口将用户订单数据通过中间件写入订单库,此时中间件同时完成订单缓存的构建;当订单完成入库行为和热点数据构建后抛订单消息,实时输出给各子系统;当新单入库完毕即刻构建订单明细信息的ES索引,为第三方提供检索支持;最后数据平台T+1实施当日数据的归档供BI等各类离线业务使用;
图2.1 数据链
2.2 自动发单与工作台
对客、商、员工工作台三端的支持是订单存储系统的基本角色,图2.1数据链在新单提交后为自动发单与工作台起到的衔接作用功不可没。自动发单即在客人提交订单后,以最快的响应速度向商户发送订单明细信息进行核实货位、确认订单等流程。工作台则协助员工介入流程及时获取订单处理人工事件。
图2.2 基于存储系统的发单与工作台关系(缩略细节)
2.3 查询与数据分析
基于订单数据为核心的主要分为在线查询和数据分析两条业务线,以对详情查询为例,访问QPS终年保持在高位,每逢假期高峰则容易造成查询瓶颈,根因复盘后在本次架构升级中我们做了调整来优化相关场景的高可用性。
在线查询以订单缓存为主,订单提交即构建热点缓存纾解查询压力,并可按配置时间参数长时段有效。非在线查询场景,以实时消息推送并结合Hive数仓T+1方式交付,凡需要长周期订单数据的场合(例如实时报表)均接入订单消息实时计算。离线BI按年度等大批量数据分析时使用Hive表,并每日凌晨低峰时段以从库低频访问的方式实施数据同步。
如此以上,我们将订单主库的访问保护在订单缓存、实时消息、Hive数仓三架马车之后,与业务尽最大可能的解耦。
三、系统升级实践
在对携程核心存储系统进行更新换代的过程中,贯穿全程需要做到的是热迁移,并达成所有操作对数据链路上的各应用透明无损的目标。我们的设计通盘分析了集团数据链路的特性,由订单缓存系统提供数据库镜像降低应用与数据库的直连耦合,继而再通过中间件对应用透明掉数据源于SQLServer / MySQL的物理关系,提供底层热迁移的操作空间。
结合无损迁移的工艺设计,注重对每一笔数据库流量的可见及可控,支持全库、Shard级、表级、CRUD操作级的流量分配策略,提供了底层数据迁移足够的实施手段。数仓衔接设计则侧重于解决数据平台百亿级离线数据与双库在线期间的同步问题,以及解决全量接入MySQL期间产生的数据问题。
以下将分三个部分分享我们在这一过程中学到的经验。
3.1 分布式订单缓存
随着业务发展,用户数和访问量越来越大,订单系统应用和服务器的压力也与日俱增。在没有引入订单缓存之前,每个应用独立连接数据库,造成查询出来的数据无法在应用间共享,并且DB每秒查询量和连接数都有上限,而酒店核心交易链路基于DB存储,存在单点故障风险。
经过埋点数据分析,订单系统是典型的读多写少,为了共享热点查询数据以及降低DB负载,一个有效的办法就是引入缓存,如图3.1,用户的请求过来时,优先查询缓存,如果存在缓存数据,则直接返回结果;缓存没有命中,则去查询DB,根据配置策略校验DB结果数据,校验通过则将DB数据写入缓存留作后续查询使用,否则不写入缓存,最后返回DB查询结果。
图3.1 订单缓存基本设计
关于引入新的缓存组件后的硬件开销,可通过收敛原来各应用分散的硬件资源来降低总成本,但还会因为中心化管理带来可用性挑战以及数据一致性等问题,故需要充分对现有系统进行容量评估、流量估算和缓存表价值分析。只缓存访问量高的热点数据表,通过恰当的缓存结构设计、数据压缩和缓存淘汰策略,最大程度提高缓存命中率,在缓存容量、硬件成本和可用性之间做好权衡。
传统的缓存设计,是一条数据库表记录对应一条缓存数据。而在订单系统中,一个订单查询多表的场景很常见,如果采用传统设计,在一次用户查询中,Redis的访问次数是随着表数量增加的,这种设计网络IO较大并且耗时较长。在盘点表维度流量数据时,我们发现有些表经常一起查询,不到30%的表其查询流量超过90%,在业务上完全可以划分为同一个抽象领域模型,然后基于hash结构进行存储,如图3.2,以订单号作为key,领域名称作为field,领域数据作为value。
这样无论是单表还是多表查询,每个订单都只需要访问一次Redis,即减少了key,又减少了多表查询次数,提升了性能。同时value基于protostuff进行压缩,还减少了Redis的存储空间,以及随之而来的网络流量开销。
图3.2 基于domain的存储结构简述
3.2 无损迁移工艺
如何做到无损热迁移是整个项目最具挑战性的地方。在工艺设计之前我们的前置工作首先完成了中间件的开发,通过中间件将数据库与业务层应用一分为二。其次抽象Dao层实现领域化,并由数据领域层向应用提供数据服务,领域之下适配SQLServer和MySQL两种数据库并统一封装。以此为基础才能委以下述工艺设计实施无损热迁移。
SQLServer和MySQL双库在线,实施双写,主写SQLServer,同步副写MySQL,如果SQLServer操作失败则整体失败,回滚双写事务。SQLServer和MySQL之间增加一路同步Job,实时查询SQLServer最近时间窗口变更的数据进行一致性校验MySQL中的条目,差异点追齐,可以确保双写期间不可预期的两边不一致,特别是还残有直连写SQLServer应用的阶段特别有用。中间件设计有配置系统,支持任一主要查询维度可按配置精准的将数据源定向到SQLServer或MySQL,并可控制是否读取后加载到订单缓存。初期设定只加载SQLServer数据源,避免因双库间的数据不一致而造成缓存数据跳跃。并在初期可设置灰度,将小批量非核心表直连MySQL验证可靠性。后期数据一致性达成预期后,订单缓存也可自由按指定数据库加载缓存。解决了查询场景下的数据一致性问题后,流量策略支持图3.2中任一可调控维度进行数据库单写。实际项目中以表维度实施单写为主,当指定表被配置单写MySQL后,所有涉及该表的CRUD行为全部定向MySQL,包括缓存加载源。最后通过中间件统一收口对外发送的订单消息,所有消息基于中间件的CUD操作发送与物理数据库无关,这样实现消息的数据源透明,且可联动以上所有工艺操作,数据链保持一致。
图3.2 操作工艺简介
3.3 数仓衔接
为了方便理解生产数据到数据仓库ODS层数据的迁移,做到对下游透明,这里简单介绍一下常规数据仓库的分层体系。通常数据仓库主要分为五层:ODS(原始数据层)、DIM(维度)、EDW(企业数仓)、CDM(通用模型层)、ADM(应用模型层),如下图所示:
图3.3.1 数据仓库分层结构
从图3.3.1上可以看出,数据仓库各层都依赖ODS层的数据,为了不影响数据平台所有应用,我们只需要将原来订单库ODS层数据源从SQLServer迁移到MySQL库即可。
从图上很直观的看出,迁移只需换个数据源不是很麻烦,但是为了保证数据质量,我们做了很多的前置工作,比如:DBA预先将生产数据同步到生产MySQL库、MySQL数据实时同步、生产两侧数据一致性校验、MySQL侧数据同步到ODS层、ODS层数据一致性校验及原有ODS层同步Job数据源切换等。
其中,生产两侧数据一致性校验和数据仓库ODS层数据一致性校验最为复杂,耗时也最长,要确保每张表、每个字段都要一致时才能切换数据源。但是,从实际操作过程中,却做不到完全一致。根据实际情况,适当处理时间类型、浮点值精度及小数位等。
下面介绍一下整体流程:
首先,对于线上数据一致校验,我们开发了在线同步Job,将SQLServer的数据和MySQL数据进行比较,发现不一致时,就将MySQL的数据以SQLServer数据为基准更新掉,确保两边数据的一致性。
其次,对于离线数据一致性校验,我们和数据仓库同事合作把MySQL侧数据同步到ODS层(以库名区分是SQLServer还是MySQL的表),并且将定时跑的任务和SQLServer侧任务在时间上尽量一致。两侧数据都准备好后,我们开发了离线数据校验脚本生成器,根据数据仓库元数据,为每张表生成一个同步Job,将其部署到调度平台。
同步任务会依赖两侧ODS层同步数据,T+1数据同步完成后,执行一致性校验,将不一致的订单号记录到不一致明细表中,并统计不一致的数据量,将结果保存到统计表中。然后在自助报表平台制作一个报表,将每天统计的不一致的表及不一致量发送到邮箱,我们每天对不一致的表进行排查找出问题,调整比较策略,更新比较Job。大致流程如下:
图3.3.2 一致性校验整体流程
最后,随着线上和离线数据逐步趋于一致后,我们将原先SQLServer同步到ODS层Job的数据源切换到MySQL。这里可能有同学会有疑问:为什么不直接使用MySQL侧ODS层的表呢?原因是,经过统计,依赖原先ODS层表的Job有上千个之多,如果让依赖Job切换到MySQL侧ODS表,修改工作量非常大,所以我们直接将原来的ODS层同步数据源直接切换成MySQL。
实际操作中,切数据源并不能一次全部切完,我们分三批进行,先找十几个不那么重要的表作为第一批,切完后运行两周,并收集下游数据问题的反馈。第一批表顺利切完两周后,我们没收到下游报数据问题,说明数据质量没问题。然后再将剩余的几百张表按重要程度分两批继续切,直到切完。
至此,我们完成了订单库从SQLServer迁移到MySQL在数据仓库层的迁移工作。
四、核心问题精编
实际上再周密的分析与设计,总是难免遇到执行过程中的各种挑战。我们总结了一些经典问题,虽然通过技术手段最终解决了这些大大小小问题并达成了目标,但是相信各位看官必定还有更好的解决方案,我们乐见共同学习与进步。
4.1 SQLServer & MySQL 流量迁移如何细粒度监控
订单系统涉及到的应用和表数量众多,一个应用对应1到n张表,一张表又对应1到n个应用,是典型的多对多关系。如图4.1,对于上层应用来说,从一个SQLServer数据库,切换到另一个MySQL数据库,其基本流程参照操作工艺章节至少分为以下几步:
从单写SQLServer变成双写SQLServer和MySQL从单读SQLServer变成单读MySQL从双写SQLServer和MySQL变成单写MySQL下线SQLServer
图4.1 应用和数据库以及表的关系图
在生产环境更换数据库系统,就像在高速公路上不停车换轮胎,需要维持原有的车速不变,且对用户无感,否则后果不敢设想。
在切换工艺中双写、单读和单写流程,环环相扣,步步相依,作为配套设计监控手段必须确认上一个操作达到预期效果才能进行下一个。如果跳过或者没有切换干净就贸然进行下一步,比如还没有双写完全一致,就开始读MySQL数据,可能造成查无此数据或者查到脏数据!那么就需要对每一个CRUD操作的读写进行监控,在迁移过程中做到360度无死角可视化流量细分控制,所见即所得。具体的做法如下:
所有应用接入中间件,CRUD由中间件根据配置控制读写哪个DB的哪张表;每一个读写操作的详细信息均写入ES,在Kibana和Grafana上可视化展示,并且通过DBTrace,可以知道每条SQL是在哪个DB上执行;按照应用级别逐步配置双写DB,通过同步Job实时比对、修复和记录两侧DB差异,再通过离线T+1校验双写中出现的最终不一致,如此往复直到双写一致;双写一致之后,就开始逐步将读SQLServer切换到读MySQL,通过ES监控和DBTrace确认完全没有SQLServer读,则表明单读MySQL完成,考虑到自增主键情况,我们采取按照表维度,按批次断写SQLServer,直至所有表都单写MySQL。
综上所述,基本方案为通过中间件为管道为所有接入的应用统一埋点,通过实时展示应用层的行为观察流量分布,并结合公司数据库侧Trace的可视化工具核实应用的流量切换行为与数据库实际QPS及负载浮动保持一致来监督迁移任务。
4.2 如何解决双写期间DB一致性问题
酒店的订单库有着二十年左右历史,经年累积,跨部门和酒店内部多个团队直接或间接依赖订单库SQLServer,要想切换到MySQL,就得先解决双写DB一致性问题,不一致主要体现在以下两点:
双写时实际仅单写了SQLServer,漏写MySQL;双写SQLServer和MySQL成功,并发、不可靠网络、GC等发生时MySQL数据有几率和SQLServer不一致;
关于双写数据一致性的保证,我们基于同步Job将SQLServer数据为准线,根据最后更新时间,拉取两侧DB数据进行比对,如果不一致则修复MySQL的数据并将不一致信息写入ES,供后续排查根因。
但也因为引入了额外的Job操作MySQL数据,带来了新的问题,那就是多表双写时,因为耗时翻倍,Job发现SQLServer有数据而MySQL没有,就立即修复了MySQL数据,造成双写失败。所以双写部分失败又加上了Failover机制,通过抛送消息,触发新一轮的比对和修复工作,直到两侧DB数据完全一致。
同步Job和Failover消息机制虽然可以让数据最终一致,但毕竟有秒级的间隔,两侧数据是不一致的,并且对于众多应用的各种场景,难免会有遗漏时单写SQLServer。对于这些漏写MySQL的地方,通过DBTrace是无法找到的,因为无法确定一个CUD操作只写入SQLServer,而未写入MySQL。那么有没有办法事前就能找出漏写MySQL的场景呢,确实被我们找出来一点,那就是更换数据库连接串,接入中间件的应用使用新连接串,然后找出所有使用旧连接串操作SQLServer的SQL,就能准确定位出漏写MySQL的流量了。
最终,我们将双写DB不一致率从十万分之二逐步降低到了几乎为0,为什么是几乎呢,因为DB的一些特性差异问题,会天然的导致数据无法完全一致,这个在后续内容会有详细的论述。
4.3 引入订单缓存后导致的数据不同步问题处理
引入缓存之后,就涉及到对缓存进行写入或者更新,业界常见的做法分为以下几种:
先写DB再写缓存先写缓存再写DB先删缓存再写DB先写DB再删缓存
在具体实施上还会进行双删缓存或者延迟双删缓存,此处不再比较各种做法的优劣。我们采用的是先写DB再删缓存方案,对于数据敏感表,会进行延迟双删,后台的同步Job定时比对、修复和记录数据库数据与Redis数据的差异,虽然设计上已经能保证最终一致性,但是在前期还是出现过大量的数据不一致。主要体现在以下几个方面:
应用有场景未接入中间件,对DB进行CUD操作之后,漏删除缓存;写DB后删除缓存延迟导致读取到缓存脏数据,比如不可靠网络、GC等造成删缓存延迟;写DB后删除缓存失败导致读取到缓存脏数据,比如Redis主从切换期间,只能读不可写;
而为了解决缓存一致性问题,如图4.3,我们在原有的缓存和DB基础上,增加了乐观锁和CUD施工标记,来限制并发情况下同时存在加载数据到缓存相互覆盖的行为,以及对当前被查数据正在进行CUD操作的感知。在此两种场景未结束期间可以做到Query流量直连DB,通过基于乐观锁的最后写入者获胜机制解决竞争问题。最终我们的缓存不一致率从百万分之二控制到了千万分之三。
图4.3 缓存一致性解决
注:图4.3当查询未命中缓存,或当前存在该数据的乐观锁或施工标记时,当次查询直连DB,直至相关事务完成后放开缓存数据自动加载功能。
4.4 存量订单数据如何一次性校准
项目启动初期我们对MySQL进行了最近N年数据的一次性铺底,这就产生了在双写阶段无法校准的如下两个场景的数据:
因生产上订单库预置保留近N年的数据,负责清理备份的Job在接入中间件前,MySQL已存在的N年外的这批数据无法被策略覆盖而清理掉。所有应用接中间件花了很长时间,接中间件双写前数据有可能不一致的,需要全部应用接中间件和全部表双写后,对之前的数据进行一次性修复。
针对第一点,我们开发了MySQL数据专项清理Job,由于订单数据库是多Shard的,Job内部根据实际Shard数设置核心线程总量,每个线程分别负责对应Shard中的指定表进行清理,并行开多台服务器分发任务进行清理,通过速度控制既保证了效率又不影响生产上数据库的负载。
针对第二点,在所有应用接中间件和所有表实现双写后,通过调整线上同步Job扫描的开始时间戳,对存量订单数据进行修复。修复时特别注意的是,扫描数据要按时间段分片处理,防止加载数据太多导致订单库服务器CPU太高。
4.5 一些数据库特性差异问题
在如此庞大的系统下进行数据库热迁移,我们就必须了解不同数据库之间的差异与不同,做到知己知彼,对症下药。MySQL与SQLServer虽同为时下流行的关系型数据库,均支持标准化SQL查询,但在细枝末节上还是有些许差异。下面我们通过迁移中所面临的问题来具体分析一下。
自增键问题,为保证数据自增序号一致,不能让两个数据库各自去进行自增,否则一旦不一致就要面临修数据甚至更大风险。因此,在数据双写时,我们将SQLServer写入后生成的自增id,回写入MySQL自增列,在数据单写MySQL时直接使用MySQL生成自增id值。
日期精度问题,双写后为了保证数据一致性,要对两侧数据进行一致性校验,类型为Date、DateTime、Timestamp的字段,由于保存精度不一致,在对比时就需要做特殊处理,截取到秒进行比较。
XML字段问题,SQLServer中支持XML数据类型,而MySQL 5.7不支持XML类型。在使用varchar(4000)代替后,遇到MySQL数据写入失败,但同步Job将SQLServer数据回写MySQL时又能正常写入的案例。经过分析,程序在写入时会将未压缩的XML字符串写入,SQLServer XML类型会自动压缩并存储,但MySQL并不会,导致长度超过4000的写入操作失败,SQLServer压缩后长度小于4000,又能够正常回写MySQL。为此我们提出应对措施,写入前压缩并校验长度,非重要字段截取后再存储,重要字段优化存储结构或更换字段类型。
下面列举一些迁移过程中常见的注意点。
SQLServer
对应MySQL
使用IDENTITY自增
使用AUTO_INCREMENT自增
MONEY、SMALL MONEY类型
DECIMAL(19,4)、DECIMAL(10,4)类型
UNIQUEIDENTIFIER类型
BINARY(16)类型
串联运算符+或||
CONCAT('string1', 'string2')函数
日期函数GETDATE()
NOW()、CURRENT_TIMESTAMP()
日期函数DATEADD()
ADDDATE()
Top子句
使用Limit
VARCHAR(n)可存储n/2个汉字
VARCHAR(n)可存储n个汉字
五、预警实践
我们的预警实践并不局限于项目推进期间的监控诉求,如何在百亿级数据中周期扫描数据写入的异常,完成项目期间双写数据一致率的复核,如何实时监控与预警订单库每个分片上订单写入量的正常趋势,如何定期验收/核验整套系统的高可用性将在以下篇幅中描述。
5.1 百亿级数据差异校验预警
要满足订单数据SQLServer迁移到MySQL库,数据质量是迁移的必要条件,数据一致性达不到要求就无法透明迁移,所以设计合理的校验方案,关乎迁移的进度。针对数据校验,我们分为线上和线下两种:
线上数据校验和预警:迁移期间我们通过同步Job,在计算出不一致数据后,将不一致的表及字段写入ElasticSearch,再用Kibana制作出不一致数据量及不一致表所占比例的监控看板,通过监控看板,我们就可以实时监控哪些表数据不一致量比较高,再根据表名称通过DBA工具排查出哪些应用对表进行了CUD操作,进一步定位漏接中间件的应用和代码。
在实际操作中,我们确实找出了大量未接中间的应用并对其改造,随着接入中间件的应用越来越多,数据一致性逐渐提高,从监控看板上看到不一致的量也慢慢降低。但是一致性始终没有降低到零,原因是应用和同步Job并发导致的,这个也是最令人头疼的问题。
或许有同学会疑问,既然双写了为什么不停止掉同步Job呢?原因是双写以SQLServer为主写,以受中间件覆盖的CUD范围为基准,除了不能保证写入MySQL的数据百分百成功外也不能保证两库的数据量相等,所以需要一致性Job兜底。由于并发的存在,虽然做不到数据百分百一致,但是可以进一步降低。
我们的做法是,一致性Job比较时设置一个5秒的稳定线(即距离当前时间5秒内的数据视为不稳定数据),订单数据时间戳在稳定线内的不进行比较,稳定线外的比较时,会再一次计算订单数据是否在稳定线内,如果确认全部数据在稳定线外,就进行比较操作,否则放弃本次比较,由下一次调度执行一致性校验。
离线数据校验和预警:订单库迁移涉及到几百张表,离线数据比较多,一年的订单相关数据就有上百亿了,对于离线数据校验比较有挑战。我们编写了数据一致性脚本生成器,为每张表生成一个比较脚本并部署到调度平台,比较脚本依赖上游SQLServer和MySQL两侧的同步Job,上游Job执行完毕后自动执行数据比较,将不一致数据的订单号写到明细表中,并根据明细表统计出不一致量,以日报的形式发出,每天对数据不一致比较高的表排查并解决。
通常一是能修复对比脚本的瑕疵,二是发现离线数据问题,就这样反复摸排解决不一致问题。对于离线数据每张表每个字段的校验是非常复杂的,我们编写UDF函数进行比较,UDF函数功能也很简单,就是将每张表的非主键字段进行拼接生成一个新字段,两侧表进行全外连接,主键或者逻辑主键相等的记录,生成新字段也应该一样,只要不一样就视为不一致数据。这里要注意日期字段截取、数据精度及末尾为零的小数处理问题。
经过三个多月的努力,我们排查出所有未接中间件的应用,并将其CUD操作全部接入中间件,开启双写后线上线下数据一致性逐步提高,达到了迁移数据的目标。
5.2 ALL Shard 实时订单总量监控
每个公司对于订单量的监控是不可或缺的,携程有一个统一预警平台Sitemon,它主要监控各类订单告警,包括酒店,机票,无线,高铁,度假。并能按照online/offline,国内/国际,或者支付方式单独搜索和展现,并对各类订单做了告警。
订单数据从SQLServer迁移到MySQL期间,我们梳理出来依赖订单库的预警策略近两百个,负责监控的相关同事对SQL Server数据源的预警策略原样复制一份连接MySQL数据源。以MySQL为数据源监控告警都添加完成后,开启报警策略,一旦订单量异常报警,NOC会收到两条通知,一条来源于SQLServer数据告警,一条来源于MySQL告警,如果两边一致,说明灰度验证通过。否则,不通过,需排查MySQL 监控问题。
经过一段时间的灰度验证,两边报警数据一致,随着SQLServer数据表下线(即单写MySQL数据),以SQLServer为数据源的预警策略也跟着及时下线。
5.3 “流浪地球”实操
为了做好系统安全保障工作,提高应对突发事件的能力,必要的演练压测等是少不了的。为此,我们制定了完备的应急预案并定期组织开展应急演练——流浪地球。演练项目包括核心/非核心应用熔断、DB熔断、Redis熔断、核心防火墙、交换机应急切换等。
以缓存为例,为了保证缓存服务的高可用,我们在演练时会下线部分节点或机器甚至切断整个Redis服务,模拟缓存雪崩、缓存击穿等场景。按照计划,在熔断前我们会先切断应用的Redis访问,一步步降低Redis负载,然后熔断Redis,以此检验在无Redis的情况下各应用系统是否能够正常运转。
但在首次演练中,熔断Redis后应用报错量就急剧上升,果断停止演练回退并查找原因。经过分析,部分应用Redis操作未统一收口,不受中间件统一控制,Redis熔断后应用随即出现异常。针对这一情况,我们分析后一方面将报错应用的订单缓存访问收口接入中间件,另一方面强化了中间件与Redis的弱依赖关系,支持一键断开Redis操作,并完善了各项指标监控。最终在第二次演练中顺利完成Redis熔断,各业务系统在全流量打入MySQL的状态下的正常运行。在最近一次的流浪地球演练中,机房网络阻断、非核心应用阻断等一轮轮故障注入后,我们的系统更是取得了很好的预期效果。
就这样,在一次次的演练中,我们发现问题,总结经验,优化系统,完善应急预案,一步步提升系统应对突发故障的能力,保证业务的连续性以及数据的完整性。做好底层数据支撑,为整个酒店订单系统保驾护航。
六、未来规划
6.1 订单缓存手工调控台
虽然我们有完善的监控看板与预警系统,但对于像熔断演练、自动化故障演练、硬件故障和维护以及不可提前预知的问题,若刚好核心开发人员未能及时在现场响应操作,系统尚不能完全自主降级可能导致部分性能有所下降,比如响应耗时增加等。在将来计划增加手工调控看板,授权后可以让NOC或者TS进行针对性操作,比如Redis全部或者部分集群宕机,可以一键切割故障Redis分片,或者根据Redis已计划中的不可用时间段来提前设置切割时间,可以最大程度保证系统的可控性。
6.2 中间件自动降级
既然可以手工进行调控,那么我们也考虑后续可以通过一些核心指标的监控,比如Redis主从切换期间,正常情况是秒级,但是我们也出现过部分Redis 10秒以上不可写的情况,此时可以监控缓存与数据库不一致的脏数据量,也可以在Redis发生故障时通过监控响应耗时异常的阀值来应用一些策略,让中间件自动降级切割掉这些故障主机保证服务的基本稳定,然后在探测到集群指标稳定后再逐步尝试恢复。
6.3 中间件接入Service Mesh
当前订单团队内部是以JAR的方式使用中间件,由中间件来屏蔽数据库底层差异和操作Redis以实现更复杂的功能,天然具备接入Service Mesh能力,接入后底层升级更加快速和无感、调用更加轻量化、更好与框架进行网格化集成以及上云更加方便,能够更好的支撑携程的国际化战略目标。
【作者简介】
荣华,携程高级研发经理,专注于后端技术项目研发管理。
军威,携程软件技术专家,负责分布式缓存系统开发 & 存储架构迁移项目。
金永,携程资深软件工程师,专注于实时计算,数据分析工程。
俊强,携程高级后端开发工程师,拥有丰富SQLServer使用经验。