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

不那么简单的分库分表:高并发下数据库架构设计的那些坑!

时间:2023-03-13 00:37:23 科技观察

本文转载自微信公众号《石山的建筑笔记》,作者中华石山。转载本文请联系石山架构笔记公众号。在本文中,我们将讨论如何为支持数百万日活跃用户的高级并行系统设计数据库架构。看到这个题目,很多人的第一反应是:数据库和表分开!但其实我想很多同学可能不明白数据库层面的分库分表是干什么用的,它们的不同功能是如何应对不同场景的。(1)以一家创业公司的发展为背景来介绍如果我们现在是一家小型创业公司,注册用户20万,每天活跃用户1万,每天单表数据1000条,以及高峰期每秒并发请求最多10个,我的天啊!这种体制,随便找个有几年工作经验的高级工程师,再带几个年轻的工程师,就可以为所欲为。因为这样的系统其实主要是前期快速开发业务功能,在一台服务器上部署一个单块系统,然后连接一个数据库。然后大家在一个项目中不停的填写各种业务代码,以尽快支持公司的业务,如下图。结果没想到,我们这么幸运,遇到了一位优秀的CEO,带领我们走上了致富之路!公司业务发展迅速,几个月后,注册用户达到2000万!日活跃用户数达到100万!单表每天新增数据量达到50万条!高峰期每秒请求数达到10000!与此同时,公司还进行了两轮融资,估值达到了惊人的数亿!一只年轻的独角兽幼年兽之节奏!那么现在大家觉得压力有点大,为什么呢?因为每天单表新增50万条数据,一个月新增1500万条数据,一年单表会达到数亿条数据。经过一段时间的运行,现在我们的单表有20到3000万条数据,勉强支撑。但是眼看着系统访问数据库的性能越来越差,单表的数据量越来越大,拖累了一些复杂查询SQL的性能!那么峰值请求现在是10000个/秒,我们的系统在线部署了20台机器,平均每台机器支持500个请求/秒。这个还是能顶住的,问题不大。但是数据库级别呢?如果此时你还是一个支持每秒几万请求的数据库服务器,我可以很负责任的告诉你,在每个高峰期都会出现以下问题:你的数据库服务器的磁盘IO和网络带宽,CPU负载,内存消耗都会达到很高的水平,数据库所在服务器的整体负载会很重,甚至几乎不堪重负,这时候如果你的数据库服务器负载过高,导致性能下降,你会发现你的SQL性能变差了。最明显的感觉就是在高峰时段你的系统所有功能运行起来都很慢,用户体验很差。可怜,点一个按钮可能要几十秒才能出结果。如果运气不好,数据库服务器的配置不是特别高,可能会出现数据库宕机的情况,因为负载太大,数据库压力很大。太大了(2)多服务器分库支持高并发读写首先我们来考虑第一个问题,数据库应该如何支持每秒几万个并发请求?要理解这个问题,首先要理解一般的数据库部署在什么配置服务器上。一般来说,如果使用普通配置的服务器来部署数据库,至少要有16核32G的机器配置。对于这种部署在很普通的机器配置上的数据库,网上一般的经验是:不要让它支持每秒超过2000个请求,一般控制在2000左右。控制在这个水平上,一般数据库负载相对合理的话,不会带来太大的压力,也没有太大的宕机风险。所以第一步是在上万并发请求的场景下部署5台服务器,在每台服务器上部署一个数据库实例。然后,在每个数据库实例中,创建相同的库,例如订单库。这时候五台服务器上都有一个订单库,名字可以类似:db_order_01、db_order_02等等。那么每个订单库都有同一张表。比如订单库中有一个订单信息表,那么此时五个订单库中都有一个订单信息表。比如db_order_01库中有一张tb_order_01表,db_order_02库中有一张tb_order_02表。这样就实现了一个分库分表的基本思路,原来的一个数据库服务器变成了五个数据库服务器,原来的一个数据库变成了五个数据库,原来的一个表变成了五个表。那么写数据的时候就需要用到数据库中间件,比如sharding-jdbc,或者mycat。可以根据orderid进行hash,然后对5取模,比如order表每天新增50万条数据。此时会有10万条数据落入db_order_01数据库的tb_order_01表,另外10万条数据落入db_order_02数据库。tb_order_02表等。这样就可以将数据平均分布在5台服务器上。查询的时候也可以使用orderid对model进行hash,到对应服务器上的数据库,从对应的表中查询数据。按照这个思路画出来的图如下所示,大家可以看看。做这一步有什么好处?第一个好处是订单表以前是一张表,现在是五张表,每张表的数据变成1/5。假设order表一年有1亿条数据,那么5张表每年都有2000万条数据。那么假设当前订单表中已经有2000万条数据。此时经过上面的拆分后,每张表只有400万条数据。而如果每天新增50万条数据,那么每张表只新增10万条数据。这是否初步缓解了单表数据量过大影响系统性能的问题?另外每秒10000个请求发送到5个数据库,每个数据库每秒承载2000个请求。是不是一下子把各个数据库服务器的并发请求降低到一个安全的范围内?这样既降低了数据库的峰值负载,又保证了高峰期的性能。(3)大量分表,保证海量数据下的查询性能。但是,上述的数据库架构还是存在一个问题,就是单表的数据量还是太大了。现在订单表分成5张表,那么如果订单一年1亿,每张表2000万,还是太大了。所以要继续分表,而且要大量分表。比如订单表可以拆分成1024张表。如果有1亿个数据量,那么每个表只能分布10万个数据量,然后5个数据库就可以分布上千个表。向上。写数据的时候需要做两条路由,先对orderidhash后的数据库个数取模,就可以路由到一个数据库,然后对那个数据库上的表个数取模,就可以路由到数据库中的一个表中。通过这一步,每个表的数据量可以很小,每年增长1亿条数据,但每个表只增长10万条数据。这个系统运行了10年,每张表可能只有百万级别的数据。这样一来,可以一次性为系统以后的运行做好充分的准备。看下图一起体验一下:(4)读写分离,支持按需扩展和性能提升。这时候,整体效果还是比较不错的。表策略保证每个表的数据量在未来10年内不会太大,可以保证单表的SQL执行效率和性能。然后,多个数据库的拆分方法可以保证每个数据库服务器承载一部分读写请求,减少每个服务器的负载。但是这个时候还有一个问题。假设每个数据库服务器每秒承载2000个请求,然后400个请求是写入,1600个请求是查询。也就是说,只有20%的SQL增删改查,80%的请求都是查询。这时候如果用户量越来越大,如果变成每台服务器承载4000个请求。然后写了800个请求,3200个请求是查询。如果按照目前的情况扩容,需要增加数据库服务器。但是这时候可能会涉及到表的迁移,因为有些表需要迁移到新的数据库服务器上去,是不是很麻烦?其实完全没有必要。数据库一般都支持读写分离,即主从架构。写的时候写到主数据库服务器,查询的时候从数据库服务器读,这样就可以将一张表的读写请求分别落地到不同的数据库上执行。在这种情况下,如果写主库的请求是每秒400个,查询从库的请求是每秒1600个,那么图大致如下图所示。写入主库时,数据会自动同步到从库,保证主库和从库数据一致。然后在查询的时候,从数据库中查询,通过数据库的主从架构达到读写分离的效果。现在的好处是如果主库写请求增加到800也没关系,不需要扩容。然后图书馆的读请求增加到3200,需要扩容。这时候可以直接挂载一个新的从库到主库上。从库有两个,每个从库支持1600个读请求。不需要因为读请求的增加而扩容主库。其实在线上生产中,你会发现读请求的增长速度远高于写请求。因此,读写分离后,大多数时候,扩展从库就可以支持更高的读请求。还有一点,对于同一张表,如果既写数据(涉及加锁)又从表中查询数据,可能会涉及到锁冲突等问题,从而影响写性能和读性能。所以,一旦读写分离,主库中的表只是写,查询不会影响,而从库中的表只是查询。(5)高并发下的数据库架构设计总结其实从简化的角度来看,在高并发场景下,数据库层面的架构设计是必须慎重考虑的。