真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離

這篇文章主要介紹“Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離”,在日常操作中,相信很多人在Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

創(chuàng)新互聯(lián)專注于嘉定網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供嘉定營(yíng)銷型網(wǎng)站建設(shè),嘉定網(wǎng)站制作、嘉定網(wǎng)頁(yè)設(shè)計(jì)、嘉定網(wǎng)站官網(wǎng)定制、微信小程序服務(wù),打造嘉定網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供嘉定網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。

寫在前面

很多小伙伴私聊我說(shuō):最近他們公司的業(yè)務(wù)涉及到多個(gè)數(shù)據(jù)源的問題,問我Spring如何實(shí)現(xiàn)多數(shù)據(jù)源的問題?;卮疬@個(gè)問題之前,首先需要弄懂什么是多數(shù)據(jù)源:多數(shù)據(jù)源就是在同一個(gè)項(xiàng)目中,會(huì)連接兩個(gè)甚至多個(gè)數(shù)據(jù)存儲(chǔ),這里的數(shù)據(jù)存儲(chǔ)可以是關(guān)系型數(shù)據(jù)庫(kù)(比如:MySQL、SQL  Server、Oracle),也可以非關(guān)系型數(shù)據(jù)庫(kù),比如:HBase、MongoDB、ES等。那么,問題來(lái)了,Spring能夠?qū)崿F(xiàn)多數(shù)據(jù)源嗎?并且還要實(shí)現(xiàn)讀者分離?答案是:必須的,這么強(qiáng)大的Spring,肯定能實(shí)現(xiàn)啊!別急,我們就一點(diǎn)點(diǎn)剖析、解決這些問題!

背景

我們一般應(yīng)用對(duì)數(shù)據(jù)庫(kù)而言都是“讀多寫少”,也就說(shuō)對(duì)數(shù)據(jù)庫(kù)讀取數(shù)據(jù)的壓力比較大,有一個(gè)思路就是說(shuō)采用數(shù)據(jù)庫(kù)集群的方案,

其中一個(gè)是主庫(kù),負(fù)責(zé)寫入數(shù)據(jù),我們稱之為:寫庫(kù);其它都是從庫(kù),負(fù)責(zé)讀取數(shù)據(jù),我們稱之為:讀庫(kù);

那么,對(duì)我們的要求是:

  • 讀庫(kù)和寫庫(kù)的數(shù)據(jù)一致;

  • 寫數(shù)據(jù)必須寫到寫庫(kù);

  • 讀數(shù)據(jù)必須到讀庫(kù);

方案

解決讀寫分離的方案有兩種:應(yīng)用層解決和中間件解決。

應(yīng)用層解決

Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離

優(yōu)點(diǎn):

  • 多數(shù)據(jù)源切換方便,由程序自動(dòng)完成;

  • 不需要引入中間件;

  • 理論上支持任何數(shù)據(jù)庫(kù);

缺點(diǎn):

  • 由程序員完成,運(yùn)維參與不到;

  • 不能做到動(dòng)態(tài)增加數(shù)據(jù)源;

中間件解決

Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離

優(yōu)點(diǎn):

  • 源程序不需要做任何改動(dòng)就可以實(shí)現(xiàn)讀寫分離;

  • 動(dòng)態(tài)添加數(shù)據(jù)源不需要重啟程序;

缺點(diǎn):

  • 程序依賴于中間件,會(huì)導(dǎo)致切換數(shù)據(jù)庫(kù)變得困難;

  • 由中間件做了中轉(zhuǎn)代理,性能有所下降;

Spring方案

原理

Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離

在進(jìn)入Service之前,使用AOP來(lái)做出判斷,是使用寫庫(kù)還是讀庫(kù),判斷依據(jù)可以根據(jù)方法名判斷,比如說(shuō)以query、find、get等開頭的就走讀庫(kù),其他的走寫庫(kù)。

DynamicDataSource

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;   /**  * 定義動(dòng)態(tài)數(shù)據(jù)源,實(shí)現(xiàn)通過集成Spring提供的AbstractRoutingDataSource,只需要實(shí)現(xiàn)determineCurrentLookupKey方法即可  * 由于DynamicDataSource是單例的,線程不安全的,所以采用ThreadLocal保證線程安全,由DynamicDataSourceHolder完成。  * @author binghe  */ public class DynamicDataSource extends AbstractRoutingDataSource{       @Override     protected Object determineCurrentLookupKey() {         // 使用DynamicDataSourceHolder保證線程安全,并且得到當(dāng)前線程中的數(shù)據(jù)源key         return DynamicDataSourceHolder.getDataSourceKey();     }   }

DynamicDataSourceHolder

/**  * 使用ThreadLocal技術(shù)來(lái)記錄當(dāng)前線程中的數(shù)據(jù)源的key  * @author binghe  */ public class DynamicDataSourceHolder {          //寫庫(kù)對(duì)應(yīng)的數(shù)據(jù)源key     private static final String MASTER = "master";       //讀庫(kù)對(duì)應(yīng)的數(shù)據(jù)源key     private static final String SLAVE = "slave";          //使用ThreadLocal記錄當(dāng)前線程的數(shù)據(jù)源key     private static final ThreadLocal holder = new ThreadLocal();       /**      * 設(shè)置數(shù)據(jù)源key      * @param key      */     public static void putDataSourceKey(String key) {         holder.set(key);     }       /**      * 獲取數(shù)據(jù)源key      * @return      */     public static String getDataSourceKey() {         return holder.get();     }          /**      * 標(biāo)記寫庫(kù)      */     public static void markMaster(){         putDataSourceKey(MASTER);     }          /**      * 標(biāo)記讀庫(kù)      */     public static void markSlave(){         putDataSourceKey(SLAVE);     }   }

DataSourceAspect

import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint;   /**  * 定義數(shù)據(jù)源的AOP切面,通過該Service的方法名判斷是應(yīng)該走讀庫(kù)還是寫庫(kù)  * @author binghe  */ public class DataSourceAspect {       /**      * 在進(jìn)入Service方法之前執(zhí)行      * @param point 切面對(duì)象      */     public void before(JoinPoint point) {         // 獲取到當(dāng)前執(zhí)行的方法名         String methodName = point.getSignature().getName();         if (isSlave(methodName)) {             // 標(biāo)記為讀庫(kù)             DynamicDataSourceHolder.markSlave();         } else {             // 標(biāo)記為寫庫(kù)             DynamicDataSourceHolder.markMaster();         }     }       /**      * 判斷是否為讀庫(kù)      *       * @param methodName      * @return      */     private Boolean isSlave(String methodName) {         // 方法名以query、find、get開頭的方法名走從庫(kù)         return StringUtils.startsWithAny(methodName, "query", "find", "get");     }   }

配置2個(gè)數(shù)據(jù)源

jdbc.properties

jdbc.master.driver=com.mysql.jdbc.Driver jdbc.master.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true jdbc.master.username=root jdbc.master.password=123456   jdbc.slave01.driver=com.mysql.jdbc.Driver jdbc.slave01.url=jdbc:mysql://127.0.0.1:3307/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true jdbc.slave01.username=root jdbc.slave01.password=123456

定義連接池

                                                                       

定義DataSource

                              

配置事務(wù)管理與動(dòng)態(tài)切面

定義事務(wù)管理器

    

定義事務(wù)策略

                                        

定義切面

                        

改進(jìn)切面實(shí)現(xiàn)

之前的實(shí)現(xiàn)我們是將通過方法名匹配,而不是使用事務(wù)策略中的定義,我們使用事務(wù)管理策略中的規(guī)則匹配。

改進(jìn)后的配置

          

改進(jìn)后的實(shí)現(xiàn)

import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map;   import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource; import org.springframework.transaction.interceptor.TransactionAttribute; import org.springframework.transaction.interceptor.TransactionAttributeSource; import org.springframework.transaction.interceptor.TransactionInterceptor; import org.springframework.util.PatternMatchUtils; import org.springframework.util.ReflectionUtils;   /**  * 定義數(shù)據(jù)源的AOP切面,該類控制了使用Master還是Slave。  * 如果事務(wù)管理中配置了事務(wù)策略,則采用配置的事務(wù)策略中的標(biāo)記了ReadOnly的方法是用Slave,其它使用Master。  * 如果沒有配置事務(wù)管理的策略,則采用方法名匹配的原則,以query、find、get開頭方法用Slave,其它用Master。  * @author binghe  *  */ public class DataSourceAspect {       private List slaveMethodPattern = new ArrayList();          private static final String[] defaultSlaveMethodStart = new String[]{ "query", "find", "get" };          private String[] slaveMethodStart;       /**      * 讀取事務(wù)管理中的策略      * @param txAdvice      * @throws Exception      */     @SuppressWarnings("unchecked")     public void setTxAdvice(TransactionInterceptor txAdvice) throws Exception {         if (txAdvice == null) {             // 沒有配置事務(wù)管理策略             return;         }         //從txAdvice獲取到策略配置信息         TransactionAttributeSource transactionAttributeSource = txAdvice.getTransactionAttributeSource();         if (!(transactionAttributeSource instanceof NameMatchTransactionAttributeSource)) {             return;         }         //使用反射技術(shù)獲取到NameMatchTransactionAttributeSource對(duì)象中的nameMap屬性值         NameMatchTransactionAttributeSource matchTransactionAttributeSource = (NameMatchTransactionAttributeSource) transactionAttributeSource;         Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");         nameMapField.setAccessible(true); //設(shè)置該字段可訪問         //獲取nameMap的值         Map map = (Map) nameMapField.get(matchTransactionAttributeSource);           //遍歷nameMap         for (Map.Entry entry : map.entrySet()) {             if (!entry.getValue().isReadOnly()) {//判斷之后定義了ReadOnly的策略才加入到slaveMethodPattern                 continue;             }             slaveMethodPattern.add(entry.getKey());         }     }       /**      * 在進(jìn)入Service方法之前執(zhí)行      *       * @param point 切面對(duì)象      */     public void before(JoinPoint point) {         // 獲取到當(dāng)前執(zhí)行的方法名         String methodName = point.getSignature().getName();           boolean isSlave = false;           if (slaveMethodPattern.isEmpty()) {             // 當(dāng)前Spring容器中沒有配置事務(wù)策略,采用方法名匹配方式             isSlave = isSlave(methodName);         } else {             // 使用策略規(guī)則匹配             for (String mappedName : slaveMethodPattern) {                 if (isMatch(methodName, mappedName)) {                     isSlave = true;                     break;                 }             }         }           if (isSlave) {             // 標(biāo)記為讀庫(kù)             DynamicDataSourceHolder.markSlave();         } else {             // 標(biāo)記為寫庫(kù)             DynamicDataSourceHolder.markMaster();         }     }       /**      * 判斷是否為讀庫(kù)      *       * @param methodName      * @return      */     private Boolean isSlave(String methodName) {         // 方法名以query、find、get開頭的方法名走從庫(kù)         return StringUtils.startsWithAny(methodName, getSlaveMethodStart());     }       /**      * 通配符匹配      *       * Return if the given method name matches the mapped name.      * 

      * The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, as well as direct      * equality. Can be overridden in subclasses.      *       * @param methodName the method name of the class      * @param mappedName the name in the descriptor      * @return if the names match      * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)      */     protected boolean isMatch(String methodName, String mappedName) {         return PatternMatchUtils.simpleMatch(mappedName, methodName);     }       /**      * 用戶指定slave的方法名前綴      * @param slaveMethodStart      */     public void setSlaveMethodStart(String[] slaveMethodStart) {         this.slaveMethodStart = slaveMethodStart;     }       public String[] getSlaveMethodStart() {         if(this.slaveMethodStart == null){             // 沒有指定,使用默認(rèn)             return defaultSlaveMethodStart;         }         return slaveMethodStart;     }      }

一主多從的實(shí)現(xiàn)

很多實(shí)際使用場(chǎng)景下都是采用“一主多從”的架構(gòu)的,所有我們現(xiàn)在對(duì)這種架構(gòu)做支持,目前只需要修改DynamicDataSource即可。

Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離

實(shí)現(xiàn)

import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger;   import javax.sql.DataSource;   import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.util.ReflectionUtils;   /**  * 定義動(dòng)態(tài)數(shù)據(jù)源,實(shí)現(xiàn)通過集成Spring提供的AbstractRoutingDataSource,只需要實(shí)現(xiàn)determineCurrentLookupKey方法即可  * 由于DynamicDataSource是單例的,線程不安全的,所以采用ThreadLocal保證線程安全,由DynamicDataSourceHolder完成。  * @author binghe  *  */ public class DynamicDataSource extends AbstractRoutingDataSource {       private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);       private Integer slaveCount;       // 輪詢計(jì)數(shù),初始為-1,AtomicInteger是線程安全的     private AtomicInteger counter = new AtomicInteger(-1);       // 記錄讀庫(kù)的key     private List slaveDataSources = new ArrayList(0);       @Override     protected Object determineCurrentLookupKey() {         // 使用DynamicDataSourceHolder保證線程安全,并且得到當(dāng)前線程中的數(shù)據(jù)源key         if (DynamicDataSourceHolder.isMaster()) {             Object key = DynamicDataSourceHolder.getDataSourceKey();              if (LOGGER.isDebugEnabled()) {                 LOGGER.debug("當(dāng)前DataSource的key為: " + key);             }             return key;         }         Object key = getSlaveKey();         if (LOGGER.isDebugEnabled()) {             LOGGER.debug("當(dāng)前DataSource的key為: " + key);         }         return key;       }       @SuppressWarnings("unchecked")     @Override     public void afterPropertiesSet() {         super.afterPropertiesSet();           // 由于父類的resolvedDataSources屬性是私有的子類獲取不到,需要使用反射獲取         Field field = ReflectionUtils.findField(AbstractRoutingDataSource.class, "resolvedDataSources");         field.setAccessible(true); // 設(shè)置可訪問           try {             Map resolvedDataSources = (Map) field.get(this);             // 讀庫(kù)的數(shù)據(jù)量等于數(shù)據(jù)源總數(shù)減去寫庫(kù)的數(shù)量             this.slaveCount = resolvedDataSources.size() - 1;             for (Map.Entry entry : resolvedDataSources.entrySet()) {                 if (DynamicDataSourceHolder.MASTER.equals(entry.getKey())) {                     continue;                 }                 slaveDataSources.add(entry.getKey());             }         } catch (Exception e) {             LOGGER.error("afterPropertiesSet error! ", e);         }     }       /**      * 輪詢算法實(shí)現(xiàn)      *       * @return      */     public Object getSlaveKey() {         // 得到的下標(biāo)為:0、1、2、3……         Integer index = counter.incrementAndGet() % slaveCount;         if (counter.get() > 9999) { // 以免超出Integer范圍             counter.set(-1); // 還原         }         return slaveDataSources.get(index);     }   }

MySQL主從復(fù)制

原理

Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離

MySQL主(master)從(slave)復(fù)制的原理:

  • master將數(shù)據(jù)改變記錄到二進(jìn)制日志(binarylog)中,也即是配置文件log-bin指定的文件(這些記錄叫做二進(jìn)制日志事件,binary log  events)

  • slave將master的binary logevents拷貝到它的中繼日志(relay log)

  • slave重做中繼日志中的事件,將改變反映它自己的數(shù)據(jù)(數(shù)據(jù)重演)

主從配置需要注意的地方

  • 主DB server和從DB  server數(shù)據(jù)庫(kù)的版本一致

  • 主DB server和從DB server數(shù)據(jù)庫(kù)數(shù)據(jù)一致[  這里就會(huì)可以把主的備份在從上還原,也可以直接將主的數(shù)據(jù)目錄拷貝到從的相應(yīng)數(shù)據(jù)目錄]

  • 主DB server開啟二進(jìn)制日志,主DB server和從DB  server的server_id都必須唯一

主庫(kù)配置(windows,Linux下也類似)

在my.ini修改:

#開啟主從復(fù)制,主庫(kù)的配置 log-bin = mysql3306-bin #指定主庫(kù)serverid server-id=101 #指定同步的數(shù)據(jù)庫(kù),如果不指定則同步全部數(shù)據(jù)庫(kù) binlog-do-db=mybatis_1128

執(zhí)行SQL語(yǔ)句查詢狀態(tài):

SHOW MASTER STATUS

Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離

需要記錄下Position值,需要在從庫(kù)中設(shè)置同步起始值。

在主庫(kù)創(chuàng)建同步用戶

#授權(quán)用戶slave01使用123456密碼登錄mysql grant replication slave on *.* to 'slave01'@'127.0.0.1' identified by '123456'; flush privileges;

從庫(kù)配置

在my.ini修改

#指定serverid,只要不重復(fù)即可,從庫(kù)也只有這一個(gè)配置,其他都在SQL語(yǔ)句中操作 server-id=102

接下來(lái),從從庫(kù)命令行執(zhí)行如下SQL語(yǔ)句。

CHANGE MASTER TO  master_host='127.0.0.1',  master_user='slave01',  master_password='123456',  master_port=3306,  master_log_file='mysql3306-bin.000006',  master_log_pos=1120;   #啟動(dòng)slave同步 START SLAVE;   #查看同步狀態(tài) SHOW SLAVE STATUS;

到此,關(guān)于“Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!


分享題目:Spring如何實(shí)現(xiàn)多數(shù)據(jù)源讀寫分離
分享網(wǎng)址:http://weahome.cn/article/gghoss.html

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部