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

如何优化单张业务表读写慢?

时间:2023-03-17 17:08:51 科技观察

上一篇文章讨论了架构优化的两种方案:冷热分离和查询分离。HotandColdSeparationQuerySeparation查询分离实际上是利用了非关系型数据库的高性能,但它的缺点也很明显:当主数据量增加时,写操作变慢;如何解决这个问题呢?可见,任何优化方案都不是最终的灵丹妙药,只有不断优化和进化。本文将介绍解决方案:分库分表,重点关注以下几点:拆分后的存储选择?分库分表实现思路?分库分表不足?拆分后的存储选择?在介绍选型之前,先介绍一下架构背景。作者曾经优化过电子商务系统。系统有两个主体:用户:千万级数据,日增长10W+订单:亿级数据,这么大的数据量,日百万级增长,在单数据库的情况下而单张表,无论是IO还是CPU都无法处理,架构的优化是必然的。经过多次讨论和尝试,最终选定了分库分表。说到分库分表,首先想到的就是存储选择。持久层的主流选择不外乎以下几种:关系型数据库:MySQL、Oracle...NoSQL:MongoDB、ES。.....NewSQL:TiDB...1.关系型数据库市面上主流的关系型数据库有3种:MySQL、Oracle、SqlServer。笔者比较喜欢MySQL,也有很多新企业在用。数据库的一种,所以本文也将重点介绍MySQL。关系数据库在任何系统中的地位都是不可或缺的。它的强约束、事务控制、SQL语法、锁……这些功能可以说是久经考验的,所以MySQL在功能上可以满足我们所有的业务需求。2.NoSQL提到NoSQL,首先想到的就是MongoDB。其分片功能从并发和数据量两个角度可以满足大数据量的一般需求,但仍需要考虑以下几点:约束条件:MongoDB不是关系型数据库,而是文档型数据库。它的记录的每一行都是一个具有灵活结构的JSON。比如在存储非常重要的订单数据时,我们不能使用MongoDB,因为订单数据必须使用强约束关系型数据库进行存储。业务功能考虑:事务控制,SQL语法,锁,各种奇怪的SQL都在现有架构上测试过了,但是MongoDB无法满足这些功能的需求。业务转型考量:拆分前使用关系使用NoSQL后改造SQL比较麻烦,项目周期会更长:熟悉度考量:如果你公司的架构组数据和NewSQL比较或者已经在用,可以选择稳定性考量:关系型数据毕竟是经过考验的,稳定性上肯定更好,但是NewSQL的稳定性却不能考虑。建议初期可以将一些不太重要的数据存储在NewSQL中。基于MySQL的分库分表什么是分表分库?分表就是将一个大的表数据拆分存储到多个结构相同的拆分表中;数据库拆分就是将一个大数据库拆分成多个结构相同的小数据库。我在之前的项目中从未使用过上面介绍的三种拆分存储技术。相反,我选择了基于MySQL的分表分库。有一个重要的考虑:分表分库对第三方的依赖性比较强。Less,业务逻辑灵活可控,不需要很复杂的底层处理,不需要重做数据库,根据不同的逻辑使用不同的SQL语句和数据源即可。目前市面上主流的分库分表分为两种模式:Proxy模式和Client模式。代理模式对业务无侵入,直接充当代理数据库。开发人员并不知道所有的事情,SQL组合、数据库路由和执行结果。合并和其他功能都存储在代理服务中。例如MyCat和ShardingSphere都提供了对Proxy模式的支持。Client模式属于业务侵入型。分库分表的逻辑放在客户端。客户端需要导入一个jar,比如Sharding-JDBC,架构图如下:市面上的分库分表中间件如下:两种模式的优缺点也很明显:Proxy模式:资源解耦,无业务入侵;缺点是运维成本比较高Client模式:代码控制灵活,运维成本低;缺点是语言限制,升级不方便。分库分表的实现思路在实现分表分库的方案时,我们需要考虑5点。1、如何选择shardkey?对于订单业务,主要涉及以下字段:user_id:用户idorder_id:订单idorder_time:下单时间store_id:店铺id经过考虑,最终选择user_id作为ShardingKey。为什么?选择user_id作为ShardingKey需要结合业务场景,订单系统中常见的业务:C端用户需要查询所有订单(user_id)后台需要根据城市(user_city_id)查询所有订单B端商户需要统计自己店铺的订单量(store_id)对于以上三种业务场景,判断优先级,首先要满足C端用户,所以使用user_id作为ShardingKey,这样查询的时候需要将user_id传递给定位到指定的库和表选择字段作为分片键。我们一般需要考虑三个需求:数据尽量均匀分布在不同的表或数据库中,跨库查询操作尽量少,这个字段的值不会改变(这一点尤为重要).2、分片策略是什么?选择user_id作为ShardingKey后,需要考虑使用sharding策略,主要分为以下三个范围。如果user_id是自增数,可以按照user_id范围进行分片。每100万册分为一个图书馆。十万份,分一桌。此时单库会被分10张表,如下表所示:Hashmodulo该方案是基于Hash值进行sharding。例如Hash函数为:hash(user_id%8),这里是对user_id取模,取具体值8,最后分8个表;这里一般为了后续扩展方便,建议选择2N次方范围分片和Hash取模的组合。比如先把user_id按照范围拆分,每100万份分一个库,用Hash取模(hash(user_id%8))把100万份数据拆分成8张表。当然,以上三种方案的优缺点也很明显,这里不再赘述需要注意的是,在拆分之前,为了避免频繁扩容,需要对未来5年、10年的数据增长,预留更多碎片。3.如何修改业务代码业务代码的修改在这里不好说,跟自己的业务有很强的关系。不过,在这里我想分享一些个人的看法。近年来,分表分库的操作变得更加容易,但是需要注意几点。我们已经习惯了微服务。对于具体表的分表分库,其影响只在该表所在的服务中。如果是单一架构的应用要分表分库,真的很麻烦。在互联网架构中,我们基本上不用外键约束。随着查询分离的普及,后台系统中有很多操作需要跨库查询,导致系统性能非常差。这时候表分库一般是和查询分离一起操作:先在ES中索引所有数据,然后在后台直接用ES查询数据。如果订单明细数据量大,还有一种常见的做法,就是先把索引字段(作为查询条件字段)存入ES,然后把明细数据存入HBase(这个方案不展开)).4、历史数据迁移?历史数据的迁移非常耗时,有时迁移几天几夜很正常。在互联网行业,别说是几天几夜,哪怕是几分钟的业务宕机都是不可接受的,这就需要我们提供无缝迁移的解决方案。还记得我们在解释查询分离时谈到的解决方案吗?我们再回顾一下,如下图所示:在迁移历史数据的时候,我们使用类似的方案来迁移历史数据,如下图所示:这种数据迁移方案的基本思路:直接迁移股票数据,增量数据监控binlog,然后通知迁移程序通过canal移动数据。新数据库全量数据,校验通过后逐步切换流量。数据迁移方案的详细步骤如下:上线canal,通过canal触发增量数据的迁移;迁移数据脚本测试通过后,将旧数据迁移到新的分表分库;注意迁移增量数据和迁移老数据的时间差,保证所有数据都迁移完,没有遗漏;第二步和第三步完成后,新建的分表分库已经有全量数据。这时候,我们可以运行数据校验程序,确保所有的数据都存储在新的数据库中;至此数据迁移完成,接下来上线新版代码。至于是直接上传灰度还是直接上传,需要根据自己的实际情况决定,回滚方案也是一样的。5、未来的扩张计划是什么?随着业务的发展,如果原有的sharding设计已经不能满足不断增长的数据需求,就需要考虑扩容。扩张计划主要取决于以下两点。分片策略是否可以让新表的数据迁移源只有一张旧表,而不是多张旧表。迁移到新的shard,这个方案和上面提到的历史数据迁移是一样的,这里不再赘述。分表分库的缺点分表分库的解决方案到此结束。以上是业界普遍的一些做法,但是这个方案还是有不足的。增量数据迁移:如何保证数据的一致性和高可用短线爆单:分表分库还是处理不了怎么办?