1. 程式人生 > >sharding-JDBC 實現讀寫分離

sharding-JDBC 實現讀寫分離

aso engine lec bus manage 均衡 map 如果 use

需求

  1. 一主兩從,做讀寫分離。
  2. 多個從庫之間實現負載均衡。
  3. 可手動強制部分讀請求到主庫上。(因為主從同步有延遲,對實時性要求高的系統,可以將部分讀請求也走主庫)

本次不討論 MySQL如何配置主從同步相關問題

庫表SQL

-- 主庫
CREATE DATABASE `master`;

CREATE TABLE `t_order` (
  `order_id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `business_id` int(4) DEFAULT NULL,
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT
CHARSET=utf8; INSERT INTO `t_order` VALUES (1,1,112); -- 從庫1 CREATE DATABASE `slave1` ; CREATE TABLE `t_order` ( `order_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, `business_id` int(4) DEFAULT NULL, PRIMARY KEY (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; INSERT INTO `t_order` VALUES
(2,2,112); -- 從庫2 CREATE DATABASE `slave2` ; CREATE TABLE `t_order` ( `order_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, `business_id` int(4) DEFAULT NULL, PRIMARY KEY (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `t_order` VALUES (3,3,112);

pom.xml

<!-- https://mvnrepository.com/artifact/io.shardingjdbc/sharding-jdbc-core -->
<dependency> <groupId>io.shardingjdbc</groupId> <artifactId>sharding-jdbc-core</artifactId> <version>2.0.0.M2</version> </dependency> <dependency> <groupId>io.shardingjdbc</groupId> <artifactId>sharding-jdbc-spring-namespace</artifactId> <version>2.0.0.M2</version> </dependency>

spring配置文件

<bean id="master" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
      destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="${jdbc.url.master}"></property>
    <property name="username" value="${jdbc.username.master}"></property>
    <property name="password" value="${jdbc.password.master}"></property>
    <property name="maxActive" value="100"/>
    <property name="initialSize" value="10"/>
    <property name="maxWait" value="60000"/>
    <property name="minIdle" value="5"/>
</bean>

<bean id="slave1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
      destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="${jdbc.url.slave1}"></property>
    <property name="username" value="${jdbc.username.slave1}"></property>
    <property name="password" value="${jdbc.password.slave1}"></property>
    <property name="maxActive" value="100"/>
    <property name="initialSize" value="10"/>
    <property name="maxWait" value="60000"/>
    <property name="minIdle" value="5"/>
</bean>

<bean id="slave2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
      destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="${jdbc.url.slave2}"></property>
    <property name="username" value="${jdbc.username.slave2}"></property>
    <property name="password" value="${jdbc.password.slave2}"></property>
    <property name="maxActive" value="100"/>
    <property name="initialSize" value="10"/>
    <property name="maxWait" value="60000"/>
    <property name="minIdle" value="5"/>
</bean>

<bean id="randomStrategy" class="io.shardingjdbc.core.api.algorithm.masterslave.RandomMasterSlaveLoadBalanceAlgorithm" />

<master-slave:data-source id="shardingDataSource" master-data-source-name="master" slave-data-source-names="slave1,slave2" strategy-ref="randomStrategy" />

單測

寫:

    @Test
    public void insert() throws Exception {

        Order record = new Order();
        record.setBusinessId(112);
        record.setUserId(111);
        record.setOrderId(12212121);

        int result = orderMapper.insertSelective(record) ;

        System.out.println( result > 0 ? "插入成功" : "插入失敗");
    }

運行結果:
技術分享圖片

查:
slave1 只有1條數據,主鍵order_id = 2 ; slave2 也只有1條數據,主鍵order_id = 3 。所以,如果查詢到的結果orderId等於1就說明讀請求進入到slave1,同理,如果查詢出來的orderId等於0 就說明讀請求進入到slave2。

 public void selectByExample3() throws Exception {

        final int[] slave1 = {0};
        final int[] slave2 = {0};

        for (int i = 0; i < 100; i++) {
            ((Runnable) () -> {
                OrderExample example = new OrderExample();
                example.createCriteria().andBusinessIdEqualTo(112);

                List<Order> orderList = orderMapper.selectByExample(example);

                if (orderList.get(0).getOrderId() == 2) {
                    System.out.printf("讀到slave1  讀到的數據是{}", JSONObject.toJSONString(orderList.get(0)));
                    slave1[0]++;
                } else if (orderList.get(0).getOrderId() == 3) {
                    System.out.printf("讀到slave2  讀到的數據是{}",                             JSONObject.toJSONString(orderList.get(0)));
                    slave2[0]++;
                }
                System.out.println(JSONObject.toJSONString(orderList));
            }).run();
        }

        System.out.println("+++++++++++++++++++++++++++++++++++++++");
        System.out.println("+++++++++++++++++++++++++++++++++++++++");
        System.out.println("slave1讀到的次數-->" + slave1[0]);
        System.out.println("slave2讀到的次數-->" + slave2[0]);
        System.out.println("+++++++++++++++++++++++++++++++++++++++");
        System.out.println("+++++++++++++++++++++++++++++++++++++++");
    }

運行截圖:

技術分享圖片

強制路由

通常做讀寫分離,都會遇到的一個問題就是主從同步延遲。有時,為了簡單解決主從同步問題,我們會想強制部分讀請求到主庫上,而非從庫上。

HintManager 分片鍵值管理器

官方文檔的解釋:
基於暗示(Hint)的分片鍵值管理器

但是對於讀寫分離這種形式的強制路由 , 其實官方文檔說的幾個方法都不適用. 我們可使用hintManager.setMasterRouteOnly() .

單測

@Test
public void HintManagerTest() {

    HintManager hintManager = HintManager.getInstance() ;

    hintManager.setMasterRouteOnly();

    OrderExample example = new OrderExample();
    example.createCriteria().andBusinessIdEqualTo(112);

    List<Order> orderList = orderMapper.selectByExample(example);

    System.out.println(JSONObject.toJSONString(orderList));

    hintManager.close();
    
}

sharding-JDBC 實現讀寫分離