上一个内容介绍了池化技术,现在的架构图如下所示:
image.png
此时数据库还是单机部署,根据一些云厂商的Benchmark的结果,在4核8G的机器上运行MySQL5.7时,大概可以支撑500的TPS和10000的QPS。这时,如果负责人说要进行全渠道推广,这无疑会引发查询量骤增的问题。那么当查询请求增加时,应该如何做主从分离来解决问题。
主从读写分离
大部分系统的访问模型是读多写紹,读写请求量的差距可能达到几个数量级。因此我们优先考虑数据库如何扛住更高的查询请求,那么首先需要把读写流量分开,这样才方便针对流量做单独的扩展,这就是主从读写分离。
这其实是个流量分离的问题,这个方法本身是一种常规的做法,即使在一个大的项目中,它也是一个应对数据库突发读流量的有效方法。
曾经出现过前端流量突增导致从库负载过高的问题,DBA会优先做一个从库扩容上去,这样对数据库的读流量会落入多个从库上,从库的负载就降了下来,然后研发再考虑使用什么方案将流量挡在数据库层之上。
主从读写的两个技术关键点
一般来说主从读写分离机制中,我们将一个数据库的数据拷贝为一份或者多份,并且写入到其他的数据库服务器中,原始的数据库我们称之为主库,主要负责数据库的写入,拷贝的目标数据库我们称为从库,主要负责支持数据查询。可以看到,主从读写分离有两个技术上的关键点:
- 一个是数据的拷贝,我们称为主从复制;
- 在主从分离的情况下,我们如何屏蔽主从分离带来的访问数据库方式的变化,让开发像是在使用单一数据库一样。
1.主从复制
我们先以MySQL为例介绍一下主从复制。
MySQL的主从复制是依赖于binlog的,也就是记录MySQL上的所有变化并以二进制形式保存在磁盘上二进制日志文件。主从复制就是将binlog中的数据从主库传输到从库上,一般这个过程是异步的,即主库上的操作不会等待binlog同步的完成。
主从复制的过程是这样的:首先从库在连接到主节点时会创建一个IO线程,用以请求主库更新的binlog,并且把接收到的binlog信息写入一个叫做relay log的日志文件中,而主库也会创建一个log dump线程来发送binlog给从库;同时从库还会创建一个SQL线程读取relay log中的内容,并且在从库中做回访,最终实现主从的一致性。这是一种比较常见的主从复制方式。
在这个方案中,使用独立的log dump线程是一种异步的方式,可以避免对主库的主题更新流程产生影响,而从库在接收到信息后并不是写入从库的存储中,而是写入一个relay log,是避免从库实际存储会比较耗时,最终造成从库和主库的延迟变长。
image
会发现基于性能的考虑,主库的写入流程并没有等待主从同步完成就会返回结果,那么在极端的情况下,比如说主库上binlog还没有来得及刷新到磁盘上就出现了磁盘损坏或者机器掉电,这就会导致binlog的丢失,最终造成主从数据的不一直。不过这种情况出现的概率很低,对于互联网的项目来说是可以容忍的。
做了主从复制后,我们就可以在写入时只写主库,在读数据时只读从库,这样即使写请求会锁表或者锁记录,也不会影响到读请求的执行。同时在读流量比较大的情况下,我们可以部署多个从库共同承担读流量,这就是说说的“一主多从”部署方式,在垂直电商项目中就可以通过这种方式来抵御较高的并发读流量,另外,从库可以当成一个备库来使用,以避免主库故障导致数据丢失。
那么是不是我无限的增加从库的数量就可以抵抗大量的并发呢?实际上并不是,因为随着从库数量的增加,从库连接上来的IO线程比较多,主库也需要创建同样多的log dump线程来处理复制的请求,对于主库资源消耗比较高,同时受限于主库的网络带宽,所以在实际使用中,一般一个主库最多挂3~5个从库。
当然主从复制也有一些缺陷,除了带来部署上的复杂度,还有就是会带来一定的主从同步的延迟,这种延迟有时候会对业务产生一定的影响。比如:
在发微博的过程中会有些同步的操作,像是更新数据库的操作,也有一些异步的操作,比如说将微博的信息同步给审核系统,所以我们在更新完主库之后,会将微博的ID写入消息队列,再由队列处理机依据ID在从库中获取微博信息再发送给审核系统。此时如果主从数据库存在延迟,会导致在从库纵获取不到微博信息,整个流程会出现异常。
这种问题解决的思路有很多,核心思想就是尽量不去从库中查询信息,以上面的例子来说,有三种解决方案:
第一种方案是数据的冗余。你可以在发送队列时不仅仅发送微博ID,而是发送队列需要的所有微博信息,借此避免从数据库中重新查询数据。
第二种方案是使用缓存。我们可以在同步写数据的同时,也把微博的数据写入到Memcached缓存里面,这样队列处理机在获取微博信息的时候会优先查询缓存,这样也可以保证数据的一致性。
最后一种方案是查询主库。我们可以在队列处理机中不查询从库而改为查询主库。不过这种方式使用起来要慎重,要明确查询的量级不会很大,是在主库的可承受范围之内,否则会对主库造成比较大的压力。
优先考虑第一种方案,因为足够简单,不过可能造成单条消息比较大,从而增加了消息发送的带宽和时间。
缓存的方式比较适合新增数据的场景,在更新数据的场景下,先更新缓存可能会造成数据的不一致。
最后不建议使用第三种方案,因为这个方案要提供一个查询主库的接口,在团队开发的过程中,很难保证其他成员不会滥用这个方法,而一旦主库承担了大量的读请求导致崩溃,对于整体系统的影响是巨大的。
主从同步的延迟,使我们排查问题时很容易忽略的一个问题。有时候我们遇到从数据库中获取不到信息的诡异问题时,会纠结于代码中是否有一些逻辑会把之前写入的内容删除,但是优惠发下你,过了一段时间再去查询又可以读到数据了,这几本就是主从延迟在作怪,所以,我们一般会把从库落后的时间作为一个重点的数据库指标做监控和报警,正常的时间是在毫秒级别,一旦落后的时间达到了秒级别就需要告警了。
2.如何访问数据库
我们已经使用主从复制技术将数据复制到了多个节点,也实现了数据库的读写分离。这时,对于数据库的使用方式发生了变化。以前只需要使用一个数据库地址就好了,现在需要使用光一个主库地址和多个从库地址,并且需要区分写入操作和查询操作,如果结合“分库分表”,复杂度会提升更多。为了降低实现的复杂度,业界涌现了很多数据库中间件来解决数据库的访问问题,这些中间件可以分为两类。
第一类以淘宝的TDDL(Taobao Distributed Data Layer)为代表,以代码形式内嵌运行在应用程序内部,可以把它看成是一种数据源的代理,它的配置管理着多个数据源,每个数据源对应一个数据库,可能是主库,可能是从库。当有一个数据库请求时,中间件将SQL语句发给某一个指定的数据源来处理,然后将处理结果返回。
这一类中间件的优点是简单易用,没有多余的部署成本,因为它是植入到应用程序内部,与应用成语一同运行的,所以比较适合运维能力较弱的小团队使用;缺点是缺乏多语言的支持,目前业界这一类的主流方案除了TDDL,还有早期的网易DDB,它们都是Java开发的,无法支持其他语言,另外版本升级也依赖使用方更新,比较困难。
另一类是单独部署的代理层方案,这一类方案代表比较多,如早起的阿里巴巴开源的Cobar。
这一类中间件部署在独立的服务器上,业务代码如同在使用单一数据库一样使用它,实际上它内部管理着很多的数据源,当有数据库请求时,它会对SQL语句做必要的改写,然后发往指定的数据源。
它一般使用标准的MySQL通信协议,所以可以很好的支持多语言。由于它是独立部署的,所以也比较方便进行维护升级,比较适合有一定运维能力的大中型团队使用。它的缺陷是所有的sql语句都需要跨两次网络:从应用到代理层和从代理层到数据源,所以性能上会有一些损耗。
image.png
在使用中间件的时候一定要保证对于中间件有足够深入的了解,否则一旦出现问你就无法快速的解决。
网友评论