澳门新浦京娱乐场网站-www.146.net-新浦京娱乐场官网
做最好的网站

spring集成mybatis实现mysql读写分离,mysql读写分离

前言

       在网址的客商高达一定规模后,数据库因为负载压力过高而改为网址的瓶颈。幸运的是当下抢先八分之四的主流数据库都提供基本热备功能,通过配备两台数据库主从关系,能够将风流倜傥台数据库的数据更新同步到另风流倜傥台服务器上。网址使用数据库的那生机勃勃功效,达成数据库读写分离,从而修正数据库负载压力。如下图所示:

 澳门新浦京娱乐场网站 1

 

  应用服务器在写多少的时候,访问主数据库,主数据库通过主从复制机制将数据更新同步到从数据库,那样当应用服务器读数据的时候,就足以经过从数据库得到数据。为了便于应用程序访谈读写抽离后的数据库,经常在应用服务器使用特地的数据库访问模块,使数据库读写分离对运用透明。

摘自《大型网址技能架构_大旨原理与案例分析》

spring集成mybatis实现mysql读写分离,mysql读写分离。       而本博客就是来落实“特意的数据库访谈模块”,使数据库读写分离对运用透明。此外,mysql数据库的主从复制能够参照笔者的mysql5.7.18的设置与主从复制。注意,数据库落成了主从复制,技术做数据库的读写抽离,所以,未有兑现数据库主从复制的记得先去贯彻数据库的主从复制

mysql读写分离,mysql读写

久远未有写过博客了,趁着年假还应该有一天,把二零一八年项目所选取的读写分离在那地概述一下会同注意点,避防现在项目再有利用到;

粮草先行粮草先行专门的学业

1 开采条件:window,idea,maven,spring boot,mybatis,druid(天猫商城数据库连接池)

spring集成mybatis实现mysql读写分离,mysql读写分离。2 数据库服务器:linux,mysql master(192.168.203.135),mysql salve(192.168.203.139)

3 读写分离以前必须先做好数据库的主从复制,关于主从复制不是该篇幅的机要描述注重,关于主从复制读者能够自行google只怕百度,教程为主都以千篇豆蔻梢头律,可行

 澳门新浦京娱乐场网站 2

留神以下几点:
a:做主从复制时,首先鲜明两台服务器的mysql没任何自定义库(不然只好够布署完后事先的事物没有办法同步,大概三个库都有完全雷同的库应该也是足以协同)
b:server_id必需配备不均等
c:防火墙无法把mysql服务端口给挡住了(默许3306)
d:确认保证两台mysql能够相互拜见
e:重新载入参数master,slave。Reset master;reset slave;开启关闭slave,start slave;stop slave;
f:主DB server和从DB server数据库的版本雷同

4 读写抽离情势:

  4-1 基于程序代码内部落实: 在代码中依据select 、insert举行路由分类,那类方法也是近些日子生育条件下利用最广大的。优点是性质较好,因为程序在代码中落实,不必要追加额外的硬件开采,短处是索要开拓人士来落到实处,运行人士不准入手。

  4-2 基于中间代理层完毕: 代理日常介于应用服务器和数据库服务器之间,代理数据库服务器收到到应用服务器的央求后基于判定后转载到,后端数据库,有以下代表性的先后。

 本文基于三种艺术的描述:

基于应用层代码完结格局(内容都以因此代码体现,须要的表明存在代码中)

1 配置pom.xml,导入供给的jar包

  

<?xml version="1.0" encoding="UTF-8"?>
<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.lishun</groupId>
    <artifactId>mysql_master_salve</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>mysql_master_salve</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</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>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.18</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.43</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

 

2 配置application.properties

server.port=9022
#mybatis配置*mapper.xml文件和实体别名
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.lishun.entity

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.password=123456
spring.datasource.username=root

#写节点
spring.datasource.master.url=jdbc:mysql://192.168.203.135:3306/worldmap
#两个个读节点(为了方便测试这里用的是同一个服务器数据库,生产环境应该不使用)
spring.datasource.salve1.url=jdbc:mysql://192.168.203.139:3306/worldmap
spring.datasource.salve2.url=jdbc:mysql://192.168.203.139:3306/worldmap

# druid 连接池 Setting
# 初始化大小,最小,最大
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM rscipc_sys_user
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.filters=stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.logSlowSql=true
#End

3 运转类(注意:别的急需spring处理的bean(service,config等)必得放在该运转类的子包下,不然会扫描不到bean,引致注入失利)

@SpringBootApplication
@MapperScan("com.lishun.mapper") //!!!!!! 注意:扫描所有mapper
public class MysqlMasterSalveApplication {
 public static void main(String[] args) {
  SpringApplication.run(MysqlMasterSalveApplication.class, args);
 }
}

4 动态数据源  DynamicDataSource

  

/**
 * @author lishun
 * @Description:动态数据源, 继承AbstractRoutingDataSource
 * @date 2017/8/9
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
 public static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);

 /**
  * 默认数据源
  */
 public static final String DEFAULT_DS = "read_ds";
 private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
 public static void setDB(String dbType) {// 设置数据源名
  log.info("切换到{}数据源", dbType);
  contextHolder.set(dbType);
 }

 public static void clearDB() {
  contextHolder.remove();
 }// 清除数据源名
 @Override
 protected Object determineCurrentLookupKey() {
  return contextHolder.get();
 }
}

5 线程池配置数据源  

@Configuration
public class DruidConfig {
 private Logger logger = LoggerFactory.getLogger(DruidConfig.class);

 @Value("${spring.datasource.master.url}")
 private String masterUrl;

 @Value("${spring.datasource.salve1.url}")
 private String salve1Url;

 @Value("${spring.datasource.salve2.url}")
 private String salve2Url;

 @Value("${spring.datasource.username}")
 private String username;

 @Value("${spring.datasource.password}")
 private String password;

 @Value("${spring.datasource.driver-class-name}")
 private String driverClassName;

 @Value("${spring.datasource.initialSize}")
 private int initialSize;

 @Value("${spring.datasource.minIdle}")
 private int minIdle;

 @Value("${spring.datasource.maxActive}")
 private int maxActive;

 @Value("${spring.datasource.maxWait}")
 private int maxWait;

 @Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
 private int timeBetweenEvictionRunsMillis;

 @Value("${spring.datasource.minEvictableIdleTimeMillis}")
 private int minEvictableIdleTimeMillis;

 @Value("${spring.datasource.validationQuery}")
 private String validationQuery;

 @Value("${spring.datasource.testWhileIdle}")
 private boolean testWhileIdle;

 @Value("${spring.datasource.testOnBorrow}")
 private boolean testOnBorrow;

 @Value("${spring.datasource.testOnReturn}")
 private boolean testOnReturn;

 @Value("${spring.datasource.filters}")
 private String filters;

 @Value("${spring.datasource.logSlowSql}")
 private String logSlowSql;

 @Bean
 public ServletRegistrationBean druidServlet() {

  logger.info("init Druid Servlet Configuration ");
  ServletRegistrationBean reg = new ServletRegistrationBean();
  reg.setServlet(new StatViewServlet());
  reg.addUrlMappings("/druid/*");
  reg.addInitParameter("loginUsername", username);
  reg.addInitParameter("loginPassword", password);
  reg.addInitParameter("logSlowSql", logSlowSql);
  return reg;
 }

 @Bean
 public FilterRegistrationBean filterRegistrationBean() {
  FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
  filterRegistrationBean.setFilter(new WebStatFilter());
  filterRegistrationBean.addUrlPatterns("/*");
  filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
  filterRegistrationBean.addInitParameter("profileEnable", "true");
  return filterRegistrationBean;
 }

 @Bean
 public DataSource druidDataSource() {
  DruidDataSource datasource = new DruidDataSource();
  datasource.setUrl(masterUrl);
  datasource.setUsername(username);
  datasource.setPassword(password);
  datasource.setDriverClassName(driverClassName);
  datasource.setInitialSize(initialSize);
  datasource.setMinIdle(minIdle);
  datasource.setMaxActive(maxActive);
  datasource.setMaxWait(maxWait);
  datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
  datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
  datasource.setValidationQuery(validationQuery);
  datasource.setTestWhileIdle(testWhileIdle);
  datasource.setTestOnBorrow(testOnBorrow);
  datasource.setTestOnReturn(testOnReturn);
  try {
   datasource.setFilters(filters);
  } catch (SQLException e) {
   logger.error("druid configuration initialization filter", e);
  }

  Map<Object, Object> dsMap = new HashMap();
  dsMap.put("read_ds_1", druidDataSource_read1());
  dsMap.put("read_ds_2", druidDataSource_read2());

  dsMap.put("write_ds", datasource);

  DynamicDataSource dynamicDataSource = new DynamicDataSource();
  dynamicDataSource.setTargetDataSources(dsMap);
  return dynamicDataSource;
 }

 public DataSource druidDataSource_read1() {
  DruidDataSource datasource = new DruidDataSource();
  datasource.setUrl(salve1Url);
  datasource.setUsername(username);
  datasource.setPassword(password);
  datasource.setDriverClassName(driverClassName);
  datasource.setInitialSize(initialSize);
  datasource.setMinIdle(minIdle);
  datasource.setMaxActive(maxActive);
  datasource.setMaxWait(maxWait);
  datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
  datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
  datasource.setValidationQuery(validationQuery);
  datasource.setTestWhileIdle(testWhileIdle);
  datasource.setTestOnBorrow(testOnBorrow);
  datasource.setTestOnReturn(testOnReturn);
  try {
   datasource.setFilters(filters);
  } catch (SQLException e) {
   logger.error("druid configuration initialization filter", e);
  }
  return datasource;
 }
 public DataSource druidDataSource_read2() {
  DruidDataSource datasource = new DruidDataSource();
  datasource.setUrl(salve2Url);
  datasource.setUsername(username);
  datasource.setPassword(password);
  datasource.setDriverClassName(driverClassName);
  datasource.setInitialSize(initialSize);
  datasource.setMinIdle(minIdle);
  datasource.setMaxActive(maxActive);
  datasource.setMaxWait(maxWait);
  datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
  datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
  datasource.setValidationQuery(validationQuery);
  datasource.setTestWhileIdle(testWhileIdle);
  datasource.setTestOnBorrow(testOnBorrow);
  datasource.setTestOnReturn(testOnReturn);
  try {
   datasource.setFilters(filters);
  } catch (SQLException e) {
   logger.error("druid configuration initialization filter", e);
  }
  return datasource;
 }

}

6 数据源申明:在service层通过数据源评释来钦赐数据源

   

/**
 * @author lishun
 * @Description: 读数据源注解
 * @date 2017/8/9
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadDataSource {
 String vlaue() default "read_ds";
}

/**
 * @author lishun
 * @Description: 写数据源注解
 * @date 2017/8/9
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteDataSource {
 String value() default "write_ds";
}

7 service aop断面来切换数据源

  

/**
 * @author lishun
 * @Description: TODO
 * @date 2017/8/9
 */
@Component
@Aspect
public class ServiceAspect implements PriorityOrdered {
 @Pointcut("execution(public * com.lishun.service.*.*(..))")
 public void dataSource(){};

 @Before("dataSource()")
 public void before(JoinPoint joinPoint){
  Class<?> className = joinPoint.getTarget().getClass();//获得当前访问的class
  String methodName = joinPoint.getSignature().getName();//获得访问的方法名
  Class[] argClass = ((MethodSignature)joinPoint.getSignature()).getParameterTypes();//得到方法的参数的类型
  String dataSource = DynamicDataSource.DEFAULT_DS;
  try {
   Method method = className.getMethod(methodName, argClass);// 得到访问的方法对象
   if (method.isAnnotationPresent(ReadDataSource.class)) {
    ReadDataSource annotation = method.getAnnotation(ReadDataSource.class);
    dataSource = annotation.vlaue();
    int i = new Random().nextInt(2)   1;    /* 简单的负载均衡 */

    dataSource = dataSource   "_"   i;
   }else if (method.isAnnotationPresent(WriteDataSource.class)){
    WriteDataSource annotation = method.getAnnotation(WriteDataSource.class);
    dataSource = annotation.value();
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
  DynamicDataSource.setDB(dataSource);// 切换数据源
 }

 /* 基于方法名
 @Before("execution(public * com.lishun.service.*.find*(..)) || execution(public * com.lishun.service.*.query*(..))")
 public void read(JoinPoint joinPoint){
  DynamicDataSource.setDB("read_ds");// 切换数据源
 }
 @Before("execution(public * com.lishun.service.*.insert*(..)) || execution(public * com.lishun.service.*.add*(..))")
 public void write(JoinPoint joinPoint){
  DynamicDataSource.setDB("write_ds");// 切换数据源
 }
 */

 @After("dataSource()")
 public void after(JoinPoint joinPoint){
  DynamicDataSource.clearDB();// 切换数据源
 }

 @AfterThrowing("dataSource()")
 public void AfterThrowing(){
  System.out.println("AfterThrowing---------------" );
 }

 @Override
 public int getOrder() {
  return 1;//数值越小该切面先被执行,先选择数据源(防止事务aop使用数据源出现空异常)
 }
}

8 测量试验 mapper的代码就不贴了,主借使service和controller

  service

@Service
@Transactional
public class WmIpInfoServiceImpl implements WmIpInfoService {
 @Autowired
 public WmIpInfoMapper wmIpInfoMapper;

 @Override
 @ReadDataSource
 public WmIpInfo findOneById(String id) {
  //wmIpInfoMapper.selectByPrimaryKey(id);
  return wmIpInfoMapper.selectByPrimaryKey(id);
 }

 @Override
 @WriteDataSource
 public int insert(WmIpInfo wmIpInfo) {
  int result = wmIpInfoMapper.insert(wmIpInfo);
  return result;
 }
}

  contrlloer

@RestController
public class IndexController {
 @Autowired
 public WmIpInfoService wmIpInfoService;
 @GetMapping("/index/{id}")
 public WmIpInfo index(@PathVariable(value = "id") String id){
  WmIpInfo wmIpInfo = new WmIpInfo();
  wmIpInfo.setId(UUID.randomUUID().toString());
  wmIpInfoService.insert(wmIpInfo);
  wmIpInfoService.findOneById(id);
  return null;
 }
}

  运营spring boot 在浏览器输入

  查看日志

  

 澳门新浦京娱乐场网站 3

 凭借中间件格局达成读写分离(mycat:首假使mycat安装使用及其注意事项)

3-1 下载
3-2 解压,配置MYCAT_HOME;
澳门新浦京娱乐场网站,3-3 修改文件 vim conf/schema.xml

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">

<mycat:schema xmlns:mycat="http://io.mycat/">
  <schema name="worldmap" checkSQLschema="false" sqlMaxLimit="100" dataNode="worldmap_node"></schema>
  <dataNode name="worldmap_node" dataHost="worldmap_host" database="worldmap" /> <!-- database:数据库名称 -->
  <dataHost name="worldmap_host" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="2" slaveThreshold="100">
    <heartbeat>select user()</heartbeat>
    <writeHost host="hostM1" url="192.168.203.135:3306" user="root" password="123456"><!--读写分离模式,写库:192.168.203.135,读库192.168.203.139-->
      <readHost host="hostR1" url="192.168.203.139:3306" user="root" password="123456" />
    </writeHost>
    <writeHost host="hostM2" url="192.168.203.135:3306" user="root" password="123456"> <!--主从切换模式,当hostM1宕机,读写操作在hostM2服务器数据库执行-->
  </dataHost>
</mycat:schema>

  配置表明:
  name:属性唯风姿潇洒标记dataHost标签,供上层的价签使用。
  maxCon:最奥斯汀接数
  minCon:最初连接数
  balance
    1、balance=0 不开启读写抽离机制,全数读操作都发送到当前可用的writehost了 .
    2、balance=1 百分之百的readhost与stand by writeHost 加入select语句的载重均衡。总的来讲,双主双从方式(M1àS1,M2àS2,并且M1和M2互为主备),平常景况下,M1,S1,S2都踏足select语句的纷纷均衡。
    3、balance=2 具备读操作都随便的在readhost和writehost上散发

  writeType 负载均衡类型,如今的取值有3种:
    1、writeType="0″, 全部写操作发送到配置的率先个writeHost。
    2、writeType="1″,全数写操作都随便的发送到配置的writeHost。
    3、writeType="2″,不执行写操作。

  switchType
    1、switchType=-1 代表不自行切换
    2、switchType=1 暗中同意值,自动切换
    3、switchType=2 基于MySQL 主从同步的情形调节是或不是切换

  dbType:数据库类型 mysql,postgresql,mongodb、oracle、spark等。

  heartbeat:用于和后端数据库进行心跳检查的说话。举例,MYSQL能够利用select user(),Oracle能够利用select 1 from dual等。
      那么些标签还会有一个connectionInitSql属性,首借使当使用Oracla数据库时,必要实施的早先化SQL语句就以此松手这里面来。举例:altersession set nls_date_format='yyyy-mm-dd hh24:mi:ss'
      当switchType=2 主从切换的言语必得是:show slave status

  writeHost、readHost:这七个标签都钦点后端数据库的相干安顿给mycat,用于实例化后端连接池。独一分裂的是,writeHost内定写实例、readHost内定读实例,
            在二个dataHost内能够定义七个writeHost和readHost。可是,要是writeHost钦定的后端数据库宕机,那么这一个writeHost绑定的兼具readHost都将不可用。
            另一方面,由于这几个writeHost宕机系统会活动的检查实验到,并切换成备用的writeHost上去。

3-4 改善文件 vim conf/server.xml

  

<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
<system>

</system>

<user name="root">
  <property name="password">123456</property>
  <property name="schemas">worldmap</property><!--与schema.xml相对应-->
  <property name="readOnly">false</property> <!--readOnly是应用连接中间件逻辑库所具有的权限。true为只读,false为读写都有,默认为false。-->
</user>

</mycat:server>

 

3-5 启动 mycat start
翻看运行日志:logs/wrapper.log;,符合规律运营成功后会有mycat.log日志,假若服务未运转成功不会有对应日志

3-6:对于开垦人士mycat约等于多个新的数据库服务端(暗中认可端口8066),开垦人士增加和删除改查不再是直接连接数据库,而是连接数据库中间件,中间件通过其自带的lua脚本进行sql判别,来路由到钦定数据库(实质依据selet,insert,update,delete关键字)

3-7:测验读写分离

  读数据路由到 192.168.203.139

  写数据路由到192.168.203.135 

 

  当主库宕机,读写操作都在192.168.203.139

  

  

3-8:注意事项
平时接受框架都会用到职业,即使都要到事务那么就都会访谈主服务器,达不到离其余效用,由此配置事务的时候要小心区分,比如只对包括增加和删除改的展开作业配置

 

好久未有写过博客了,趁着年假还会有一天,把2018年项目所接纳的读写抽离在这概述一下及其注意点,以免今后项...

  在接收缓存后,使绝大相当多的数目读操作访问都能够不经过数据库就能够时不我待,可是依然有一点读操作(包蕴未命中缓存的,和缓存过期的)和总体的写操作需求拜会数据库,当网址的访问量继续增添后,数据库会因为负载压力过高引致成为网址的属性瓶颈。

陈设读写数据源(主从数据库)

  方今当先伍分一的主流数据库都提供了宗旨热备作用,通过布署两台数据库的主从关系,能够将生龙活虎台数据库服务器的多寡同步到另风流浪漫台服务器上,网址使用数据库的那大器晚成成效,能够兑现数据库的读写抽离,进而修改数据库的负荷压力。

       mysqldb.properties

#主数据库数据源
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.0.4:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false
jdbc.username=root
jdbc.password=123456
jdbc.initialSize=1
jdbc.minIdle=1
jdbc.maxActive=20
jdbc.maxWait=60000
jdbc.removeAbandoned=true
jdbc.removeAbandonedTimeout=180
jdbc.timeBetweenEvictionRunsMillis=60000
jdbc.minEvictableIdleTimeMillis=300000
jdbc.validationQuery=SELECT 1
jdbc.testWhileIdle=true
jdbc.testOnBorrow=false
jdbc.testOnReturn=false

#从数据库数据源
slave.jdbc.driverClassName=com.mysql.jdbc.Driver
slave.jdbc.url=jdbc:mysql://192.168.0.221:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false
slave.jdbc.username=root
slave.jdbc.password=123456
slave.jdbc.initialSize=1
slave.jdbc.minIdle=1
slave.jdbc.maxActive=20
slave.jdbc.maxWait=60000
slave.jdbc.removeAbandoned=true
slave.jdbc.removeAbandonedTimeout=180
slave.jdbc.timeBetweenEvictionRunsMillis=60000
slave.jdbc.minEvictableIdleTimeMillis=300000
slave.jdbc.validationQuery=SELECT 1
slave.jdbc.testWhileIdle=true
slave.jdbc.testOnBorrow=false
slave.jdbc.testOnReturn=false

    主、从数据库的地址记得改成温馨的,账号和密码也急需改成温馨的;别的安顿项,大家能够研商自行设置

  应用服务器在写多少的时候,访问主数据库,主数据库通过主从复制机制将数据更新同步到从数据库,那样当应用服务器读数据的时候,就足以经过从数据库获得多少,为了便于应用程序访问读写分离后的数据库,日常在应用服务器端用特地的数据采访模块,使数据库读写分离对接受透明。

       mybatis-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop.xsd
     http://www.springframework.org/schema/tx 
     http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- master数据源 -->
    <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!-- 基本属性 url、user、password -->  
        <property name="driverClassName" value="${jdbc.driverClassName}" />  
        <property name="url" value="${jdbc.url}" />  
        <property name="username" value="${jdbc.username}" />  
        <property name="password" value="${jdbc.password}" />  
        <property name="initialSize" value="${jdbc.initialSize}" />  
        <property name="minIdle" value="${jdbc.minIdle}" />   
        <property name="maxActive" value="${jdbc.maxActive}" />  
        <property name="maxWait" value="${jdbc.maxWait}" />
        <!-- 超过时间限制是否回收 -->
        <property name="removeAbandoned" value="${jdbc.removeAbandoned}" />
        <!-- 超过时间限制多长; -->
        <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />
        <!-- 用来检测连接是否有效的sql,要求是一个查询语句-->
        <property name="validationQuery" value="${jdbc.validationQuery}" />
        <!-- 申请连接的时候检测 -->
        <property name="testWhileIdle" value="${jdbc.testWhileIdle}" />
        <!-- 申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能 -->
        <property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
        <!-- 归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能  -->
        <property name="testOnReturn" value="${jdbc.testOnReturn}" />
    </bean>

    <!-- slave数据源 -->
    <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${slave.jdbc.driverClassName}" />  
        <property name="url" value="${slave.jdbc.url}" />  
        <property name="username" value="${slave.jdbc.username}" />  
        <property name="password" value="${slave.jdbc.password}" />  
        <property name="initialSize" value="${slave.jdbc.initialSize}" />  
        <property name="minIdle" value="${slave.jdbc.minIdle}" />   
        <property name="maxActive" value="${slave.jdbc.maxActive}" />  
        <property name="maxWait" value="${slave.jdbc.maxWait}" />
        <property name="removeAbandoned" value="${slave.jdbc.removeAbandoned}" />
        <property name="removeAbandonedTimeout" value="${slave.jdbc.removeAbandonedTimeout}" />
        <property name="timeBetweenEvictionRunsMillis" value="${slave.jdbc.timeBetweenEvictionRunsMillis}" />
        <property name="minEvictableIdleTimeMillis" value="${slave.jdbc.minEvictableIdleTimeMillis}" />
        <property name="validationQuery" value="${slave.jdbc.validationQuery}" />
        <property name="testWhileIdle" value="${slave.jdbc.testWhileIdle}" />
        <property name="testOnBorrow" value="${slave.jdbc.testOnBorrow}" />
        <property name="testOnReturn" value="${slave.jdbc.testOnReturn}" />
    </bean>

    <!-- 动态数据源,根据service接口上的注解来决定取哪个数据源 -->
    <bean id="dataSource" class="com.yzb.util.DynamicDataSource">  
        <property name="targetDataSources">      
          <map key-type="java.lang.String">      
              <!-- write or slave -->    
             <entry key="slave" value-ref="slaveDataSource"/>      
             <!-- read or master   -->  
             <entry key="master" value-ref="masterDataSource"/>      
          </map>               
        </property>   
        <property name="defaultTargetDataSource" ref="masterDataSource"/>      

    </bean>

    <!-- Mybatis文件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:mybatis-config.xml" /> 
        <property name="dataSource" ref="dataSource" />
        <!-- 映射文件路径 -->
        <property name="mapperLocations" value="classpath*:dbmappers/*.xml" />
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.yzb.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

    <!-- 事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 声明式开启 -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" order="1"/>

    <!-- 为业务逻辑层的方法解析@DataSource注解  为当前线程的HandleDataSource注入数据源 -->    
    <bean id="dataSourceAspect" class="com.yzb.util.DataSourceAspect" />    
    <aop:config proxy-target-class="true">    
        <aop:aspect id="dataSourceAspect" ref="dataSourceAspect" order="2">    
            <aop:pointcut id="tx" expression="execution(* com.yzb.service.impl..*.*(..)) "/>    
            <aop:before pointcut-ref="tx" method="before" />                
        </aop:aspect>    
    </aop:config>
</beans>

数据库读写分离

架构如下图:

澳门新浦京娱乐场网站 4

总结:

数据库读写分离减轻了数据库的下压力,也晋级了上上下下网址的属性,但还要会衍生出来八个难题:

1,数据同步的主题材料。日常选拔数据库自带的数目复制机制消除,能够参谋:

2,应用程序对于数据源选择的主题素材。对于应用程序来讲,前边的写操作全部要走主库,而读操作要走从库。这么些操作日常经过中间件数据访谈层来完结。

通过数据库读写分离,那么些等第,系统现身管理本领理论上得以完成5000 了。

AOP达成数据源的动态切换

DataSource.java

package com.yzb.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**  
 * RUNTIME  
 * 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。  
 *  
 */  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD) 
public @interface DataSource
{
    String value();
}

DataSourceAspect.java

package com.yzb.util;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

public class DataSourceAspect
{
    /**
     * 在dao层方法获取datasource对象之前,在切面中指定当前线程数据源
     */
    public void before(JoinPoint point)
    {

        Object target = point.getTarget();
        String method = point.getSignature().getName();
        Class<?>[] classz = target.getClass().getInterfaces();                        // 获取目标类的接口, 所以@DataSource需要写在接口上
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
                .getMethod().getParameterTypes();
        try
        {
            Method m = classz[0].getMethod(method, parameterTypes);
            if (m != null && m.isAnnotationPresent(DataSource.class))
            {
                DataSource data = m.getAnnotation(DataSource.class);
                System.out.println("用户选择数据库库类型:"   data.value());
                HandleDataSource.putDataSource(data.value());                        // 数据源放到当前线程中
            }

        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
    }

DynamicDataSource.java

package com.yzb.util;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource
{

    /**
     * 获取与数据源相关的key 此key是Map<String,DataSource> resolvedDataSources 中与数据源绑定的key值
     * 在通过determineTargetDataSource获取目标数据源时使用
     */
    @Override
    protected Object determineCurrentLookupKey()
    {
        return HandleDataSource.getDataSource();
    }

}

HandleDataSource.java

package com.yzb.util;

public class HandleDataSource
{
    public static final ThreadLocal<String> holder = new ThreadLocal<String>();

    /**
     * 绑定当前线程数据源
     * 
     * @param key
     */
    public static void putDataSource(String datasource)
    {
        holder.set(datasource);
    }

    /**
     * 获取当前线程的数据源
     * 
     * @return
     */
    public static String getDataSource()
    {
        return holder.get();
    }
}

service接口上行使@DataSource达成数据源的内定

package com.yzb.service;

import java.util.List;

import com.yzb.model.Person;
import com.yzb.util.DataSource;

public interface IPersonService {

    /**
     * 加载全部的person
     * @return
     */
    List<Person> listAllPerson();

    /**
     * 查询某个人的信息
     * @param personId
     * @return
     */
    @DataSource("slave")            // 指定使用从数据源
    Person getPerson(int personId);

    boolean updatePerson(Person person);
}

注意点

  测量检验的时候,怎样知道读取的是从数据库了? 大家得以纠正从数据库中查询到的这条记下的某些字段的值,以区分主、从数据库;

       事务要求注意,尽量确定保证在贰个数额源上进行工作;

       当有个别service上有八个aop时,须求在意aop织入的各类难点,利用order关键字调节好织入顺序;

  项目总体育工作程github地址:

  测试url:

参考

  《大型网址本领架构_中央原理与案例深入分析》

  Spring MyBatis完毕数据库读写抽离方案

本文由澳门新浦京娱乐场网站发布于数据库,转载请注明出处:spring集成mybatis实现mysql读写分离,mysql读写分离