1.搭建环境
1.1 搭建Mysql主从服务
1.1.1 下载Mysql镜像
docker pull mysql
:5.7
1.1.2 创建Mysql挂载目录
mkdir
-p
/opt
/mysql
/master
/{conf
,data
,logs
}
mkdir
-p
/opt
/mysql
/slave
/{conf
,data
,logs
}
1.1.3 进入conf目录中编辑my.cnf
主配置文件
[client
]
port
=3306
default-character
-set
=utf8
[mysql
]
default-character
-set
=utf8
[mysqld
]
character_set_server
=utf8
sql_mode
=NO_ENGINE_SUBSTITUTION
,STRICT_TRANS_TABLES
# 配置大小写不敏感
lower_case_table_names
=1
server_id
=66 ###服务器id
log
-bin
=mysql
-bin ###开启日志文件
从配置文件
port
=3306
default-character
-set
=utf8
[mysql
]
default-character
-set
=utf8
[mysqld
]
character_set_server
=utf8
sql_mode
=NO_ENGINE_SUBSTITUTION
,STRICT_TRANS_TABLES
# 配置大小写不敏感
lower_case_table_names
=1
server_id
=88 ###服务器id
log
-bin
=mysql
-bin ###开启日志文件
binlog_do_db
=test
1.1.4 运行Mysql容器
docker run
-it
--name mysql
-master
-p
3308:3306 -e MYSQL_ROOT_PASSWORD
=root
--privileged
=true -v
/opt
/mysql
/master
/conf
/my
.cnf
:/etc
/mysql
/my
.cnf
-v
/opt
/mysql
/master
/data
:/var
/lib
/mysql
-v
/opt
/mysql
/master
/logs
:/var
/log
/mysql
-d
--restart
=always mysql
:5.7
docker run
-it
--name mysql
-slave
-p
3309:3306 -e MYSQL_ROOT_PASSWORD
=root
--privileged
=true -v
/opt
/mysql
/slave
/conf
/my
.cnf
:/etc
/mysql
/my
.cnf
-v
/opt
/mysql
/slave
/data
:/var
/lib
/mysql
-v
/opt
/mysql
/slave
/logs
:/var
/log
/mysql
-d
--restart
=always mysql
:5.7
1.1.5 查看容器
1.1.6 连接到主服务器
查询主对应配置文件中的server_id,查询到了说明已经以上步骤ok。
show variables like
'%server_id%';
查询主状态信息
show master status
;
1.1.6 连接从服务器
同步主服务器配置
change master to master_host
='192.168.0.171',master_user
='root',master_password
='root',
master_log_file
='mysql-bin.000005',master_log_pos
=1188;
开始同步
start slave
;
检查从服务器复制功能状态
SHOW SLAVE STATUS
;
Slave_IO_Running 、Slave_SQL_Running 都为Yes说明配置成功,验证效果就不截图了。
1.2 搭建Mycat环境
参考博客:https://blog.csdn.net/weixin_39781526/article/details/84867397
1.2.1 获取资源
#先获取mycat包
wget Mycat
-server
-1.6.5-release
-20180122220033-linux
.tar
.gz
;
1.2.2 编写Dockerfile
FROM java
:8
USER root
COPY Mycat
-server
-1.6.5-release
-20180122220033-linux
.tar
.gz
/
RUN tar
-zxf
/Mycat
-server
-1.6.5-release
-20180122220033-linux
.tar
.gz
ENV MYCAT_HOME
=/mycat
ENV PATH
=$PATH
:$
{MYCAT_HOME
}/bin
WORKDIR $MYCAT_HOME
/bin
RUN chmod u
+x
./mycat
EXPOSE
8066 9066
CMD
["./mycat", "console"]
1.2.3 获取配置文件
tar
-zxvf Mycat
-server
-1.6.5-release
-20180122220033-linux
.tar
.gz
mv mycat
/conf
/ conf
1.2.4 修改mycat配置文件
真正需要修改的只有三个文件,具体官方文档写的很详细; (1):schema.xml ,(2):server.xml,(3):rule.xml; 现在搭建的是Mysql读写分离,修改schema.xml、server.xm即可。rule.xml用于分表分库配置文件。
<?xml version
="1.0"?>
<!DOCTYPE mycat
:schema SYSTEM
"schema.dtd">
<mycat
:schema xmlns
:mycat
="http://io.mycat/">
<!-- TESTDB1 是mycat的逻辑库名称,链接需要用的
-->
<schema name
="mycat_testdb" checkSQLschema
="false" sqlMaxLimit
="100" dataNode
="dn1"></schema
>
<!-- database 是MySQL数据库的库名
-->
<dataNode name
="dn1" dataHost
="localhost1" database
="test" />
<!--
dataNode节点中各属性说明:
name:指定逻辑数据节点名称;
dataHost:指定逻辑数据节点物理主机节点名称;
database:指定物理主机节点上。如果一个节点上有多个库,可使用表达式db$
0-99, 表示指定
0-99这
100个数据库;
dataHost 节点中各属性说明:
name:物理主机节点名称;
maxCon:指定物理主机服务最大支持
1000个连接;
minCon:指定物理主机服务最小保持
10个连接;
writeType:指定写入类型;
0,只在writeHost节点写入;
1,在所有节点都写入。慎重开启,多节点写入顺序为默认写入根据配置顺序,第一个挂掉切换另一个;
dbType:指定数据库类型;
dbDriver:指定数据库驱动;
balance:指定物理主机服务的负载模式。
0,不开启读写分离机制;
1,全部的readHost与stand by writeHost参与select语句的负载均衡,简单的说,当双主双从模式
(M1
->S1,M2
->S2,并且M1与 M2互为主备
),正常情况下,M2
,S1
,S2都参与select语句的负载均衡;
2,所有的readHost与writeHost都参与select语句的负载均衡,也就是说,当系统的写操作压力不大的情况下,所有主机都可以承担负载均衡;
-->
<dataHost name
="localhost1" maxCon
="1000" minCon
="10" balance
="3" writeType
="0" dbType
="mysql" dbDriver
="native" switchType
="1" slaveThreshold
="100">
<heartbeat>select
user()</heartbeat
>
<!-- 可以配置多个主从
-->
<writeHost host
="hostM1" url
="192.168.0.171:3308" user
="root" password
="root">
<!-- 可以配置多个从库
-->
<readHost host
="hostS2" url
="192.168.0.171:3309" user
="root" password
="root" />
</writeHost
>
</dataHost
>
</mycat
:schema
>
<?xml version
="1.0" encoding
="UTF-8"?>
<!-- - - Licensed under the Apache License
, Version
2.0 (the
"License");
- you may not use
this file except in compliance with the License
. - You
may obtain a copy of the License at
- - http
://www
.apache
.org
/licenses
/LICENSE
-2.0
- - Unless required by applicable law or agreed to in writing
, software
-
distributed under the License is distributed on an
"AS IS" BASIS
, - WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND
, either express or implied
. - See the
License
for the specific language governing permissions and
- limitations
under the License
. -->
<!DOCTYPE mycat
:server SYSTEM
"server.dtd">
<mycat
:server xmlns
:mycat
="http://io.mycat/">
<!-- 读写都可用的用户
-->
<user name
="root" defaultAccount
="true">
<property name
="password">123456</property
>
<property name
="schemas">mycat_testdb
</property
>
<!-- 表级 DML 权限设置
-->
<!--
<privileges check
="false">
<schema name
="TESTDB" dml
="0110" >
<table name
="tb01" dml
="0000"></table
>
<table name
="tb02" dml
="1111"></table
>
</schema
>
</privileges
>
-->
</user
>
<!-- 只读用户
-->
<user name
="user">
<property name
="password">user
</property
>
<property name
="schemas">mycat_testdb
</property
>
<property name
="readOnly">true</property
>
</user
>
</mycat
:server
>
1.2.5 执行Dockerfile
docker build
-t mycat
:1.6.5 .
1.2.6 编写mycat启动脚本
docker run
-d
--rm
-p
:8066:8066 --name mycat
-mysql
-p
9066:9066 -v $PWD
/logs
:/mycat
/logs
-v $PWD
/conf
:/mycat
/conf mycat
:1.6.5
1.2.7 验证效果
连接这四个服务 在root服务修改数据,其他三个服务的数据自动同步相应的修改。 在user服务修改数据报错,只允许读数据。
2 SpringBoot整合Mycat实现读写分离
2.1 导入Maven依赖
<project xmlns
="http://maven.apache.org/POM/4.0.0" xmlns
:xsi
="http://www.w3.org/2001/XMLSchema-instance" xsi
:schemaLocation
="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion
>
<groupId>com
.wanzh
</groupId
>
<artifactId>mycat_demo
</artifactId
>
<version>0.0.1-SNAPSHOT
</version
>
<parent>
<groupId>org
.springframework
.boot
</groupId
>
<artifactId>spring
-boot
-starter
-parent
</artifactId
>
<version>2.0.4.RELEASE
</version
>
<relativePath
/> <!-- lookup parent from repository
-->
</parent
>
<dependencies>
<dependency>
<groupId>org
.springframework
.boot
</groupId
>
<artifactId>spring
-boot
-starter
-aop
</artifactId
>
</dependency
>
<dependency>
<groupId>org
.springframework
.boot
</groupId
>
<artifactId>spring
-boot
-starter
-web
</artifactId
>
</dependency
>
<dependency>
<groupId>org
.mybatis
.spring
.boot
</groupId
>
<artifactId>mybatis
-spring
-boot
-starter
</artifactId
>
<version>1.3.2</version
>
</dependency
>
<dependency>
<groupId>mysql
</groupId
>
<artifactId>mysql
-connector
-java
</artifactId
>
<scope>runtime
</scope
>
</dependency
>
<dependency>
<groupId>org
.springframework
.boot
</groupId
>
<artifactId>spring
-boot
-starter
-test
</artifactId
>
<scope>test
</scope
>
</dependency
>
<dependency>
<groupId>com
.alibaba
</groupId
>
<artifactId>druid
</artifactId
>
<version>1.0.23</version
>
</dependency
>
</dependencies
>
<build>
<plugins>
<plugin>
<groupId>org
.springframework
.boot
</groupId
>
<artifactId>spring
-boot
-maven
-plugin
</artifactId
>
</plugin
>
</plugins
>
</build
>
</project
>
2.2 配置application.yml
spring
:
datasource
:
###可读数据源
select
:
jdbc
-url
: jdbc
:mysql
://192.168.0.171:8066/mycat_testdb
driver
-class-name
: com
.mysql
.jdbc
.Driver
username
: user
password
: user
####可写数据源
update
:
jdbc
-url
: jdbc
:mysql
://192.168.0.171:8066/mycat_testdb
driver
-class-name
: com
.mysql
.jdbc
.Driver
username
: root
password
: 123456
type
: com
.alibaba
.druid
.pool
.DruidDataSource
2.3 配置多数据源环境
package com
.wanzh
.config
;
import org
.springframework
.context
.annotation
.Lazy
;
import org
.springframework
.stereotype
.Component
;
@Component
@Lazy(false)
public class DataSourceContextHolder {
private static final ThreadLocal
<String> contextHolder
= new ThreadLocal<>();
public static void setDbType(String dbType
) {
contextHolder
.set(dbType
);
}
public static String
getDbType() {
return contextHolder
.get();
}
public static void clearDbType() {
contextHolder
.remove();
}
}
package com
.wanzh
.config
;
import javax
.sql
.DataSource
;
import org
.springframework
.boot
.context
.properties
.ConfigurationProperties
;
import org
.springframework
.boot
.jdbc
.DataSourceBuilder
;
import org
.springframework
.context
.annotation
.Bean
;
import org
.springframework
.context
.annotation
.Configuration
;
@Configuration
public class DataSourceConfig {
@Bean(name
= "selectDataSource")
@ConfigurationProperties(prefix
= "spring.datasource.select")
public DataSource
dataSource1() {
return DataSourceBuilder
.create().build();
}
@Bean(name
= "updateDataSource")
@ConfigurationProperties(prefix
= "spring.datasource.update")
public DataSource
dataSource2() {
return DataSourceBuilder
.create().build();
}
}
package com
.wanzh
.config
;
import java
.util
.HashMap
;
import java
.util
.Map
;
import javax
.sql
.DataSource
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.beans
.factory
.annotation
.Qualifier
;
import org
.springframework
.context
.annotation
.Primary
;
import org
.springframework
.jdbc
.datasource
.lookup
.AbstractRoutingDataSource
;
import org
.springframework
.stereotype
.Component
;
@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {
@Autowired
@Qualifier("selectDataSource")
private DataSource selectDataSource
;
@Autowired
@Qualifier("updateDataSource")
private DataSource updateDataSource
;
@Override
protected Object
determineCurrentLookupKey() {
System
.out
.println("DataSourceContextHolder:::" + DataSourceContextHolder
.getDbType());
return DataSourceContextHolder
.getDbType();
}
@Override
public void afterPropertiesSet() {
Map
<Object, Object> map
= new HashMap<>();
map
.put("selectDataSource", selectDataSource
);
map
.put("updateDataSource", updateDataSource
);
setTargetDataSources(map
);
setDefaultTargetDataSource(updateDataSource
);
super.afterPropertiesSet();
}
}
2.4 AOP拦截动态切换数据源
package com
.wanzh
.aop
;
import org
.aspectj
.lang
.JoinPoint
;
import org
.aspectj
.lang
.annotation
.Aspect
;
import org
.aspectj
.lang
.annotation
.Before
;
import org
.springframework
.context
.annotation
.Lazy
;
import org
.springframework
.core
.annotation
.Order
;
import org
.springframework
.stereotype
.Component
;
import com
.wanzh
.config
.DataSourceContextHolder
;
@Aspect
@Component
@Lazy(false)
@Order(0)
public class SwitchDataSourceAOP {
@Before("execution(* com.wanzh.service.*.*(..))")
public void process(JoinPoint joinPoint
) {
String methodName
= joinPoint
.getSignature().getName();
if (methodName
.startsWith("get") || methodName
.startsWith("count") || methodName
.startsWith("find")
|| methodName
.startsWith("list") || methodName
.startsWith("select") || methodName
.startsWith("check")) {
DataSourceContextHolder
.setDbType("selectDataSource");
} else {
DataSourceContextHolder
.setDbType("updateDataSource");
}
}
}
2.5 整合Mybatis
package com
.wanzh
.entity
;
public class User {
private int id
;
private String name
;
public int getId() {
return id
;
}
public void setId(int id
) {
this.id
= id
;
}
public String
getName() {
return name
;
}
public void setName(String name
) {
this.name
= name
;
}
}
package com
.wanzh
.mapper
;
import java
.util
.List
;
import org
.apache
.ibatis
.annotations
.Param
;
import org
.apache
.ibatis
.annotations
.Select
;
import com
.wanzh
.entity
.User
;
public interface UserMapper {
@Select("SELECT id,name FROM sys_user ")
public List
<User> findUser();
@Select("insert into sys_user(name) values (#{name}); ")
public List
<User> insertUser(@Param("name") String name
);
}
package com
.wanzh
.service
;
import java
.util
.List
;
import com
.wanzh
.entity
.User
;
public interface UserService {
public List
<User> findUser();
public List
<User> insertUser(String name
);
}
package com
.wanzh
.service
.impl
;
import java
.util
.List
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.stereotype
.Service
;
import com
.wanzh
.entity
.User
;
import com
.wanzh
.mapper
.UserMapper
;
import com
.wanzh
.service
.UserService
;
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper
;
public List
<User> findUser() {
return userMapper
.findUser();
}
public List
<User> insertUser(String name
) {
return userMapper
.insertUser(name
);
}
}
package com
.wanzh
.controller
;
import java
.util
.List
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.web
.bind
.annotation
.RequestMapping
;
import org
.springframework
.web
.bind
.annotation
.RequestMethod
;
import org
.springframework
.web
.bind
.annotation
.RestController
;
import com
.wanzh
.entity
.User
;
import com
.wanzh
.service
.UserService
;
@RestController
public class UserController {
@Autowired
private UserService userService
;
@RequestMapping("/findUser")
public List
<User> findUser() {
return userService
.findUser();
}
@RequestMapping(value
="/insertUser",method
=RequestMethod
.POST
)
public List
<User> insertUser(String name
) {
return userService
.insertUser(name
);
}
}
package com
.wanzh
;
import org
.mybatis
.spring
.annotation
.MapperScan
;
import org
.springframework
.boot
.SpringApplication
;
import org
.springframework
.boot
.autoconfigure
.SpringBootApplication
;
@SpringBootApplication
@MapperScan(basePackages
= "com.wanzh.mapper")
public class App {
public static void main(String
[] args
) {
SpringApplication
.run(App
.class, args
);
}
}
2.6 Postman测试
参考每特教育Mycat课程内容,但是电脑配置有点低,这边就改用docker进行搭建了。