`

使用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
分享到:
评论

相关推荐

    MySQL Proxy 快速实现读写分离以及负载均衡

    本文将详细介绍如何使用 MySQL Proxy 快速实现读写分离及负载均衡。 #### 二、安装 MySQL Proxy ##### 安装步骤: 1. **下载预编译安装包**:首先需要下载 MySQL Proxy 的预编译安装包。例如,可以通过如下命令...

    Docker搭建MySQLl基于ShardingSphere-Proxy读写分离(docker-compose)

    docker_compose搭建shardingSphereProxyMysql主从读写分离

    amoeba-mysql-3.0.4-BETA.tar.gz 实现集群管理 读写分离

    Amoeba主要解决以下问题: a). 数据切分后复杂数据源整合 b). 提供数据切分规则并降低数据切分规则给数据库带来的影响 c). 降低数据库与客户端连接 d). 读写分离路由 通过Amoeba实现读写分离

    Sharding-Proxy

    在实际项目中,使用 Sharding-Proxy 可以帮助开发者快速构建分布式数据库系统,应对大数据量、高并发的业务场景。配合提供的 `shardingsphere-proxy` 压缩包,你可以轻松搭建 Sharding-Proxy 环境,开始探索和实践...

    proxy与mycat对比测试

    Mycat不仅可以实现数据库的读写分离,还能进行水平拆分,将大数据量的表分散到多个物理节点上,从而提高查询效率。在MySQL主从场景下,Mycat能自动处理主从切换,并提供故障恢复机制,保证服务的高可用性。 测试...

    mysql-proxy

    MySQL Proxy是一款轻量级的开源工具,主要用于MySQL数据库的读写分离、IO优化以及实现一定的负载均衡。在大型系统中,数据库通常是性能瓶颈的关键因素,MySQL Proxy的出现就是为了缓解这一问题,提高系统的整体效率...

    Ubuntu10下如何搭建MySQL Proxy读写分离探讨

    在本文中,我们将深入探讨如何在Ubuntu 10.04.2 LTS系统上搭建MySQL Proxy,实现数据库的读写分离。MySQL Proxy是一款轻量级的应用程序,它位于客户端和MySQL服务器之间,允许我们对客户端与服务器之间的通信进行...

    mysql读写分离实现

    ### MySQL读写分离实现 #### 一、MySQL读写分离概述 MySQL的读写分离是一种常用的数据库优化技术,主要用于提高数据库系统的并发处理能力和可用性。它通过将数据查询(读操作)与数据更新(写操作)分布在不同的...

    MySQL搭建Amoeba_读写分离.docx

    在本文中,我们将详细介绍如何使用 Amoeba 实现 MySQL 读写分离的配置过程。Amoeba 是一个基于 MySQL 的 proxy,能够集中地响应应用的请求,并根据用户事先设置的规则,将 SQL 请求发送到特定的数据库上执行,从而...

    构建高性能web之路------mysql读写分离实战.pdf

    本文将详细介绍如何构建一个高性能的Web应用,并通过MySQL的读写分离来实现这一目标。 #### 二、读写分离概念 **读写分离**是指将数据读取操作和写入操作分开处理的一种策略。通常情况下,写操作(如插入、更新、...

    MySQL读写分离集群最佳实践

    通过上述配置,我们成功地实现了一个简单的MySQL读写分离集群。该集群不仅能够有效提升系统的性能,还具有良好的可扩展性和高可用性。此外,MaxScale作为代理服务器,极大地简化了客户端的实现难度,使得开发人员...

    Mysql+haproxy+mycat+pxc+zookeeper实现高可用集群

    本文将详细介绍如何使用Mysql、Haproxy、Mycat、PXC、Zookeeper实现高可用集群,涵盖了整个架构图、应用程序的访问流程、数据的读写分离、负载均衡、数据库节点的搭建、Zookeeper的应用等多个方面。 整体架构图 在...

    分布式数据库Proxy解决方案(mysql负载均衡)

    它提供了一系列工具和服务,帮助用户快速部署和管理 MySQL 数据库集群。 - **快速配置**: 提供简单易用的配置方法,让用户能够快速设置 AmoebaForMysql。 - **启动流程**: 文档中详细介绍了如何启动 AmoebaForMysql...

    mysql 读写分离软件

    4. **故障切换**:在主库发生故障时,读写分离软件能实现快速切换,将写操作指向新的主库,通常需要人工干预或自动检测机制。 5. **扩展性**:随着业务的增长,可以轻松地添加更多的从库来分摊读负载,而不会影响写...

    sharding-proxy-3.0.0和zookeeper-3.5.4-beta.zip

    其中,Sharding-Proxy是作为一个独立的数据库服务器存在,支持MySQL、PostgreSQL和Oracle等多种协议,使得应用程序可以像操作单个数据库一样对分布式数据库进行透明化的访问。 在标题中提到的"sharding-proxy-3.0.0...

    mysql-5.6.13.tar.gz

    - 负载均衡:通过MySQL Proxy或MySQL Fabric实现读写分离和负载均衡。 - 高可用集群:采用主备复制、多主复制或Galera Cluster实现高可用性。 通过理解以上知识点,用户可以有效地安装、配置和管理MySQL 5.6.13,...

    linux_基于主从结构读写分离MYSQL集群

    linux_基于主从结构读写分离MYSQL集群

    MySQL Proxy的安装及基本命令使用教程

    MySQL Proxy是一款轻量级的中间件,用于在MySQL服务器之间路由SQL查询,其核心功能是实现读写分离,提升数据库集群的性能和可用性。它允许主数据库处理事务性操作,而将只读的SELECT查询分发到从库,以此减轻主库的...

Global site tag (gtag.js) - Google Analytics