ShardingJdbc基於Zookeeper實現分散式治理

  • 2021 年 10 月 25 日
  • 筆記

隨著數據規模的不斷膨脹,使用多節點集群的分散式方式逐漸成為趨勢。在這種情況下,如何高效、自動化管理集群節點,實現不同節點的協同工作,配置一致性,狀態一致性,高可用性,可觀測性等,就成為一個重要的挑戰。

集群管理的複雜性體現在,一方面我們需要把所有的節點,不管是底層資料庫節點,還是中間件或者業務系統節點的狀態都統一管理起來,並且能實時探測到最新的配置變動情況,進一步為集群的調控和調度提供依據。

另一方面,不同節點之間的統一協調,分庫分表策略以及規則同步,也需要我們能夠設計一套在分散式情況下,進行全局事件通知機制以及獨佔性操作的分散式協調鎖機制。在這方面,ShardingJDBC採用了Zookeeper/Etcd來實現配置的同步,狀態變更通知,以及分散式鎖來控制排他操作。

ShardingJDBC分散式治理

ShardingJDBC集成了Zookeeper/Etcd,用來實現ShardingJDBC的分散式治理,下面我們先通過一個應用程式來演示一下實現原理。

安裝Zookeeper

  • 通過這個地址下載Zookeeper

    //mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.6.3/apache-zookeeper-3.6.3-bin.tar.gz

  • 常用操作命令

    #啟動ZK服務:      
    bin/zkServer.sh start
    #查看ZK服務狀態: 
    bin/zkServer.sh status
    #停止ZK服務:    
    bin/zkServer.sh stop
    #重啟ZK服務:    
    bin/zkServer.sh restart
    #連接伺服器
    zkCli.sh -timeout 0 -r -server ip:port
    
  • 單機安裝

    初次使用zookeeper,需要將conf目錄下的zoo_sample.cfg文件copy一份重命名為zoo.cfg

    修改dataDir目錄,dataDir表示日誌文件存放的路徑(關於zoo.cfg的其他配置資訊後面會講)

Sharding-JDBC集成Zookeeper

本階段演示的項目程式碼:sharding-jdbc-split-zookeeper,項目結構如圖9-1所示。

image-20210729212359651

圖9-1 項目結構

添加jar包依賴

引入jar包依賴(只需要依賴下面兩個包即可)

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-governance-repository-zookeeper-curator</artifactId>
    <version>5.0.0-alpha</version>
</dependency>
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-governance-spring-boot-starter</artifactId>
    <version>5.0.0-alpha</version>
    <exclusions>
        <exclusion>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-test</artifactId>
        </exclusion>
    </exclusions>
</dependency>

其他基礎jar包(所有項目都是基於spring boot集成mybatis拷貝的)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.72</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>

添加配置文件

增加基礎的分庫分表配置-application.properties

spring.shardingsphere.datasource.names=ds-0,ds-1
spring.shardingsphere.datasource.common.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.common.driver-class-name=com.mysql.jdbc.Driver

spring.shardingsphere.datasource.ds-0.username=root
spring.shardingsphere.datasource.ds-0.password=123456
spring.shardingsphere.datasource.ds-0.jdbc-url=jdbc:mysql://192.168.221.128:3306/shard01?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8

spring.shardingsphere.datasource.ds-1.username=root
spring.shardingsphere.datasource.ds-1.password=123456
spring.shardingsphere.datasource.ds-1.jdbc-url=jdbc:mysql://192.168.221.128:3306/shard02?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8

spring.shardingsphere.rules.sharding.default-database-strategy.standard.sharding-column=user_id
spring.shardingsphere.rules.sharding.default-database-strategy.standard.sharding-algorithm-name=database-inline

spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=ds-$->{0..1}.t_order_$->{0..1}
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=order_id
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=t-order-inline

spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=order_id
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=snowflake

spring.shardingsphere.rules.sharding.sharding-algorithms.database-inline.type=INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.database-inline.props.algorithm-expression=ds-$->{user_id % 2}
spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-inline.type=INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-inline.props.algorithm-expression=t_order_$->{order_id % 2}
spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-item-inline.type=INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-item-inline.props.algorithm-expression=t_order_item_$->{order_id % 2}

spring.shardingsphere.rules.sharding.key-generators.snowflake.type=SNOWFLAKE
spring.shardingsphere.rules.sharding.key-generators.snowflake.props.worker-id=123

增加Zookeeper配置

# 治理名稱(在zookeeper上的節點名稱)
spring.shardingsphere.governance.name=sharding-jdbc-split-zookeeper
# 本地配置是否覆蓋配置中心配置。如果可覆蓋,每次啟動都以本地配置為準.
spring.shardingsphere.governance.overwrite=true

# Zookeeper/etcd
spring.shardingsphere.governance.registry-center.type=ZooKeeper
spring.shardingsphere.governance.registry-center.server-lists=192.168.221.131:2181
# 之所以加下面兩個參數,是因為默認的鏈接超時時間是1500毫秒,由於時間較短導致啟動時很容易超時,導致連接失敗
# 重試次數
spring.shardingsphere.governance.registry-center.props.maxRetries=4
# 重試間隔時間
spring.shardingsphere.governance.registry-center.props.retryIntervalMilliseconds=6000

啟動項目進行測試

啟動過程中看到如下日誌,表示配置zookeeper成功,啟動的時候會先把本地配置保存到zookeeper中,後續我們可以在zookeeper中修改相關配置,然後同步通知給到相關的應用節點。

2021-07-29 21:31:25.007  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Client environment:java.io.tmpdir=C:\Users\mayn\AppData\Local\Temp\
2021-07-29 21:31:25.007  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Client environment:java.compiler=<NA>
2021-07-29 21:31:25.007  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Client environment:os.name=Windows 10
2021-07-29 21:31:25.007  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Client environment:os.arch=amd64
2021-07-29 21:31:25.007  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Client environment:os.version=10.0
2021-07-29 21:31:25.007  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Client environment:user.name=mayn
2021-07-29 21:31:25.007  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Client environment:user.home=C:\Users\mayn
2021-07-29 21:31:25.007  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Client environment:user.dir=E:\教研-課件\vip課程\第五輪\03 高並發組件\09 ShardingSphere基於Zookeeper實現分散式治理\sharding-jdbc-readwrite-zookeeper
2021-07-29 21:31:25.007  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Client environment:os.memory.free=482MB
2021-07-29 21:31:25.007  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Client environment:os.memory.max=7264MB
2021-07-29 21:31:25.007  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Client environment:os.memory.total=501MB
2021-07-29 21:31:25.009  INFO 112916 --- [           main] org.apache.zookeeper.ZooKeeper           : Initiating client connection, connectString=192.168.221.131:2181 sessionTimeout=60000 watcher=org.apache.curator.ConnectionState@68e2d03e
2021-07-29 21:31:25.012  INFO 112916 --- [           main] org.apache.zookeeper.common.X509Util     : Setting -D jdk.tls.rejectClientInitiatedRenegotiation=true to disable client-initiated TLS renegotiation
2021-07-29 21:31:25.020  INFO 112916 --- [           main] org.apache.zookeeper.ClientCnxnSocket    : jute.maxbuffer value is 1048575 Bytes
2021-07-29 21:31:25.023  INFO 112916 --- [           main] org.apache.zookeeper.ClientCnxn          : zookeeper.request.timeout value is 0. feature enabled=false
2021-07-29 21:31:25.030  INFO 112916 --- [           main] o.a.c.f.imps.CuratorFrameworkImpl        : Default schema

接著訪問如下介面進行測試。

@RestController
@RequestMapping("/t-order")
public class TOrderController {

    @Autowired
    ITOrderService orderService;

    @GetMapping
    public void init() throws SQLException {
        orderService.initEnvironment();
        orderService.processSuccess();
    }
}

配置中心的數據結構說明

註冊中心的數據結構如下

namespace: 就是spring.shardingsphere.governance.name

namespace
├──users                                     # 許可權配置
├──props                                     # 屬性配置
├──schemas                                   # Schema 配置
├      ├──${schema_1}                        # Schema 名稱1
├      ├      ├──datasource                  # 數據源配置
├      ├      ├──rule                        # 規則配置
├      ├      ├──table                       # 表結構配置
├      ├──${schema_2}                        # Schema 名稱2
├      ├      ├──datasource                  # 數據源配置
├      ├      ├──rule                        # 規則配置
├      ├      ├──table                       # 表結構配置

rules全局配置規則

可包括訪問 ShardingSphere-Proxy 用戶名和密碼的許可權配置

- !AUTHORITYusers:  - root@%:root  - [email protected]:shardingprovider:  type: NATIVE

props屬性配置

ShardingSphere相關屬性配置

executor-size: 20sql-show: true

/schemas/${schemeName}/dataSources

多個資料庫連接池的集合,不同資料庫連接池屬性自適配(例如:DBCP,C3P0,Druid, HikariCP)。

ds_0:   dataSourceClassName: com.zaxxer.hikari.HikariDataSource  props:    url: jdbc:mysql://127.0.0.1:3306/demo_ds_0?serverTimezone=UTC&useSSL=false    password: null    maxPoolSize: 50    connectionTimeoutMilliseconds: 30000    idleTimeoutMilliseconds: 60000    minPoolSize: 1    username: root    maxLifetimeMilliseconds: 1800000ds_1:   dataSourceClassName: com.zaxxer.hikari.HikariDataSource  props:    url: jdbc:mysql://127.0.0.1:3306/demo_ds_1?serverTimezone=UTC&useSSL=false    password: null    maxPoolSize: 50    connectionTimeoutMilliseconds: 30000    idleTimeoutMilliseconds: 60000    minPoolSize: 1    username: root    maxLifetimeMilliseconds: 1800000

/schemas/${schemeName}/rule

規則配置,可包括數據分片、讀寫分離等配置規則

rules:- !SHARDING  defaultDatabaseStrategy:    standard:      shardingAlgorithmName: database-inline      shardingColumn: user_id  keyGenerators:    snowflake:      props:        worker-id: '123'      type: SNOWFLAKE  shardingAlgorithms:    t-order-inline:      props:        algorithm-expression: t_order_$->{order_id % 2}      type: INLINE    database-inline:      props:        algorithm-expression: ds-$->{user_id % 2}      type: INLINE    t-order-item-inline:      props:        algorithm-expression: t_order_item_$->{order_id % 2}      type: INLINE  tables:    t_order:      actualDataNodes: ds-$->{0..1}.t_order_$->{0..1}      keyGenerateStrategy:        column: order_id        keyGeneratorName: snowflake      logicTable: t_order      tableStrategy:        standard:          shardingAlgorithmName: t-order-inline          shardingColumn: order_id

/schemas/${schemeName}/table

表結構配置,暫時不支援動態修改

configuredSchemaMetaData:  tables:    t_order:      columns:        order_id:          caseSensitive: false          dataType: 0          generated: true          name: order_id          primaryKey: true        user_id:          caseSensitive: false          dataType: 0          generated: false          name: user_id          primaryKey: false        address_id:          caseSensitive: false          dataType: 0          generated: false          name: address_id          primaryKey: false        status:          caseSensitive: false          dataType: 0          generated: false          name: status          primaryKey: falseunconfiguredSchemaMetaDataMap:  ds-0:  - t_order_complex  - t_order_interval  - t_order_item_complex

動態生效

除了table相關的配置無法動態更改之外,其他配置在zookeeper上修改之後,在不重啟應用節點時,都會同步到相關服務節點。

比如,我們修改圖9-2所示的紅色部分的位置,把t_order_$->{0..1}修改成t_order_$->{0..4},這樣就會生成4個分片,並且取模規則也做相應更改。

然後點擊保存後,在不重啟應用節點時,重新發起介面測試請求,就可以看到修改成功後的結果。

//localhost:8080/swagger-ui.html

image-20210729215100735

圖9-2 zookeeper配置中心

註冊中心節點

在zookeeper伺服器上,還存在以下節點資訊。

namespace   ├──states   ├    ├──proxynodes   ├    ├     ├──${your_instance_ip_a}@${your_instance_pid_x}@${UUID}   ├    ├     ├──${your_instance_ip_b}@${your_instance_pid_y}@${UUID}   ├    ├     ├──....   ├    ├──datanodes   ├    ├     ├──${schema_1}   ├    ├     ├      ├──${ds_0}   ├    ├     ├      ├──${ds_1}   ├    ├     ├──${schema_2}   ├    ├     ├      ├──${ds_0}   ├    ├     ├      ├──${ds_1}   ├    ├     ├──....

這個是註冊中心節點,用來保存shardingsphere-proxy中間件的伺服器實例資訊、以及實例運行情況。

運行實例標識由運行伺服器的 IP 地址和 PID 構成。

運行實例標識均為臨時節點,當實例上線時註冊,下線時自動清理。 註冊中心監控這些節點的變化來治理運行中實例對資料庫的訪問等。

由於註冊中心會在後續的內容中講,所以這裡暫時不展開。

分散式治理總結

引入zookeeper這樣一個角色,可以協助ShardingJDBC完成以下功能

  • 配置集中化:越來越多的運行時實例,使得散落的配置難於管理,配置不同步導致的問題十分嚴重。將配置集中於配置中心,可以更加有效進行管理。
  • 配置動態化:配置修改後的分發,是配置中心可以提供的另一個重要能力。它可支援數據源和規則的動態切換。
  • 存放運行時的動態/臨時狀態數據,比如可用的 ShardingSphere 的實例,需要禁用或熔斷的數據源等。
  • 提供熔斷資料庫訪問程式對資料庫的訪問和禁用從庫的訪問的編排治理能力。治理模組仍然有大量未完成的功能(比如流控等)。

到目前為止,ShardingSphere中Sharding-JDBC部分的內容就到這裡結束了,另外一個組件Sharding-Proxy就沒有展開了,因為它相當於實現了資料庫層面的代理,也就是說,不需要開發者在應用程式中配置資料庫分庫分表的規則,而是直接把Sharding-Proxy當作資料庫源連接,Sharding-Proxy相當於Mysql資料庫的代理,當請求發送到Sharding-Proxy之後,在Sharding-Proxy上會配置相關的分片規則,然後根據分片規則進行相關處理。

關注[跟著Mic學架構]公眾號,獲取更多精品原創