当前位置: 首页 > 科技观察

容易明白!OLTP场景下的数据分布式设计原则

时间:2023-03-18 16:41:29 科技观察

前言这几年一直在做分布式项目,在OLTP(OnlineTransactionSystem)场景下的数据分布式架构方面做了大量的工作。实践。为了避免篇幅过长,本文分为设计篇和技术篇。设计类文章主要介绍数据拆分的理论和方法,以及一些原理和经验。技术篇将主要介绍分库分表中间件的设计和使用实践,以及如何搭建一个完整的分布式数据服务平台。一般来说,对于分布式架构,应用层很容易做分布式,因为往往是无状态的(或者通过将数据传递到DB、缓存、MQ等实现无状态),只需要在流量入口,即是,在应用程序前面加一个负载均衡器(如Nginx、HAProxy、F5)就可以了,在大型单体架构中也是可以的。所以一般我们讲分布式架构的时候,很重要的一个环节就是做数据的分发。传统的单体中心化架构中数据的分布并不像应用程序那么简单,因为每个节点的数据可能不同,需要解决路由、多副本一致性,甚至多写冲突等问题。虽然实现方案很复杂,但数据的分布本质上由两个简单的思想组成:复制和分片。Replication技术在传统关系型数据库中也很常见,主要用于主备和双活,如MySQLReplication、OracleDataGuard等,Fragments在数据库中也有相应的产品。比如MySQLFabric和OracleSharding,但是和replication相比,这些数据库厂商的sharding方案还没有被大众广泛接受。NewSQL数据库往往内置分片机制,基于paxos和raft算法保证复制一致性。分库分表和NewSQL方案的比选可以参考我之前的文章《分库分表 vs NewSQL数据库》。在OLTP场景中,将replication和sharding的思想应用到传统的关系型数据库中。还有两个比较熟悉的名字,库表分离和读写分离。分库分表就是把原来的单库表拆分出来。它是实现基于传统关系型数据库的分布式架构改造的主要途径。因此,第一个问题是:为什么要拆分?什么时候需要拆分?容量、性能、水平扩展、微服务单机数据库存储、CPU、内存等资源都有上限瓶颈。当数据量和访问量达到一定程度时,性能会急剧下降,也就是说,通过scaleup的垂直扩展方式是一个上限,成本也比较高。如果要实现scaleout水平扩展,需要将原表的数据拆分成多个物理数据库表进行存储(水平拆分)。另外,如果是微服务架构,拆分出来的服务属于不同的系统,对应不同的数据库。实际上,垂直拆分已经进行了。拆分的方法有哪些?1.垂直拆分垂直拆分一般更接近于业务的拆分方式。这种方式在做微服务的时候用的最多。具体来说,会基于DDD(DomainDrivenDesign)技术或者业务能力。对于拆分,一般是确定了boundedcontext,拆分规则比较明确。这种方式对应用程序的侵入性较小,往往只需要配置自己独立的数据库(可能是物理机,也可能只是不同的真实列),至多一个数据访问层进行多数据源选择。此外,还有一种纵向拆分的场景。由于数据冷热,同一行数据不同列的访问频率差异很大,或者一些大字段如Text、Blob影响读写效率。这时,这些列也会被拆分到不同的表中。这种方法一般不常见,在做性能优化的时候经常会考虑到。垂直拆分垂直拆分的优点:拆分后业务清晰,拆分规则清晰。通常很容易根据系统或交易系统进行集成或扩展。数据维护简单,架构复杂度低。垂直拆分的缺点:部分业务表无法join,只能通过应用层接口解决。每个业务的限制不同单库性能瓶颈的存在往往会导致分布式事务场景。由于垂直切分是将表按照业务分类分散到不同的库中,因此部分业务表会过大,单库读写存储会出现瓶颈。这时候就需要水平拆分来解决。2.水平拆分水平拆分技术性更强,将一张表的数据分布到多个数据库和表中。具体方法可以分为:只有数据库,只有表,数据库和表。比如订单表只分库(ds1.order,ds2.order...dsk.order),只分表(ds.order_0,ds.order_1...ds.order_n),分表数据库和表格(ds1.order_0、ds2.order_1...dsk.order_n)。水平拆分水平拆分的优点:如果操作数据分布在同一个数据库中,可以支持join、子查询等复杂SQL。解决了单一数据库的性能瓶颈,支持横向扩展。由于应用没有拆分,如果有分布式数据访问层,应用改造就少了。水平拆分的缺点:拆分规则,分库分表的数量需要仔细设计。如果涉及到多个数据库,就会出现分布式事务的场景。当发生数据扩容时,数据迁移的工作量很大。跨数据库连接通常需要跨数据库连接。应用实现,性能不佳数据库无法直接支持数据合并、聚合、分页等。数据库是否有分区表,需要分库分表?传统关系型数据库的分区表本质上还是共享CPU和内存,所以仍然面临scaleup的问题,而且分区表支持的partitionkey往往不够灵活。但是一些新的NewSQL分布式数据库,比如OceanBase,分区表是分散在不同的存储节点上的,这样可以避免单机性能瓶颈。拆分具体步骤1、确定拆分方式根据业务特点选择合适的拆分方式,一般结合使用。1)垂直拆分场景:字段长度和访问频率差异较大,字段表,微服务注意:尽量不要拆分需要在同一个事务中操作的表2)水平拆分场景:数据量大,超过单表单库性能注意:是否存在跨库事务,是否存在非shardingkey操作表的场景,会涉及到数据库表扫描事务2,确定拆分字段1)垂直拆分表,并且按照功能模块拆分字段可以直接按table来拆分。如果要拆分某些列,则需要添加关联列甚至冗余列。2)水平拆分字段保证拆分的表有分片键,多为主键或唯一索引,这些列需要包含分片信息。如果请求中没有包含分片信息,则需要全局路由表。3.确定拆分规则1)Range适用于业务字段按一定规则有序递增的业务字段,如日期,交易ID等,这种方式,例如0-9999->library1,10000~19999->图书馆2...;20150101-20161231->库1,20170101-20171231->库2....这种方式天然支持水平扩展,方便冷热分离、归档、按需扩展,但容易出现负载不均衡.如果单个数据库压力大,还需要进行数据迁移。2)哈希数据分布比较均衡。一般路由是根据mod库/表的数量来计算的。它本质上是一个预分配。因此,扩容时需要进行数据迁移。通常,有一致性哈希和加倍扩展方法。3)应用定制路由规则由应用定制,配置分片ID对应的库表序号,可以通过路由表、配置文件或其他定制算法。这种方式灵活性最高,易于实现动态变化。在我们的项目中,使用了方法1、2、3。4.确定拆分个数1)假设目标数据量为T(根据业务发展需要估算)2)单表数据量建议为P(例如MySQL为500w),则分表数=T/P3)当前配置典型业务场景在单库性能稳定的前提下,对应的数据容量上限L单库性能可根据cpu(80%以上)、磁盘IO(磁盘使用率100%iowait出现并逐渐增加)、事务tps稳定性(tps大振幅波动)等系统指标判断其瓶颈状态,从而获得容量上限的评价。4)分库数量=T/L数据库表数量,关系到以后的扩容和运维需求。它不应该太多或太少。以上主要是从容量的角度来计算的。在实际场景中,综合考虑了硬件成本预算、数据清洗和归档策略等因素。拆分后如何扩展?1.垂直扩展垂直拆分后,如果应用的数据库压力过大,可以通过增加其资源配置(CPU、内存、PCIE)进行垂直扩展。2.水平扩展在水平拆分中,可以通过增加数据库服务器来扩展容量。该方法需要进行数据迁移。如果哈希一致,则迁移最近节点的数据。如果容量增加一倍,则需要迁移所有节点的一半数据。一致性哈希方式迁移的数据量虽小,但容易造成数据的不均匀性。所以,我们项目中采用的双扩的方式就是把表提前分好,比如分成128张表。在初始阶段,这些表平均分布在4台数据库服务器上。随着业务的增加和数据量的增加,扩容到8个数据库,只需要将原来4个数据库中的表各搬出一半到新的4台服务器上,然后修改SQL路由就可以了。倍增:为了应对整体数据量的增长,扩容后的物理机是原来的两倍。如果单个数据库有热点数据压力,只能将数据库中的部分数据迁移出新扩容的数据库。单库扩容:应对某一片数据的快速增长,分片后扩容到独立的物理机面临问题引入分布式事务问题跨库Join问题多库合并排序分页问题SQL路由,重写问题多数据解决源管理问题多维拆分带来的数据汇总查询等操作问题:尽量避免分布式事务、跨节点join、排序场景,避免使用数据库分布式事务,提供灵活的事务支持(idempotent,redemption,reliabilitymessage,TCC)应用层解决join问题,提供分布式数据访问层汇总库,二级索引库,小表广播。技术章节详细介绍了分布式数据访问层。读写分离在实际业务场景中,对数据库的读写频率是不一样的。有的写多读少,比如交易流水表;一些读写平衡,比如订单表;有的读多写少,比如客户、信息、配置信息表。数据分片解决了单点性能瓶颈和横向扩展能力,适用于写入压力比较大的场景。在这种读多写少的场景下,如果能够满足单个数据库的容量,可以通过读写分离来解决读压力大的问题。具体来说,写操作可以路由到主库,读操作可以根据权重和机房分布在主库和各个从库之间。读写分离在读写分离模式下,有几点需要注意:1)主从延迟。从从库读取数据有一定的延时(一般在毫秒级,写入压力大的时候可能是秒级),所以在选择这种方式的时候,业务必须允许一定的数据延时,比如一般外部查询交易就是这样完成的。2)在同一个事务中,不能从数据库中读取数据,因为数据延迟可能会读到脏数据,违反了事务的一致性,所以必须从主库中读取。在实际开发中,数据访问层可以根据是否关闭事务自动提交自动判断是否必须在主库中读取。3)对于数据延迟容忍度很低的查询事务,可以在开发时从主库单独封装一个查询接口,或者在入参中加上“是否需要强一致性”标志,根据这个选择slavemaster交易执行时的标记。图书馆还是从图书馆看书。实际项目中,有分库分表和读写分离方式的场景,但一般情况下,避免使用分库分表+读写分离的复杂方案,因为之后的读写压力分库分表不高。会太大。原理与体会数据分发是一个系统工程,需要从领域建模、场景划分、数据访问、数据迁移和扩展等多方面进行综合考虑,在实施之前必须做好整体设计。这是我们的一些设计。原则和经验:1)用简单的方法解决问题。能不拆尽量不要拆,不要为了分配而拆。读写分离就可以解决问题,就不用分库分表了。2)对于分片,需要选择一个合适的分片规则(可以保证90%的交易不会跨片),梳理所有的场景,在实施前提前规划。3)数据访问层要设计的功能强大,但使用场景一定要明确,不能避免盲目滥用。比如我们项目中的数据访问中间件虽然支持分布式事务XA,但是一般不推荐;支持DDL,但禁止在网上交易中使用;支持多库链事务提交,但默认只支持严格的单库事务。4)制定应用开发规范,明确SQL使用的限制和要求,SQL尽量简单。比如我们的项目使用的是MySQL,部署在PCServer上。单机性能比DB2和Oracle在小型机上差很多。因此,禁止使用触发器、外键和连接。SQL操作必须携带索引和拆分列(数据访问层也会检查),主键必须自增等。5)尽量使用灵活的事务来解决跨数据库、跨系统的事务问题。能用MQ最终一致性就别用Saga和TCC。