實施MySQL ReplicationDriver支持讀寫分離

import java.sql.Connection;
import java.sql.ResultSet;
import java.util.Properties;
 
import com.mysql.jdbc.ReplicationDriver;
 
public class ReplicationDriverDemo {
 
  public static void main(String[] args) throws Exception {
    ReplicationDriver driver = new ReplicationDriver();
 
    Properties props = new Properties();
 
    // We want this for failover on the slaves
    props.put("autoReconnect", "true");
 
    // We want to load balance between the slaves
    props.put("roundRobinLoadBalance", "true");
 
    props.put("user", "foo");
    props.put("password", "bar");
 
    //
    // Looks like a normal MySQL JDBC url, with a
    // comma-separated list of hosts, the first
    // being the 'master', the rest being any number
    // of slaves that the driver will load balance against
    //
 
    Connection conn =
        driver.connect("jdbc:mysql:replication://master,slave1,slave2,slave3/test",
            props);
 
    //
    // Perform read/write work on the master
    // by setting the read-only flag to "false"
    //
 
    conn.setReadOnly(false);
    conn.setAutoCommit(false);
    conn.createStatement().executeUpdate("UPDATE some_table ....");
    conn.commit();
 
    //
    // Now, do a query from a slave, the driver automatically picks one
    // from the list
    //
 
    conn.setReadOnly(true);
 
    ResultSet rs =
      conn.createStatement().executeQuery("SELECT a,b FROM alt_table");
 
     .......
  }
}

 

MySQL 提供支持讀寫分離的驅動類:java

com.mysql.jdbc.ReplicationDriver

替代mysql

com.mysql.jdbc.Driver

注意,全部參數主從統一:web

jdbc:mysql:replication://<master>,<slave>.../...?...=... 

固然,用戶名和密碼也必須相同spring

 

觸發Slave的狀況sql

  1. 設置 auto_commit = false安全

  2. 設置 readOnly 爲 trueapp

 

綜上特色,讀寫分離依賴於事務ide

 

經常使用使用場景:測試

第一種, 事務管理使用【註解】支持ui

一般,事務管理在Service層,只須要簡單的操做便可支持讀寫分離:

1

2

@Transactional(propagation=Propagation.REQUIRED, readOnly = true)

public List<OrderBase> findOrderList(String orderCode);

事務開啓後,查詢自動切換到從庫。

注意:@Transactional 默認的readOnly參數是false,更新操做不須要特別的改動。propagation是指的事務傳播方式,默認設置是Require,指的是「本次操做須要事務支持,若是沒有事務開啓一個事務,若是有事務,加入到該事務中」

 

考慮複雜一點的狀況,當Service中出現自我方法的調用時:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@Transactional(propagation=Propagation.REQUIRED, readOnly = true)

public OrderBase getOrder(String orderCode) {

    findSubOrderList(orderCode);

}

 

@Transactional(propagation=Propagation.REQUIRED, readOnly = true)

public List<OrderSub> findSubOrderList(String orderCode) {

}

 

@Transactional(propagation=Propagation.REQUIRED, readOnly = false)

public void updateOrder(OrderBase orderBase) {

    findSubOrderList(orderBase.getCode());

    ...

}

當外部調用getOrder時,getOrder方法的@Transaction註解生效,設置從庫查詢。

當外部調用updateOrder時,updateOrder方法的@Transaction註解生效,設置操做主庫。

注意,這兩個方法都調用了findSubOrderList方法,而調用的對象是this,不是被spring事務管理器替換過的service對象,因此findSubOrderList方法上的@Transaction註解無效,會根據上文環境來查主庫和從庫

這種特性對於業務來講是恰當好處的,生效的事務是在最外層的方法上,能夠避免在一個事務內部出現讀寫庫不統一的狀況。

 

更復雜一點的狀況,當service中調用了其它類的service:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// OrderSerivceImpl:

@Transactional(propagation=Propagation.REQUIRED, readOnly = true)

public OrderBase getOrder(String orderCode) {

    orderCouponService.getById(couponId);

}

  

@Transactional(propagation=Propagation.REQUIRED, readOnly = false)

public OrderBase createOrder(OrderGeneratorDto dto) {

    orderCouponService.saveCoupon(coupon);

}

 

@Transactional(propagation=Propagation.REQUIRED, readOnly = false)

public OrderBase updateOrder(OrderBase orderBase) {

    orderCouponService.getById(couponId);

}

  

// OrderCouponServiceImpl:

@Transactional(propagation=Propagation.REQUIRED, readOnly = true)

public OrderCoupon getById(Integer couponId) {

}

  

@Transactional(propagation=Propagation.REQUIRED, readOnly = false)

public OrderCoupon saveCoupon(OrderCoupon coupon) {

}

1, 當外部調用OrderSerivce的getOrder時,getOrder方法的@Transaction註解生效,設置從庫查詢。

getOrder內部調用了OrderCouponService的getById方法,因爲orderCouponService是spring提供的對象,通過了事務管理,因此getById方法上的@Transaction註解生效,

咱們知道Require這個事務傳播的特性,getById不會建立新的事務,因此依舊是由從庫讀取數據。

 

2, 當外部調用OrderSerivce的saveOrder時,saveOrder方法的@Transaction註解生效,設置操做主庫。

saveOrder內部調用了OrderCouponService的saveCoupon方法,一樣因爲Require的特性,沒有建立新事務,操做主庫。

 

3, 當外部調用OrderSerivce的updateOrder時,updateOrder方法的@Transaction註解生效,設置操做主庫。

updateOrder內部調用了OrderCouponService的getById方法,一樣因爲Require的特性,沒有建立新事務,從主庫讀出數據。

這些特性也是很好的,咱們只須要關心最外部調用的方法的註解內容,就能夠肯定走的哪一個庫。

 

 

更復雜點的狀況是新開事務的狀況,建議謹慎對待

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// OrderSerivceImpl:

@Transactional(propagation=Propagation.REQUIRED, readOnly = true)

public Price getOrderPrice(String orderCode) {

    // 不恰當的業務邏輯!此處只是演示

    otherService.updateById(id, xxx);

    foo = otherService.getById(id);

}

   

// OtherServiceImpl:

@Transactional(propagation=Propagation.REQUIRED, readOnly = true)

public ... getById(Integer id) {

}

   

@Transactional(propagation=Propagation.REQUIRED_NEW, readOnly = false)

public void updateById(id, ...) {

}

該想法是構想把OrderSerivce的getOrderPrice查詢走從庫,其中一個小邏輯更新庫設置操做主庫。在沒有設置主從的狀況下,這種方式是支持的,並不會出現問題。

但在設置了主從的狀況下,這種業務邏輯操做就「不安全」了,由於,updateById走的是主庫,它的更新操做是依賴於主從同步的,頗有可能getById取到了「過時」的數據。

這種狀況在業務上來講是應該要避免的,若是不能避免,最好的辦法是讓外部都走主庫,保證數據來源的一致性。

 

綜上,事務管理配置用註解的方式仍是蠻方便的。

 

第二種, 事務管理使用【XML配置】支持

XML配置的事務是以判斷指定名稱開頭的方法來實現的,跟註解配置事務是相似的。能夠把select和get斷定爲readOnly,傳播機制設定爲Require。

 

 第三種,使用支持讀事務的入口類

鑑於現有代碼Service層被融合到web和admin中,在Service層的注入會影響多個系統,而單獨寫方法,難免繁瑣,使用代理方法支持事務的讀比較靈活。

流程:

 

這個模式好處在於能夠根據業務的須要,合理安排開發和測試的工做,影響範圍可控。

實現代碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

public class ReadOnlyTransFactoryBean<T> implements MethodInterceptor, FactoryBean<T>, ApplicationContextAware {

 

    private Logger logger = Logger.getLogger(ReadOnlyTransFactoryBean.class);

 

    /**

     * 代理的Service類

     */

    Class<T> serviceInterface;

 

    /**

     * 代理的Service名

     */

    String delegateBeanName;

 

    ApplicationContext applicationContext;

 

    public Class<T> getServiceInterface() {

        return serviceInterface;

    }

 

    public void setServiceInterface(Class<T> serviceInterface) {

        this.serviceInterface = serviceInterface;

    }

 

    public String getDelegateBeanName() {

        return delegateBeanName;

    }

 

    public void setDelegateBeanName(String delegateBeanName) {

        this.delegateBeanName = delegateBeanName;

    }

 

    Enhancer enhancer = new Enhancer();

 

    @Override

    public T getObject() throws Exception {

        // 使用CGlib加強,提供代理功能

        enhancer.setSuperclass(serviceInterface);

        enhancer.setCallback(this);

        return (T) enhancer.create();

    }

 

    @Override

    public Class<?> getObjectType() {

        return this.serviceInterface;

    }

 

    @Override

    public boolean isSingleton() {

        return true;

    }

 

    @Override

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        this.applicationContext = applicationContext;

    }

 

    @Override

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

        T service = applicationContext.getBean(delegateBeanName, serviceInterface);

        DataSourceTransactionManager txManager = applicationContext.getBean(DataSourceTransactionManager.class);

        DefaultTransactionDefinition definition = new DefaultTransactionDefinition(DefaultTransactionDefinition.PROPAGATION_REQUIRED);

        definition.setReadOnly(true);

        logger.info("Start ReadOnly Transactional!");

        TransactionStatus transaction = txManager.getTransaction(definition);

        Object result = method.invoke(service, args);

        if (!transaction.isCompleted()) {

            txManager.commit(transaction);

        }

        logger.info("End ReadOnly Transactional!");

        return result;

    }

}

按需配置:

1

2

3

4

5

<!-- ReadOnly Transaction Service -->

<bean id="readOnlyOrderPlatformService" class="com.qding.order.service.util.ReadOnlyTransFactoryBean">

        <property name="serviceInterface" value="com.qding.order.service.IOrderService" />

        <property name="delegateBeanName" value="orderPlatformService" />

</bean>

使用時請注意:原有@Autowired方式注入請改爲@Resource指定名稱的方式,以區別不一樣入口

1

2

3

4

5

@Resource(name="orderPlatformService")

protected IOrderService orderService;

 

@Resource(name="readOnlyOrderPlatformService")

protected IOrderService readOnlyOrderService;

相關文章
相關標籤/搜索