全部產品
Search
文件中心

Lindorm:分庫分表(Alias功能)

更新時間:Jul 06, 2024

本文介紹分庫分表功能的使用情境和使用方法。

背景資訊

  • 表變更商務邏輯中設定了訪問某個表A,突然有一天需要修改為表B,此時只能修改配置進行線上變更。
  • 分庫分表

    業務大部分情境只訪問最近一周的資料,可以每隔一周建立一張表來儲存,這樣可以確保高效的查詢熱資料。在這個情境中需要自己來維護表的建立和刪除,帶來一定的業務複雜性。

適用情境

  • 時間序列情境

    業務資料具有明顯的時間特性,可以基於時間來建立不同的索引,這樣既能降低單個索引的大小,又能提升查詢效能。整個過程中業務不需要自己維護索引建立和刪除。

  • 重建索引情境

    在不影響已有索引查詢下,重建新的索引,待索引建完後,指向新的索引訪問。整個過程中業務不需要代碼變更。

建立Alias指向已有的索引表

curl "http://solrhost:8983/solr/admin/collections?action=CREATEALIAS&name=your_alias_name&collections=your_collection_name_A"
            

以上URL表示:建立一個Alias名為your_allias_name,其指向一個索引表your_collection_name_A。商務邏輯中可以只設定訪問your_alias_name,核心會自動轉寄請求到真實的索引表上。

如果需要變更索引表名為your_collection_name_B,執行更改Alias命令。

curl "http://solrhost:8983/solr/admin/collections?
action=ALIASPROP&name=your_alias_name&collections=your_collection_name_B"
            

業務代碼上不需要任何變更即可訪問新的索引表。

自動分表

搜尋服務支援按照時間欄位自動分表,大大簡化商務邏輯。下面以具體的樣本來介紹:業務要求以周為單位建立索引表,並且能夠自動刪除舊的索引表。

curl "http://solrhost:8983/solr/admin/collections?action=CREATEALIAS&name=test_router_alias&router.start=NOW-30DAYS/DAY&router.autoDeleteAge=/DAY-90DAYS&router.field=your_timestamp_l&router.name=time&router.interval=%2B7DAY&router.maxFutureMs=8640000000&create-collection.collection.configName=_indexer_default&create-collection.numShards=2"
            
參數說明
router.startNOW-30DAYS/DAY第一個collection建立的時間點,範例中給出的NOW-30DAYS/DAY代表以30天前開始建立索引。
router.interval+7DAY間隔多久建立新的索引表,範例中給出的是每隔7天建立一個索引表。
router.autoDeleteAge/DAY-90DAYS自動淘汰多久前的索引表,範例中給出的是淘汰90天前的索引表,其值必須大於router.start。
router.fieldyour_timestamp_l分表的時間欄位,預設業務中需要攜帶這個欄位,並指定時間值,例如當前的系統時間戳System.currentTimeMillis()。
router.maxFutureMs8640000000代表最大容忍寫入的時間欄位your_date_dt與目前時間的差值,防止寫入過大的時間欄位或者過小的時間值,範例中給出的是100天,即不能寫入一條資料的時間比目前時間大100天或小100天。
collection.collection.configName_indexer_default代表建立的索引表依賴的配置集,可以設定為自己的配置集名稱,參考配置集更新
create-collection.numShards2建立的索引表shard個數,預設為2。

上面的業務含義,以30天前(假設今天是3月7日)開始建立索引,每隔7天建立一個索引,your_timestamp_l,並且它的值與目前時間在100天以內,周期性的刪除90天到期的索引。

效果如下:

說明
  • 業務必須帶有時間欄位,可以為Date類型,也可以為Long類型。
  • 查詢時,預設是查詢全部索引表。此時,可以單獨指定查詢某個索引表。需要通過API或者URL擷取到所有collection列表,然後提取其中的時間欄位來判斷真實訪問的collection,可參見下面的代碼範例。

刪除Alias

普通的Alias可以直接通過下面的命令刪除。

curl "http://solrhost:8983/solr/admin/collections?action=DELETEALIAS&name=your_alias_name"
            

如需刪除具有自動分表的Alias,還需要主動刪除關聯的collection。

  1. 取得關聯Alias的collection列表。
    curl "http://solrhost:8983/solr/admin/collections?action=LIST"
                        

    其中collection名稱以test_router_alias開頭的都是關聯該Alias的collection。

  2. 刪除Alias。
    curl "http://solrhost:8983/solr/admin/collections?action=DELETEALIAS&name=test_router_alias"
                        
  3. 刪除所有collection。
    curl "http://solrhost:8983/solr/admin/collections?action=DELETE&name=collection_name"
                        

參考文檔

CREATEALIAS:CREATEALIAS

LIST:LIST

如何指定long型時間進行查詢

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.ClusterStateProvider;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.util.StrUtils;

import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

public class SolrDemo {
  private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
      .append(DateTimeFormatter.ISO_LOCAL_DATE).appendPattern("[_HH[_mm[_ss]]]")
      .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
      .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
      .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
      .toFormatter(Locale.ROOT).withZone(ZoneOffset.UTC);

  private static final String zkHost = "localhost:2181/solr";
  private CloudSolrClient cloudSolrClient;
  private ClusterStateProvider clusterStateProvider;

  public SolrDemo() {
    cloudSolrClient = new CloudSolrClient.Builder(
        Collections.singletonList(zkHost), Optional.empty()).build();
    cloudSolrClient.connect();
    clusterStateProvider = cloudSolrClient.getClusterStateProvider();
  }

  public void close() throws Exception {
    if (null != cloudSolrClient) {
      cloudSolrClient.close();
    }
  }

  private List<String> findCollection(String aliasName, long start, long end) {
    List<String> collections = new ArrayList<>();
    if (start > end) {
      return collections;
    }
    //基於[start, end]尋找合適的collection
    if (clusterStateProvider.getState(aliasName) == null) {
      // 拿到當前aliasName對應的所有collection列表
      // test_router_alias_2020-03-04, test_router_alias_2020-02-26, test_router_alias_2020-02-19, test_router_alias_2020-02-12, test_router_alias_2020-02-05
      List<String> aliasedCollections = clusterStateProvider.resolveAlias(aliasName);

      // 從collection名稱中提取出時間日期
      // 2020-03-04T00:00:00Z=test_router_alias_2020-03-04,
      // 2020-02-26T00:00:00Z=test_router_alias_2020-02-26,
      // 2020-02-19T00:00:00Z=test_router_alias_2020-02-19,
      // 2020-02-12T00:00:00Z=test_router_alias_2020-02-12,
      // 2020-02-05T00:00:00Z=test_router_alias_2020-02-05
      List<Map.Entry<Instant, String>> collectionsInstant = new ArrayList<>(aliasedCollections.size());
      for (String collectionName : aliasedCollections) {
        String dateTimePart = collectionName.substring(aliasName.length() + 1);
        Instant instant = DATE_TIME_FORMATTER.parse(dateTimePart, Instant::from);
        collectionsInstant.add(new AbstractMap.SimpleImmutableEntry<>(instant, collectionName));
      }

      // 根據查詢時間找出合理的collection
      Instant startI = Instant.ofEpochMilli(start);
      Instant endI = Instant.ofEpochMilli(end);
      for (Map.Entry<Instant, String> entry : collectionsInstant) {
        Instant colStartTime = entry.getKey();
        if (!endI.isBefore(colStartTime)) {
          collections.add(entry.getValue());
          System.out.println("find collection: " + entry.getValue());
          if (!startI.isBefore(colStartTime)) {
            break;
          }
        }
      }
    } else {
      collections.add(aliasName);
    }
    System.out.println("query " + collections);
    return collections;
  }

  public void run() throws Exception {
    try {
      // [2020-03-07 2020-03-10]
      long start = 1583538686312L;
      long end = 1583797886000L;
      String aliasName = "test_router_alias";
      String collections = StrUtils.join(findCollection(aliasName, start, end), ',');
      QueryResponse res = cloudSolrClient.query(collections, new SolrQuery("*:*"));
      for (SolrDocument sd : res.getResults()) {
        System.out.println(sd.get("id") + " " + sd.get("gmtCreate_l"));
      }
    } finally {
      cloudSolrClient.close();
    }
  }

  public static void main(String[] args) throws Exception {
    SolrDemo solrDemo = new SolrDemo();
    solrDemo.run();
    solrDemo.close();
  }
}