MySQL读写分离

96
ntop
0.1 2016.03.12 16:52* 字数 1714

让我们考虑一个场景:一个个人blog网站的作者,每天会编辑并发表一篇博客。由于内容质量比较好,所以每篇博客都有很高的阅读量(100000(R):1(W)),但是每天作者早上起来编辑新文章的时候都会发现,编辑过程非常辛苦,经常编辑的内容提交不上去,浏览器转圈圈。

这就是典型的并发问题,单机数据库承担了太多的请求,导致作者无法提交编辑的内容。一个直觉的想法是,多加几台服务器,把压力分担到多台服务器上,但是这样会带来一个问题,多台数据库之间的数据同步,这是一个很复杂的问题,一个简化的方案是主从结构 - 一台主库负责写,多台从库负责读,这样数据同步方案就会变得简单,让所有的从库及时从主库同步数据即可。而这正是前面介绍的 - MySQL主从复制(BinaryLog)

在实现上有两种方式:

  1. 应用层实现 在应用层,比如使用SpringJDBC/myBatis/Hibernate访问数据库时配置多数据源,这些组件会通过算法把请求分流到不同的数据源。
  2. 代理实现 这种方式是在应用层和数据库集群之间添加一个代理服务,应用层访问代理,代理根据请求类型(读/写)自动分流到不同的数据库服务器。

下面简单的介绍这两种方案:

应用层实现

基于应用层实现的读写分离拓扑结构如下图,这种方式在Web层已经决定了读写的方向,所有的写操作写到Master,所有的读操作按一定算法分流到不同的Slave服务器。

Paste_Image.png

基于这种方案的实现,由于应用框架的不同实现方式各不相同,在基于SpringJDBC的框架一种简单的方式,就是定义多个数据库连接,一个MasterDataSource和一个SlaveDataSource。更新数据时我们读取MasterDataSource,查询数据时我们读取SlaveDataSource。

<!-- 配置访问数据库的数据源信息 -->
<bean id="masterDataSource" class="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
    <property name="url" value="jdbc:mysql://xxxx:3306/dbname"/>
    <property name="user" value="root"/>
    <property name="password" value="l"/>
</bean>

<bean id="slaveDataSource" class="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
    <property name="url" value="jdbc:mysql://xxxx:3306/dbname"/>
    <property name="user" value="root"/>
    <property name="password" value="l"/>
</bean>

<!-- 配置Spring上下文,提供 jdbcTemplate 写操作模板实现 -->
<bean id="writeTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="masterDataSource"/>
</bean>

<!-- 配置Spring上下文,提供 jdbcTemplate 读操作模板实现 -->
<bean id="readTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="slaveDataSource"/>
</bean>

这种做法在只有一个从库(Slave)的情况下还好说,但是有多个从库的时候就不好办了。解决的办法也很多,比如集成 AbstractRoutingDataSource 自己实现算法。

下面给出一些链接,包含其他组件的配置:

在应用层实现读写分离不需要做底层复杂的配置,而且性能比较好,但是对应用的侵入性比较强,不利于扩展。而代理实现完全屏蔽了读写分离的细节,从工程上讲,这是比较好的一种实现方式。

代理实现


代理实现的拓扑结构大概如下图,Web请求集中到单一的数据源(代理),但是代理会重新根据读/写不同,把请求分流到不同的数据服务器。

loadbalance for db

MySQL-Proxy MySQL官方提供的一个基于代理的负载均衡,可以理解SQL语言,在接收到SQL请求后,会根据请求类型自动把请求分流到Master和Slaves。

注:MySQL-Proxy现在是Alpha版本,不建议在生产环境使用

安装

MySQL-Proxy提供多种安装方式,比较推荐的还是通过官方提供的二进制文件安装(在不支持的平台上,再考虑自己通过源码编译安装)。

shell> cd /usr/local
shell> tar zxf mysql-proxy-0.8.5-platform.tar.gz

安装后可以把 $MYSQL-PROXY-DIR/bin 目录添加到环境变量,方便使用。主要使用 mysql-proxy 命令,这个命令可以用来启动SQL代理,比如:

> mysql-proxy --proxy-backend-addresses 192.168.0.1:3306 --proxy-backend-addresses 192.168.0.2:3306

同时设定了主库和从库的地址,这个命令可以使用多次,从而设置多个从库地址(mysql-proxy有好多参数没有仔细研究)。

也可以这样使用:

> mysql-proxy --proxy-backend-addresses=MySQL.example.com:3306

仅仅把SQL代理作为一个普通的代理服务使用。

MySQL-Proxy的工作模式其实并不仅仅这样,它可以通过lua脚本实现更多复杂的逻辑,这样可以更精确的控制代理服务的行为。关于如何利用lua脚本来控制代理行为,最好是参见文档,
这里面提供了一些基础的系统方法,通过这些方法可以组合出复杂的路由分发逻辑。

MySQL-Proxy目前还在Alpha阶段,官方并不建议在生产环境使用,并且网络上普遍评论不够稳定,并且性能也不高。所以不太想做太详细的介绍。目前用的比较多的是HAProxy ,它工作在传输层(TCP/IP),不会直接解释SQL语言,所以在性能上会比MySQL-Proxy高很多,而且在真实环境中用的比较多。

HAProxy在使用方式上和MySQL-Proxy略有区别,它要求在应用层做读写分离,并需要给HAProxy分别配置读/写端口,一个用来做写操作,一个用来做读操作。然后在应用层还需要做一点修改,这又回到了最开始的的应用层做读写分离的情况,唯一的区别是这里把情况由原来的一写多读变成了一写一读。

那么再考虑我们最开始的解决方案 - 复杂的负载均衡处理。这种方案的缺点是所有的数据库节点都是同等的,多个节点之间的同步是比较复杂的,但是庆幸的是,这些问题已经有了很多好的解决方案,比如 MySQL CLUSTER 或者 GALERA CLUSTER,这些集群方案完全屏蔽了复杂的读写分离设计,对外的开发接口是简单统一的。

所以更一般的方案是HAProxy+集群方案。

参考阅读

  1. MySQL Load Balancing with HAProxy - Tutorial
  2. Load balancing with HAProxy
  3. Amobe实现MySQL读写分离
  4. mysql配置mysql-proxy读写分离
  5. Simple benchmark on MySQL Proxy vs HAProxy
  6. MySQL主从复制与读写分离

这篇写的虎头蛇尾,实在是写不下去,在咖啡厅的时候,有个老男人一直在旁边吹牛逼泡妹子...

后端
Web note ad 1