数据库中间件分库分表恭喜,你的公司终于发展到一定规模了,你需要考虑高可用,甚至是分库分表但是你知道分库分表需要哪些要素吗?拆分过程复杂,未雨绸缪,不要等到真正开始工作,各种意想不到的工作接踵而至,甚至一发不可收拾。本文意在打通数据库中间件的广度,且不论实现深度如何,至于库表纵横划分的概念和原因,不做过多解释。因此本文针对有一定研发经验,正在寻找选型拆分流程的专业人士。层次以下,作用域在JAVA和MySQL中定义。我们先来看分库分表入口的层级。①编码层在同一个项目中创建多个数据源,根据条件直接在代码中使用ifelse进行路由。Spring有一个动态切换数据源的抽象类。有关详细信息,请参阅AbstractRoutingDataSource。如果项目不是很大,这种方法可以快速分库。但是缺点也很明显,需要写很多代码来照顾每一个分支。当涉及到跨库查询、聚合、循环计算合并结果的场景需要巨大的工作量时。如果项目是裂变的,这些代码大部分是不能共享的,大部分是通过copy来共享的。从长远来看,代码不会被编码。②框架层适合这种情况公司的ORM框架是统一的,但是很多时候不太现实。主要是修改或增强现有ORM框架的功能,在SQL中添加一些自定义原语或提示。通过实现一些拦截器(比如Mybatis的Interceptor接口),加入一些自定义的解析来控制数据的流向,虽然效果更好,但是会改变一些已有的编程体验。很多时候需要修改框架的源码,不推荐这样做。③驱动层基于编码层和框架真正??的数据库中间件至少要从驱动层开始。你是什??么意思?其实就是重写一个JDBC驱动,在内存中维护一个路由列表,然后将请求转发到真正的数据库连接中。像TDDL、ShardingJDBC等,都是切入这一层。包括MysqlConnector/J的Failover协议(具体指“负载均衡”、“复制”、“farbic”等),也是直接在驱动上修改的。Request的流向一般是这样的:④代理层代理层的数据库中间件伪装成数据库,接受来自业务端的链接。然后加载业务方的请求,解析或者转发给真实的数据库。像MySQLRouter、MyCat等,都切入这一层。请求的流程一般如下:⑤实现层SQL特殊版本支持,比如Mysql集群本身支持各种特性,mariadbgalera集群支持点对点双主,Greenplum支持分片等,需要改变存储,这通常是一个解决方案,不讨论。技术终究会趋同,选择任何一种都是可行的。但最终的选择受到开发者熟悉程度、社区活跃度、公司适用性、官方维护、可扩展性、公司现有数据库产品等多种因素的影响。选择或开发一个合适的,朋友们会更快乐。驱动层和代理层的比较通过上面的层级描述可以看出,我们在选择或者开发中间件的时候,主要关注的是驱动层和代理层。在这两层,可以更好地控制和细粒度地管理数据库连接和路由。但它们的区别也很明显。驱动层只支持JAVA,支持richDB。驱动层中间件只支持Java作为开发语言,后端支持所有关系型数据库。如果你的开发语言固定,后台数据源类型丰富,推荐使用此方案。驱动层中间件需要维护很多数据库连接。比如一张表分10个库,java中每个Connection需要维护10个数据库连接。如果item太多,会出现连接爆炸(我们算一下,如果每个item有6个instance,连接池中的minIdle等于5,3个item的总连接数是10*6*5*3=900)。对于Postgres这样的数据库,每个连接对应一个进程,压力会很大。数据聚合通过多次查询在业务实例中进行数据聚合,比如countsum等,然后在业务实例的内存中进行聚合。路由表存在于业务实例的内存中,可以通过轮询或被动通知的方式更新路由表。集中管理所有集群的配置管理集中在一处,运维负担小,DBA即可完成相关操作。代理层的典型实现是异构支持,而DB支持有限的代理层中间件,恰恰相反。只支持一种后端关系型数据库,但支持多种开发语言。如果您的系统是异构的并且都具有相同的SLA要求,则建议使用此解决方案。运维负担大。代理层需要维护有限数量的数据库连接(MySQLRouter的粘性连接除外)。但是,作为一个独立的服务,既要考虑单独部署,又要考虑高可用,这会增加很多额外的节点,更不用说使用影子节点的公司了。另外代理层是请求的唯一入口,稳定性要求极高。一旦出现内存密集型聚合查询导致节点崩溃,那将是一场灾难性的事故。典型实现的共同点篇幅有限,不再过多讨论。访问每个中间件推广页面,可以看到一长串的特性,即白名单;还可以看到一长串的限制,也就是黑名单。它限制了你的游戏方式。增强分布式能力后,分库分表本身就是一个阉割过的数据库。使用限制来保证数据平衡,尽可能均匀地拆分数据库的数据。比如用户库按省划分,即使按userid建模也会多。没有deeppaging,没有splitkeys的deeppaging会取出repository取出的页数之前的所有页。所有数据都在内存中排序和计算。很容易造成内存溢出。减少子查询子查询会导致SQL解析混乱和解析错误,尽量减少SQL子查询。最小事务原则将单机事务涉及的数据库范围最小化,即数据库操作最小化,将同一类型的数据库/表划分为一个。数据平衡原则将数据库的数据尽可能平均的拆分。例如,用户数据库按省划分是不均衡的。按userid取模会更统一。通常不支持特殊函数distinct、having、union、in或等。或者支持的话,使用后会增加风险,需要修改。产品推荐重点关注MyCat和ShardingJDBC。此外,还有大量的其他中间件。如果不熟悉,建议不要轻举妄动。数据库中间件不好维护,你会发现很多半死不活的项目。以下列表排名不分先后。有的只有HA功能没有split功能:Atlas,Kingshard,DBProxy,mysqlrouter,MaxScale,58Oceanus,ArkProxy,CtripDAL,Tsharding,Youtubevitess,NeteaseDDB,Heisenberg,proxysql,Mango,DDAL,Datahekr,MTAtlas、MTDDL、Zebra、Cobar、CobarKhan,几乎各大厂商都有自己的数据库中间件(我们也找了几个喜欢用开源组件加公司前缀做产品),就是不给我们用。分库分表无论采用哪一级流程方案,都会面临以下工作流程。信息采集统计涉及的业务和项目工程范围越大,分库难度越大。有时候一个复杂的SQL可能涉及到四五个业务方,这种SQL需要重点关注。确定分库分表的规模,是只分几个表,还是全部涉及。点数越多,工作量越大,几乎是线性的。还有一些涉及全身的项目。比如在下面的过程中,受影响的链接就不仅仅是分库这么简单了。确定参与人员,除了分库分表组件的技术支持人员外,参与人数最多的应该是最熟悉系统和现有代码的人员。只有他们才能确定哪些SQL应该被丢弃,SQL的影响等。确定分库分表策略,确定分库分表的维度和splitkey。分段键(即路由数据的列)一旦确定,就不允许修改。因此,在早期的架构设计中,应该先建立起来,才能进行后续的工作;多数据维度意味着有不同的切分键,达到不同条件查询的效果。这就涉及到数据冗余(多次写入、数据同步),会比较复杂。前期数据正则化数据库表结构不符合要求,需要提前正则化。比如shardingkey的字段名不同或者类型不同。在实现分库分表策略时,这些特点会导致策略过于庞大,难以维护。ScanallSQL扫描项目中的所有SQL,根据splitkey一条一条判断是否能正常运行。在判断过程中,肯定会出现大量不合规的SQL,必须给出改造方案,这是主要的工作量之一。验证工具支持直接在原项目上修改验证是可行的,但是会遇到很多问题,主要是效率太低。我倾向于先设计一些验证工具,输入需要验证的SQL或列表,然后打印路由信息和结果进行判断。技术准备,建议先找个例子体验一下下面的每个点,然后根据自己的团队预估难度。如下:整理出中间件不支持的所有SQL类型,可能会导致崩溃。不支持的SQL的注意事项。考虑一个通用的主键生成器。考虑如何在没有拆分键的情况下处理SQL。考虑如何扫描整个数据库,例如计划任务。进行遍历,考虑跨库跨表查询如何改造,准备一些工具集。在实施阶段,数据的迁移、分库分表都会重新影响数据的分布。不管是全量还是增量,都会涉及到数据的迁移,所以Databus是必不可少的。一个理想的状态是所有的增删改查都是消息,可以通过订阅MQ进行双写。但一般情况下,还是需要模拟这种状态,比如使用Canal组件。如何保证数据安全切换将在其他章节讨论。充分测试分库分表都要经过充分的测试,SQL的每一句都要经过严格的校验。如果可以使用单元测试或自动化测试工具,则需要全面覆盖。一旦数据路由错误,尤其是增删改查,会造成很大的麻烦。在测试阶段,将验证过程输出到单独的日志文件中,充分测试后查看日志文件是否有错误的数据流。SQL复检强烈建议进行统一的SQL复检。主要根据功能描述来判断SQL的正确性,也就是通常所说的review。演练程序在离线环境下进行了多次演练,确保不出问题。制定了新的分库分表的SQL规范后,项目中的SQL就会受到束缚,不能随意编写。许多普遍支持的操作可能无法在拆分环境中工作。所以,在上线之前,涉及到的SQL应该有一个确认的过程,哪怕是经过了充分的测试。题外话不要接受没有支持的作品,它是行不通的。分库分表是一种战略性的技术方案。很多时候无法回滚或者回滚方案复杂。如果要拆分的数据库表涉及多个业务方,公司技术人员复杂,则必须由CTO亲自负责协调,并有专业细心的架构师进行监督。未经授权的协调员将陷入尴尬境地,导致流程失控,项目交付困难。真正经历过的人,就会知道它的痛苦!
