资讯详情

一个复杂系统的拆分改造,压力真大!

点击上蓝色字体,选择星标

回复66获得面试宝典

e0f003a3b389fcf2c4e5af1b458ca5f5.jpeg

1 为什么要拆分?

先看对话。

从以上对话可以看出拆分的原因:

1)

系统中的每个应用程序之间都没有连接,同一功能在每个应用程序中都实现了。其结果是改变一个功能,需要同时改变系统中的所有应用程序。这种情况主要存在于历史悠久的系统中。由于各种原因,系统中的每个应用程序都形成了自己的小闭环业务;

2)

从设计之初,数据模型只支持某一类业务,来到新类业务后,必须重写代码才能实现。因此,项目延期极大地影响了业务的访问速度;

3)

各种随意的if else、写死逻辑散落在应用的各个角落,到处都是坑,开发维护起来战战兢兢;微信搜索微信官方账号:Java项目选择,回复:java 领取资料 。

4)

无论是应用还是应用,系统支持现有业务都在颤抖DB无法承受业务快速发展带来的压力;

5)

不改变的话,最终的结果就是把系统做死了。

2 拆前准备什么?

2.1 多维度把握业务复杂性

系统与业务谈的问题,系统和业务的关系?

我们最期待的理想情况是第一种关系(车辆和人)。如果业务感觉不合适,我们可以立即更换新的。但现实更像是心脏起搏器和人之间的关系。这并不意味着你可以通过改变来改变它。系统连接的业务越多,耦合就越紧密。如果你在真正掌握业务复杂性之前采取行动,最终的结果是带走你的心脏。

如何把握业务的复杂性?需要多维度的思考和实践。

一个是技术层面,通过与pd以及对开发的讨论,熟悉现有的应用领域模型,以及优缺点,这种讨论只能让人们有一个一般的概述,需要通过需求、转换、优化等实践来掌握更多的细节。

熟悉每个应用程序后,我们需要从系统层面构思。如果我们想建立一个平台产品,最重要和最困难的是功能集中控制,打破每个应用程序的小闭环,统一关闭。这一决心更多的是开发、产品、业务方和团队之间的共识,根据业务或客户需求组织资源。

此外,还应与业务方保持功能沟通和计划沟通,以确保应用程序在拆分后满足使用需求和扩展需求,并获得其支持。

2.2 定义边界:高内聚、低耦合、单一责任!

掌握业务复杂性后,需要开始定义每个应用程序的服务边界。什么是好的边界?像葫芦娃兄弟一样的应用是好的!

例如,葫芦娃兄弟(应用程序)之间的技能是相互独立的,遵循单一的责任原则。例如,水娃只能喷水,火娃只能喷火,看不见的宝宝不能喷水,但可以看不见。更重要的是,葫芦娃兄弟最终可以合并为金刚葫芦娃,即这些应用程序虽然相互独立,但相互连接,最终成为我们的平台。

这里很多人会想,如何控制拆分粒度?很难有一个明确的结论,只能说是业务场景、目标和进度的妥协。但总体原则是从大的服务边界开始,不要太详细,因为随着结构和业务的发展,应用程序自然会再次被拆分,使正确的事情自然发生是最合理的。

2.3 确定拆分后的应用目标

一旦系统的宏观应用拆分图出来,就要落实到具体的应用拆分上。

首先要确定的是应用程序拆分后的目标。拆分优化没有底部,可能越来越深,越来越没有结果,然后影响自己和团队的士气。例如,本期的目标是设定db、第二阶段可以重新设计数据模型。

2.4 确定要拆分的当前架构状态、代码状态和依赖状态,并推断出各种可能的异常。

。应用拆分最怕中途说他*是的,这一块不能移动,原来这样的设计是有原因的,必须考虑其他方式!此时的压力可以想象,在整个节奏不符合预期后,它很可能会遇到相同的问题。此时,不仅同事的士气会下降,他们也会失去信心,这可能会导致拆分失败。

2.5 给自己留个锦囊,有准备。

包里有四个字准备好了,可以贴在桌面或者手机上。在以后的具体实施过程中,多想想方案有多种选择吗?复杂的问题可以拆解吗?实际操作中有计划吗?在具体的实践过程中,应用拆分是一个详细的词。多一个计划,多一个计划,不仅可以提高成功的概率,还可以给自己信心。

2.6 放松,缓解压力

收拾好心情,开干!

3 实践

3.1 db拆分实践

DB拆分在整个应用程序拆分环节中是最复杂的,分为垂直拆分和水平拆分两个场景,我们都遇到过。垂直拆分是将库中的每个表拆分到合适的数据库中。例如,如果库中有消息表和人员组织结构表,则更适合将这两个表拆分到独立的数据库中。

水平拆分:以消息表为例,单表超过千万行记录,查询效率较低,此时将其分库分表。

3.1.1 主键id接入全局id发生器

DB拆分的第一件事就是用全局id发生器生成每个表的主键id。为什么?

例如,如果我们有一个表,两个字段id和token,id是自增的主键生成,要以token维度分库分表,继续使用自增主键会出现问题。

在正向迁移扩展中,通过自添加的主键,必须是新的分库分表中唯一的。但是,我们应该考虑迁移失败的场景。如下图所示,假设新的表中插入了新的记录,主键id也是2,假设此时开始回滚,需要将两张表的数据合并成一张表(反向回流),就会产生主要冲突!

因此,在迁移之前,首先要利用全局id发生器生成id替代主键自增id。这里有几种全局唯一id可选择生成方法。

1)snowflake:https://github.com/twitter/snowflake;(非全局递增)

2) mysql专门生成全局唯一的新表id(利用auto_increment功能)(全局递增);

3)有人说只有一张表怎么保证高可用?那两张表(两者不同db),一张表产生奇数,一张表产生偶数。或者n张表,每张表负责不同的步长范围(非全局增加)

4)……

我们使用阿里巴巴内部tddl-sequence(mysql 内存),保证全局唯一但不增加,在使用中遇到一些坑:

1)对按主键id排序的sql因为id不再保证增加,可能会出现混乱场景,此时可以转化为按gmt_create排序;

2)报主键冲突问题。这里往往是代码改造不彻底或者改错造成的,比如忘记给某一insert sql的id添加#{},导致继续使用自增,造成冲突;

3.1.2 建新表&迁移数据&binlog同步

1) 新表字符集的建议是utf8mb支持表情符。,否则可能会导致慢sql!从经验的角度来看,索引不时建议在提前列出计划时写下这些要点,然后逐一检查;

2) 使用全同步工具或自己写作job进行全量迁移;全量数据迁移必须在业务低峰作,并根据系统情况调整并发数;

3) 增量同步。全迁移完成后可使用binlog增量同步工具追逐数据,如阿里内部使用精卫,其他企业可能有自己的增量系统,或使用阿里开源cannal/otter:https://github.com/alibaba/canal?spm=5176.100239.blogcont11356.10.5eNr98

https://github.com/alibaba/otter/wiki/QuickStart?spm=5176.100239.blogcont11356.21.UYMQ17

同步获得增量binlog位点必须在完全迁移之前,否则数据将丢失。例如,如果我在中午12点开始全同步,并在13点完成全同步,则增量同步binlog的位点一定要选在12点之前。

位点在前面会导致重复记录吗?不!MySQL binlog是row 模式,如一个delete删除了100条语句记录,binlog记录的不是一个delete的逻辑sql,而是会有100条binlog记录。insert插入子插入一个记录,如果主要冲突,则无法插入。

3.1.3 联表查询sql改造

现在主键已经进入全局唯一id,新的库表、索引已经建立,数据也在实时追平,现在能开始切库吗?no!

考虑以下非常简单的联表查询sql,如果B表被拆分到另一个库里,这个sql怎么办?毕竟不支持跨库联表查询!

因此,在切库之前,系统中需要查询数百个联表sql改造完毕。

如何改造?

1) 

业务上松耦合后技术才能松耦合,继而避免联表sql。但短期内不现实,需要时间沉淀;

每个应用的库里都冗余一份表,缺点:等于没有拆分,而且很多场景不现实,表结构变更麻烦;

就像订单表一样,冗余商品id字段,但是我们需要冗余的字段太多,而且要考虑字段变更后数据更新问题;

4.1)通过RPC调用来获取另一张表的数据,然后再内存拼接。1)适合job类的sql,或改造后RPC查询量较少的sql;2)不适合大数据量的实时查询sql。假设10000个ID,分页RPC查询,每次查100个,需要5ms,共需要500ms,rt太高。

4.2)本地缓存另一张表的数据

适合数据变化不大、数据量查询大、接口性能稳定性要求高的sql。

3.1.4切库方案设计与实现(两种方案)

以上步骤准备完成后,就开始进入真正的切库环节,这里提供两种方案,我们在不同的场景下都有使用。

:快,成本低;

1)如果要回滚得联系DBA执行线上停写操作,风险高,因为有可能在业务高峰期回滚;

2)只有一处地方校验,出问题的概率高,回滚的概率高

举个例子,如果面对的是比较复杂的业务迁移,那么很可能发生如下情况导致回滚:

sql联表查询改造不完全;

sql联表查询改错&性能问题;

索引漏加导致性能问题;

字符集问题

此外,binlog逆向回流很可能发生字符集问题(utf8mb4到gbk),导致回流失败。这些binlog同步工具为了保证强最终一致性,一旦某条记录回流失败,就卡住不同步,继而导致新老表的数据不同步,继而无法回滚!

第2步“打开双写开关,先写老表A再写新表B”,这时候确保写B表时try catch住,异常要用很明确的标识打出来,方便排查问题。第2步双写持续短暂时间后(比如半分钟后),可以关闭binlog同步任务。

1)将复杂任务分解为一系列可测小任务,步步为赢;

2)线上不停服,回滚容易;

3)字符集问题影响小

1)流程步骤多,周期长;

2)双写造成RT增加

3.1.5 开关要写好

不管什么切库方案,开关少不了,这里开关的初始值一定要设置为null!

如果随便设置一个默认值,比如”读老表A“,假设我们已经进行到读新表B的环节了。这时重启了应用,在应用启动的一瞬间,最新的“读新表B”的开关推送等可能没有推送过来,这个时候就可能使用默认值,继而造成脏数据!

3.2 拆分后一致性怎么保证?

以前很多表都在一个数据库内,使用事务非常方便,现在拆分出去了,如何保证一致性?

性能较差,几乎不考虑。

(如何用消息系统避免分布式事务?)

用得较多,实现最终一致,分为加数据补偿,删数据补偿两种。

3.3 应用拆分后稳定性怎么保证?

a)防御式编程,制定好各种降级策略;

  • 比如缓存主备、推拉结合、本地缓存……

b)遵循快速失败原则,一定要设置超时时间,并异常捕获;

c)强依赖转弱依赖,旁支逻辑异步化

  • 我们对某一个核心应用的旁支逻辑异步化后,响应时间几乎缩短了1/3,且后面中间件、其它应用等都出现过抖动情况,而核心链路一切正常;

d)适当保护第三方,慎重选择重试机制

a)设计一个好的接口,避免误用

  • 遵循接口最少暴露原则;很多同学搭建完新应用后会随手暴露很多接口,而这些接口由于没人使用而缺乏维护,很容易给以后挖坑。听到过不只一次对话,”你怎么用我这个接口啊,当时随便写的,性能很差的“;

  • 不要让使用方做接口可以做的事情;比如你只暴露一个getMsgById接口,别人如果想批量调用的话,可能就直接for循环rpc调用,如果提供getMsgListByIdList接口就不会出现这种情况了。

  • 避免长时间执行的接口;特别是一些老系统,一个接口背后对应的可能是for循环select DB的场景。

b)容量限制

  • 按应用优先级进行流控;不仅有总流量限流,还要区分应用,比如核心应用的配额肯定比非核心应用配额高;

  • 业务容量控制。有些时候不仅仅是系统层面的限制,业务层面也需要限制。举个例子,对saas化的一些系统来说,”你这个租户最多1w人使用“。

a)

b)

  • 例:例如我们改造时候发现一年前留下的坑,去掉后整个集群cpu使用率下降1/3

c) 

  • 说实话,线上出现问题,如果没有预案,再怎么处理都会超时。曾经遇到过一次DB故障导致脏数据问题,最终只能硬着头皮写代码来清理脏数据,但是时间很长,只能眼睁睁看着故障不断升级。经历过这个事情后,我们马上设想出现脏数据的各种场景,然后上线了三个清理脏数据的job,以防其它不可预知的产生脏数据的故障场景,以后只要遇到出现脏数据的故障,直接触发这三个清理job,先恢复再排查。

d)

  • 应用的cpu、内存、网络、磁盘心中有数

    • 正则匹配耗cpu

    • 耗性能的job优化、降级、下线(循环调用rpc或sql)

    • 慢sql优化、降级、限流

    • tair/redis、db调用量要可预测

    • 例:tair、db

举个例子: 某一个接口类似于秒杀功能,qps非常高(如下图所示),请求先到tair,如果找不到会回源到DB,当请求突增时候,甚至会触发tair/redis这层缓存的限流,此外由于缓存在一开始是没数据的,请求会穿透到db,从而击垮db。

这里的核心问题就是tair/redis这层资源的使用不可预测,因为依赖于接口的qps,怎么让请求变得可预测呢?

如果我们再增加一层本地缓存(guava,比如超时时间设置为1秒),保证单机对一个key只有一个请求回源,那样对tair/redis这层资源的使用就可以预知了。假设有500台client,对一个key来说,一瞬间最多500个请求穿透到Tair/redis,以此类推到db。

再举个例子:

比如client有500台,对某key一瞬间最多有500个请求穿透到db,如果key有10个,那么请求最多可能有5000个到db,恰好这些sql的RT有些高,怎么保护DB的资源?

可以通过一个定时程序不断将数据从db刷到缓存。这里就将不可控的5000个qps的db访问变为可控的个位数qps的db访问。

4  总结

这是应用拆分过程中的最有价值的实践经验!

 

某个周五和组里同事吃饭时讨论到某一个功能存在风险,约定在下周解决,结果周一刚上班该功能就出现故障了。以前讲小概率不可能发生,但是概率再小也是有值的,比如p=0.00001%,互联网环境下,请求量足够大,小概率事件就真发生了。

这个词看上去有点玄乎,顾名思义,就是在借者一些事情,来提升另外一种能力,前者称为假,后者称为真。在任何一个单位,对核心系统进行大规模拆分改造的机会很少,因此一旦你承担起责任,就毫不犹豫地全力以赴吧!不要被过程的曲折所吓倒,心智的磨砺,才是本真。

来源 | r6d.cn/xBVa

PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。

 
      

 关注公众号:Java后端编程,回复下面关键字 

 
      

要Java学习完整路线,回复 

缺Java入门视频,回复:

要Java面试经验,回复 

缺Java项目,回复:

进Java粉丝群:

 
      

PS:如果觉得我的分享不错,欢迎大家随手点赞、在看。

 
      

(完)

加我"" 获取一份 最新Java面试题资料

请备注:666,不然不通过~

最近好文

1、Kafka 3.0重磅发布,弃用 Java 8 的支持!

2、你只会用 ! = null 判空?嘿嘿!

3、这次,Swagger-ui遇到对手了!

4、一个基于Spring Boot+Vue+Redis的物联网智能家居系统

5、本机号码一键登录原理与应用

 
      

 
      
最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:关注公众号并回复 java 领取,更多内容陆续奉上。

标签: 压力变送器上mb

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台