热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

getprivateprofilestring读不到数据_从零到千万用户,我是如何一步步优化MySQL数据库的?...

写在前面很多小伙伴留言说让我写一些工作过程中的真实案例,写些啥呢?想来想去,写一篇我在以前公司从零开始到用户超千万的数据库架构升级演变的过

3ce3cacbd7b32ec6f2b14d74206213b2.png

写在前面

很多小伙伴留言说让我写一些工作过程中的真实案例,写些啥呢?想来想去,写一篇我在以前公司从零开始到用户超千万的数据库架构升级演变的过程吧。

本文记录了我之前初到一家创业公司,从零开始到用户超千万,系统压力暴增的情况下是如何一步步优化MySQL数据库的,以及数据库架构升级的演变过程。升级的过程极具技术挑战性,也从中收获不少。希望能够为小伙伴们带来实质性的帮助。

业务背景

我之前呆过一家创业工作,是做商城业务的,商城这种业务,表面上看起来涉及的业务简单,包括:用户、商品、库存、订单、购物车、支付、物流等业务。但是,细分下来,还是比较复杂的。这其中往往会牵扯到很多提升用户体验的潜在需求。例如:为用户推荐商品,这就涉及到用户的行为分析和大数据的精准推荐。如果说具体的技术的话,那肯定就包含了:用户行为日志埋点、采集、上报,大数据实时统计分析,用户画像,商品推荐等大数据技术。

公司的业务增长迅速,仅仅2年半不到的时间用户就从零积累到千万级别,每天的访问量几亿次,高峰QPS高达上万次每秒,双十一期间的访问量和QPS是平时的几倍。数据的写压力来源于用户下单,支付等操作,尤其是赶上双十一大促期间,系统的写压力会成倍增长。然而,读业务的压力会远远大于写压力,据不完全统计,读业务的请求量是写业务的请求量的50倍左右。

接下来,我们就一起来看看数据库是如何升级的。

最初的技术选型

作为创业公司,最重要的一点是敏捷,快速实现产品,对外提供服务,于是我们选择了公有云服务,保证快速实施和可扩展性,节省了自建机房等时间。整体后台采用的是Java语言进行开发,数据库使用的MySQL。整体如下图所示。

b10590a5ec3b35ef213665ba7fd663f5.png

读写分离

随着业务的发展,访问量的极速增长,上述的方案很快不能满足性能需求。每次请求的响应时间越来越长,比如用户在H5页面上不断刷新商品,响应时间从最初的500毫秒增加到了2秒以上。业务高峰期,系统甚至出现过宕机。在这生死存亡的关键时刻,通过监控,我们发现高峰期MySQL CPU使用率已接近80%,磁盘IO使用率接近90%,slow  query(慢查询)从每天1百条上升到1万条,而且一天比一天严重。数据库俨然已成为瓶颈,我们必须得快速做架构升级。

当Web应用服务出现性能瓶颈的时候,由于服务本身无状态,我们可以通过加机器的水平扩展方式来解决。而数据库显然无法通过简单的添加机器来实现扩展,因此我们采取了MySQL主从同步和应用服务端读写分离的方案。

MySQL支持主从同步,实时将主库的数据增量复制到从库,而且一个主库可以连接多个从库同步。利用此特性,我们在应用服务端对每次请求做读写判断,若是写请求,则把这次请求内的所有DB操作发向主库;若是读请求,则把这次请求内的所有DB操作发向从库,如下图所示。

5bcb1c93807f13a8d7db44cb5d8f2949.png

实现读写分离后,数据库的压力减少了许多,CPU使用率和IO使用率都降到了5%以内,Slow Query(慢查询)也趋近于0。主从同步、读写分离给我们主要带来如下两个好处:

  • 减轻了主库(写)压力:商城业务主要来源于读操作,做读写分离后,读压力转移到了从库,主库的压力减小了数十倍。

  • 从库(读)可水平扩展(加从库机器):因系统压力主要是读请求,而从库又可水平扩展,当从库压力太时,可直接添加从库机器,缓解读请求压力。

当然,没有一个方案是万能的。读写分离,暂时解决了MySQL压力问题,同时也带来了新的挑战。业务高峰期,用户提交完订单,在我的订单列表中却看不到自己提交的订单信息(典型的read after  write问题);系统内部偶尔也会出现一些查询不到数据的异常。通过监控,我们发现,业务高峰期MySQL可能会出现主从复制延迟,极端情况,主从延迟高达数秒。这极大的影响了用户体验。

那如何监控主从同步状态?在从库机器上,执行show slave  status,查看Seconds_Behind_Master值,代表主从同步从库落后主库的时间,单位为秒,若主从同步无延迟,这个值为0。MySQL主从延迟一个重要的原因之一是主从复制是单线程串行执行(高版本MySQL支持并行复制)。

那如何避免或解决主从延迟?我们做了如下一些优化:

  • 优化MySQL参数,比如增大innodb_buffer_pool_size,让更多操作在MySQL内存中完成,减少磁盘操作。
  • 使用高性能CPU主机。
  • 数据库使用物理主机,避免使用虚拟云主机,提升IO性能。
  • 使用SSD磁盘,提升IO性能。SSD的随机IO性能约是SATA硬盘的10倍甚至更高。
  • 业务代码优化,将实时性要求高的某些操作,强制使用主库做读操作。
  • 升级高版本MySQL,支持并行主从复制。

垂直分库

读写分离很好的解决了读压力问题,每次读压力增加,可以通过加从库的方式水平扩展。但是写操作的压力随着业务爆发式的增长没有得到有效的缓解,比如用户提交订单越来越慢。通过监控MySQL数据库,我们发现,数据库写操作越来越慢,一次普通的insert操作,甚至可能会执行1秒以上。

另一方面,业务越来越复杂,多个应用系统使用同一个数据库,其中一个很小的非核心功能出现延迟,常常影响主库上的其它核心业务功能。这时,主库成为了性能瓶颈,我们意识到,必须得再一次做架构升级,将主库做拆分,一方面以提升性能,另一方面减少系统间的相互影响,以提升系统稳定性。这一次,我们将系统按业务进行了垂直拆分。如下图所示,将最初庞大的数据库按业务拆分成不同的业务数据库,每个系统仅访问对应业务的数据库,尽量避免或减少跨库访问。

f5a987dca20ab3e5284994aec4870ae0.png

垂直分库过程,我们也遇到不少挑战,最大的挑战是:不能跨库join,同时需要对现有代码重构。单库时,可以简单的使用join关联表查询;拆库后,拆分后的数据库在不同的实例上,就不能跨库使用join了。

例如,通过商家名查询某个商家的所有订单,在垂直分库前,可以join商家和订单表做查询,也可以直接使用子查询,如下如示:

select * from tb_order where supplier_id in (select id from supplier where name=’商家名称’);

分库后,则要重构代码,先通过商家名查询商家id,再通过商家id查询订单表,如下所示:

select id from supplier where name=’商家名称’
select * from tb_order where supplier_id in (supplier_ids )

垂直分库过程中的经验教训,使我们制定了SQL最佳实践,其中一条便是程序中禁用或少用join,而应该在程序中组装数据,让SQL更简单。一方面为以后进一步垂直拆分业务做准备,另一方面也避免了MySQL中join的性能低下的问题。

经过近十天加班加点的底层架构调整,以及业务代码重构,终于完成了数据库的垂直拆分。拆分之后,每个应用程序只访问对应的数据库,一方面将单点数据库拆分成了多个,分摊了主库写压力;另一方面,拆分后的数据库各自独立,实现了业务隔离,不再互相影响。

水平分库

读写分离,通过从库水平扩展,解决了读压力;垂直分库通过按业务拆分主库,缓存了写压力,但系统依然存在以下隐患:

  • 单表数据量越来越大。如订单表,单表记录数很快就过亿,超出MySQL的极限,影响读写性能。
  • 核心业务库的写压力越来越大,已不能再进一次垂直拆分,此时的系统架构中,MySQL 主库不具备水平扩展的能力。

此时,我们需要对MySQL进一步进行水平拆分。

033b0014bab68050adf2c82294dd38b1.png

水平分库面临的第一个问题是,按什么逻辑进行拆分。一种方案是按城市拆分,一个城市的所有数据在一个数据库中;另一种方案是按订单ID平均拆分数据。按城市拆分的优点是数据聚合度比较高,做聚合查询比较简单,实现也相对简单,缺点是数据分布不均匀,某些城市的数据量极大,产生热点,而这些热点以后可能还要被迫再次拆分。按订单ID拆分则正相反,优点是数据分布均匀,不会出现一个数据库数据极大或极小的情况,缺点是数据太分散,不利于做聚合查询。比如,按订单ID拆分后,一个商家的订单可能分布在不同的数据库中,查询一个商家的所有订单,可能需要查询多个数据库。针对这种情况,一种解决方案是将需要聚合查询的数据做冗余表,冗余的表不做拆分,同时在业务开发过程中,减少聚合查询。

经过反复思考,我们最后决定按订单ID做水平分库。从架构上,将系统分为三层:

  • 应用层:即各类业务应用系统
  • 数据访问层:统一的数据访问接口,对上层应用层屏蔽读写分库、分表、缓存等技术细节。
  • 数据层:对DB数据进行分片,并可动态的添加shard分片。

水平分库的技术关键点在于数据访问层的设计,数据访问层主要包含三部分:

  • 分布式缓存
  • 数据库中间件
  • 数据异构中间件

而数据库中间件需要包含如下重要的功能:

  • ID生成器:生成每张表的主键
  • 数据源路由:将每次DB操作路由到不同的分片数据源上

ID生成器

ID生成器是整个水平分库的核心,它决定了如何拆分数据,以及查询存储-检索数据。ID需要跨库全局唯一,否则会引发业务层的冲突。此外,ID必须是数字且升序,这主要是考虑到升序的ID能保证MySQL的性能(若是UUID等随机字符串,在高并发和大数据量情况下,性能极差)。同时,ID生成器必须非常稳定,因为任何故障都会影响所有的数据库操作。

我们系统中ID生成器的设计如下所示。

17f74b92d0c7e38f58623bb7e58f3a0b.png

  • 整个ID的二进制长度为64位
  • 前36位使用时间戳,以保证ID是升序增加
  • 中间13位是分库标识,用来标识当前这个ID对应的记录在哪个数据库中
  • 后15位为自增序列,以保证在同一秒内并发时,ID不会重复。每个分片库都有一个自增序列表,生成自增序列时,从自增序列表中获取当前自增序列值,并加1,做为当前ID的后15位
  • 下一秒时,后15位的自增序列再次从1开始。

水平分库是一个极具挑战的项目,我们整个团队也在不断的迎接挑战中快速成长。

为了适应公司业务的不断发展,除了在MySQL数据库上进行相应的架构升级外,我们还搭建了一套完整的大数据实时分析统计平台,在系统中对用户的行为进行实时分析。

关于如何搭建大数据实时分析统计平台,对用户的行为进行实时分析,我们后面再详细介绍。

a6eca09ca55df6f373e67b1e2df51acf.png

bc20a4010f3c83503d553912a9747169.png点个在看支持我吧,转发就更好了


推荐阅读
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • 本文总结和分析了JDK核心源码(2)中lang包下的基础知识,包括常用的对象类型包和异常类型包。在对象类型包中,介绍了Object类、String类、StringBuilder类、StringBuffer类和基本元素的包装类。在异常类型包中,介绍了Throwable类、Error类型和Exception类型。这些基础知识对于理解和使用JDK核心源码具有重要意义。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • 2021最新总结网易/腾讯/CVTE/字节面经分享(附答案解析)
    本文分享作者在2021年面试网易、腾讯、CVTE和字节等大型互联网企业的经历和问题,包括稳定性设计、数据库优化、分布式锁的设计等内容。同时提供了大厂最新面试真题笔记,并附带答案解析。 ... [详细]
  • 本文介绍了在PostgreSQL中批量导入数据时的优化方法。包括使用unlogged表、删除重建索引、删除重建外键、禁用触发器、使用COPY方法、批量插入等。同时还提到了一些参数优化的注意事项,如设置effective_cache_size、shared_buffer等,并强调了在导入大量数据后使用analyze命令重新收集统计信息的重要性。 ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 流数据流和IO流的使用及应用
    本文介绍了流数据流和IO流的基本概念和用法,包括输入流、输出流、字节流、字符流、缓冲区等。同时还介绍了异常处理和常用的流类,如FileReader、FileWriter、FileInputStream、FileOutputStream、OutputStreamWriter、InputStreamReader、BufferedReader、BufferedWriter等。此外,还介绍了系统流和标准流的使用。 ... [详细]
  • 基于分布式锁的防止重复请求解决方案
    一、前言关于重复请求,指的是我们服务端接收到很短的时间内的多个相同内容的重复请求。而这样的重复请求如果是幂等的(每次请求的结果都相同,如查 ... [详细]
  • 视图分区_组复制常规操作网络分区amp;混合使用IPV6与IPV4 | 全方位认识 MySQL 8.0 Group Replication...
    网络分区对于常规事务而言,每当组内有事务数据需要被复制时,组内的成员需要达成共识(要么都提交,要么都回滚)。对于组成员资格的变更也和保持组 ... [详细]
  • MySQL外键1对多问题的解决方法及实例
    本文介绍了解决MySQL外键1对多问题的方法,通过准备数据、创建表和设置外键关联等步骤,实现了用户分组和插入数据的功能。详细介绍了数据准备的过程和外键关联的设置,以及插入数据的示例。 ... [详细]
author-avatar
玲子0909_366
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有