使用mysql-proxy 快速实现mysql 集群 读写分离
目前较为常见的mysql读写分离分为两种: 
1、 基于程序代码内部实现:在代码中对select操作分发到从库;其它操作由主库执行;这类方法也是目前生产环境应用最广泛,知名的如DISCUZ X2。优点是性能较好,因为在程序代码中实现,不需要增加额外的设备作为硬件开支。缺点是需要开发人员来实现,运维人员无从下手。
2、 基于中间代理层实现:我们都知道代理一般是位于客户端和服务器之间,代理服务器接到客户端请求后通过判断然后转发到后端数据库。在这有两个代表性程序

mysql-proxy:mysql-proxy为mysql开源项目,通过其自带的lua脚本进行sql判断,虽然是mysql官方产品,但是mysql官方并不建议将mysql-proxy用到生产环境。  
amoeba:由陈思儒开发,作者曾就职于阿里巴巴,现就职于盛大。该程序由java语言进行开发,目前只听说阿里巴巴将其用于生产环境。另外,此项目严重缺少维护和推广(作者有个官方博客,很多用户反馈的问题发现作者不理睬) 
经 过上述简单的比较,通过程序代码实现mysql读写分离自然是一个不错的选择。但是并不是所有的应用都适合在程序代码中实现读写分离,像大型SNS、 B2C这类应用可以在代码中实现,因为这样对程序代码本身改动较小;像一些大型复杂的java应用,这种类型的应用在代码中实现对代码改动就较大了。所 以,像这种应用一般就会考虑使用代理层来实现。
下面我们看一下如何搭建mysql-proxy来实现mysql读写分离 
 
环境拓扑如下: 
 
关于mysql、mysql主从的搭建,在此不再演示,如下的操作均在mysql-proxy(192.168.1.200)服务器进行 
一、安装mysql-proxy 
1、安装lua  (mysql-proxy需要使用lua脚本进行数据转发) 
#tar zxvf lua-5.1.4.tar.gz 
#cd lua-5.1.4 
#vi Makefile,修改INSTALL_TOP= /usr/local/lua 
#make posix 
#make install 
 
2、安装libevent 
#tar zxvf libevent-2.0.8-rc.tar.gz 
#cd libevent-2.0.8-rc 
#./configure --prefix=/usr/local/libevent 
#make && make install 
 
3、安装check 
#tar zxvf check-0.9.8.tar.gz 
#cd check-0.9.8 
#./configure && make && make install 
 
4、安装mysql客户端 
#tar zxvf mysql-5.0.92.tar.gz 
#cd mysql-5.0.92 
#./configure --without-server && make && make install 
 
5、设置环境变量 (安装mysql-proxy所需变量) 
#vi /etc/profile 
export LUA_CFLAGS="-I/usr/local/lua/include" LUA_LIBS="-L/usr/local/lua/lib -llua -ldl" LDFLAGS="-L/usr/local/libevent/lib -lm" 
export CPPFLAGS="-I/usr/local/libevent/include" 
export CFLAGS="-I/usr/local/libevent/include" 
# source /etc/profile 
 
6、安装mysql-proxy 
#tar zxvf mysql-proxy-0.6.0.tar.gz 
#cd mysql-proxy-0.6.0 
# ./configure --prefix=/usr/local/mysql-proxy --with-mysql --with-lua 
#make && make install 
 
7、启动mysql-proxy 
本次对两台数据库实现了读写分离;mysql-master为可读可写,mysql-slave为只读 
#/usr/local/mysql-proxy/sbin/mysql-proxy --proxy-backend-addresses=192.168.1.201:3306 --proxy-read-only-backend-addresses=192.168.1.202:3306 --proxy-lua-script=/usr/local/mysql-proxy/share/mysql-proxy/rw-splitting.lua &  
 
注:如果正常情况下启动后终端不会有任何提示信息,mysql-proxy启动后会启动两个端口4040和4041,4040用于SQL转发,4041用于管理mysql-proxy。如有多个mysql-slave可以依次在后面添加 
 
 
二、测试 
1、连接测试 
因为默认情况下mysql数据库不允许用户在远程连接 
mysql>grant all privileges on *.* to identified by '123456'; 
mysql>flush privileges; 
 
客户端连接 
#mysql -uroot -p123456 -h192.168.1.200 -P4040 
 
 
2、读写分离测试 
为了测试出mysql读写分离的真实性,在测试之前,需要开启两台mysql的log功能,然后在mysql-slave服务器停止复制 
① 、在两台mysql配置文件my.cnf中加入log=query.log,然后重启
② 、在mysql-slave上执行SQL语句stop slave
③ 、在两台mysql上执行#tail -f /usr/local/mysql/var/query.log
④ 、在客户端上连接mysql(三个连接以上),然后执行create、select等SQL语句,观察两台mysql的日志有何变化
注:生产环境中除了进行程序调试外,其它不要开启mysql查询日志,因为查询日志记录了客户端的所有语句,频繁的IO操作将会导致mysql整体性能下降 
 
总 结:在上述环境中,mysql-proxy和mysql-master、mysql-slave三台服务器均存在单点故障。如果在可用性要求较高的场合, 单点隐患是绝对不允许的。为了避免mysql-proxy单点隐患有两种方法,一种方法是mysql-proxy配合keepalived做双机,另一种 方法是将mysql-proxy和应用服务安装到同一台服务器上;为了避免mysql-master单点故障可以使用DRBD+heartbear做双 机;避免mysql-slave单点故障增加多台mysql-slave即可,因为mysql-proxy会自动屏蔽后端发生故障的mysql- slave。
附: mysql-proxy LUA 读写分离脚本代码:
--[[
--
-- author : KDr2 
-- version 0.01
-- SYNOPSIS:
---  1.维护了一个连接池
---  2.读写分离,简单的将select开头的语句放到slave上执行
---  3.事务支持,所有事务放到master上执行,事务中不更改连接
---  4.简单日志
--
--]]
--- config vars
local min_idle_connections = 4
local max_idle_connections = 8
local log_level=1
local encoding="utf8"
--- end of config
-- 事务标识,在事务内不归还连接
local transaction_flags={}
setmetatable(transaction_flags,{__index=function() return 0 end})
-- log system
log={
   level={debug=1,info=2,warn=3,error=4},
   funcs={"debug","info","warn","error"},
}
function log.log(level,m)
   if level >= log_level then
      local msg="[" .. os.date("%Y-%m-%d %X") .."] ".. log.funcs[level] .. ": " .. tostring(m)
      print(msg) -- TODO  write msg into a log file.
   end
end
for i,v in ipairs(log.funcs) do
   log[v]=function(m) log.log(log.level[v],m) end
end
-- connect to server
function connect_server() 
   log.info(" starting connect_server ... ")
   local least_idle_conns_ndx = 0
   local least_idle_conns = 0
   
   for i = 1, #proxy.backends do
      local s = proxy.backends[i]
      local pool = s.pool 
      local cur_idle = pool.users[""].cur_idle_connections
log.debug("[".. s.address .."].connected_clients = " .. s.connected_clients)
      log.debug("[".. s.address .."].idling_connections = " .. cur_idle)
      log.debug("[".. s.address .."].type = " .. s.type)
      log.debug("[".. s.address .."].state = " .. s.state)
if s.state ~= proxy.BACKEND_STATE_DOWN then
         -- try to connect to each backend once at least
         if cur_idle == 0 then
            proxy.connection.backend_ndx = i
            log.info("server [".. proxy.backends[i].address .."] open new connection")
            return
         end
         -- try to open at least min_idle_connections
         if least_idle_conns_ndx == 0 or
            ( cur_idle < min_idle_connections and 
              cur_idle < least_idle_conns ) then
            least_idle_conns_ndx = i
            least_idle_conns = cur_idle
         end
      end
   end
if least_idle_conns_ndx > 0 then
      proxy.connection.backend_ndx = least_idle_conns_ndx
   end
   
   if proxy.connection.backend_ndx > 0 then 
      local s = proxy.backends[proxy.connection.backend_ndx]
      local pool = s.pool 
      local cur_idle = pool.users[""].cur_idle_connections
if cur_idle >= min_idle_connections then
         -- we have 4 idling connections in the pool, that's good enough
         log.debug("using pooled connection from: " .. proxy.connection.backend_ndx)
         return proxy.PROXY_IGNORE_RESULT
      end
   end
   -- open a new connection 
   log.info("opening new connection on: " .. proxy.backends[proxy.connection.backend_ndx].address)
end
---
-- auth.packet is the packet
function read_auth_result( auth )
   if auth.packet:byte() == proxy.MYSQLD_PACKET_OK then
      -- 连接正常
      proxy.connection.backend_ndx = 0
   elseif auth.packet:byte() == proxy.MYSQLD_PACKET_EOF then
      -- we received either a 
      -- * MYSQLD_PACKET_ERR and the auth failed or
      -- * MYSQLD_PACKET_EOF which means a OLD PASSWORD (4.0) was sent
      log.error("(read_auth_result) ... not ok yet");
   elseif auth.packet:byte() == proxy.MYSQLD_PACKET_ERR then
      log.error("auth failed!")
   end
end
--- 
-- read/write splitting
function read_query( packet ) 
   log.debug("[read_query]")
   log.debug("authed backend = " .. proxy.connection.backend_ndx)
   log.debug("used db = " .. proxy.connection.client.default_db)
if packet:byte() == proxy.COM_QUIT then
      proxy.response = {
         type = proxy.MYSQLD_PACKET_OK,
      }
      return proxy.PROXY_SEND_RESULT
   end
if proxy.connection.backend_ndx == 0 then
      local is_read=(string.upper(packet:sub(2))):match("^SELECT")
      local target_type=proxy.BACKEND_TYPE_RW
      if is_read then target_type=proxy.BACKEND_TYPE_RO end
      for i = 1, #proxy.backends do
         local s = proxy.backends[i]
         local pool = s.pool 
         local cur_idle = pool.users[proxy.connection.client.username].cur_idle_connections
         
         if cur_idle > 0 and 
            s.state ~= proxy.BACKEND_STATE_DOWN and 
            s.type == target_type then
            proxy.connection.backend_ndx = i
            break
         end
      end
   end
   -- sync the client-side default_db with the server-side default_db
   if proxy.connection.server and proxy.connection.client.default_db ~= proxy.connection.server.default_db then
      local server_db=proxy.connection.server.default_db
      local client_db=proxy.connection.client.default_db
      local default_db= (#client_db > 0) and client_db or server_db
      if #default_db > 0 then
         proxy.queries:append(2, string.char(proxy.COM_INIT_DB) .. default_db)
         proxy.queries:append(2, string.char(proxy.COM_QUERY) .. "set names '" .. encoding .."'")
         log.info("change database to " .. default_db);
      end
   end
   if proxy.connection.backend_ndx > 0 then
      log.debug("Query[" .. packet:sub(2) .. "] Target is [" .. proxy.backends[proxy.connection.backend_ndx].address .."]")
   end
   proxy.queries:append(1, packet)
   return proxy.PROXY_SEND_QUERY
end
---
-- as long as we are in a transaction keep the connection
-- otherwise release it so another client can use it
function read_query_result( inj ) 
   local res      = assert(inj.resultset)
   local flags    = res.flags
if inj.id ~= 1 then
      -- ignore the result of the USE <default_db>
      return proxy.PROXY_IGNORE_RESULT
   end
   is_in_transaction = flags.in_trans
if flags.in_trans then
      transaction_flags[proxy.connection.server.thread_id] = transaction_flags[proxy.connection.server.thread_id] + 1
   elseif inj.query:sub(2):lower():match("^%s*commit%s*$") or inj.query:sub(2):lower():match("^%s*rollback%s*$") then
      transaction_flags[proxy.connection.server.thread_id] = transaction_flags[proxy.connection.server.thread_id] - 1
      if transaction_flags[proxy.connection.server.thread_id] < 0 then transaction_flags[proxy.connection.server.thread_id] = 0 end
   end
   
   log.debug("transaction res : " .. tostring(transaction_flags[proxy.connection.server.thread_id]));
   if transaction_flags[proxy.connection.server.thread_id]==0 or transaction_flags[proxy.connection.server.thread_id] == nil then 
      -- isnot in a transaction, need to release the backend
      proxy.connection.backend_ndx = 0
   end
end
--- 
-- close the connections if we have enough connections in the pool 
-- 
-- @return nil - close connection 
-- IGNORE_RESULT - store connection in the pool 
function disconnect_client() 
   log.debug("[disconnect_client]") 
   if proxy.connection.backend_ndx == 0 then 
      for i = 1, #proxy.backends do 
         local s = proxy.backends[i] 
         local pool = s.pool 
         local cur_idle = pool.users[proxy.connection.client.username].cur_idle_connections 
         
         if s.state ~= proxy.BACKEND_STATE_DOWN and 
            cur_idle > max_idle_connections then 
            -- try to disconnect a backend 
            proxy.connection.backend_ndx = i 
            log.info("[".. proxy.backends[i].address .."] closing connection, idling: " .. cur_idle) 
            return 
         end 
      end 
      return proxy.PROXY_IGNORE_RESULT 
   end 
end
http://www.open-open.com/lib/view/open1345864902321.html
使用mysql-proxy 快速实现mysql 集群 读写分离的更多相关文章
- MySQL集群读写分离的自定义实现
		
基于MySQL Router可以实现高可用,读写分离,负载均衡之类的,MySQL Router可以说是非常轻量级的一个中间件了.看了一下MySQL Router的原理,其实并不复杂,原理也并不难理解, ...
 - docker+mysql集群+读写分离+mycat管理+垂直分库+负载均衡
		
依然如此,只要大家跟着我的步骤一步步来,100%是可以测试成功的 centos6.8已不再维护,可能很多人的虚拟机中无法使用yum命令下载docker, 但是阿里源还是可以用的 因为他的centos- ...
 - Mysql集群读写分离(Amoeba)
		
Amoeba原理戳这里:Amoeba详细介绍 实验环境 Master.Amoeba--IP:192.168.1.5 Slave---IP:192.168.1.10 安装JDK JDK下载地址:http ...
 - MySQL集群系列2:通过keepalived实现双主集群读写分离
		
在上一节基础上,通过添加keepalived实现读写分离. 首先关闭防火墙 安装keepalived keepalived 2台机器都要安装 rpm .el6.x86_64/ 注意上面要替换成你的内核 ...
 - 十四、linux-MySQL的数据库集群读写分离及高可用性、备份等
		
一.数据库集群及高可用性 二.mysql实现读写分离 mysql实现读写分离有多种方式: 1)代码语言(php\python\java等)层面实现读写分离,找开发进行实现. 2)通过软件工具实现读写分 ...
 - MySQL Cluster 7.3.5 集群配置参数优化(优化篇)
		
按照前面的教程:MySQL Cluster 7.3.5 集群配置实例(入门篇),可快速搭建起基础版的MySQL Cluster集群,但是在生成环境中,还是有很多问题的,即配置参数需要优化下, 当前生产 ...
 - MySQL Cluster 7.3.5 集群配置实例(入门篇)
		
一.环境说明: CentOS6.3(32位) + MySQL Cluster 7.3.5,规划5台机器,资料如下: 节点分布情况: MGM:192.168.137. NDBD1:192.168.137 ...
 - HAProxy+keepalived+MySQL 实现MHA中slave集群负载均衡的高可用
		
HAProxy+keepalived+MySQL实现MHA中slave集群的负载均衡的高可用 Ip地址划分: 240 mysql_b2 242 mysql_b1 247 haprox ...
 - MariaDB Galera Cluster 部署(如何快速部署MariaDB集群)
		
MariaDB Galera Cluster 部署(如何快速部署MariaDB集群) [日期:--] 来源:Linux社区 作者:Linux [字体:大 中 小] MariaDB作为Mysql的一个分 ...
 
随机推荐
- 51nod 1088 最长回文子串
			
1088 最长回文子串 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 回文串是指aba.abba.cccbccc.aaaa这种左右对称的字符串. 输入一 ...
 - java getDocumentBase() 得到的文件夹路径
			
参考一个百度知道上的回答 举例说来,假设你的项目文件是xx,而这个xx文件夹是在D盘下的yy文件夹里,即项目文件的完整路径D:\yy\xx,则编译运行文件后,在xx文件夹里会产生名为build的文件夹 ...
 - jmeter(十五)Jmeter默认报告优化
			
一.本文目的: 之前写了两篇文章搭建持续集成接口测试平台(Jenkins+Ant+Jmeter)和ANT批量执行Jmeter脚本,功能实现上都没有什么问题,但是最后生成的报告有一点小问题,虽然不影响使 ...
 - 【图片匹配】--- SIFT_Opencv3.1.0_C++_ubuntu
			
最近在捣鼓图片相似性匹配算法.这里先说一点必要的题外话: 如果是在同一个object不同角度拍摄的多张图片中,使用SIFT可以有不错的效果: 如果是寻找类别相同的图片(可能不是同一object),SI ...
 - java IO流 复制图片
			
(一)使用字节流复制图片 //字节流方法 public static void copyFile()throws IOException { //1.获取目标路径 //(1)可以通过字符串 // St ...
 - Spring注解驱动开发之web
			
前言:现今SpringBoot.SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解.原理,比如@Conditional.@Import.@Ena ...
 - HTML5应用缓存与Web Workers
			
1.什么是应用程序缓存 HTML5引入了应用程序缓存,这意味着web应用可进行缓存,并可在没有因特网链接时进行访问. 2.应用缓存的优势 离线浏览 用户可在应用离线时使用它们 ...
 - 【intellij idea】汇总
			
1 右键无法创建,找不到scala class https://blog.csdn.net/u011513853/article/details/52896230 2 缩进 https://jingy ...
 - IPython notebook快捷键(Jupyter notebook)
			
转自“https://blog.csdn.net/eswai/article/details/53642802” 本文整理了神器IPython Notebook(或Jupyter Notebook)的 ...
 - WordPress更改固定链接出现404
			
新浪SAE的前端采用的是nginx,nginx是不识别.htaccess的. 最后学习了新浪SAE官方教程——应用配置模块 – AppConfig终于把问题解决! 1.修改你SAE SDK站点目录下的 ...