查询请求增长时,如何作主从分离?

依据一些云厂商的 Benchmark 的结果,在 4 核 8G 的机器上运行 MySQL 5.7 时,大概能够支撑 500 的 TPS 和 10000 的 QPS。这时,运营负责人说正在准备双十一活动,而且公司层面会继续投入资金在全渠道进行推广,这无疑会引起查询量骤然增长的问题。那么当查询请求增长时,应该如何作主从分离来解决问题。
前端


主从读写分离数据库

其实,大部分系统的访问模型是读多写少,读写请求量的差距可能达到几个数量级。缓存


这很好理解,刷朋友圈的请求量确定比发朋友圈的量大,淘宝上一个商品的浏览量也确定远大于它的下单量。所以,咱们优先考虑数据库如何抵抗更高的查询请求,那么首先你须要把读写流量区分开,由于这样才方便针对读流量作单独的扩展,这就是咱们所说的主从读写分离。服务器


它实际上是个流量分离的问题,就比如道路交通管制同样,一个四车道的大马路划出三个车道给领导外宾经过,另一个车道给咱们使用,优先保证领导先行,就是这个道理。网络


这个方法自己是一种常规的作法,即便在一个大的项目中,它也是一个应对数据库突发读流量的有效方法。并发


我目前的项目中就曾出现过前端流量突增致使从库负载太高的问题,DBA 兄弟会优先作一个从库扩容上去,这样对数据库的读流量就会落入到多个从库上,从库的负载就降了下来,而后研发同窗再考虑使用什么样的方案将流量挡在数据库层之上。运维


主从读写的两个技术关键点异步

通常来讲在主从读写分离机制中,咱们将一个数据库的数据拷贝为一份或者多份,而且写入到其它的数据库服务器中,原始的数据库咱们称为主库,主要负责数据的写入,拷贝的目标数据库称为从库,主要负责支持数据查询。能够看到,主从读写分离有两个技术上的关键点:ide

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 在从库中获取微博信息再发送给审核系统。此时若是主从数据库存在延迟,会致使在从库中获取不到微博信息,整个流程会出现异常。

图片


这个问题解决的思路有不少,核心思想就是尽可能不去从库中查询信息,纯粹以上面的例子来讲,我就有三种解决方案:


第一种方案是数据的冗余。你能够在发送消息队列时不只仅发送微博 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 没有足够了解,这两个问题咱们都没有很快解决,后来不得已只能切回原来的组件,在找到问题以后再进行切换

相关文章
相关标签/搜索