分布式存儲-ShardingSphere(應用)


分布式存儲-ShardingSphere(應用)

前面我們沒有使用中間件去進行分庫分表而沒有使用任何中間件,本章給大家介紹一個分庫分表的中間件shardingSphere 。它包含三款開源分布式數據庫中間件解決方案.

Sharding-JDBC【服務端代理】本篇我們主要聊他它定位的是一個增強版的JDBC驅動,簡單來說就是在應用端來完成數據庫分庫分表相關的路由和分片操作。我們的業務代碼在操作數據庫的時候,就會通過Sharding-JDBC的代碼連接到數據庫。也就是分庫分表的一些核心動作,比如SQL解析,路由,執行,結果處理,都是由它來完成的,它工作在客戶端。

Sharding-Proxy【客戶端代理】:簡單來說,以前我們的應用是直連數據庫,引入了Sharding-Proxy之后,我們的應用是直連Sharding-Proxy,然后Sharding-Proxy通過處理之后再轉發到mysql中。這種方式的好處在於,用戶不需要感知到分庫分表的存在,相當於正常訪問mysql

Sharding-Sidecar:它主要定位於 Kubernetes 的雲原生數據庫代理,現在還沒有正式發布。

Sharding-JDBC的相關概念說明

  • 邏輯表邏輯表可以理解為數據庫中的視圖,是一張虛擬表。可以映射到一張物理表,也可以由多張物理表組成,這些物理表可以來自於不同的數據源。比如定義一個t_order,當我們針對t_order表操作時,會根據分片規則映射到實際的物理表進行相關事務操作。
    • spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=ds-$-> {0.}.t_order_$->{0.}
  • 廣播表:廣播表也叫全局表,也就是它會存在於多個庫中冗余,避免跨庫查詢問題,比如省份、字典等一些基礎數據,為了避免分庫分表后關聯表查詢這些基礎數據存在跨庫問題,所以可以把這些數據同步給每一個數據庫節點,這個就叫廣播表。
    • # 廣播表, 其主節點是ds0
    • spring.shardingsphere.sharding.broadcast-tables=t_config spring.shardingsphere.sharding.tables.t_config.actual-data-nodes=ds$-> {0}.t_config
  • 綁定表】:我們有些表的數據是存在邏輯的主外鍵關系的,跨庫關聯查詢也比較麻煩我們就可以通過這個讓他們在同一個庫中,比如order_id=1001的數據在node1,它所有的明細數據也放到node1。這樣關聯查詢的時候就還在同一個庫中。
    • # 綁定表規則,多組綁定規則使用數組形式配置
    • spring.shardingsphere.rules.sharding.binding-tables=t_order,t_order_item
    • 如果存在多個綁定表規則,可以用數組的方式聲明
    • spring.shardingsphere.rules.sharding.binding-tables[0]= # 綁定表規則列表
    • spring.shardingsphere.rules.sharding.binding-tables[x]= # 綁定表規則列表

Sharding-JDBC的使用【普通使用】

和所有的第三方一樣,我們只需要導入maven即可。我們這里只要說他的配置。我們在自己進行數據庫分庫分表的時候,需要:設計數據庫的分庫、分表規則、以及主鍵id的算法。那在使用sharding的時候我們就需要配置這些東西。

private static Map<String, DataSource> createDataSourceMap(){
    //代表真實的數據源
    Map<String,DataSource> dataSourceMap=new HashMap<>();
    //邏輯庫,真實的數據庫
    dataSourceMap.put("ds0",DataSourceUtil.createDataSource("shard01"));
    dataSourceMap.put("ds1",DataSourceUtil.createDataSource("shard02"));
    return dataSourceMap;
}
//創建分片規則
// * 針對數據庫
// * 針對表
   //* 一定要配置分片鍵
   //* 一定要配置分片算法
   //* 完全唯一id的問題
    //根據uuid取模進行分庫
    //根據orderId取模進行分辨

private static ShardingRuleConfiguration createShardingRuleConfiguration(){
    ShardingRuleConfiguration configuration=new ShardingRuleConfiguration();
    //把邏輯表和真實表的對應關系添加到分片規則配置中
    configuration.getTables().add(getOrderTableRuleConfiguration());
    //設置數據庫分庫規則
    configuration.setDefaultDatabaseShardingStrategy(
            new StandardShardingStrategyConfiguration
                    ("user_id","db-inline"));
    Properties properties=new Properties();
    //這里就使用user_id和2取模,然后找到真正的數據庫, ds指的是我們的邏輯庫
    properties.setProperty("algorithm-expression","ds${user_id%2}");
    //設置分庫策略
    configuration.getShardingAlgorithms().
            put("db-inline",new ShardingSphereAlgorithmConfiguration("INLINE",properties));



    //設置表的分片規則(數據的水平拆分) 分片鍵 和 分片算法,算法我們使用inline 算法
    configuration.setDefaultTableShardingStrategy(new StandardShardingStrategyConfiguration
            ("order_id","order-inline"));
    //設置分表策略
    Properties props=new Properties();
    props.setProperty("algorithm-expression","t_order_${order_id%2}");
    configuration.getShardingAlgorithms().put("order-inline",
            new ShardingSphereAlgorithmConfiguration("INLINE",props));



    //設置主鍵生成策略
    // * UUID
    // * 雪花算法
    Properties idProperties=new Properties();
    idProperties.setProperty("worker-id","123");
    configuration.getKeyGenerators().put("snowflake",new ShardingSphereAlgorithmConfiguration(
            "SNOWFLAKE",idProperties));
    return configuration;
}
//配置邏輯表以及表的id策略
private static ShardingTableRuleConfiguration getOrderTableRuleConfiguration(){
    //配置邏輯表格和真實表格的關系
    ShardingTableRuleConfiguration tableRuleConfiguration=
            new ShardingTableRuleConfiguration("t_order","ds${0..1}.t_order_${0..1}");
    tableRuleConfiguration.setKeyGenerateStrategy(new KeyGenerateStrategyConfiguration("order_id","snowflake"));
    return tableRuleConfiguration;
}

// 使用ShardingSphere創建一個數據源
// 我們可以傳遞多個數據源。以及分分庫分表的規則配置
public static DataSource getDatasource() throws SQLException {
    return ShardingSphereDataSourceFactory
            .createDataSource(createDataSourceMap(), Collections.singleton(createShardingRuleConfiguration()),new Properties());
}

配置寫好,只用獲取上面的getDatasource方法即可,獲取一個數據源即可,sharding會根據我們上面的配置進行處理。

創建數據源的代碼

public class DataSourceUtil {

    private static final String HOST = "127.0.0.1";

    private static final int PORT = 3306;

    private static final String USER_NAME = "root";

    private static final String PASSWORD = "123456";

    public static DataSource createDataSource(final String dataSourceName) {
        HikariDataSource result = new HikariDataSource();
        result.setDriverClassName("com.mysql.jdbc.Driver");
        result.setJdbcUrl(String.format("jdbc:mysql://%s:%s/%s?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8", HOST, PORT, dataSourceName));
        result.setUsername(USER_NAME);
        result.setPassword(PASSWORD);
        return result;
    }
}

測試的時候通過getDatasource獲取數據源,在寫sql的時候我們一律使用邏輯表 ,他會攔截我們的sql自動根據我們配置的規則進行路由

 

Sharding-JDBC的使用【SpringBoot】

SpringBoot中還是只要導入相關的jar,他自動會對sql進行攔截,下面我們聊一下常見的他的提供的分片算法。

  • 【自動分片算法】:我們只用配置好,然后在springBoot中操作我們配置的邏輯表或者庫就可以達到相應的效果。他只要提供一下幾種。
    • 根據數據容量分片我們可以自定義數據范圍區間,操作時候他會自動進行路由。比如我們可以設定一個表200條數據,總共有600條那他每個表格就只會存儲200條數據
    • server.port=8080
      spring.mvc.view.prefix=classpath:/templates/
      spring.mvc.view.suffix=.html
      
      spring.shardingsphere.datasource.names=ds-0
      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://127.0.0.1:3306/shard01?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
      
      spring.shardingsphere.rules.sharding.tables.t_order_volume_range.actual-data-nodes=ds-0.t_order_volume_range_$->{0..2}
      spring.shardingsphere.rules.sharding.tables.t_order_volume_range.table-strategy.standard.sharding-column=user_id
      spring.shardingsphere.rules.sharding.tables.t_order_volume_range.table-strategy.standard.sharding-algorithm-name=t-order-volume-range
      
      spring.shardingsphere.rules.sharding.tables.t_order_volume_range.key-generate-strategy.column=order_id
      spring.shardingsphere.rules.sharding.tables.t_order_volume_range.key-generate-strategy.key-generator-name=snowflake
      
      
      # 基於范圍的分片策略
      spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-volume-range.type=VOLUME_RANGE
      spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-volume-range.props.range-lower=200
      # 數據存儲范圍最大的容量
      spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-volume-range.props.range-upper=600
      # 每個范圍的區間是200
      spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-volume-range.props.sharding-volume=200
      
      spring.shardingsphere.rules.sharding.key-generators.snowflake.type=SNOWFLAKE
      spring.shardingsphere.rules.sharding.key-generators.snowflake.props.worker-id=123
      View Code
    • 根據分片邊界進行分片】:我們可以設置0-1000 是一個區間(存儲在第一張表中) 10001-20000是一個區間(存儲在第二張表中) 300000-無窮大是一個區間(存儲在第三張表中)
    • spring.shardingsphere.rules.sharding.tables.t_order_boundary_range.actual-data-nodes=ds-0.t_order_boundary_range_$->{0..3}
      spring.shardingsphere.rules.sharding.tables.t_order_boundary_range.table-strategy.standard.sharding-column=user_id
      spring.shardingsphere.rules.sharding.tables.t_order_boundary_range.table-strategy.standard.sharding-algorithm-name=t-order-boundary-range
      
      spring.shardingsphere.rules.sharding.tables.t_order_boundary_range.key-generate-strategy.column=order_id
      spring.shardingsphere.rules.sharding.tables.t_order_boundary_range.key-generate-strategy.key-generator-name=snowflake
      
      spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-boundary-range.type=BOUNDARY_RANGE
      # 0-1000 是一個區間  10001-20000是一個區間  300000-無窮大是一個區間
      spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-boundary-range.props.sharding-ranges=1000,20000,300000
      
      spring.shardingsphere.rules.sharding.key-generators.snowflake.type=SNOWFLAKE
      spring.shardingsphere.rules.sharding.key-generators.snowflake.props.worker-id=123
      View Code
    • 根據時間段進行分片】 :比如一年為一張表。
    • # 配置12個表
      spring.shardingsphere.rules.sharding.tables.t_order_interval.actual-data-nodes=ds-0.t_order_interval_$->{0..12}
      spring.shardingsphere.rules.sharding.tables.t_order_interval.table-strategy.standard.sharding-column=create_time
      spring.shardingsphere.rules.sharding.tables.t_order_interval.table-strategy.standard.sharding-algorithm-name=t-order-auto-interval
      
      spring.shardingsphere.rules.sharding.tables.t_order_interval.key-generate-strategy.column=order_id
      spring.shardingsphere.rules.sharding.tables.t_order_interval.key-generate-strategy.key-generator-name=snowflake
      
      spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-auto-interval.type=AUTO_INTERVAL
      # 時間開始區間
      spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-auto-interval.props.datetime-lower=2010-01-01 23:59:59
      # 時間結束區間
      spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-auto-interval.props.datetime-upper=2021-01-01 23:59:59
      # 以s為單位的一年
      spring.shardingsphere.rules.sharding.sharding-algorithms.t-order-auto-interval.props.sharding-seconds=31536000
      
      spring.shardingsphere.rules.sharding.key-generators.snowflake.type=SNOWFLAKE
      spring.shardingsphere.rules.sharding.key-generators.snowflake.props.worker-id=123
      View Code
  • 【自定義分片算法】:如果他的提供的分片算法不滿足我們的需求,我們可以實現自己的分片算法。實際上就是spi。
  • 【SPI】:實際上就是某些擴展某些框架的方法,比如dubbo,springBoot、等一些常見的框架或者中間件都會給用戶提供自己發揮的空間,因為他們無法滿足所有用戶的要求,所以sharding也是一樣的。那我們需要怎么做呢?
    • 在工程的META-INF/services/目錄下,以接口的全限定名作為文件名,文件內容為實現接口的服務類。(因為我們需要擴展他的接口,所以我們創建的文件名稱就必須和他的接口名稱一致,內容就寫我們實現類的全限定名

      •  
    •  當然在springBoot配置中還要指定要使用自己的算法

    • spring.shardingsphere.rules.sharding.sharding-algorithms.standard-mod.props.algorithm-class-name=自定義算法的路徑
    • 然后實現我們自己的分片算法。
    • public class StandardModTableShardAlgorithm implements StandardShardingAlgorithm<Long> {
      
      
      
          private Properties props=new Properties();
      
          /**
           * 用於處理=和IN的分片。
           * @param collection 表示目標分片的集合
           * @param preciseShardingValue 邏輯表相關信息
           * @return
           * Mod
           */
          @Override
          public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
      
              for(String name:collection){
                  //根據order_id的值進行取模,得到一個目標值
                  //Order_id%4=3
                  //name.endsWith,  "order_3".endWith("")
                  if(name.endsWith(String.valueOf(preciseShardingValue.getValue()%4))){
                      return name;
                  }
              }
              throw new UnsupportedOperationException();
          }
      
          /**
           * 用於處理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND將按照全庫路由處理
           * @param collection
           * @param rangeShardingValue
           * @return
           */
          @Override
          public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
              Collection<String> result=new LinkedHashSet<>(collection.size());
              for(Long i=rangeShardingValue.getValueRange().lowerEndpoint();i<=rangeShardingValue.getValueRange().upperEndpoint();i++){
                  for(String name:collection){
                      if(name.endsWith(String.valueOf(i%4))){
                          result.add(name);
                      }
                  }
              }
              return result;
          }
      
          /**
           * 初始化對象的時候調用的方法
           */
          @Override
          public void init() {
      
          }
      
          /**
           * 對應分片算法(sharding-algorithms)的類型
           * @return
           */
          @Override
          public String getType() {
              return "STANDARD_MOD";
          }
      
      
          @Override
          public Properties getProps() {
              return this.props;
          }
      
          /**
           * 獲取分片相關屬性
           * @param properties
           */
          @Override
          public void setProps(Properties properties) {
              this.props=properties;
          }
      }
      View Code
  • 我們寫一個自己的spi就知道他的底層是如何實現
    • 新建一個項目,類似於提供解析的工具類。寫一個spi的加載類。這個類中,我們用一個容器去存儲,所有我們提供的工具類。並且在加載類的時候,加載所有的工具類的子類下面的實現類。
      • public class ParserManager {
        
            //存儲所有的實現類
            private final static ConcurrentHashMap<String, Parser> registeredParser=new ConcurrentHashMap<String, Parser>();
        
            static {
                // 加載實現了接口的所有類
                loadInitialParser();
                // 加載自己的實現類
                initDefaultStrategy();
            }
        
            //SPI的加載
            private static void loadInitialParser(){
                //SPI的api方法
                //加載META-INF/service目錄下 Parser的實現類
                ServiceLoader<Parser> parserServiceLoader=ServiceLoader.load(Parser.class);
                for (Parser parser : parserServiceLoader) {
                    registeredParser.put(parser.getType(), parser);
                }
            }
        
            private static void initDefaultStrategy(){
                Parser jsonParser=new JsonParser();
                Parser xmlParser=new XmlParser();
                registeredParser.put(jsonParser.getType(),jsonParser);
                registeredParser.put(xmlParser.getType(),xmlParser);
            }
            public static Parser getParser(String key){
                return registeredParser.get(key);
            }
        }
        View Code
    • 然后使用maven進行項目打包,把上面的代碼作為一個jar,並且在項目中導入那個jar,並且使用jar中的功能
      • public class ParserController {
        
            @GetMapping("/{type}")
            public String parser(@PathVariable("type") String type){
                try {
                    return ParserManager.getParser(type).parser(new File(""));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return "不支持該種解析方式";
            }
        }
        View Code

    • 創建一個文件名稱為接口名稱的文件,並且在里面寫上我們自己的實現類的路徑
      •  
    • 后我們自己對他原來的功能進行擴展  
      • ublic class WordParser implements Parser {
        
            @Override
            public String parser(File file) throws Exception {
                return "我是基於word解析";
            }
        
            @Override
            public String getType() {
                return "word";
            }
        }
        View Code
    • 接下來就可以在不改變原來框架的基礎上使用我們自己實現的功能了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM