在上一课中,我们引入了数据库连接池技术,通过这个技术,我们成功解决了数据库连接复用的问题。这一改进虽然没有改变垂直电商系统的整体架构,但在系统内部的数据库交互过程中引入了数据库连接池,从而减少了频繁创建连接的开销,测试结果显示性能得到了显著的提升,达到了80%的性能增长。
此时,你的数据库仍然是单机部署,基于一些云厂商的基准测试结果,MySQL 5.7 在一台拥有 4 核 8G 内存的机器上大致能够支撑每秒 500 个事务处理(TPS)和每秒 10000 个查询请求处理(QPS)。
然而,运营团队通知你,即将举办双十一促销活动,公司计划在全渠道推广,这必然会导致查询请求急剧增加。在这种情况下,我们需要考虑如何实施主从分离来解决潜在的性能问题。
主从读写分离
其实,大部分系统的访问模型是读多写少,读写请求量的差距可能达到几个数量级。
理解起来很容易,因为某些操作的请求量通常远高于其他操作。比如,在社交媒体上查看朋友圈的请求数量可能远大于发布新动态的数量,或者在电商平台上,浏览商品的请求量可能远高于下单的数量。因此,我们的首要考虑是如何处理更多的查询请求,这就需要我们首先将读取和写入流量进行区分,这就是主从读写分离的概念。
它其实是个流量分离的问题,就好比道路交通管制一样,一个四车道的大马路划出三个车道给领导外宾通过,另外一个车道给我们使用,优先保证领导先行,就是这个道理。
主从读写的两个技术关键点
主从读写分离的核心概念是将一个数据库的数据复制到其他数据库服务器上,其中原始数据库称为主库,主要用于写入数据,而复制的目标数据库称为从库,主要用于处理数据查询。这个机制涉及两个关键技术点:1. 数据的复制,也被称为主从复制;2. 如何在主从分离的情况下屏蔽对数据库的访问方式变化,以确保开发人员可以像使用单一数据库一样进行操作。
1. 主从复制
MySQL的主从复制依赖于二进制日志(binlog),这是一种记录MySQL上所有更改的机制,并以二进制形式存储在磁盘上的日志文件中。主从复制的过程是将主库的binlog数据传输到从库上,通常是异步进行的,这意味着主库上的操作不会等待binlog同步完成。
主从复制的具体过程如下:首先,从库会连接到主库并创建一个IO线程,用于请求主库的binlog更新,并将接收到的binlog信息写入一个名为relay log的日志文件中。同时,主库也会创建一个log dump线程来将binlog发送给从库。从库还会创建一个SQL线程,负责读取relay log中的内容并在从库上进行回放,以确保主从之间的数据一致性。
这种方式的主从复制采用独立的log dump线程,以异步方式进行,避免了对主库的主要更新流程产生影响。从库将接收到的信息写入relay log,而不是直接写入从库的存储,以避免对从库的实际存储造成负担,从而减少主从同步的延迟。
出于性能考虑,主库的写入流程通常不会等待主从同步完成就立即返回结果。这意味着在极端情况下,例如主库的binlog数据还没有来得及刷新到磁盘上时发生了磁盘损坏或机器掉电,可能会导致binlog的丢失,进而导致主从数据不一致的情况发生。然而,这种情况发生的概率非常低,对于互联网项目来说是可以容忍的。
主从复制的好处之一是在写入时只需写主库,而在读取数据时只需读从库,这意味着即使写入请求会锁定表或记录,也不会影响读取请求的执行。此外,在处理大量读取流量时,可以部署多个从库来分担读取压力,这称为“一主多从”部署方式。在您的垂直电商项目中,可以通过这种方式来处理高并发的读取流量。此外,从库还可以用作备份,以防止主库故障导致数据丢失。
并不是无限制地增加从库的数量就能够轻松抵御大量并发请求。随着从库数量的增加,从库连接的IO线程也会增多,主库需要创建同样数量的log dump线程来处理复制请求,这会增加主库的资源消耗,同时受到主库网络带宽的限制。因此,在实际使用中,一般一个主库最多能够连接3到5个从库,超过这个数量可能会导致性能下降。
当然,主从复制也有一些缺陷,除了带来了部署上的复杂度,还有就是会带来一定的主从同步的延迟,这种延迟有时候会对业务产生一定的影响。
在发微博过程中,一些操作需要同步执行,例如更新数据库,而另一些操作可以异步执行,比如将微博信息发送给审核系统。为了解决主从数据库存在延迟时可能导致异常的问题,您采取了一种消息队列的方式,将微博的ID写入消息队列,再由队列处理机在从库中获取微博信息发送给审核系统。这种异步处理方式可以降低对数据库的实时性要求,提高系统的稳定性。此时如果主从数据库存在延迟,会导致在从库中获取不到微博信息,整个流程会出现异常。
这个问题解决的思路有很多,核心思想就是尽量不去从库中查询信息,纯粹以上面的例子来说,我就有三种解决方案:
第一种方案是数据的冗余。你可以在发送消息队列时不仅仅发送微博 ID,而是发送队列处理机需要的所有微博信息,借此避免从数据库中重新查询数据。
第二种方案是使用缓存。我可以在同步写数据库的同时,也把微博的数据写入到 Memcached 缓存里面,这样队列处理机在获取微博信息的时候会优先查询缓存,这样也可以保证数据的一致性。
最后一种方案是查询主库。我可以在队列处理机中不查询从库而改为查询主库。不过,这种方式使用起来要慎重,要明确查询的量级不会很大,是在主库的可承受范围之内,否则会对主库造成比较大的压力。
我会优先考虑第一种方案,因为这种方式足够简单,不过可能造成单条消息比较大,从而增加了消息发送的带宽和时间。
主从同步的延迟问题确实可能会导致一些令人困惑的情况,特别是在诊断问题时。您提到的从数据库中获取不到信息,然后过一段时间又可以读到数据的情况,很可能是由于主从延迟引起的。为了及时发现并解决这些问题,监控和报警主从延迟是一个明智的做法。通常情况下,主从同步延迟应该在毫秒级别,一旦延迟超过秒级别,就需要触发警报,以便及时采取措施来修复同步问题。这有助于确保数据库的数据一致性和可用性。
2. 如何访问数据库
随着主从复制和读写分离的实施,数据库的使用方式发生了显著变化。您需要处理主库地址和多个从库地址,以及区分写入操作和查询操作。当您还考虑到分库分表等更复杂的数据库管理需求时,这种复杂性会进一步增加。
为了简化数据库访问和管理,数据库中间件成为了一个有用的解决方案。数据库中间件通常可以分为两类,每一类都有不同的特点和优势。
第一类数据库中间件,以淘宝的TDDL(Taobao Distributed Data Layer)为代表,通常以代码形式嵌入到应用程序内部运行。它可以被视为数据源的代理,负责管理多个数据源,每个数据源对应一个数据库,可以是主库或从库。当应用程序发起数据库请求时,中间件会将SQL语句发送到特定的数据源进行处理,然后将处理结果返回给应用程序。
这类中间件的优点在于简单易用,它们没有额外的部署成本,因为它们嵌入到应用程序内部,与应用程序一起运行,因此适合运维能力较弱的小团队使用。然而,这类中间件的缺点是缺乏多语言的支持。目前,业界主流的解决方案如TDDL和早期的网易DDB都是用Java语言开发的,无法支持其他编程语言。此外,版本升级也需要使用方自行更新,可能较为困难。
另一类是单独部署的代理层方案,这一类方案代表比较多,如早期阿里巴巴开源的 Cobar,基于 Cobar 开发出来的 Mycat,360 开源的 Atlas,美团开源的基于 Atlas 开发的 DBProxy 等等。
这一类中间件部署在独立的服务器上,业务代码如同在使用单一数据库一样使用它,实际上它内部管理着很多的数据源,当有数据库请求时,它会对 SQL 语句做必要的改写,然后发往指定的数据源。
这类中间件通常使用标准的MySQL通信协议,因此可以很好地支持多种编程语言。由于它们是独立部署的,因此维护和升级相对方便,更适合具备一定运维能力的中大型团队使用。然而,这些中间件的缺点是所有SQL语句都需要经过两次网络传输:从应用到代理层,再从代理层到数据源,这可能导致性能略有损耗。
这些中间件,对你而言,可能并不陌生,但是我想让你注意到是,在使用任何中间件的时候一定要保证对于中间件有足够深入的了解,否则一旦出了问题没法快速地解决就悲剧了。
在之前的项目中,我们一直使用自研的分库分表组件来处理数据库的分片,但后来发现这个组件偶尔会导致一些问题,比如在某些情况下分库分表不生效,需要扫描所有的库和表,还有查询延迟达到了秒级别。因为对 Sharding-JDBC 这个组件不够了解,所以我们没有能够快速解决这些问题。最后,为了稳定项目,我们只能暂时切回原来的组件,然后在找到问题并解决之后再考虑切换回 Sharding-JDBC。
原文始发于微信公众号(二进制跳动):国庆专栏-数据库优化方案:查询请求增加时,如何做主从分离?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/166950.html