龙空技术网

基于ProxySQL实现的MySQL高可用及读写分离

空山细兩 1562

前言:

如今朋友们对“mysql双机热备方案”可能比较关怀,你们都想要分析一些“mysql双机热备方案”的相关资讯。那么小编同时在网摘上汇集了一些有关“mysql双机热备方案””的相关内容,希望大家能喜欢,大家一起来学习一下吧!

读写分离-正式服务器配置

本文利用ProxySQL实现MYSQL负载均衡和读写分离。ProxySQL为C语言开发,做为MYSQL的中间件其性能优越稳定。经过作者多方比较,最后在生产环境使用。除官方给出它的特点外,本人给出的其他特点如下:

读写分离模式下,如果从机的主宕机,将不会再读取这个从机的数据。可以根据数据库名称路由到不同的服务器。使用hostgroup主机组管理一组mysql实例,相同的主机组实现同一个负载均衡。自带SQLite3数据库,做为其内部管理持久化,不再依赖外部其他任何数据库。可通过配置查询规则进行缓存。有强大的后台监控机制,时间以us(微秒)为单位。可开放web监控界面(一般用不到,后台的监控已经相当好),默认关闭。可对外开放api接口,默认关闭。可以Mysql的多种集群模式进行管理与配置:主从,MGR,MHR,PXC等。官方文档相当完整,只是仅为英文版本。所以目前国内使用ProxySQL的并不多。Percona公司官方建议使用ProxySQL(或HaProxy)做为PXC的中间件。准备工作部署图

主机

程序

角色

从机

192.168.1.166

(166主机)

mysql5.7

proxysql:2.4

keepalived

master主

192.168.1.147

192.168.1.147

(147主机)

mysql5.7

slave从

192.168.1.187

(187主机)

mysql5.7

proxysql:2.4

keepalived

master备

192.168.1.188

192.168.1.188

(188主机)

mysql5.7

slave从

说明:

图中虚线部分,可以组成双机热备。当上述某个可写入节点宕机后,proxysql可自动切换。示例:如果master166宕机,则proxySQL的写将切换到master备187主机,同时因slave1(188主机)访问master166失败,读也会自动切换到147主机。由于有两个主,所以,对于increment自动生成的自增主键,必须要每台主机不同,请注意以下docker配置中的auto_increment_xx的配置。准备主机环境,ubuntu18.04,并安装Docker(最新版本即可),请直接参考Docker官网安装步骤。因为是多真机部署,为实现主从之间通讯,可以选择使用host模式或swarm模式。建议使用swarm。四台主机,创建swarm集群。166主机执行,创建swarm集群,输出的的join串请copy

# docker swarm init --advertise-addr 192.168.1.166

执行成功后,将输出加入到这个swarm集群的 token请copy,

如果忘记可以使用docker swarm join-token worder 重新获取

docker swarm join --token SWMTKN-1-0r868ggtk... 192.168.1.166:2377

其他所有主机,执行以下语句,即加入到swarm集群

# docker swarm join --token SWMTKN-1-0r868ggtk... 192.168.1.166:2377

最后swarm集群效果,在166执行添加overlay网卡

# docker network create -d overlay --attachable myingress

配置主从关系

说明:

建议将所有启动脚本放到每台主机的相同目录下,每一个主机的脚本目录都在:/home/mysqlreplcluster/start.sh中。mysql之间内部通讯依然使用3306端口,此端口影射到宿主机的3307端口。所以,在外部访问mysql将使用3307端口。166与187主机启动脚本注意修改server-id值,可根据要求,修改mysql root用户的密码,以下仅为演示,设置密码为123456。

#!/bin/bash

id=166

echo 'stop'

docker stop mysql5-${id}

echo 'remove'

docker rm mysql5-${id}

echo 'start'

docker run --name mysql5-${id} -d \

--hostname mysql5-${id} \

--network myingress \

-p 3307:3306 \

-e TZ=Asia/Shanghai \

-e MYSQL_ROOT_PASSWORD=123456\

-v /home/mysqlreplcluster/data:/var/lib/mysql \

mysql:5.7 \

--server-id=${id} \

--log-bin=mysql_bin \

--relay-log=relay_bin \

--binlog_format=MIXED \

--expire_logs_days=30 \

--binlog_ignore_db=sys,information_schema,performance_schema \

--max-connections=512 \

--max-allowed-packet=128M \

--default-time-zone=+8:00 \

--skip-name-resolve \

--skip-host-cache \

--host-cache-size=0 \

--log-slave-updates \

--sync_binlog=10 \

--sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION \

--auto_increment_increment=2 \

--auto_increment_offset=1 \

--read-only=OFF

188主机与147主机启动脚本此两台主机为从机,所以需要设置read_only=ON。proxySQL在运行时,会动态读取此值,并设置为只读服务器。

#!/bin/bash

id=188

echo 'Stop'

docker stop mysql5-${id}

echo 'Remove'

docker rm mysql5-${id}

echo 'Start'

docker run --name mysql5-${id} -d \

--hostname mysql5-${id} \

--network myingress \

-p 3307:3306 \

-e TZ=Asia/Shanghai \

-e MYSQL_ROOT_PASSWORD=123456 \

-v /home/mysqlreplcluster/data:/var/lib/mysql \

mysql:5.7 \

--server-id=${id} \

--log-bin=mysql_bin \

--relay-log=relay_bin \

--binlog_format=MIXED \

--expire_logs_days=30 \

--binlog_ignore_db=sys,information_schema,performance_schema \

--max-connections=512 \

--max-allowed-packet=128M \

--default-time-zone=+8:00 \

--skip-name-resolve \

--skip-host-cache \

--host-cache-size=0 \

--sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION \

--read-only=ON

配置主从

166主机登录mysql并创建用户:

# mysql -uroot -p123456 -h192.168.1.166 -P3307

mysql> -- 创建主从复制账户

mysql> create user ‘repl’@’%’ identified by ‘123456’;

mysql> grant replication slave,replication client on *.* to ‘repl’@’%’;

mysql> -- 创建proxysql监控账户

mysql> create user ‘monitor’@’%’ identified by ‘123456’;

mysql> grant usage on *.* to ‘monitor’@’%’;

mysql> flush privileges;

187主机登录并执行:

# mysql -uroot -p123456 -h192.168.1.187 -P3307

mysql> change master to master_host=’mysql5-166’,master_port=3306,

master_user=’repl’,master_password=’123456’;

mysql> start slave;

mysql> show slave status\G

Slave_IO_Running: Yes

Slave_SQL_Running: Yes

再回到166主机,并执行,实现与187的双主:

# mysql -uroot -p123456 -h192.168.1.166 -P3307

mysql> change master to master_host=’mysql5-187’,master_port=3306,

master_user=’repl’,master_password=’123456’;

mysql> start slave;

mysql> show slave status\G

Slave_IO_Running: Yes

Slave_SQL_Running: Yes

147主机执行:

# mysql -uroot -p123456 -h192.168.1.147 -P3307

mysql> change master to master_host=’mysql5-166’,master_port=3306,

master_user=’repl’,master_password=’123456’;

mysql> start slave;

mysql> show slave status\G

Slave_IO_Running: Yes

Slave_SQL_Running: Yes

188主机执行:

# mysql -uroot -p123456 -h192.168.1.88 -P3307

mysql> change master to master_host=’mysql5-187’,master_port=3306,

master_user=’repl’,master_password=’123456’;

mysql> start slave;

mysql> show slave status\G

Slave_IO_Running: Yes

Slave_SQL_Running: Yes

启动proxysql

ProxySQL的配置文件

admin_credentials="admin:admin;radmin:radmin"中的第二个用户radmin密码radmin为远程登录到proxysql的用户。第一个用户admin仅为localhost登录使用。请修改server_vesion指定真实数据库的版本信息。修改monitor_password="123456"为监控密码。

datadir="/var/lib/proxysql"

admin_variables=

{

admin_credentials="admin:admin;radmin:radmin"

mysql_ifaces="0.0.0.0:6032"

}

mysql_variables=

{

threads=4

max_connections=4096

default_query_delay=0

default_query_timeout=36000000

have_compress=true

poll_timeout=2000

interfaces="0.0.0.0:6033"

default_schema="information_schema"

stacksize=1048576

server_version="5.7"

connect_timeout_server=3000

monitor_username="monitor"

monitor_password="123456"

monitor_history=600000

monitor_connect_interval=60000

monitor_ping_interval=10000

monitor_read_only_interval=1500

monitor_read_only_timeout=500

ping_interval_server_msec=120000

ping_timeout_server=500

commands_stats=true

sessions_sort=true

connect_retries_on_failure=10

}

启动ProxySQL

启动脚本:/home/proxysql/start.sh6032端口和于管理ProxySQL,影射到宿主机为16032。6033端口为访问数据库端口,影射到宿主机为3308。

#!/bin/bash

echo 'Stop'

docker stop proxysql

echo 'Remove'

docker rm proxysql

echo 'Start'

docker run --name proxysql -d \

--hostname proxysql \

--network myingress \

-p 16032:6032 \

-p 3308:6033 \

-e TZ=Asia/Shanghai \

-v /home/proxysql/conf/proxysql.cnf:/etc/proxysql.cnf:ro \

-v /home/proxysql/data:/var/lib/proxysql \

proxysql/proxysql:2.4.3

配置proxysql

登录:

使用radmin远程账户,宿主机端口16032。

root@server166:/home/proxysql# mysql -uradmin -pradmin -h192.168.1.166 -P16032 --prompt 'Admin> '

mysql: [Warning] Using a password on the command line interface can be insecure.

Welcome to the MySQL monitor. Commands end with ; or \g.

Your MySQL connection id is 4

Server version: 5.7 (ProxySQL Admin Module)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

Admin>

配置mysql_servers:

mysql-_servers表,中保存了mysql数据库的主机、端口及访问权重使用insert into mysql_servers的SQL语句

Admin> insert into mysql_servers(hostgroup_id,hostname,port) values

(10,’mysql5-166’,3306),(10,’mysql5-187’,3306),

(20,’mysql5-147’,3306),(20,’mysql5-188’,3306);

Admin> load mysql servers to runtime;

Admin> save mysql servers to disk;

select * from mysql_servers;

配置用户:

Admin> insert into mysql_users(username,password,default_hostgroup,defaut_schema)

values(‘root’,’123456’,10,’mysql’);

Admin> load mysql users to runtime;

Admin> save mysql users to disk;

select username,password,default_hostgroup,default_schema from mysql_users;

配置读写分离:

Admin> insert into mysql_replication_hostgroups(writer_hostgroup,reader_hostgroup)

values(10,20);

Admin> load mysql servers to runtime;

Admin> save mysql servers to disk;

select * from mysql_replication_hostgroups;

配置查询路由:

先设置 root用户缓存,对于刚查询过的数据,再5s内再次查询,将会命中缓存。因select ..for update会执行锁表操作,所以让此类语句在可写的数据库上执行。所有的select操作,都在从库执行。因root用户的默认操作数据库组为10,所以其他的所有操作insert,update...都会在可写组执行。

Admin> insert into mysql_query_rules(rule_id,active,apply,username,cache_ttl) values

(10,1,0,’root’,5000);

admin> insert into mysql_query_rules(rule_id,active,apply,match_digest,destination_group)

values

(20,1,1,’^SELECT.*FOR update$’,10),

(30,1,1,’^SELECT’,20);

Admin> load mysql query rules to runtime;

Admin> save mysql query rules to disk;

select rule_id,active,apply,username,cache_ttl,match_digest,destination_hostgroup from mysql_query_rules;

宕机测试

先登录ProxySQL:

通过3308端口,访问proxySQL,proxySQL会将不同的请求分发给不同的数据库实例。

# mysql -uroot -p123456 -h192.168.1.166 -P3308 --prompt 'SQL> '

mysql: [Warning] Using a password on the command line interface can be insecure.

Welcome to the MySQL monitor. Commands end with ; or \g.

Your MySQL connection id is 2

Server version: 5.7 (ProxySQL)

SQL> show databases;

+--------------------+

| Database |

+--------------------+

| information_schema |

| mysql |

| performance_schema |

| repl_testdb |

| sys |

+--------------------+

5 rows in set (0.06 sec)

查询宕机测试

目前查询走的是147主机:

宕机147:

# docker stop mysql5-147

再次查询:

第一次查询出错,第二次查询命中188主机,切换成功,后面可正常提供查询。

147宕机的情况下,执行一些操作,正常执行:

启动147:

# docker start mysql5-147

独立登录147查询结果已经同步完成:

root@server147:/home/mysqlreplcluster# mysql -uroot -p123456 -h192.168.1.147 -P3307

mysql> select * from repl_testdb.stud;

+----+-------+

| id | name |

+----+-------+

| 12 | Jerry |

+----+-------+

1 row in set (0.00 sec)

写入宕机测试

先查看目前使用的主机:

宕机187:

# docker stop mysql5-187

再次写入:

1:第一次查询异常

2:第二次查询因命中187的从机147没有查询到数据。

3:再次查询,转换成166的从机188,查询出结果。

4:重要功能:ProxySQL再次查询时,因47为从,而它的主子187已经宕机,不再查询147。

启动187:

# docker start mysql5-187

独立登录187,数据已经同步完成:

root@server187:~/temp# mysql -uroot -p123456 -h192.168.1.187 -P3307

mysql> select * from repl_testdb.stud;

+----+------------+

| id | name |

+----+------------+

| 12 | Jerry |

| 14 | mysql5-187 |

| 15 | mysql5-166 |

+----+------------+

3 rows in set (0.00 sec)

宕机测试都通过。

配置Keepalived

说明:

keepalived+ProxySQL请配置多个主机,形成多机热备。

以下仅配置一台主机,建议在187上再配置一个kp+ProxySQL,见部署图。

安装keepalived

# apt install keepalived

添加配置文件

# cat /etc/keepalived/keepalived.conf

注意:请根据网卡名称不同,修改网卡地址

unicast_src_ip 192.168.1.166 为本机ipunicast_peer是keepalived组ip。interface enp6s0f1指定需要绑定的网卡名称。

lobal_defs {

router_id lb-master

}

vrrp_instance VI-kube-master {

state MASTER

priority 120

dont_track_primary

interface enp6s0f1

virtual_router_id 88

advert_int 3

unicast_src_ip 192.168.1.166

unicast_peer {

192.168.1.166

192.168.1.187

}

authentication {

auth_type PASS

auth_pass 1111

}

virtual_ipaddress {

192.168.1.88/24 dev enp6s0f1 label enp6s0f1:vip

}

}

启动kp:

# systemctl start keepalived

查看虚拟ip:

局域网内,任意主机都以访问这个虚拟ip:

事务测试

测试在同一个事务中,在没有提交事务的情况下,是否可以查询出事务内之前写入数据。

登录

为了模拟真实的操作,登录时选择使用虚拟IP+ProxySQL的端口:

# mysql -uroot -p123456 -h192.168.1.88 -P3308 --prompt 'ProxySQL> '

命令行测试

结论:测试通过。

-- 开启事务

ProxySQL> start transaction;

Query OK, 0 rows affected (0.00 sec)

-- 写入一行数据

ProxySQL> insert into stud(name) values('Jerry');

Query OK, 1 row affected (0.02 sec)

-- 事务内查询,如果这儿能查询到数据则说明 测试通过 ,以下测试通过。

ProxySQL> select * from stud;

+----+-------+

| id | name |

+----+-------+

| 21 | Jerry |

+----+-------+

1 row in set (0.00 sec)

-- 以下提交或是回滚都可以,即结束这个事务。

ProxySQL> rollback;

Query OK, 0 rows affected (0.05 sec)

Java代码测试

测试通过

Class.forName("com.mysql.cj.jdbc.Driver");

String url = "jdbc:mysql://61.164.242.166:3308/repl_testdb" +

"?characterEncoding=UTF-8&serverTimezone=

Asia/Shanghai&useUnicode=true&useSSL=false";

Connection con = DriverManager.getConnection(url,"root","123456");

con.setAutoCommit(false); //开启事务

String sql = "insert into stud(id,name) values(?,?)";

PreparedStatement pst = con.prepareStatement(sql);//新的st对象

int id = 3;

pst.setInt(1,id);

pst.setString(2,"Jack"+id);

int rows = pst.executeUpdate();

System.err.println("写入:"+rows);

pst.close();

sql = "select * from stud where id=?";

pst = con.prepareStatement(sql); //为了测试,再这个连接内,开启一个新的st对象

pst.setInt(1,id);

while (true){ //查询,直到查询出数据才停止

ResultSet rs = pst.executeQuery();

if(rs.next()){

String name = rs.getString("name");

/*如这儿直接可以查询到数据本示例测试通过*/

System.err.println(System.currentTimeMillis()+ " 查询到数据了:"+name);

break;

}else{

System.err.println(System.currentTimeMillis()+" 没有查询到数据");

}

rs.close();

}

con.commit(); //提交事务

con.setAutoCommit(true); //设置为原始值

pst.close();

con.close();

测试结果:

测试通过,仅用一次,就查询出了结果。

写入:1

1661499195116 查询到数据了:Jack3

SpringDataSource测试

结论:测试通过。

创建数据源:

package wj.ha.utils;

import com.zaxxer.hikari.HikariConfig;

import com.zaxxer.hikari.HikariDataSource;

import javax.sql.DataSource;

/**

* 数据库连接池

*/

public class DSUtils {

private static DataSource dataSource;

static {

try {

HikariConfig config = new HikariConfig();

config.setDriverClassName("com.mysql.cj.jdbc.Driver");

config.setJdbcUrl("jdbc:mysql://61.164.242.166:3308/repl_testdb" +

"?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false");

config.setUsername("root");

config.setPassword("123456");

config.setMaximumPoolSize(5);

config.setIdleTimeout(3000);

dataSource = new HikariDataSource(config);

} catch (Exception e) {

throw new RuntimeException(e.getMessage(), e);

}

}

public static DataSource getDataSource() {

return dataSource;

}

}

测试代码:

final DataSource ds = DSUtils.getDataSource();

DataSourceTransactionManager ptm = new DataSourceTransactionManager(ds);

TransactionTemplate tt = new TransactionTemplate(ptm);

TransactionCallbackWithoutResult tx =

new TransactionCallbackWithoutResult() {

@Override

public void doInTransactionWithoutResult(TransactionStatus status) {

JdbcTemplate jt = new JdbcTemplate(ds);

jt.queryForList("select * from stud where name='Jerry'");//模拟一次查询

jt = new JdbcTemplate(ds);//插入数据

int id = 7;

String sql = "insert into stud(id,name) values(?,?)";

int rows = jt.update(sql, id, "Jerry" + id);

System.err.println("写入:" + rows);

while (true) {

jt = new JdbcTemplate(ds);

List<Map<String, Object>> list =

jt.queryForList("select * from stud where id=?",

new Object[]{id}, new int[]{Types.INTEGER});

if (list != null && !list.isEmpty()) {

System.err.println(System.currentTimeMillis() + " 查询到数据了:" + list);

break;

} else {

System.err.println(System.currentTimeMillis() + " 没有查询到数据");

}

}

status.setRollbackOnly();//设置回滚

}

};

tt.execute(tx);

运行结果,事务内,可以查询出数据,测试通过:

写入:1

1661499524303 查询到数据了:[{id=7, name=Jerry7}]

原理

登录ProxySQL Admin管理后台,通过查询stats_proxysql_message_metrics这个表,可知,只要开启了事务,在这个事务内,只会有一个可写的数据库给当前用户提供服务:

select sessionId,user,db,hostgroup,cli_host,srv_host

from stats_mysql_processlist;

总结:

ProxySQL后台强大的统计功能真心不错。

另可以打开6080端口,用于在浏览器上查看,以是两个相关参数:

admin-web_enabled 默认值false

admin-web_port 默认值 6080

标签: #mysql双机热备方案