mysql數據庫主從分離

數據庫的讀寫分離的好處有哪些?

1)將讀操作和寫操作分離到不同的數據庫上,避免主服務器出現性能瓶頸;

2) 主服務器進行寫操作時,不影響查詢應用服務器的查詢性能,降低阻塞,提高併發;

3) 數據擁有多個容災副本,提高數據安全性,同時當主服務器故障時,可立即切換到其他服務器,提高系統可用性;

說到mysql數據庫主從分離,需要注意以下3點:

1.主從數據庫數據一致(主從同步

2.增刪改走主庫

3.查詢走從庫

 

一般來講,讀寫分離有兩種實現方式;第一種是依靠中間件(比如:MyCat,sharding),第二種是應用程序自己去做分離。這裏我們選擇程序自己來做,主要是利用Spring提供的路由數據源,以及AOP。

項目目錄結構:

首先引入相關依賴

		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
		</dependency>
		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>3.1</version>
		</dependency>

 

數據源配置類

 

@Configuration
public class DynamicDataSourceConfig {

    private Logger logger = LogManager.getLogger(DynamicDataSourceConfig.class);

    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource() {
        logger.info("into master data source");
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    public DataSource slaveDataSource() {
        logger.info("into slave data source");
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DataSource myRoutingDataSource() {
        logger.info("into my routing data source");
        Map<Object, Object> targetDataSources = new HashMap<>(16);
        targetDataSources.put(DBTypeEnum.MASTER, masterDataSource());
        targetDataSources.put(DBTypeEnum.SLAVE, slaveDataSource());
        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        myRoutingDataSource.setDefaultTargetDataSource(masterDataSource());
        myRoutingDataSource.setTargetDataSources(targetDataSources);
        return myRoutingDataSource;
    }

sqlSessionFactory配置類

@EnableTransactionManagement
@Configuration
public class MybatisConfig {

    @Resource
    private DataSource myRoutingDataSource;

    @Value("${mybatis.type.aliases.package}")
    private String typeAliasPackage;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
        sqlSessionFactoryBean.setConfigLocation(resolver.getResource("classpath:mybatis-config.xml"));
        sqlSessionFactoryBean.setTypeAliasesPackage(typeAliasPackage);
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 創建SqlSessionTemplate對象
     *
     * @param sqlSessionFactory 創建SqlSessionTemplate所需的SessionFactory對象
     * @return 創建成功的SqlSessionTemplate對象
     */
    @Bean
    public SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    /**
     * 創建事務用的Transaction Manager對象
     *
     * @return 創建成功的Transaction Manager對象
     */
    @Bean
    public PlatformTransactionManager createTransactionManager() {
        return new DataSourceTransactionManager(myRoutingDataSource);
    }
}

將數據源設置到本地線程ThreadLocal中

/**
 * 通過ThreadLocal將數據源設置到每個線程上下文中
 */
public class DBContextHolder {

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

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

    public static DBTypeEnum get() {
        return contextHolder.get();
    }

    public static void master() {
        set(DBTypeEnum.MASTER);
        System.out.println("切換到master");
    }

    public static void slave() {
        set(DBTypeEnum.SLAVE);
        System.out.println("切換到slave");
    }
}

AOP切面類


@Aspect
@Order(1)
@Component
public class DataSourceAop {

    @Pointcut("!@annotation(com.carrefour.cn.cdm.permission.annotation.Master) " +
            "&& execution(* com.carrefour.cn.cdm.permission.service..*.select*(..)) " +
            "|| execution(* com.carrefour.cn.cdm.permission.service..*.find*(..)) " +
            "|| execution(* com.carrefour.cn.cdm.permission.service..*.get*(..)))")
    public void readPointcut() {

    }

    @Pointcut("@annotation(com.carrefour.cn.cdm.permission.annotation.Master) " +
            "|| execution(* com.carrefour.cn.cdm.permission.service..*.insert*(..)) " +
            "|| execution(* com.carrefour.cn.cdm.permission.service..*.add*(..)) " +
            "|| execution(* com.carrefour.cn.cdm.permission.service..*.create*(..)) " +
            "|| execution(* com.carrefour.cn.cdm.permission.service..*.update*(..)) " +
            "|| execution(* com.carrefour.cn.cdm.permission.service..*.edit*(..)) " +
            "|| execution(* com.carrefour.cn.cdm.permission.service..*.delete*(..)) " +
            "|| execution(* com.carrefour.cn.cdm.permission.service..*.remove*(..))")
    public void writePointcut() {

    }

    @Before("readPointcut()")
    public void read() {
        DBContextHolder.slave();
    }

    @Before("writePointcut()")
    public void write() {
        DBContextHolder.master();
    }


    /**
     * 另一種寫法:if...else...  判斷哪些需要讀從數據庫,其餘的走主數據庫
     */
//    @Before("execution(* com.cjs.example.service.impl.*.*(..))")
//    public void before(JoinPoint jp) {
//        String methodName = jp.getSignature().getName();
//
//        if (StringUtils.startsWithAny(methodName, "get", "select", "find")) {
//            DBContextHolder.slave();
//        }else {
//            DBContextHolder.master();
//        }
//    }
}

新建一個Master註解類

如果需要查詢走主庫,可以直接在service層的方法上加上@Master即可。

 

單元測試結果

1.查詢由從庫切換爲主庫

2.查詢走從庫

3.新增/修改/刪除走主庫(修改、刪除這裏就不一一測試了)

至此,mysql數據庫主從分離就完成了。

參考:https://www.jianshu.com/p/8904af2c029a

           https://www.cnblogs.com/panxuejun/p/5887118.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章