<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
實際生產的過程中為了實現資料庫的高可用,不會只有一個資料庫節點。至少會搭建主從複製的資料庫架構,從庫可以作為主庫的資料備份,以免主資料庫損壞的情況下丟失資料;當存取量增加的時候可以作為讀節點承擔部分流量等。下面就進行從零開始搭建MySQL的主從架構。
以MySQL一主兩從架構為為例,也就是一個master節點下有兩個slave節點,在這套架構下,寫操作統一交給master節點,讀請求交給slave節點處理。
為了保證master節點和slave節點資料一致,在master節點寫入資料後,會同時將資料複製到對應的slave節點。主從複製資料的過程中會用到三個執行緒,master節點上的binlog dump執行緒,slave節點的IO執行緒和SQL執行緒。
主從複製的核心流程:
MySQL的主從複製模式分為:全同步複製,非同步複製,半同步複製,增強半同步複製。
全同步複製
全同步複製,就是當主庫執行完一個事物之後,要求所有的從庫也都必須執行完該事務,才可以返回處理結果給使用者端;因此雖然全同步複製資料一致性得到保證了,但是主庫完成一個事物需要等待所有從庫也完成,效能就比較低了。
非同步複製
非同步複製,當主庫提交事務後會通知binlog dump執行緒傳送binlog紀錄檔給從庫,一旦binlog dump執行緒將binlog紀錄檔傳送給從庫之後,不需要等到從庫也同步完成事務,主庫就會講處理結果返回給使用者端。
因為主庫只管自己執行完事務,就可以將處理結果返回給使用者端,而不用關係從庫是否執行完事務,這就可能導致短暫的主從資料不一致的問題了,比如剛在主庫插入的資料,如果馬上在從庫查詢就可能查詢不到。
當主庫提交食物後,如果宕機掛掉了,此時可能binlog還沒來得及同步給從庫,這時候如果為了回覆故障切換主從節點的話,就會出現資料丟失的問題,所以非同步複製雖然效能高,但資料一致性上是比較弱的。
MySQL預設採用的是非同步複製模式。
半同步複製
半同步複製就是在同步複製和非同步中做了折中選擇,我們可以結合著MySQL官網來看下是半同步和主從複製的過程。
當主庫提交事務後,至少還需要一個從庫返回接收到binlog紀錄檔,併成功寫入到relaylog的訊息,這個的時候,主庫才會講處理結果返回給使用者端。
相比前兩種複製方式,半同步複製較好地兼顧了資料一致性以及效能損耗的問題。
同時,半同步複製也存在以下幾個問題:
增強半同步複製
增強半同步複製是MySQL5.7.2後的版本對半同步複製做的一個改進,原理幾乎是一樣的,主要是解決幻讀的問題。
主庫設定了引數rpl_semi_sync_master_wait_point=AFTER_SYNC後,主庫在儲存引擎提交事務前,必須先首都哦啊從庫資料同步完成的確認資訊後,才能提交事務,以此來解決幻讀問題。
準備資料來源
config/datasource.properties
# masters
spring.datasource.masters.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.masters.url=jdbc:mysql://192.168.1.111:3306/monomer_order?useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true&zeroDateTimeBehavior=convertToNull
spring.datasource.masters.username=root
spring.datasource.masters.password=123456
# slaves
spring.datasource.slaves[0].driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.slaves[0].url=jdbc:mysql://192.168.1.112:3306/monomer_order?useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true&zeroDateTimeBehavior=convertToNull
spring.datasource.slaves[0].username=root
spring.datasource.slaves[0].password=123456
設定資料來源
package com.xinxin.order.context.config; import com.alibaba.druid.pool.DruidDataSourceFactory; import lombok.Data; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.*; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.util.CollectionUtils; import javax.sql.DataSource; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @Data @Configuration @PropertySource("classpath:config/datasource.properties") @ConfigurationProperties(prefix = "spring.datasource") public class DataSourceConfig { /** * 主庫資料來源資訊 */ private Map<String, String> masters; /** * 從庫資料來源資訊 */ private List<Map<String, String>> slaves; @SneakyThrows @Bean public DataSource masterDataSource() { log.info("masters:{}", masters); if (CollectionUtils.isEmpty(masters)) { throw new Exception("主庫資料來源不能為空"); } return DruidDataSourceFactory.createDataSource(masters); } @SneakyThrows @Bean public List<DataSource> slaveDataSources() { if (CollectionUtils.isEmpty(slaves)) { throw new Exception("從庫資料來源不能為空"); } final ArrayList<DataSource> dataSources = new ArrayList<>(); for (Map<String, String> slaveProperties : slaves) { log.info("slave:{}", slaveProperties); dataSources.add(DruidDataSourceFactory.createDataSource(slaveProperties)); } return dataSources; } @Bean @Primary @DependsOn({"masterDataSource", "slaveDataSources"}) public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSources") List<DataSource> slaveDataSources) { final Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceContextHolder.MASTER, masterDataSource); for (int i = 0; i < slaveDataSources.size(); i++) { targetDataSources.put(DataSourceContextHolder.SLAVE + i, slaveDataSources.get(i)); } final DataSourceRouter dataSourceRouter = new DataSourceRouter(); dataSourceRouter.setTargetDataSources(targetDataSources); dataSourceRouter.setDefaultTargetDataSource(masterDataSource); return dataSourceRouter; } @Bean public DataSourceTransactionManager dataSourceTransactionManager( @Qualifier("routingDataSource") DataSource routingDataSource) { return new DataSourceTransactionManager(routingDataSource); } }
資料來源上下文切換
package com.xinxin.order.context.config; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @Slf4j public class DataSourceContextHolder { public static final String MASTER = "master"; public static final String SLAVE = "slave"; private static ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public static void setDatasourceType(String dataSourceType) { if (StringUtils.isBlank(dataSourceType)) { log.error("dataSourceType為空"); } log.info("設定dataSource: {}", dataSourceType); CONTEXT_HOLDER.set(dataSourceType); } public static String getDataSourceType() { return CONTEXT_HOLDER.get() == null ? MASTER : CONTEXT_HOLDER.get(); } public static void remove() { CONTEXT_HOLDER.remove(); } }
資料來源路由實現類
package com.xinxin.order.context.config; import lombok.extern.slf4j.Slf4j; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; @Slf4j public class DataSourceRouter extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { log.info("當前資料來源為: {}", DataSourceContextHolder.getDataSourceType()); return DataSourceContextHolder.getDataSourceType(); } }
資料來源切換註解
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ReadOnly { String value() default DataSourceContextHolder.MASTER; }
動態資料來源切換切面
package com.xinxin.order.aspect; import com.xinxin.order.annotation.ReadOnly; import com.xinxin.order.context.config.DataSourceContextHolder; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; @Slf4j @Aspect @Component public class DynamicDataSourceAspect implements Ordered { @Before(value = "execution(* *(..))&& @annotation(readOnly)") public void before(JoinPoint joinPoint, ReadOnly readOnly) { log.info(joinPoint.getSignature().getName() + "走從庫"); DataSourceContextHolder.setDatasourceType(DataSourceContextHolder.SLAVE); } @After(value = "execution(* *(..))&& @annotation(readOnly)") public void after(JoinPoint joinPoint, ReadOnly readOnly) { log.info(joinPoint.getSignature().getName() + "清除資料來源"); DataSourceContextHolder.remove(); } @Override public int getOrder() { return 0; } }
專案整合讀寫分離主要是通過收到注入資料來源,並通過攔截器設定當前執行緒的資料來源型別,需要使用資料來源的地方會通過資料來源路由器讀取當前執行緒的資料來源型別後返回實際的資料來源進行資料庫的操作。
到此這篇關於Java MySQL主從複製的原理圖解及範例使用的文章就介紹到這了,更多相關Java MySQL主從複製內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45