这篇文章,我们来谈谈如何设计一个支持数百万日活用户的高阶并行系统的数据库架构?看到这个题目,很多人的第一反应就是:数据库和表分开!但其实我想很多同学可能不明白数据库层面的分库分表是干什么用的,它们的不同功能是如何应对不同场景的。1.以一家创业公司的发展为背景来介绍如果我们现在是一家小型创业公司,有20万注册用户,每天1万活跃用户,每天1000条单表数据,以及高峰期每秒最大并发请求数才10个。天啊!这种系统,随便找个有几年工作经验的高级工程师,再带几个年轻的工程师,想干什么就干什么。因为这样的系统其实主要是前期快速开发业务功能,在一台服务器上部署一个单块系统,然后连接一个数据库。然后大家在一个项目中不停的填写各种业务代码,以尽快支持公司的业务,如下图。结果没想到,我们这么幸运,遇到了一位带领我们走上致富之路的优秀CEO!公司业务发展迅速。几个月下来,注册用户已经达到2000万!100万日活跃用户!单表每天新增数据量达到50万条!高峰期每秒请求数达到10000!与此同时,公司还进行了两轮融资,估值达到了惊人的数亿!充满活力的年轻独角兽的节奏!那么,现在大家感觉有点压力,为什么呢?因为单表每天新增50万条数据,一个月新增1500万条数据,一年单表达到上亿条数据。经过一段时间的运行,现在我们的单表有20到3000万条数据,勉强支撑。但是眼看着系统访问数据库的性能越来越差,单表的数据量越来越大,拖累了一些复杂查询SQL的性能!那么现在峰值请求是每秒10000个。我们的系统在线部署了20台机器,每台机器平均每秒支持500个请求。这个还是能顶住的,问题不大。但是数据库级别呢?如果此时你还是一台支持每秒几万请求的数据库服务器,我可以很负责任的告诉你,在每个高峰期都会出现以下问题:你的数据库服务器的磁盘IO、网络带宽、CPU负载、内存消费将达到很高的水平。数据库所在的服务器整体负载会非常大,甚至几乎不堪重负。你的数据库服务器负载过高,导致性能下降,你会发现你的SQL性能更差。最明显的感觉就是在高峰时段你的系统所有功能运行起来都很慢,用户体验很差。单击一个按钮可能需要几十秒才能得到结果。如果运气不好,数据库服务器的配置不是特别高,可能会因为负载太高,数据库压力太大,导致数据库宕机。多服务器分库支持高并发读写首先我们来考虑第一个问题,数据库应该如何支持每秒几万个并发请求?要理解这个问题,首先要理解通用数据库部署在什么配置服务器上。一般来说,如果使用普通配置的服务器来部署数据库,至少要有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,到对应服务器上的数据库,从对应的表中查询数据。按照这个思路画出来的图如下所示,大家可以看看。做这一步有什么好处?第一个好处是原来的订单表只有一张表,现在变成了5张表,每张表的数据变成了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.高并发下数据库架构设计总结其实从简化的角度来看,在高并发场景下,数据库层面的架构设计是必须慎重考虑的。尤其是分库支持高并发请求时,大量的分表保证了每张表的数据量不会太大,读写分离实现了主从的按需扩展数据库和性能保证。本文从大角度梳理思路。你可以结合你公司的业务和项目来考虑你的系统应该如何分库分表。另外,在具体实现分库分表时,还需要数据库中间件来实现分库分表和读写分离。可以参考sharding-jdbc或者mycat的官网。里面的文档很详细。使用说明。
