- 前言
- 为什么要写这本书
- 读者对象
- 如何阅读本书
- 勘误和支持
- 致谢
- 第一部分 安全运维篇
- 第 1 章 Linux 服务器安全运维
- 第 2 章 Linux 网络安全运维
- 第 3 章 数据安全工具 DRBD、extundelete
- 第二部分 运维故障排查篇
- 第 4 章 Linux 系统运维故障排查思路
- 第 5 章 Linux 故障排查案例实战
- 第三部分 自动化运维篇
- 第 6 章 轻量级运维利器 pssh、pdsh 和 mussh
- 第 7 章 分布式监控系统 Ganglia
- 第 8 章 基于 nagios 的分布式监控报警平台 Centreon
- 第 9 章 通过 Ganglia 与 Centreon 构建智能化监控报警平台
- 第四部分 集群架构篇
- 第 10 章 高性能 Web 服务器 Nginx
- 第 11 章 高性能集群软件 Keepalived
- 第 12 章 千万级高并发负载均衡软件 HAProxy
- 第 13 章 构建高性能的 MySQL 集群系统
- 第 14 章 高性能负载均衡集群软件 HAProxy
13.4 MySQL 读写分离解决方案
在 MMM 集群架构中,通过提供虚拟的读、写 IP 地址,将数据库的读写功能分离出来,但是这仅仅设定了读、写的 VIP 地址,并没有真正实现业务系统中所说的读、写分离功能,因为应用程序不可能在需要读的时候就去找可读的 VIP,在写的时候就去找可写的 VIP,要解决这个问题,可由两种方法来实现。
第一种实现读、写分离的方式是通过修改程序,将读、写操作提取出来,并分别在程序的连接池中设定可读、可写的 VIP 地址,这种方法要修改业务系统的程序,实现起来相对比较困难,如果是新开发的程序,可以在开发时就预留这样的接口,而如果程序已经在运行,修改的难度是相当大的,采用这种方法基本行不通。
第二种实现读、写分离的方法是通过一个数据库透明代理,也就是在业务系统和数据库之间提供一个代理接口,由这个接口来完成业务系统读、写请求的分发,将读操作分发到后端只读的数据库服务器上,而将写请求分发到后端可写的数据库服务器上。常见的读、写分离软件有 Amoeba 和 MySQL-Proxy。
MySQL-Proxy 是 MySQL 官方推出的一个处在业务系统和 MySQL 数据库之间的程序,这个代理可以用来分析、监控和变换通信数据,但是 MySQL 官方建议不要将 MySQL-Proxy 用于生产环境。事实上,MySQL-Proxy 确实很不稳定,它的读、写分离功能都是通过一个 lua 脚本来实现的,而这个脚本 bug 很多,所以不建议通过 MySQL-Proxy 来实现读、写分离功能,不过可以作为线下测试使用。
Amoeba 是一个开源项目,致力于 MySQL 的分布式数据库前端代理层,它主要在应用层访问 MySQL 的时候充当 SQL 路由器功能,具有负载均衡、高可用性、SQL 过滤、读写分离等功能,通过 Amoeba 可以实现数据源的高可用、负载均衡、数据切片等功能。本节将重点介绍 Amoeba 作为 MySQL 读、写分离代理接口的实现过程。
13.4.1 通过 Amoeba 实现 MySQL 读写分离
1.MMM 整合 Amoeba 应用架构
在实际的应用环境中,Amoeba 可与简单的 MySQL 主从复制架构进行整合,实现读与写的分离操作。但是这样存在安全性问题,例如 MySQL 的 Master 节点出现故障或者任何一个 Slave 节点故障,那么 Amoeba 并不能自动屏蔽这些故障的 MySQL 节点,可能会导致前端应用程序无法读取数据库的情况。为了解决这个问题,Amoeba 经常与 MMM 集群架构一起使用,这样如果任意 MySQL 节点故障,MMM 集群就能自动屏蔽故障节点,从而保证 Amoeba 一直能够连接到正常的 MySQL 节点。这里要介绍的 Amoeba 应用环境就是在 MMM 集群的基础上构建的,整个架构如图 13-17 所示。
图 13-17 MMM 整合 Amoeba 应用架构
此架构其实就是在 MMM 集群架构的基础上增加了 Amoeba Server 服务器,这样前端所有应用程序的请求都将提交到 Amoeba Server 上,然后 Amoeba Server 根据自身的读、写配置参数将读请求分配到可读的每个 MySQL 节点,而将写请求分配到可写的 MySQL 节点上。这个架构由于在底层使用了 MMM 集群,因此,Amoeba Server 不用担心会将请求分配到一个故障的 MySQL 节点,因为 MMM 集群会自动转移故障节点到健康节点上。
2.Amoeba 的安装
可以从 http://sourceforge.net/projects/amoeba/下载最新版本的 Amoeba,这里下载的是 amoeba-mysql-3.0.5-RC-distribution.zip。Amoeba 的安装非常简单,直接解压即可使用,这里将 Amoeba 解压到/usr/local/amoeba 目录下,这样就完成安装了。
Amoeba 框架是基于 Java SE1.5 开发的,因此,还需要安装 Java 环境,建议使用 Java SE1.5 以上的 JDK 版本,这里使用的 JDK 版本为 jdk1.6.0_25。将 JDK 安装到/usr/local/目录下,然后设置 Java 环境变量,信息如下:
export JAVA_HOME=/usr/local/jdk1.6.0_25 export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/ lib/tools.jar export PATH=$JAVA_HOME/bin:$PATH
将这些内容添加到系统的/etc/profile 文件中即可完成 Java 环境的设置。
3.配置 Amoeba
Amoeba 的配置文件在本环境下位于/usr/local/amoeba/conf 目录。配置文件比较多,但是仅仅使用读、写分离功能,只需配置二个文件即可,分别是 dbServers.xml 和 amoeba.xml,如果需要配置 IP 访问控制,还需要修改 access_list.conf 文件。下面首先介绍 dbServers.xml 文件的配置,内容如下:
<?xml version="1.0" encoding="gbk"?> <!DOCTYPE amoeba:dbServers SYSTEM "dbserver.dtd"> <amoeba:dbServers xmlns:amoeba="http://amoeba.meidusa.com/"> <dbServer name="abstractServer" abstractive="true"> <factoryConfig class="com.meidusa.amoeba.mysql.net.MysqlServerConnectionFactory"> <property name="connectionManager">${defaultManager}</property> <property name="sendBufferSize">64</property> <property name="receiveBufferSize">128</property> # 下面这个配置是设置 Amoeba 要连接的 mysql 数据库的端口,默认是 3306 <property name="port">3306</property> # 下面这个配置是设置 Amoeba 默认连接的数据库名,当连接业务系统连接 amoeba 时,操作 # 表必须显式指定数据库名,即采用 dbname.tablename 的方式,不支持“use dbname ” # 指定默认库 <property name="schema">repldb</property> # 下面这两个配置是设置 Amoeba 连接后端数据库服务器的账号和密码,因此需要在所有后 # 端数据库器上创建该用户,并授权 Amoeba 服务器可连接 <property name="user">ixdba</property> <property name="password">xxxxxx</property> </factoryConfig> <poolConfig class="com.meidusa.toolkit.common.poolable.PoolableObjectPool"> <property name="maxActive">500</property># 配置最大连接数,默认是 500 <property name="maxIdle">500</property> # 配置最大空闲连接数 <property name="minIdle">1</property> # 配置最小空闲连接数 <property name="minEvictableIdleTimeMillis">600000</property> <property name="timeBetweenEvictionRunsMillis">600000</property> <property name="testOnBorrow">true</property> <property name="testOnReturn">true</property> <property name="testWhileIdle">true</property> </poolConfig> </dbServer> # 下面这个配置用来设置一个后端可写 dbServer ,这里定义为 writedb ,这个名字可以任意命名,后面还 # 会用到 <dbServer name="writedb" parent="abstractServer"> <factoryConfig> # 下面这个配置是指定可写数据库的 IP 地址,也就是 MMM 集群提供对外访问的可写 VIP 地址 <property name="ipAddress">192.168.88.30</property> </factoryConfig> </dbServer> # 下面这段配置用来设置一个后端可读 dbServer ,这里定义了 4 台可读 dbServer ,分别是 slave1 、 # slave2 、slave3 和 slave4 ,这个名字也可以任意命名,每个 dbServer 对应的 IP 地址就是 # MMM 集群提供对外访问的可读 VIP 地址 <dbServer name="slave1" parent="abstractServer"> <factoryConfig> <property name="ipAddress">192.168.88.31</property> </factoryConfig> </dbServer> <dbServer name="slave2" parent="abstractServer"> <factoryConfig> <property name="ipAddress">192.168.88.32</property> </factoryConfig> </dbServer> <dbServer name="slave3" parent="abstractServer"> <factoryConfig> <property name="ipAddress">192.168.88.33</property> </factoryConfig> </dbServer> <dbServer name="slave4" parent="abstractServer"> <factoryConfig> <property name="ipAddress">192.168.88.34</property> </factoryConfig> </dbServer> # 下面的配置是定义一个虚拟的 dbServer ,实际上相当于一个 dbServer 组,这里将可读的数据库 IP 统一 # 放到一个组中,将这个组的名字命名为 myslaves <dbServer name="myslaves" virtual="true"> <poolConfig class="com.meidusa.amoeba.server.MultipleServerPool"> # 下面的配置是选择调度算法 1 表示复制均衡,2 表示权重,表示 HA ,这里选择 1 ,实现读操作的负载均衡 <property name="loadbalance">1</property> # 下面的配置是设置 dbServer 组中的成员,这里将所有可读的 dbServer 全部加进来 <property name="poolNames">slave1,slave2,slave3,slave4</property> </poolConfig> </dbServer> </amoeba:dbServers>
另一个配置文件 amoeba.xml 的内容如下:
<?xml version="1.0" encoding="gbk"?> <!DOCTYPE amoeba:configuration SYSTEM "amoeba.dtd"> <amoeba:configuration xmlns:amoeba="http://amoeba.meidusa.com/"> <proxy> <service name="Amoeba for Mysql" class="com.meidusa.amoeba.mysql.server.MySQLService"> # 下面的配置是设置 Amoeba 监听的端口,默认是 8066 ,可根据情况进行修改 <property name="port">8066</property> # 下面的配置是设置监听的接口,如果不设置,默认监听所有的 IP <!-- <property name="ipAddress">127.0.0.1</property> --> <property name="connectionFactory"> <bean class="com.meidusa.amoeba.mysql.net.MysqlClientConnectionFactory"> <property name="sendBufferSize">128</property> <property name="receiveBufferSize">64</property> </bean> </property> <property name="authenticateProvider"> <bean class="com.meidusa.amoeba.mysql.server.MysqlClientAuthenticator"> # 下面这两个配置主要是设置客户端连接 Amoeba 时需要使用的账号和密码,需要说明的是,如果部署了 Amoeba , # 那么业务系统中程序连接数据库的地址就是#Amoeba 服务器的地址,连接用户名和密码就是这里设置的 # 账名和密码 <property name="user">root</property> <property name="password">xxxxxx</property> <property name="filter"> <bean class="com.meidusa.toolkit.net.authenticate.server.IPAccessController"> <property name="ipFile">${amoeba.home}/conf/access_list.conf</property> </bean> </property> </bean> </property> </service> <runtime class="com.meidusa.amoeba.mysql.context.MysqlRuntimeContext"> <property name="executeThreadSize">128</property> <property name="statementCacheSize">500</property> <property name="serverCharset">utf8</property> <property name="queryTimeout">60</property> </runtime> </proxy> <connectionManagerList> <connectionManager name="defaultManager" class="com.meidusa.toolkit.net. MultiConnectionManagerWrapper"> <property name="subManagerClassName">com.meidusa.toolkit.net. AuthingableConnectionManager</property> </connectionManager> </connectionManagerList> <dbServerLoader class="com.meidusa.amoeba.context.DBServerConfigFileLoader"> <property name="configFile">${amoeba.home}/conf/dbServers.xml</property> </dbServerLoader> <queryRouter class="com.meidusa.amoeba.mysql.parser.MysqlQueryRouter"> <property name="ruleLoader"> <bean class="com.meidusa.amoeba.route.TableRuleFileLoader"> <property name="ruleFile">${amoeba.home}/conf/rule.xml</property> <property name="functionFile">${amoeba.home}/conf/ruleFunctionMap.xml</property> </bean> </property> <property name="sqlFunctionFile">${amoeba.home}/conf/functionMap.xml</property> <property name="LRUMapSize">1500</property> # 下面这个选项设置 Amoeba 默认的池,这里设置为 writedb <property name="defaultPool">writedb</property> # 下面这两个选项默认是注释掉的,需要取消注释,这里用来指定前面定义好的两个读、写池, # 分别是可写池 writedb 和可读池 slaves <property name="writePool">writedb</property> <property name="readPool">myslaves</property> <property name="needParse">true</property> </queryRouter> </amoeba:configuration>
这样 Amoeba 就配置完成了。
4.设置 Amoeba 登录数据库权限
这里假定 Amoeba 服务器的 IP 地址为 192.168.88.35,在 MMM 集群的所有 MySQL 节点上执行如下操作,为 Amoeba 访问 MMM 集群中所有 MySQL 数据库节点授权:
mysql> GRANT ALL ON repldb.* TO 'ixdba'@'192.168.88.35' IDENTIFIED BY 'xxxxxx'; mysql> flush privileges;
5.启动 Amoeba
在 Amoeba 服务器上执行如下命令,启动 Amoeba:
[root@amoebaserver bin]#/usr/local/amoeba/bin/launcher 2014-03-18 18:17:45 [INFO] Project Name=Amoeba-MySQL, PID=22474 , starting... log4j:WARN log4j config load completed from file:/usr/local/amoeba/conf/log4j.xml 2014-03-18 18:17:50,462 INFO context.MysqlRuntimeContext - Amoeba for Mysql current versoin=5.1.45-mysql-amoeba-proxy-3.0.4-BETA log4j:WARN ip access config load completed from file:/usr/local/amoeba/conf/access_ list.conf 2014-03-18 18:17:55,643 INFO net.ServerableConnectionManager - Server listening on 0.0.0.0/0.0.0.0:8066. [root@cloud0 bin]# netstat -tulnp |grep java tcp 0 0 :::8066 :::* LISTEN 22474/java
由此可知 Amoeba 启动正常。
6.测试 Amoeba 实现读、写分离和负载均衡
要测试 Amoeba 实现读、写分离和负载均衡功能,需要在 MMM 集群的所有 MySQL 节点开启 MySQL 的查询日志。查询日志可以记录数据库中建立的客户端连接和执行的语句。开启方法很简单,在 MySQL 配置文件/etc/my.cnf 中添加如下内容:
log=/var/log/mysql_query_log
当然,mysql_query_log 文件要事先存在,并且对 MySQL 用户可写。
为了测试方便,这里在每个 MySQL 节点的 test 库中创建一张表,表的名字为 mmm_test,例如在 Master1 节点,创建表的过程如下:
mysql> use test; mysql> create table mmm_test(id int,email varchar(60)) ; mysql> insert into mmm_test (id,email) values (100,'this is 192.168.88.20');
上面的操作是在 mmm_test 表的 email 字段中插入一条记录“this is 192.168.88.20”,而字符串“this is 192.168.88.20”就是个 IP 标识,这样做的目的是区分多个 MySQL 节点不同 IP 地址的情况。接着在 Master2 节点同样执行上面的 SQL 操作,所不同的是 mmm_test 表 email 字段的内容修改为“this is 192.168.88.21”。依此类推,分别在 Slave1 和 Slave2 节点执行相同的操作。
接着,在远程 MySQL 客户端通过 Amoeba 配置文件中指定的用户名、密码、端口以及 Amoeba 服务器的 IP 地址连接 MySQL 数据库,操作过程如图 13-18 所示。
图 13-18 测试 Amoeba 实现读操作的负载均衡功能
从图 13-18 可以看出,客户端连接到的 Server version 为 5.1.45-mysql-amoeba-proxy-3.0.4-BETA,可见客户端连接的是 Amoeba 实例而不是 MySQL 实例,而从下面的查询 test 库中 mmm_test 表的内容来看,Amoeba 依次将 4 次 select 请求均衡地分配到 MMM 集群中 4 个可读 MySQL 节点上,由此可知,Amoeba 实现了读操作的负载均衡。
下面继续进行 SQL 测试,创建两个表 mmm_test1 和 mmm_test2,操作过程如下:
mysql> create table mmm_test1(id int,email varchar(60)); Query OK, 0 rows affected (0.04 sec) mysql> create table mmm_test2(id int,email varchar(60)); Query OK, 0 rows affected (0.04 sec) mysql> insert into mmm_test1 (id,email) values (101,'mmm_test1@126.com'); Query OK, 1 row affected (0.02 sec) mysql> drop table mmm_test2; Query OK, 0 rows affected (0.10 sec)
为了确定创建的两个表是否已经正常同步到 MMM 集群的其他节点,可分别登录每个 MySQL 节点进行查询,接着还要确定 MySQL 的写操作是否分配到可写的节点 Master1,可通过查看每个 MySQL 节点的查询日志。Master1 节点的 MySQL 查询日志信息如图 13-19 所示。
图 13-19 Master1 节点 MySQL 查询日志
Master2 节点的 MySQL 查询日志信息如图 13-20 所示。
图 13-20 Master2 节点 MySQL 查询日志
其他节点的信息基本类似,这里不一一列出。从 MySQL 的查询日志中可以进一步验证 Amoeba 实现了读负载均衡,而写操作在 Master1 节点执行了。虽然 Master2 节点也有相关写操作的日志,但这是 MySQL 的复制线程执行的写操作,因为除了 Master1,其他 MySQL 节点都是 read_only 状态,是无法执行写操作的。
13.4.2 通过 Keepalived 构建高可用的 Amoeba 服务
在上面介绍的 MMM 整合 Amoeba 应用方案中,虽然通过 Amoeba 实现了 MySQL 的读、写分离,但是这个架构并不完美,因为还存在 Amoeba 服务器单点故障,也就是说当 Amoeba 出现故障后,业务系统将无法访问 MySQL 服务。要解决这个问题其实非常简单,通过 Keepalived 给 Amoeba 做高可用即可,通过 Keepalived 实现 Amoeba 高可用并实现 MySQL 集群读、写分离的架构如图 13-21 所示。
图 13-21 通过 Keepalived 实现 Amoeba 高可用并实现 MySQL 集群读、写分离架构
关于如何通过 Keepalived 构建高可用的 Amoeba 服务,这里不再介绍了,因为前面章节已经介绍了很多关于如何使用 Keepalived 构建应用系统高可用集群的案例,Amoeba 高可用集群的构建方法与之前介绍的案例完全相同。
至此,关于如何构建高性能的 MySQL 集群系统介绍完毕。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论