原文:http://guweigang.com/blog/2013/11/18/mysql-binlog-in-realtime/

众所周知,MySQL是最受欢迎的互联网数据库(没有之一)———————为开源而生。发展初期,很多公司都受益于其易用性和经济性。随着这些公司的成长,越来越多的公司投入到MySQL的开发中,因此MySQL的特性也越来越丰富,如:不同特性的存储引擎、Binlog主从复制方案等。

今天我们要探讨的就是如何实时解析MySQL Binlog,以及其所带来的巨大的业务价值。我可以在一瞬间说出很多应用场景,如:主从复制、缓存系统、检索引擎等。如下图所示,MySQL的Binlog不仅仅能用于MySQL服务之间的主从复制,还能造福其他非MySQL服务:

在一个成熟的商业系统中我们有且仅有一份正确的全量数据(这里指MySQL中的数据)。而面对现在应用丰富的功能以及用户越来越高的要求,MySQL并不能一直很好的满足我们。比如:我们需要使用缓存系统来加速服务,需要完备的检索系统来提供搜索服务,需要事件触发实时地发送通知等。但是摆在我们面前的问题是只有一份数据,且我们直接操作的都是MySQL(本文用MySQL来代表所有数据库系统),如何能让除MySQL之外的其他系统拥有同样的数据?且我们总是希望MySQL中的数据与其他系统的数据差异越小越好,越实时越好。当然我们可能会有一系列方案来解决这个问题,最典型的方法有两种:

  • 双写, 当你在更新MySQL的同时更新缓存系统、更新检索引擎,触发事件发送通知。这么做的确能解决问题,但是因为都是异构系统,没有一种机制能够保证写入成功。也就是说随着时间的推移,数据差异会越来越大。不仅如此,如果外部系统很多,这么做还会影响到主业务逻辑的响应时间。
  • 定时任务,在主业务逻辑中耦合更新其他系统的代码是不OK的,外部系统只有几个还行,如果要更新100个外部系统,这时候当如何处理呢?通过定时任务异步地去做这件事情看上去是一个更好的方案。的确如此,但是任务执行频率是多少呢?每1分钟?每10分钟?当然我们可以根据业务需要制定这个频率,乍想一下没啥问题,不过再想一下,定时任务具体用来做什么呢?

    • 定时导出全量:在数据量比较少的时候这是最省事儿的方法,当然下游每次必须清空自己的数据重新全量导入,数据量稍大一点代价就特别大。
    • 业务系统维护一个队列(在业务库中新建表当作队列):在业务系统中操作数据的时候顺便把自己操作的日志写到队列里面,然后由下游来消费这个队列。

      这种方式由业务系统来维护操作记录,好处是能保证数据的业务完整性,坏处是在业务端耦合了非业务相关的逻辑,每当数据有变更时都需要开启事务保证业务操作和其操作日志都能正确地写入。如果以后因为某些原因要直接操作DB,这种情况就很危险了,在操作DB时无法模拟复杂的业务逻辑计算和关系。

    • 定时扫表找出变更记录:每条数据都会有一个updateTime (on update CURRENT_TIMESTAMP)字段,当该条记录被修改时,MySQL会隐式地更新这个字段为当前时间。这种方法使业务端远离水火,以一种更解耦的方式来处理增量,非常适合无状态缓存更新之类的场景,当然如果表记录数过大可能会有慢查询。但如果存在状态流转的的数据,这种方式会丢失状态流转方向。

如果你使用队列方式,很可能会写出下面这段代码:

$db = getDI()->get("test_db");
$db->begin();
$db->update("// logic operate");
$db->insert("// operate logs");
$db->commit();

而且很可能你设计的操作日志表看起来会是这样子的:

 event_id   user_id   object_id    level   event_type   mcid   addtime             
      152 
      153 
      154 
      48 
      48 
      48 
 3007209739 
 3007209739 
 3007209739 
   200 
   202 
   201 
         23 
         24 
         24 
    0 
    0 
    0 
 2013-07-17 04:06:05 
 0000-00-00 00:00:00 
 0000-00-00 00:00:00 

如果你扫表的话可能会写出下面这段SQL:

SELECT * FROM `products` WHERE updateTime > '2013-11-12 12:00:00' ORDER BY updateTime LIMIT 1000;

下面我来具体介绍下我们的应用场景,以及我们是如何利用MySQL Binlog解决这个问题的,先上图:

这是我们整个系统的架构图,左边可以看成用户系统,右边则是商业系统。浏览器向前端搜索服务集群(图中红色部份)发送请求后,前端集群会把用户的请求数据发往很多个后端服务,而不同的后端之间并不是同构的,所以基本上都会有一个“长连接/协议转换代理层”(图中红色部份),然后再由后端决定是否满足用户需求,最后再由前端集群拼接来自不同后端服务的结果并呈现给网民。

看似一个海量访问的服务,其实是由无数个小服务组成的,图中所示就是我们组做的小服务,在具体场景中蓝色部分做的事情是把前端服务集群的协议转换成FASTCGI协议,并转发给后端服务UI/PHP-FPM。虽然名称叫UI,但是做的不是我们所理解的UI的工作,而是对于后面的检索和数据流来说它是最前面的一部分,所以才叫UI。它承担的工作是解析代理层发来的数据,并根据请求数据和参数构造结构化的查询条件,当然这个构造的过程可能仍需要请求很多外部服务,然后向检索系统(BS)发出请求,BS返回的是一个实体ID集合(你可以理解为商品ID),UI根据实体ID到数据库(这里省略了缓存系统)里面查询实体详情,整个完程就是这样。

我们这个架构中也存在上面描述的异构系统,BS(Basic Search)模块是对Lucene的二次开发,它需要DB中的数据来建立索引,而客户会时不时地往DB里面写一些数据,为了能让UI实时地从BS中检索到新数据,DB的所有变更要能实时地体现到BS中,这是用双写/定时任务无法完成的。所以我们开发了基于MySQL Binlog的异步事件框架(AdPipe)。

AdPipe由以下几部分组成:

  • BIZ framework: 大家都知道PHP是最快的,所以经常需要变更的事件逻辑使用PHP写。
  • PHP ext: 为了能在PHP用户空间写Binlog事件逻辑,不可避免地需要PHP扩展。
  • Binlog listener: 与MySQL Server连接,实现Binlog议协。

来看看BIZ框架的代码:

首先是启动脚本

上面代码主要职责是连接MySQL Server,并且设置Binlog的位置,建立连接后根据接收到的事件调用BinlogEvent相应的方法。而BinlogEvent所有方法仅仅返回一些状态码(IGNORE, SUCCESS, L_EXIT, SIGNAL_QUIT),这些状态码控制着消息该如何处理。

其次是事件处理类,BinlogEvent:

看171行,getHandleClass方法通过获取一个业务对象来处理事件,如果没有找到处理某个DB事件的类,那么默认会调用DefaultEvent来处理。

请看DefaultEvent类:

由于Binlog事件和DB的scheme是息息相关的,所以在DefaultEvent中也调用了Model类来获取表字段。每个事件处理类都有三个方法,onWrite, onUpdate, onDelete,你可以在方法中获取DB变更的数据,然后根据业务需求做各种变换,然后打包成消息。

接下来就说到消息类,这里的消息是指逻辑消息,是经过业务代码变换后准备发往BS的消息,Message

以上就是AdPipe系统中biz framework的代码,当然代码要能运行,需要安装binlog listener和php-binlog扩展,请点击下面链接查看:

具体安装过程可以分别查看它们的readme。

如果biz framework挂了,会影响其他系统吗?

这是个好问题,如果biz framework挂了,我们也不用担心,因为在我们的架构中AdPipe的上游和下游都是队列,上游是binlog队列,而且文件存在MySQL服务器上:

     filename           position    
 mysql-bin.000001 
 mysql-bin.000002 
      ...         
    422655739    
    124544114    
      ...        

而我们的下游是AMQ之类的消息队列,BS通过AMQ来获取消息。上下游均通过队列解耦,所以biz framework不幸挂了,不会影响其他系统。

如果biz framework挂了,能快速恢复运行吗?

当然可以,biz framework每次处理完事件后,都会保存该事件的filename和position,所以如果服务挂掉,可以在连接MySQL服务器之后显示地设置filename和position,如:binlog_set_position($link, $filename, $position)。

下游(BS)如果挂了,能快速恢复吗?

这是个非常关键的问题,如果BS挂了它需要重建索引。随着系统的运行,增量数据越来越多,如果给BS一份最原始的基准基量文件,那么BS要消费从系统启动至今的所有增量消息,这可能需要几个月甚至一年。如果我们有一份最新的全量基准文件,情况就大不一样了,所以AdPipe另一个功能是定时生成最新的全量基准文件,以供下游恢复数据使用,这样下游只需追有限的增量消息便能跟上DB的数据。

所以这进一步引发我们对这个事情的思考,能不能设计一个通用的事件触发系统呢,这里我们只考虑 文件(inotify)/MySQL(binlog)/MongoDB(oplog),其他类似于redis也有keyspace notifications功能。

[转]mysql binlog in realtime的更多相关文章

  1. Canal - 数据同步 - 阿里巴巴 MySQL binlog 增量订阅&消费组件

    背景 早期,阿里巴巴 B2B 公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求 ,主要是基于trigger的方式获取增量变更.从 2010 年开始,公司开始逐步尝试数据库日志解析,获取增量变 ...

  2. 原创工具binlog2sql:从MySQL binlog得到你要的SQL

    从MySQL binlog得到你要的SQL.根据不同设置,你可以得到原始SQL.回滚SQL.去除主键的INSERT SQL等. 用途 数据回滚 主从切换后数据不一致的修复 从binlog生成标准SQL ...

  3. MySQL binlog中的事件类型

    MySQL binlog记录的所有操作实际上都有对应的事件类型的,譬如STATEMENT格式中的DML操作对应的是QUERY_EVENT类型,ROW格式下的DML操作对应的是ROWS_EVENT类型. ...

  4. MySQL Binlog Mixed模式记录成Row格式

    背景: 一个简单的主从结构,主的binlog format是Mixed模式,在执行一条简单的导入语句时,通过mysqlbinlog导出发现记录的Binlog全部变成了Row的格式(明明设置的是Mixe ...

  5. MySQL binlog的格式解析

    我搜集到了一些资料,对理解代码比较有帮助. 在头文件中binlog_event.h中,有描述 class Log_event_header class Log_event_footer 参见[Myst ...

  6. Mysql binlog

    理解Mysql binlog 日志的三种模式   本文介绍下,mysql中binlog日志的三种模式,了解了各种模式的不同之处,才能更好地应用.有需要的朋友建议参考下.   一,模式1 Row Lev ...

  7. MySQL bin-log 日志清理方式

    MySQL bin-log 作用   1.数据恢复:如果你的数据库出问题了,而你之前有过备份,那么可以看日志文件,找出是哪个命令导致你的数据库出问题了,想办法挽回损失. 2.主从服务器之间同步数据:主 ...

  8. Mysql Binlog 三种格式介绍及分析

    一.Mysql Binlog格式介绍       Mysql binlog日志有三种格式,分别为Statement,MiXED,以及ROW! 1.Statement:每一条会修改数据的sql都会记录在 ...

  9. Mysql binlog日志解析

    1. 摘要: Mysql日志抽取与解析正如名字所将的那样,分抽取和解析两个部分.这里Mysql日志主要是指binlog日志.二进制日志由配置文件的log-bin选项负责启用,Mysql服务器将在数据根 ...

随机推荐

  1. 转:Delphi各种Socket组件的模式和模型

    Delphi的大多数书籍里面都没有提到delphi的各种socket通信组件的模式和模型,有的书只讲解了windows的socket模式和模型,并没有归纳各种组件采用的模型,所以我们的程序员并不知道如 ...

  2. 配置 node.js 环境

    安装 Node.js 1. 下载 Node.js, 首先到官网 http://nodejs.org/download/ 的下载页面下载 Windows 版本, 这里有两种版本,推荐 .msi 的安装程 ...

  3. Android源代码结构分析

    Google提供的Android包含了:Android源代码,工具链,基础C库,仿真环境,开发环境等,完整的一套.第一级别的目录和文件如下所示:----------------├── Makefile ...

  4. Servlet 利用Session实现不重复登录

    import java.io.IOException;import java.io.PrintWriter;import java.util.ArrayList;import java.util.It ...

  5. Servlet的5种方式实现表单提交(注册小功能),后台获取表单数据

    用servlet实现一个注册的小功能 ,后台获取数据. 注册页面: 注册页面代码 : <!DOCTYPE html> <html> <head> <meta ...

  6. Linux内核同步机制--转发自蜗窝科技

    Linux内核同步机制之(一):原子操作 http://www.wowotech.net/linux_kenrel/atomic.html 一.源由 我们的程序逻辑经常遇到这样的操作序列: 1.读一个 ...

  7. mysql 字符串处理优化

    周五下午,同事突然说有个存储过程要帮忙优化,就拿来看看,大概看了下: 数据库端需求:数据库中要存储一个AppID字段,对应一个Account可以自行设置自己的AppID(我就不从业务上多说了), 以前 ...

  8. Sql Server 孤立用户解决办法

    Sql Server 孤立用户 是我们经常遇到的事情,今天详细的梳理了下,希望能帮到你 当把用户数据库从一台 Sql Server 使用备份和恢复的方式迁移到另一台服务器.数据库恢复以后,原先用户定义 ...

  9. 利用Lambda获取属性名称

    感谢下面这篇博文给我的思路: http://www.cnblogs.com/daimage/archive/2012/04/10/2440186.html 上面文章的博主给出的代码是可用的,但是调用方 ...

  10. 利用fiddler模拟发送json数据的post请求

    fiddler是调试利器,有许多好用的功能,这里简单的介绍一下利用fiddler模拟发送post请求的例子 先简单介绍一下失败的例子,最后给出正确的方法