六、主从复制和读写分离

一、数据库主从复制

1.主服务器配置

(1)修改my.conf文件 

vim /etc/my.conf

在mysqld段下添加配置

binlog-do-db=db1 
binlog-ignore-db=mysql 
#启用二进制日志 
log-bin=mysql-bin 
#服务器唯一ID,一般取IP最后一段 
server-id=131

 

(2)重启mysql服务

service mysqld restart

 

(3)登录mysql并建立帐户并授权slave

mysql>GRANT FILE ON *.* TO 'backup'@'%' IDENTIFIED BY '123456'; mysql>GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* to 'backup'@'%' identified by '123456';

一般不用root帐号 

“%”表示所有客户端都可能连,只要帐号,密码正确 

此处可用具体客户端IP代替,比如,192.168.145.226,加强安全

 

(4)刷新权限

mysql> FLUSH PRIVILEGES;

 

(5)查询master的状态 

mysql> show master status; 

六、主从复制和读写分离_第1张图片

 

 

2.从服务器配置

(1)修改my.conf文件 

[mysqld] server-id=166

 

(2)连接主服务器

mysql>change master to master_host='192.168.6.130',master_port=3306,master_user='backup',master_password='123456',master_log_file='mysql-bin.000002',master_log_pos=120;

master_port为mysql服务器端口号

master_user为执行同步操作的数据库账户

master_log_pos为120

就是show master status 中的position对应的值

master_log_file 为mysql-bin.000002

就是show master status中的file对应的值

 

(3)启动从服务器复制功能 

mysql>start slave;

 

(4)检查从服务器复制功能状态 

命令行

mysql> show slave status /G

 

客户端

SHOW SLAVE STATUS;

 

二、spring data完成读写分离

1.实现流程

(1)配置文件application-dev

# mysql connect attribute
spring.datasource.master.jdbc-url=jdbc:mysql://192.168.6.130:3306/technology_mall_goods?characterEncoding=utf-8
spring.datasource.master.username=root
spring.datasource.master.password=root
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.slave1.jdbc-url=jdbc:mysql://192.168.6.131:3306/technology_mall_goods?characterEncoding=utf-8
spring.datasource.slave1.username=root
spring.datasource.slave1.password=root
spring.datasource.slave1.driver-class-name=com.mysql.jdbc.Driver

# Hikari will use the above plus the following to setup connection pooling
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=DatebookHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1

 

(2)数据源枚举类DataSourceTypeEnum

public enum DataSourceTypeEnum { MASTER, SLAVE1, SLAVE2; }

 

(3)获取使用的数据源的key DynamicRoutingDataSource

在访问数据库时会调用该类的 determineCurrentLookupKey() 方法获取数据库实例的 key

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

   private final Logger logger = LoggerFactory.getLogger(getClass());

   /**
    * 当为null时,表示使用的数据源是默认数据源(@Primary注释的数据源)
    * @return
    */
   @Override
   protected Object determineCurrentLookupKey() {
      logger.info("Current DataSource is [{}]",DynamicDataSourceContextHolder.getDataSourceKey());

      return DynamicDataSourceContextHolder.getDataSourceKey();
   }
}

 

(4)数据源配置类DataSourceConfiguration

该类中生成多个数据源实例并将其注入到 ApplicationContext 中

@Configuration
public class DataSourceConfiguration {

   @Bean
   @Primary
   @ConfigurationProperties("spring.datasource.master")
   public DataSource masterDataSource() {
      return DataSourceBuilder.create().build();
   }

   @Bean
   @ConfigurationProperties("spring.datasource.slave1")
   public DataSource slave1DataSource() {
      return DataSourceBuilder.create().build();
   }

   @Bean
   public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                @Qualifier("slave1DataSource") DataSource slave1DataSource) {
      Map targetDataSources = new HashMap<>();
      targetDataSources.put(DataSourceTypeEnum.MASTER, masterDataSource);
      targetDataSources.put(DataSourceTypeEnum.SLAVE1, slave1DataSource);

      DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
      dynamicRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
      dynamicRoutingDataSource.setTargetDataSources(targetDataSources);
      return dynamicRoutingDataSource;
   }
}

 

(5)数据库配置类

@EnableTransactionManagement
@Configuration
public class MyBatisConfig {

   @Resource(name = "dynamicDataSource")
   private DataSource dynamicDataSource;

   @Bean
   public SqlSessionFactory sqlSessionFactory() throws Exception {
      SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
      sqlSessionFactoryBean.setDataSource(dynamicDataSource);
      sqlSessionFactoryBean.setTypeAliasesPackage("com.tmall.infrastructure.po");
      sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/*.xml"));
      return sqlSessionFactoryBean.getObject();
   }

   @Bean
   public PlatformTransactionManager platformTransactionManager() {
      return new DataSourceTransactionManager(dynamicDataSource);
   }
}

 

(6)切换数据源类DynamicDataSourceContextHolder

该类为数据源上下文配置,用于切换数据源

public class DynamicDataSourceContextHolder {

   private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

   private static final ThreadLocal contextHolder = new ThreadLocal<>();

   private static final AtomicInteger counter = new AtomicInteger(-1);

   public static void set(DataSourceTypeEnum dbType) {
      contextHolder.set(dbType);
   }

   public static void master() {
      set(DataSourceTypeEnum.MASTER);
   }

   public static void slave() {
      //  轮询
      int datasourceKeyIndex  = counter.getAndIncrement() % 2;
      if (counter.get() > 9999) {
         counter.set(-1);
      }
      if (datasourceKeyIndex  == 0) {
         set(DataSourceTypeEnum.SLAVE1);
         logger.info("Switch To slave1");
      } else {
         set(DataSourceTypeEnum.SLAVE1);
         logger.info("Switch To slave1(2)");
      }
   }

   public static DataSourceTypeEnum getDataSourceKey() {
      return contextHolder.get();
   }

   public static void clearDataSourceKey() {
      contextHolder.remove();
   }
}

 

(7)切面类DynamicDataSourceAspect

动态数据源切换的切面,切 DAO 层,通过 DAO 层方法名判断使用哪个数据源,实现数据源切换

@Aspect
@Component
public class DynamicDataSourceAspect {

   private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

   private final String[] QUERY_PREFIX = {"select"};

   @Pointcut("execution( * com.tmall.infrastructure.dao.*.*(..))")
   public void daoAspect() { }

   @Before("daoAspect()")
   public void switchDataSource(JoinPoint point) {
      Boolean isQueryMethod = isQueryMethod(point.getSignature().getName());
      if (isQueryMethod) {
         DynamicDataSourceContextHolder.slave();
         logger.info("Switch DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
      }
   }

   @After("daoAspect()")
   public void restoreDataSource(JoinPoint point) {
      DynamicDataSourceContextHolder.clearDataSourceKey();
      logger.info("Restore DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
   }

   private Boolean isQueryMethod(String methodName) {
      for (String prefix : QUERY_PREFIX) {
         if (methodName.startsWith(prefix)) {
            return true;
         }
      }
      return false;
   }
}

 

2.测试

(1)调用查询方法时

六、主从复制和读写分离_第2张图片

 

(2)调用更改方法时

六、主从复制和读写分离_第3张图片

 

 

你可能感兴趣的:(java,mysql,项目实战)