之前做的某个项目中,mysql搭了集群,本来打算基于主从复制做读写分离的,因为不知道如何读写分离,于是改用了pxc集群,集群中的每个mysql都能读写,比较简单。最近比较空闲,调查一下mysql读写分离如何实现,特此记录。
前提: 搭建mysql主从复制集群,使得数据单向同步,可以参考mysql集群搭建与总结
这里在一台机器上通过docker映射不同的端口,搭建了mysql的主从复制集群(一主一从)
容器名 | 端口 |
---|---|
mysql-master | 3000 |
mysql-slave | 3001 |
方式一: 配置多个数据源
最简单的方式就是配置两个数据源,写入数据库的时候使用3000,从数据库读取的时候使用3001端口,这里以nodejs+sequelize为例,实现读写分离.
mysql-read-write.js
const Sequelize = require('sequelize')
//创建sequelize实例
const sequelize = new Sequelize('test', null, null, {
dialect: 'mysql',
replication: {
read: [
{ host: '101.133.213.103', username: 'root', password: '123456', port: 3001 }
],
write: { host: '101.133.213.103', username: 'root', password: '123456', port: 3000 }
},
pool: { // If you want to override the options used for the read/write pool you can do so here
max: 20,
idle: 5000
},
})
//创建User Model,相当于建表
const User = sequelize.define('user', {
userName: {
type: Sequelize.STRING,
allowNull: false, //是否允许为空
primaryKey: true,
comment: '用户名,唯一'
},
}, {
timestamps: false, //默认为true,自动生成`createdAt`和`updatedAt`字段
freezeTableName: false //默认为false,表名自动是使用复数形式,如user->users
});
async function addUser({ userName }) {
await User.create({
userName,
})
}
async function getUser({ userName }) {
const result = await User.findOne({
attributes: ['userName'],
where: { userName }
})
return result.dataValues
}
(async () => {
//创建user表
await sequelize.sync({
force: false //如果表已经存在,则不会重新创建表
})
//写入一条数据
await addUser({ userName: 'selenium' })
//查询一条数据
const user = await getUser({ userName: 'selenium' })
console.log(user)
})()
通过上面的代码,我们往mysql-master插入了一条数据,并且从mysql-slave中读取了这条数据,但是真的是这样吗?
读写分离验证:
从库停止主从复制stop slave;
- 此时写入一条数据 ‘sakura’,只有mysql-master看得到这条数据,证明只写入到了mysql-master
- 查询userName为 ‘sakura’ 的用户,发现查询不到,证明是从mysql-slave上读取的
方式二:使用mycat中间件
配置多个数据源实现读写分离很简单,但是很明显,数据库与代码的耦合度很高。比较好的方式是使用mycat中间件。
它会拦截程序发送给mysql的sql语句,然后进行解析,将sql语句发送到合适的mysql中.
#下载mycat镜像
docker pull selenium39/mycat
#启动mycat容器
docker run -itd --privileged=true -p 8066:8066 -p 9066:9066 --name mycat selenium39/mycat
#进入容器,安装vim
docker exec -it mycat bash
apt-get udpate
apt-get install -y vim
#修改server.xml
vim /mycat/conf/server.xml
主要是两处修改:
- 将客户端的ip加入白名单,默认只能localhost访问
- 设置逻辑数据库的用户名和密码和数据库名称(虽然实际数据库有两个,但开发人员只能感知到一个逻辑数据库)
#修改schema.xml
vim /mycat/conf/schema.xml
schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="db_node">
</schema>
<dataNode name="db_node" dataHost="db_host" database="test" />
<dataHost name="db_host" maxCon="1000" minCon="10" balance="3"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="101.133.213.103:3000" user="root"
password="123456">
<!-- can have multi read hosts -->
<readHost host="hostS2" url="101.133.213.103:3001" user="root" password="123456" />
</writeHost>
<!-- <writeHost host="hostM2" url="localhost:3316" user="root" password="123456"/> -->
</dataHost>
</mycat:schema>
主要是定义真实的读数据库和写数据库,并与逻辑数据库相关联
注意这里的balance指定的是 3
配置完成后,重启mycat容器,就可以通过连接逻辑数据库,进行业务开发,而mycat则会将自动的将读写分发到不同的数据库,实现读写分离.
//代码示例
//创建sequelize实例
const config = {
host: '101.133.213.103',
dialect: 'mysql',
port: 8066
}
const sequelize = new Sequelize('TESTDB', 'root', '123456',config )
方式三:使用sharding-proxy中间件
以后再写🥰