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

从零到千万级用户,如何一步步优化MySQL数据库?

时间:2023-03-13 13:54:04 科技观察

作者个人研发在高并发场景下提供了一个简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。开源半年多以来,已成功为十几家中小企业提供精准定时调度解决方案,经受住了生产环境的考验。为了造福更多的童鞋,这里给出开源框架的地址:https://github.com/sunshinelyz/mykit-delay写在前面,很多小伙伴留言说让我写一些真实的案例在工作流程,应该怎么写??思前想后,写一篇关于我之前公司的数据库架构从无到有到过千万用户升级演进的过程。这篇文章记录了我初入一家创业公司,从零开始到用户过千万,系统压力暴增,一步步优化MySQL数据库,以及数据库架构升级的演进过程。升级过程在技术上极具挑战性,但也很有收获。希望能给朋友们带来实质性的帮助。业务背景我之前在一家创业公司工作,从事商场业务。从表面上看,商城的业务似乎涉及简单的业务,包括:用户、商品、库存、订单、购物车、支付、物流等业务。但是,将其细分仍然更加复杂。这往往涉及到很多提升用户体验的潜在需求。比如:为用户推荐产品,这涉及到用户行为分析和大数据的精准推荐。如果说具体的技术,那肯定包括:用户行为日志埋点、收集、上报、大数据实时统计分析、用户画像、产品推荐等大数据技术。公司业务发展迅速。在不到两年半的时间里,用户数量从零积累到数千万。日访问量上亿,峰值QPS高达每秒数万次。双十一期间的访问量和QPS是平时的好几倍。数据的写入压力来自于用户的下单、支付等操作。尤其是双十一大促期间,系统的写入压力会成倍增加。但是,阅读业务的压力会远大于写作的压力。据不完全统计,阅读业务的请求量是写作业务的50倍左右。接下来我们看看数据库是如何升级的。初期技术选型作为初创公司,最重要的是敏捷,快速落地产品,对外提供服务,所以我们选择了公有云服务,保证快速落地和可扩展性,节省自建机房的时间.整体后台使用Java语言开发,数据库使用MySQL。整体如下图所示。读写分离随着业务的发展和访问量的快速增长,上述方案很快就无法满足性能需求。每个请求的响应时间越来越长。比如用户在H5页面不断刷新商品,响应时间从最初的500毫秒增加到2秒多。在业务高峰期,系统甚至出现宕机。在这生死存亡的关键时刻,通过监控,我们发现MySQL在高峰期的CPU使用率接近80%,磁盘IO使用率接近90%,慢查询(slowquery)有从每天100个增加到10,000个。情况每天都在恶化。数据库好像成了瓶颈,必须赶紧升级架构。当Web应用服务出现性能瓶颈时,由于服务本身是无状态的,我们可以通过水平扩展的方式加机器来解决。显然,数据库不能通过简单的增加机器来扩展,所以我们在应用服务器上采用了MySQL主从同步、读写分离的方案。MySQL支持主从同步,将数据从主库实时增量复制到从库,一个主库可以连接多个从库进行同步。利用这个特性,我们在应用服务器端对每个请求进行读写判断。如果是写请求,则本次请求中的所有DB操作都会发送到主库;如果是读请求,则将本次请求中的所有DB操作发送操作发送到从库,如下图所示。实现读写分离后,数据库压力大大降低,CPU占用率和IO占用率均降低到5%以下,SlowQuery(慢查询)也趋近于0。主从同步和读写分离主要给我们带来以下两个好处:减轻主库的(写)压力:商城的业务主要来自于读操作。压力降低数十倍。从库(读)可以水平扩展(添加从库机器):因为系统压力主要是读请求,从库可以水平扩展。当从库压力过大时,可以直接增加从库机器来缓解读请求的压力。当然,没有一种解决方案是完美的。读写分离暂时解决了MySQL的压力问题,但也带来了新的挑战。业务高峰期,用户提交订单后,在订单列表中看不到自己提交的订单信息(典型的readafterwrite问题);偶尔系统会出现一些异常,找不到数据。通过监控,我们发现MySQL在业务高峰期可能会出现主从复制延时,极端情况下主从延时可达几秒。这极大地影响了用户体验。那么如何监控主从同步状态呢?在从库机器上,执行showslavestatus查看Seconds_Behind_Master的值,代表主从同步从库落后于主库的时间,单位秒。如果主从同步没有延迟,这个值为0。MySQL主从延迟的一个重要原因是主从复制是单线程串行执行的(高版本的MySQL支持并行复制)。那么如何避免或解决主从延迟呢?我们做了如下优化:优化MySQL参数,比如增加innodb_buffer_pool_size,让更多的操作在MySQL内存中完成,减少磁盘操作。使用高性能CPU主机。数据库使用物理主机,避免使用虚拟云主机,提高IO性能。使用SSD磁盘来提高IO性能。SSD的随机IO性能大约是SATA硬盘的10倍甚至更高。业务代码优化强制某些实时性要求高的操作使用主库进行读操作。升级到更高版本的MySQL以支持并行主从复制。垂直分馆的读写分离,很好的解决了阅读压力的问题。每增加一次阅读压力,就可以通过增加从库来横向扩展。然而,随着业务的爆发式增长,写操作的压力并没有得到有效缓解。比如用户提交订单越来越慢。通过监控MySQL数据库,我们发现数据库写操作越来越慢,一个普通的插入操作甚至可能执行1秒以上。另一方面,业务变得越来越复杂。多个应用系统使用同一个数据库,一个小的非核心功能被延迟,往往会影响到主数据库上的其他核心业务功能。这时候主库就成了性能瓶颈。我们意识到必须再次升级架构,拆分主库,一方面提升性能,另一方面减少系统间的交互,提高系统稳定性。.这次我们按业务垂直拆分系统。如下图所示,将最初庞大的数据库按照业务拆分成不同的业务数据库,每个系统只访问业务对应的数据库,尽可能避免或减少跨库访问。我们在数据库垂直分片的过程中也遇到了很多挑战。最大的挑战是不能跨库join,现有代码需要重构。单个数据库时,可以简单的使用join关联表查询;数据库拆分后,拆分后的数据库在不同的实例上,不能跨库join。例如通过商户名查询某商户的所有订单,在垂直分库之前,可以将商户和订单表联合起来查询,也可以直接使用子查询,如下所示:select*fromtb_orderwheresupplier_idin(selectidfromsupplierwherename='商户名称');分库后需要重构代码,先通过商户名称查询商户id,再通过商户id查询订单表,如下:selectidfromsupplierwherename='供应商名称'select*fromtb_orderwheresupplier_idin(supplier_ids)数据库垂直划分过程中的经验教训让我们制定了SQL最佳实践,其中之一是在程序中禁用或使用lessjoin,而是在程序中组装数据,使SQL更简单。一方面为以后业务进一步垂直拆分做准备,另一方面也避免了MySQL中join性能低下的问题。经过近十天的加班调整底层架构和重构业务代码,终于完成了数据库的垂直拆分。拆分后,每个应用只访问对应的数据库。一方面,将单点数据库拆分成多个,分担主库的写入压力;另一方面,拆分后的数据库相互独立,实现业务隔离,不再相互影响。横向分库,读写分离,通过分库的横向扩展解决阅读压力;垂直分库通过按业务拆分主库来缓存写入压力,但系统仍然存在以下隐患:单表数据量越来越大。比如订单表,单表记录数很快就会超过1亿条,超出了MySQL的限制,影响了读写性能。核心业务库的写入压力越来越大,不可能再进行一次垂直拆分。在此时的系统架构中,MySQL主库不具备横向扩展的能力。此时,我们需要进一步对MySQL进行横向拆分。水平分区数据库面临的第一个问题就是,按照什么逻辑来拆分。一种解决方案是按城市拆分,一个城市的所有数据都在一个数据库中;另一种解决方案是按订单ID平均拆分数据。按城市拆分的好处是数据聚合度比较高,做聚合查询比较简单,实现也比较简单。缺点是数据分布不均,部分城市数据量巨大,造成热点,未来可能需要这些热点。被迫再次分裂。按订单ID拆分则相反。优点是数据分布均匀,不会出现一个数据库中的数据极大或极小的情况。缺点是数据过于分散,不利于聚合查询。例如,按照订单ID拆分后,一个商户的订单可能分布在不同的数据库中,查询一个商户的所有订单可能需要查询多个数据库。针对这种情况,一种解决方案是对需要聚合查询的数据做冗余表。不拆分冗余表,同时在业务发展过程中,减少聚合查询。经过反复思考,我们最终决定按订单ID做横向分库。在架构上,系统分为三层:应用层:各类业务应用系统数据访问层:统一的数据访问接口,屏蔽读写分库、分表、缓存等技术细节上层应用层。数据层:对DB数据进行分片,动态添加分片。水平分库的技术关键在于数据访问层的设计。数据访问层主要包括三部分:分布式缓存数据库中间件数据异构中间件数据库中间件需要包含以下重要功能:ID生成器:生成每个表的主键数据源路由:将每个DB操作路由到不同的sharding数据源ID生成器ID生成器是整个水平分片数据库的核心,它决定了如何拆分数据,查询存储检索数据。ID需要跨数据库全局唯一,否则会造成业务层的冲突。另外,ID必须是数字,并且是升序排列,主要是因为ID升序可以保证MySQL的性能(如果是UUID之类的随机字符串,在高并发大数据量的情况下性能极差)。同时,ID生成器必须非常稳定,因为任何故障都会影响所有的数据库操作。我们系统中ID生成器的设计如下所示。整个ID的二进制长度为64位,前36位使用时间戳来保证ID是按升序递增的。中间13位为分库标识,用于标识当前ID对应于哪个数据库。最后15位是一个自动递增序列。为了保证在同一秒内并发时,ID不会重复。每个分片库都有一个自增序列表。生成自增序列时,从自增序列列表中获取当前自增序列值,当前ID的后15位加1。15位递增序列再次从1开始。横向分库是一个非常具有挑战性的项目,我们整个团队也在不断迎接挑战的过程中快速成长。为适应公司业务的不断发展,除了对MySQL数据库进行相应的架构升级外,我们还搭建了一套完整的大数据实时分析统计平台,对用户进行实时分析系统中的行为。如何搭建大数据实时分析统计平台,对用户行为进行实时分析,后面会详细介绍。