<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試題:SMM架構(gòu)如何做讀寫分離?

          共 9994字,需瀏覽 20分鐘

           ·

          2019-11-10 23:23

          7c2e6f90a4c4479d4228c10198624c9b.webp048d49f477c94cf769af6ab2201bac53.webp來源:http://1t.click/atjG


          讀寫分離要做的事情就是對(duì)于一條SQL該選擇哪個(gè)數(shù)據(jù)庫去執(zhí)行,至于誰來做選擇數(shù)據(jù)庫這件事兒,無非兩個(gè),要么中間件幫我們做,要么程序自己做。因此,一般來講,讀寫分離有兩種實(shí)現(xiàn)方式。


          第一種是依靠中間件(比如:MyCat),也就是說應(yīng)用程序連接到中間件,中間件幫我們做SQL分離;第二種是應(yīng)用程序自己去做分離。這里我們選擇程序自己來做,主要是利用Spring提供的路由數(shù)據(jù)源,以及AOP


          然而,應(yīng)用程序?qū)用嫒プ鲎x寫分離最大的弱點(diǎn)(不足之處)在于無法動(dòng)態(tài)增加數(shù)據(jù)庫節(jié)點(diǎn),因?yàn)閿?shù)據(jù)源配置都是寫在配置中的,新增數(shù)據(jù)庫意味著新加一個(gè)數(shù)據(jù)源,必然改配置,并重啟應(yīng)用。當(dāng)然,好處就是相對(duì)簡單。


          ae43379d439ba12a4073c413b9c6a8b8.webp



          #?AbstractRoutingDataSource ??


          基于特定的查找key路由到特定的數(shù)據(jù)源。它內(nèi)部維護(hù)了一組目標(biāo)數(shù)據(jù)源,并且做了路由key與目標(biāo)數(shù)據(jù)源之間的映射,提供基于key查找數(shù)據(jù)源的方法。

          4a0a2f81e1cc311b53af3ecfb757c496.webp



          # 實(shí)踐? ?


          ?maven依賴


          <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.0modelVersion>
          <groupId>com.cjs.examplegroupId><artifactId>cjs-datasource-demoartifactId><version>0.0.1-SNAPSHOTversion><packaging>jarpackaging>
          <name>cjs-datasource-demoname><description>description>
          <parent><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-parentartifactId><version>2.0.5.RELEASEversion><relativePath/> parent>
          <properties><project.build.sourceEncoding>UTF-8project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding><java.version>1.8java.version>properties>
          <dependencies><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-aopartifactId>dependency><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-jdbcartifactId>dependency><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-webartifactId>dependency><dependency><groupId>org.mybatis.spring.bootgroupId><artifactId>mybatis-spring-boot-starterartifactId><version>1.3.2version>dependency><dependency><groupId>org.apache.commonsgroupId><artifactId>commons-lang3artifactId><version>3.8version>dependency>
          <dependency><groupId>mysqlgroupId><artifactId>mysql-connector-javaartifactId><scope>runtimescope>dependency><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-testartifactId><scope>testscope>dependency>dependencies>
          <build><plugins><plugin><groupId>org.springframework.bootgroupId><artifactId>spring-boot-maven-pluginartifactId>plugin>


          plugins>build>project>

          數(shù)據(jù)源配置


          application.yml


          spring:  datasource:    master:      jdbc-url: jdbc:mysql://192.168.102.31:3306/test      username: root      password: 123456      driver-class-name: com.mysql.jdbc.Driver    slave1:      jdbc-url: jdbc:mysql://192.168.102.56:3306/test      username: pig   # 只讀賬戶      password: 123456      driver-class-name: com.mysql.jdbc.Driver    slave2:      jdbc-url: jdbc:mysql://192.168.102.36:3306/test      username: pig   # 只讀賬戶      password: 123456      driver-class-name: com.mysql.jdbc.Driver

          多數(shù)據(jù)源配置

          package com.cjs.example.config;
          import com.cjs.example.bean.MyRoutingDataSource;import com.cjs.example.enums.DBTypeEnum;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
          import javax.sql.DataSource;import java.util.HashMap;import java.util.Map;
          /** * 關(guān)于數(shù)據(jù)源配置,參考SpringBoot官方文檔第79章《Data Access》 * 79. Data Access * 79.1 Configure a Custom DataSource * 79.2 Configure Two DataSources */
          @Configurationpublic class DataSourceConfig {
          @Bean@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build(); }
          @Bean@ConfigurationProperties("spring.datasource.slave1")public DataSource slave1DataSource() {return DataSourceBuilder.create().build(); }
          @Bean@ConfigurationProperties("spring.datasource.slave2")public DataSource slave2DataSource() {return DataSourceBuilder.create().build(); }
          @Beanpublic DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,@Qualifier("slave1DataSource") DataSource slave1DataSource,@Qualifier("slave2DataSource") DataSource slave2DataSource) { Map targetDataSources = new HashMap<>(); targetDataSources.put(DBTypeEnum.MASTER, masterDataSource); targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource); targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource); MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource(); myRoutingDataSource.setDefaultTargetDataSource(masterDataSource); myRoutingDataSource.setTargetDataSources(targetDataSources);return myRoutingDataSource; }
          }

          這里,我們配置了4個(gè)數(shù)據(jù)源,1個(gè)master,2兩個(gè)slave,1個(gè)路由數(shù)據(jù)源。前3個(gè)數(shù)據(jù)源都是為了生成第4個(gè)數(shù)據(jù)源,而且后續(xù)我們只用這最后一個(gè)路由數(shù)據(jù)源。


          MyBatis配置


          package com.cjs.example.config;
          import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;
          import javax.annotation.Resource;import javax.sql.DataSource;
          @EnableTransactionManagement@Configurationpublic class MyBatisConfig {
          @Resource(name = "myRoutingDataSource")private DataSource myRoutingDataSource;
          @Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(myRoutingDataSource); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));return sqlSessionFactoryBean.getObject(); }
          @Beanpublic PlatformTransactionManager platformTransactionManager() {return new DataSourceTransactionManager(myRoutingDataSource); }}

          由于Spring容器中現(xiàn)在有4個(gè)數(shù)據(jù)源,所以我們需要為事務(wù)管理器和MyBatis手動(dòng)指定一個(gè)明確的數(shù)據(jù)源。


          設(shè)置路由key / 查找數(shù)據(jù)源


          目標(biāo)數(shù)據(jù)源就是那前3個(gè)這個(gè)我們是知道的,但是使用的時(shí)候是如果查找數(shù)據(jù)源的呢?


          首先,我們定義一個(gè)枚舉來代表這三個(gè)數(shù)據(jù)源


          package?com.cjs.example.enums;public?enum?DBTypeEnum?{????MASTER,?SLAVE1,?SLAVE2;}

          接下來,通過ThreadLocal將數(shù)據(jù)源設(shè)置到每個(gè)線程上下文中

          package com.cjs.example.bean;
          import com.cjs.example.enums.DBTypeEnum;
          import java.util.concurrent.atomic.AtomicInteger;
          public class DBContextHolder {
          private static final ThreadLocal contextHolder = new ThreadLocal<>();
          private static final AtomicInteger counter = new AtomicInteger(-1);
          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() {// 輪詢int index = counter.getAndIncrement() % 2;if (counter.get() > 9999) { counter.set(-1); }if (index == 0) {set(DBTypeEnum.SLAVE1); System.out.println("切換到slave1"); }else {set(DBTypeEnum.SLAVE2); System.out.println("切換到slave2"); } }
          }

          獲取路由key

          package com.cjs.example.bean;
          import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import org.springframework.lang.Nullable;
          public class MyRoutingDataSource extends AbstractRoutingDataSource {@Nullable@Overrideprotected Object determineCurrentLookupKey() {return DBContextHolder.get(); }
          }

          設(shè)置路由key


          默認(rèn)情況下,所有的查詢都走從庫,插入/修改/刪除走主庫。我們通過方法名來區(qū)分操作類型(CRUD)


          package com.cjs.example.aop;
          import com.cjs.example.bean.DBContextHolder;import org.apache.commons.lang3.StringUtils;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;
          @Aspect@Componentpublic class DataSourceAop {
          @Pointcut("!@annotation(com.cjs.example.annotation.Master) " +"&& (execution(* com.cjs.example.service..*.select*(..)) " +"|| execution(* com.cjs.example.service..*.get*(..)))")public void readPointcut() {
          }
          @Pointcut("@annotation(com.cjs.example.annotation.Master) " +"|| execution(* com.cjs.example.service..*.insert*(..)) " +"|| execution(* com.cjs.example.service..*.add*(..)) " +"|| execution(* com.cjs.example.service..*.update*(..)) " +"|| execution(* com.cjs.example.service..*.edit*(..)) " +"|| execution(* com.cjs.example.service..*.delete*(..)) " +"|| execution(* com.cjs.example.service..*.remove*(..))")public void writePointcut() {
          }
          @Before("readPointcut()")public void read() { DBContextHolder.slave(); }
          @Before("writePointcut()")public void write() { DBContextHolder.master(); }

          /** * 另一種寫法:if...else... 判斷哪些需要讀從數(shù)據(jù)庫,其余的走主數(shù)據(jù)庫 */// @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();// }// }}


          有一般情況就有特殊情況,特殊情況是某些情況下我們需要強(qiáng)制讀主庫,針對(duì)這種情況,我們定義一個(gè)主鍵,用該注解標(biāo)注的就讀主庫

          package?com.cjs.example.annotation;public @interface Master {}

          例如,假設(shè)我們有一張表member

          package com.cjs.example.service.impl;
          import com.cjs.example.annotation.Master;import com.cjs.example.entity.Member;import com.cjs.example.entity.MemberExample;import com.cjs.example.mapper.MemberMapper;import com.cjs.example.service.MemberService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;
          import java.util.List;
          @Servicepublic class MemberServiceImpl implements MemberService {
          @Autowiredprivate MemberMapper memberMapper;
          @Transactional@Overridepublic int insert(Member member) {return memberMapper.insert(member); }
          @Master@Overridepublic int save(Member member) {return memberMapper.insert(member); }
          @Overridepublic List selectAll() {return memberMapper.selectByExample(new MemberExample()); }
          @Master@Overridepublic String getToken(String appId) {// 有些讀操作必須讀主數(shù)據(jù)庫// 比如,獲取微信access_token,因?yàn)楦叻鍟r(shí)期主從同步可能延遲// 這種情況下就必須強(qiáng)制從主數(shù)據(jù)讀return null; }}
          ?

          # 測試? ?


          package com.cjs.example;
          import com.cjs.example.entity.Member;import com.cjs.example.service.MemberService;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;
          @RunWith(SpringRunner.class)@SpringBootTestpublic class CjsDatasourceDemoApplicationTests {
          @Autowiredprivate MemberService memberService;
          @Testpublic void testWrite() { Member member = new Member(); member.setName("zhangsan"); memberService.insert(member); }
          @Testpublic void testRead() {for (int i = 0; i < 4; i++) { memberService.selectAll(); } }
          @Testpublic void testSave() { Member member = new Member(); member.setName("wangwu"); memberService.save(member); }
          @Testpublic void testReadFromMaster() { memberService.getToken("1234"); }
          }


          查看控制臺(tái)


          9268dfc3a4a1535306244f6e6f90e363.webp

          3f7114bd4bdc18ccc5927836b913f1d3.webp


          # 工程結(jié)構(gòu)? ?


          5c6bf82b73fb0d393b16575fad60e869.webp


          - End -

          c3ee6ee9b71c876e51c707639d73419d.webp



          點(diǎn),?4dd683cd45e05fda2c1f1fbcf40f37be.webp

          瀏覽 150
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  X X X日韩 | 日韩人妻丰满无码区A片 | 天天综合网永久 | 青青草国产视频在线观看 | a√天堂中文字幕在线 |