文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

开发 | 再见了,公司的“烂系统”

2024-12-03 07:31

关注

图片来自 Pexels 

为什么要拆分?

先看一段对话:

 

从上面对话可以看出拆分的理由:

[[391482]] 

拆前准备什么?

多维度把握业务复杂度

一个老生常谈的问题,系统与业务的关系?

 

我们最期望的理想情况是第一种关系(车辆与人),业务觉得不合适,可以马上换一辆新的。

但现实的情况是更像心脏起搏器与人之间的关系,不是说换就能换。一个系统接的业务越多,耦合越紧密。

如果在没有真正把握住业务复杂度之前贸然行动,最终的结局就是把心脏带飞。

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

一个是技术层面,通过与 PD 以及开发的讨论,熟悉现有各个应用的领域模型,以及优缺点,这种讨论只能让人有个大概,更多的细节如代码、架构等需要通过做需求、改造、优化这些实践来掌握。

各个应用熟悉之后,需要从系统层面来构思,我们想打造平台型的产品,那么最重要也是最难的一点就是功能集中管控,打破各个应用的业务小闭环,统一收拢。

这个决心更多的是开发、产品、业务方、各个团队之间达成的共识,“按照业务或者客户需求组织资源”。

此外也要与业务方保持功能沟通、计划沟通,确保应用拆分出来后符合使用需求、扩展需求,获取他们的支持。

定义边界,原则:高内聚,低耦合,单一职责

业务复杂度把握后,需要开始定义各个应用的服务边界。怎么才算是好的边界?像葫芦娃兄弟一样的应用就是好的!

举个例子,葫芦娃兄弟(应用)间的技能是相互独立的,遵循单一职责原则,比如水娃只能喷水,火娃只会喷火,隐形娃不会喷水喷火但能隐身。

更为关键的是,葫芦娃兄弟最终可以合体为金刚葫芦娃,即这些应用虽然功能彼此独立,但又相互打通,最后合体在一起就成了我们的平台。

[[391484]] 

这里很多人会有疑惑,拆分粒度怎么控制?很难有一个明确的结论,只能说是结合业务场景、目标、进度的一个折中。

但总体的原则是先从一个大的服务边界开始,不要太细,因为随着架构、业务的演进,应用自然而然会再次拆分,让正确的事情自然发生才最合理。

确定拆分后的应用目标

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

首先要确定的就是某一应用拆分后的目标。拆分优化是没有底的,可能越做越深,越做越没结果,继而又影响自己和团队的士气。

比如说可以定这期的目标就是将 DB、应用分拆出去,数据模型的重新设计可以在第二期。

确定当前要拆分应用的架构状态

例如,代码情况、依赖状况,并推演可能的各种异常。

动手前的思考成本远远低于动手后遇到问题的解决成本。应用拆分最怕的是中途说“他*的,这块不能动,原来当时这样设计是有原因的,得想别的路子!”

这时的压力可想而知,整个节奏不符合预期后,很可能会接二连三遇到同样的问题,这时不仅同事们士气下降,自己也会丧失信心,继而可能导致拆分失败。

给自己留个锦囊,“有备无患”

锦囊就四个字“有备无患”,可以贴在桌面或者手机上。

在以后具体实施过程中,多思考下“方案是否有多种可以选择?复杂问题能否拆解?实际操作时是否有预案?”,应用拆分在具体实践过程中比拼得就是细致二字,多一份方案,多一份预案,不仅能提升成功概率,更给自己信心。

放松心情,缓解压力

收拾下心情,开干!

改造实践

DB 拆分实践

DB 拆分在整个应用拆分环节里最复杂,分为垂直拆分和水平拆分两种场景,我们都遇到了。垂直拆分是将库里的各个表拆分到合适的数据库中。

比如一个库中既有消息表,又有人员组织结构表,那么将这两个表拆分到独立的数据库中更合适。

水平拆分:以消息表为例好了,单表突破了千万行记录,查询效率较低,这时候就要将其分库分表。

 

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

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

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

 

正向迁移扩容中,通过自增的主键,到了新的分库分表里一定是唯一的,但是,我们要考虑迁移失败的场景,如下图所示,新的表里假设已经插入了一条新的记录,主键 id 也是 2。

 

这个时候假设开始回滚,需要将两张表的数据合并成一张表(逆向回流),就会产生主键冲突!

因此在迁移之前,先要用全局唯一 id 发生器生成的 id 来替代主键自增 id。

这里有几种全局唯一 id 生成方法可以选择:

我们使用的是阿里巴巴内部的 tddl-sequence(MySQL+内存),保证全局唯一但非递增。

在使用上遇到一些坑:

 

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

新表字符集建议是 utf8mb4,支持表情符。新表建好后索引不要漏掉,否则可能会导致慢 SQL!

从经验来看索引被漏掉时有发生,建议事先列计划的时候将这些要点记下,后面逐条检查。

使用全量同步工具或者自己写 job 来进行全量迁移;全量数据迁移务必要在业务低峰期时操作,并根据系统情况调整并发数。

增量同步:全量迁移完成后可使用 binlog 增量同步工具来追数据,比如阿里内部使用精卫,其他企业可能有自己的增量系统,或者使用阿里开源的。

  1. cannal/otter:https://github.com/alibaba/canal?spm=5176.100239.blogcont11356.10.5eNr98 
  2. 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 语句插入一条记录,如果主键冲突,插入不进去。

③联表查询 SQL 改造

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

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

 

因此,在切库之前,需要将系统中上百个联表查询的 SQL 改造完毕。如何改造呢?

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

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

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

内存拼接:通过 RPC 调用来获取另一张表的数据,然后再内存拼接。

假设 10000 个 ID,分页 RPC 查询,每次查 100 个,需要 5ms,共需要 500ms,RT 太高。

本地缓存另一张表的数据,适合数据变化不大、数据量查询大、接口性能稳定性要求高的 SQL。

 

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

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

DB 停写方案,如下图:

 

优点:快,成本低。

缺点:

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

字符集问题:此外,binlog 逆向回流很可能发生字符集问题(utf8mb4 到 gbk),导致回流失败。

这些 binlog 同步工具为了保证强最终一致性,一旦某条记录回流失败,就卡住不同步,继而导致新老表的数据不同步,继而无法回滚!

双写方案,如下图:

 

第 2 步“打开双写开关,先写老表 A 再写新表 B”,这时候确保写 B 表时 try catch 住,异常要用很明确的标识打出来,方便排查问题。

第 2 步双写持续短暂时间后(比如半分钟后),可以关闭 binlog 同步任务。

优点:

缺点:

⑤开关要写好

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

如果随便设置一个默认值,比如“读老表 A”,假设我们已经进行到读新表 B 的环节了。

这时重启了应用,在应用启动的一瞬间,最新的“读新表 B”的开关推送等可能没有推送过来,这个时候就可能使用默认值,继而造成脏数据!

拆分后一致性怎么保证?

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

如下:

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

一句话:怀疑第三方,防备使用方,做好自己!

 

①怀疑第三方

防御式编程,制定好各种降级策略;比如缓存主备、推拉结合、本地缓存……

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

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

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

②防备使用方

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

容量限制:

③做好自己

a)单一职责

b)及时清理历史坑:例:例如我们改造时候发现一年前留下的坑,去掉后整个集群 CPU 使用率下降 1/3。

c)运维 SOP 化:说实话,线上出现问题,如果没有预案,再怎么处理都会超时。

曾经遇到过一次 DB 故障导致脏数据问题,最终只能硬着头皮写代码来清理脏数据,但是时间很长,只能眼睁睁看着故障不断升级。

经历过这个事情后,我们马上设想出现脏数据的各种场景,然后上线了三个清理脏数据的 job,以防其它不可预知的产生脏数据的故障场景,以后只要遇到出现脏数据的故障,直接触发这三个清理 job,先恢复再排查。

d)资源使用可预测:应用的 CPU、内存、网络、磁盘心中有数。

举个例子:某一个接口类似于秒杀功能,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 访问。

 

总结

①做好准备面对压力!

②复杂问题要拆解为多步骤,每一步可测试可回滚!

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

③墨菲定律:你所担心的事情一定会发生,而且会很快发生,所以准备好你的 SOP(标准化解决方案)!

某个周五和组里同事吃饭时讨论到某一个功能存在风险,约定在下周解决,结果周一刚上班该功能就出现故障了。

以前讲小概率不可能发生,但是概率再小也是有值的,比如 P=0.00001%,互联网环境下,请求量足够大,小概率事件就真发生了。

④借假修真

这个词看上去有点玄乎,顾名思义,就是在借着一些事情,来提升另外一种能力,前者称为假,后者称为真。

在任何一个单位,对核心系统进行大规模拆分改造的机会很少,因此一旦你承担起责任,就毫不犹豫地全力以赴吧!

不要被过程的曲折所吓倒,心智的磨砺,才是本真。

作者:zhanlijun

编辑:陶家龙

出处:cnblogs.com/LBSer/p/6195309.html

 

来源:博客园内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯