使用mysql-proxy 快速实现mysql 集群 读写分离

为什么80%的码农都做不了架构师?>>>   hot3.png

使用mysql-proxy 快速实现mysql 集群 读写分离



目前较为常见的mysql读写分离分为两种:
1、
基于程序代码内部实现:在代码中对select操作分发到从库;其它操作由主库执行;这类方法也是目前生产环境应用最广泛,知名的如DISCUZ
X2。优点是性能较好,因为在程序代码中实现,不需要增加额外的设备作为硬件开支。缺点是需要开发人员来实现,运维人员无从下手。 


2、 基于中间代理层实现:我们都知道代理一般是位于客户端和服务器之间,代理服务器接到客户端请求后通过判断然后转发到后端数据库。在这有两个代表性程序


02005629_LhUe.jpg

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

转载于:https://my.oschina.net/barter/blog/93354

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/301928.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

50万年薪程序员,被百万网民怒喷后,却迎来大撕逼

全世界只有3.14 % 的人关注了数据与算法之美前几天&#xff0c;我们年轻气盛的小卢写了一篇关于“程序员锁库跑路&#xff0c;最终致创业公司倒闭”的文章&#xff0c;语言有些偏激&#xff0c;数据汪在此替小卢给大伙道个歉&#xff0c;至于为何不让他本人来呢&#xff1f;因为…

.NET轻量级配置中心AgileConfig

描述基于NetCore开发的轻量级配置中心&#xff0c;部署简单、配置简单&#xff0c;使用简单&#xff0c;可以根据个人或者公司需求采用。部署简答&#xff0c;最少只需要一个数据节点&#xff0c;支持docker部署支持多节点分布式部署来保证高可用配置支持按照应用隔离&#xff…

人生苦短,我用Python!

在大数据时代&#xff0c;信息更新非常快速&#xff0c;计算机语言也犹如雨后春笋般被我们所熟知。C语言、C、Java等可谓是各领风骚、独占鳌头&#xff0c;而Python则是一门近几年崛起很快也很火的编程语言。虽说编程语言难分好坏&#xff0c;各有千秋。但Python到底有什么魔力…

预售┃没有标题,配得上这款“俄罗斯方块”

▲数据汪特别推荐点击上图进入玩酷屋在之前的文章时&#xff0c;马斯提到数学存在一种现象叫“梯次掉队”&#xff0c;原因在于孩子的数学思维地基没有打牢。&#xff08;传送门&#xff09;提到初中孩子需要空间想象能力时&#xff0c;很多父母疑惑为何需要&#xff1f;关于这…

读Getting Started With Windows PowerShell笔记

使用中Powershell的操作跟Linux中的终端操作很多地方是一致的&#xff0c;当然&#xff0c;还是有着Windows自己的特色&#xff0c;比如&#xff0c;不分大小写。之前命令行中的命令大部分在这里也可以用&#xff0c;而且用法一样。选中后点右键&#xff0c;即复制到剪切板。不…

NET问答: String 和 string 到底有什么区别?

咨询区 Peter O.&#xff1a;开门见山&#xff0c;参考如下例子&#xff1a;string s "Hello world!"; String s "Hello world!";请问这两者有什么区别&#xff0c;在实际使用上要注意一些什么&#xff1f;回答区 Derek Park&#xff1a;string 是 C# 中…

LVS负载均衡-NET、DR模式配置

模型一&#xff1a;NAT模型的配置 实验环境&#xff1a; 采用VMware虚拟机&#xff0c;版本6.0.5 操作系统&#xff1a;Red Hat Enterprise Linux 5 (2.6.18) 虚拟机1&#xff1a;充当Director&#xff1a;网卡1(桥接):192.168.0.33&#xff08;对外&#xff09;&#xff0c;网…

编程语言的“别样”编年史

全世界只有3.14 % 的人关注了数据与算法之美代码是一门语言&#xff0c;这门语言搭建了人与计算机沟通的桥梁。通过编写代码&#xff0c;人类可以“命令”计算机开发网页、开发软件、搭建游戏... ... 这门语言并不是上帝的发明&#xff0c;它是前辈们发挥聪明才智创造出来的&am…

也可以改为while(input[0])或while(cininput[0])

2019独角兽企业重金招聘Python工程师标准>>> <<c primer plus>> // static.cpp -- using a static local variable #include <iostream> // constants const int ArSize 10; // function prototype void strcount(const char * str); int main()…

.NET Core HttpClient请求异常分析

【导读】最近项目上每天间断性捕获到HttpClient请求异常&#xff0c;感觉有点奇怪&#xff0c;于是乎观察了两三天&#xff0c;通过日志以及对接方沟通确认等等&#xff0c;查看对应版本源码&#xff0c;尝试添加部分配置发布后&#xff0c;观察十几小时暂无异常情况出现&#…

python 小甲鱼 代码_Python小代码

先自我介绍一下&#xff0c;本人是正在自学Python的小白&#xff0c;没事分享一下自己写的小代码&#xff0c;欢迎在评论区补充。游戏管理系统&#xff1a;代码如下&#xff1a;def healthe(m):if m"Y"or y:print("欢迎&#xff0c;请进入游戏&#xff01;"…

原来这些行业的“潜规则”是这样的...

全世界只有3.14 % 的人关注了数据与算法之美在日常生活中&#xff0c;我们往往受限于专业和工作&#xff0c;对自己所处行业之外的事物了解不多。今天&#xff0c;数据汪带大家扒一扒各个行业中不为人知的“潜规则”&#xff0c;看看你们知道几个&#xff1f;看完上面20个“潜规…

“工业互联网平台“将成为工业制造企业的标配

目 录1. 概述2. 背景3. 评述1. 概述“‘工业互联网平台’将成为工业制造企业的标配”的命题既是基于工业生产企业现实情况的判断&#xff0c;又是对工业企业未来发展的需求判断。前途是光明的&#xff0c;但是道路是曲折的。前途的光明是基于工业企业现实…

预售┃每个人都应该学习编程,因为它会教你如何思考

▲数据汪特别推荐点击上图进入玩酷屋扎克伯格11岁开始学习编程&#xff0c;创办Facebook&#xff1b;比尔盖茨13岁学习编程&#xff0c;创办微软……乔布斯说&#xff1a;“每一个人都应该学习电脑编程&#xff0c;因为它会教你如何思考。"现在在北京上海&#xff0c;顶级…

python3抓取图片_通过Python3 爬虫抓取漫画图片

引言&#xff1a;最近闲来无事所以想着学习下python3&#xff0c;看了好长时间的文档&#xff0c;于是用python3写了一个漫画抓取的程序&#xff0c;好了 废话不多说上码&#xff01;第一步&#xff1a;准备环境 和类库&#xff0c;我用的是python3.5 禁用 python2.x &…

有个厉害的程序员女朋友是什么体验?

全世界只有3.14 % 的人关注了数据与算法之美自古妇女能顶半边天&#xff0c;在 IT 界&#xff0c;女生的力量也越来越强大&#xff0c;杰出的女性闪耀在我们身边。有人说&#xff1a;世界上有两种程序员一种是程序员一种是女程序员据数据汪了解&#xff0c;世界上第一个程序员 …

out参数不用赋值?这么神奇吗!

首先提醒大家一下&#xff0c;docs.microsoft.com上的《C# 指南》是这样描述out 参数修饰符[1]的&#xff1a;作为 out 参数传递的变量在方法调用中传递之前不必进行初始化。但是&#xff0c;被调用的方法需要在返回之前赋一个值。请注意上面加粗的话&#xff0c;然后看看下面的…

年底求职难?起薪28万的数据岗位,人才缺口达150万,不限专业学历……

全世界只有3.14 % 的人关注了数据与算法之美2018下半年开始&#xff0c;“寒冬”说愈演愈烈。事实上企业的结构调整、人才升级是常见的现象&#xff0c;而且并非所有互联网企业都在“缩招”&#xff0c;很多企业甚至计划在明年扩大校招。人工智能、数据技术的迅猛发展&#xff…

在.NET 6中使用DateOnly和TimeOnly

千呼万唤始出来在.NET 6(preview 4)中引入了两个期待已久的类型&#xff0c;将作为核心库的一部分。DateOnly和TimeOnly允许开发人员表示DateTime的日期或时间部分。这两个类型为值类型&#xff08;struct type&#xff09;&#xff0c;可以在代码中独立处理日期或时间概念时使…

预售┃要孩子逻辑清晰、善于思考,别忽视空间想象力的游戏锻炼!

▲数据汪特别推荐点击上图进入玩酷屋在之前的文章时&#xff0c;马斯提到数学存在一种现象叫“梯次掉队”&#xff0c;原因在于孩子的数学思维地基没有打牢。&#xff08;传送门&#xff09;提到初中孩子需要空间想象能力时&#xff0c;很多父母疑惑为何需要&#xff1f;关于这…