亿级数据的核心表新增一个字段,远不止一句简单的“ALTER TABLE”,锁表风险、页分裂、索引性能衰减……每一个问题都可能引发线上事故。如何在不影响业务的前提下,只需简单的配置,即可实现字段的动态扩展?本文将带你揭秘中台团队的实战解决方案。
背景
软件行业中,唯一不变的因素就是“变化”。一个新项目上线后,业务需要对现有功能做一些改动或升级,而实现这个功能必须要新增字段。新增字段乍一看无所谓,但如果加上一些前提条件,这张表是一张数亿级数据的表,而且是公司的核心热点表,你还能随心所欲地加么。抛开加字段过程中的锁表问题不说,后续随着字段和数据的不断增多,还会引发MySQL的页分裂、碎片化、索引性能衰减等一系列问题。
如何处理这个问题呢?我们最直接能想到的就是两种方式,扩展字段和扩展表。当然也有人可能会想到用非关系型数据库解决。而数据库选型往往是在项目初期时考虑,如果是老项目,实现起来就有不小的难度和风险,并且非关系型数据库也有自己无能为力的方面。所以其他方式我们暂且先按下不表,下面我们主要讨论怎么基于现有条件更优雅地解决这个问题。
扩展字段
扩展字段是最容易想到的解决方案,在表中增加一个扩展字段,以JSON格式存储数据。这也是我们之前一直采用的方式,我们的使用方式是这样的:
但这个方案也存在一些问题:
不可索引。extend里的字段无法建立索引进行检索。这里有经验的读者可能会提出,MySQL 5.7.8版本支持JSON数据类型,可以为扩展字段中的某一个或一部分字段建立索引。是的没错,但如果你之前用的是更老的版本,就需要升级,MySQL版本升级也不是说升就能升的,懂的都懂,这里就不展开说了。
并发覆盖。当对extend并发更新的时候,会出现覆盖问题。这里我们采用了CAS更新的方式去避免,但是随着extend中的字段不断增多,冲突问题越来越频繁,CAS策略就影响到了接口成功率。
重复工作。每次新增字段都需要为内部类增加属性,拉分支,重新打包上线。
如果你是中台部门,除了上面的硬伤,还有一些更伤脑筋的问题在等着你:
数据膨胀。你不可能基于当前的扩展字段,来者不拒地存,字段总有一天会超长,就像一柄达摩克利斯之剑。当然,你拥有的剑还不止一柄,你同样需要担心字段不断膨胀之后的数据库性能。
然而你也不能来者皆拒,业务部门因为你的拒绝,需要为一两个字段去自己新建一张表存储维护,成也很高,于是你陷入了两难。维护黑洞。随着业务迭代,你的维护成本会越来越高,面对几百个扩展字段,你无法快速知道这些字段具体是哪个业务、什么场景在用,当前还有没有在使用、可不可以被下掉。
扩展表
另一种方案是扩展表。扩展表将扩展字段中的每个字段转成一行,存储到另外一张表中
扩展表解决了扩展字段无法索引的问题,由于把字段拆开了存,也很大程度上缓解了并发问题。同时,由于扩展数据不在主表存储了,也释放了主表的压力,让加字段更从容一些。但也引进来一个新问题:本来一条记录的许多属性,变成了多条记录,行数成倍增加了。为解决这个问题,我们基于主表现有的分库分表逻辑,对扩展表也进行了分库和分表。
扩展表方案貌似解决了扩展字段方案的大部分问题,可索引、没有并发覆盖问题、不影响主表性能。但还是没有解决字段维护问题,你还是不知道哪些字段场景在用什么字段,哪些字段可以下线。并且在实践中,我们还归纳出其他一些使用场景:
场景1:有些业务期望将数据存储在订单中台后,在订单的后续某个节点传递给下游服务。
场景2:有些业务期望扩展字段中的某些字段与主表上的某些字段一起进行检索。
这两个场景实现起来比较机械,我们也不希望每次都去开发。
现在的方案
为了满足上面两种使用场景,并且实现只需简单的配置,即可实现字段的动态扩展的愿景,最终,我们将整个系统拆分为三部分:数据管理、数据存储、数据检索。数据管理部分用于管理动态字段准入、接口透传信息、检索要求、归属以及其他基本信息,数据存储部分核心还是采用扩展表的方案,数据检索采用ES集群及自研ES管理系统ECP。