全部产品
云市场
云游戏

分库分表(Alias功能)

更新时间:2020-07-09 14:22:08

前言

设想您有没有遇到过这样的问题:

1、表变更业务逻辑中设置了访问某个表A,突然有一天需要修改为表B,此时只能修改配置进行线上变更。

2、分库分表

业务大部分场景只访问最近一周的数据,可以每隔一周新建一张表来存储,这样可以确保高效的查询热数据。在这个场景中需要自己来维护表的创建和删除,带来一定的业务复杂性。

本文介绍的Alias(别名)将会完美的解决上面两个问题。

适用场景

时间序列场景

业务数据具有明显的时间特性,可以基于时间来创建不同的索引,这样既能降低单个索引的大小,又能提升查询性能。整个过程中业务不需要自己维护索引创建和删除。

重建索引场景

在不影响已有索引查询下,重建新的索引,待索引建完后,指向新的索引访问。整个过程中业务不需要代码变更。

注意:下面文档中关于curl命令如何访问,具体可参考

如何使用Alias

基本功能:创建Alias指向已有的索引表
  1. 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命令。

修改Alias

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

这样,业务代码上不需要任何变更即可访问新的索引表。

高级功能:自动分表

搜索服务支持按照时间字段自动分表,大大简化业务逻辑。下面以具体的示例来介绍:业务要求以周为单位创建索引表,并且能够自动删除旧的索引表。

  1. 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.start NOW-30DAYS/DAY 第一个collection创建的时间点,样例中给出的NOW-30DAYS/DAY代表以30天前开始新建索引
router.interval +7DAY 间隔多久创建新的索引表,样例中给出的是每隔7天新建一个索引表
router.autoDeleteAge /DAY-90DAYS 自动淘汰多久前的索引表,样例中给出的是淘汰90天前的索引表,其值必须大于router.start
router.field your_timestamp_l 分表的时间字段,默认业务中需要携带这个字段,并指定时间值,例如当前的系统时间戳System.currentTimeMillis()
router.maxFutureMs 8640000000 代表最大容忍写入的时间字段your_date_dt与当前时间的差值,防止写入过大的时间字段或者过小的时间值,样例中给出的是100天,即不能写入一条数据的时间比当前时间大100天或小100天
collection.collection.configName _indexer_default 代表创建的索引表依赖的配置集,可以设置为自己的配置集名称,参考配置集更新
create-collection.numShards 2 创建的索引表shard个数,默认为2

上面的业务含义,以30天前(假设今天是3月7日)开始创建索引,每隔7天新建一个索引,your_timestamp_l,并且它的值与当前时间在100天以内,周期性的删除90天过期的索引。

效果如下(如何访问Solr Web)solr_alias

  1. 注意事项
  2. 1.业务必须带有时间字段,可以为Date类型,也可以为Long类型。
  3. 2.查询时,默认是查询全部索引表。此时,可以单独指定查询某个索引表。需要通过API或者URL获取到所有collection列表,然后提取其中的时间字段来判断真实访问的collection,可参见下面的代码样例。
删除Alias

普通的Alias可以直接通过下面的命令删除。

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

具有自动分表的Alias删除,还需要主动删除关联的collection。

  1. 取得关联Alias的collection列表
    1. curl "http://solrhost:8983/solr/admin/collections?action=LIST"
    其中collection名称以test_router_alias开头的都是关联该Alias的collection。
  2. 删除Alias
    1. curl "http://solrhost:8983/solr/admin/collections?action=DELETEALIAS&name=test_router_alias"
  3. 删除所有collection
    1. curl "http://solrhost:8983/solr/admin/collections?action=DELETE&name=collection_name"

参考文档

https://lucene.apache.org/solr/guide/7_3/collections-api.html#createaliashttps://lucene.apache.org/solr/guide/7_3/collections-api.html#list

如何指定long型时间进行查询

  1. import org.apache.solr.client.solrj.SolrQuery;
  2. import org.apache.solr.client.solrj.impl.CloudSolrClient;
  3. import org.apache.solr.client.solrj.impl.ClusterStateProvider;
  4. import org.apache.solr.client.solrj.response.QueryResponse;
  5. import org.apache.solr.common.SolrDocument;
  6. import org.apache.solr.common.util.StrUtils;
  7. import java.time.Instant;
  8. import java.time.ZoneOffset;
  9. import java.time.format.DateTimeFormatter;
  10. import java.time.format.DateTimeFormatterBuilder;
  11. import java.time.temporal.ChronoField;
  12. import java.util.AbstractMap;
  13. import java.util.ArrayList;
  14. import java.util.Collections;
  15. import java.util.List;
  16. import java.util.Locale;
  17. import java.util.Map;
  18. import java.util.Optional;
  19. public class SolrDemo {
  20. private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
  21. .append(DateTimeFormatter.ISO_LOCAL_DATE).appendPattern("[_HH[_mm[_ss]]]")
  22. .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
  23. .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
  24. .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
  25. .toFormatter(Locale.ROOT).withZone(ZoneOffset.UTC);
  26. private static final String zkHost = "localhost:2181/solr";
  27. private CloudSolrClient cloudSolrClient;
  28. private ClusterStateProvider clusterStateProvider;
  29. public SolrDemo() {
  30. cloudSolrClient = new CloudSolrClient.Builder(
  31. Collections.singletonList(zkHost), Optional.empty()).build();
  32. cloudSolrClient.connect();
  33. clusterStateProvider = cloudSolrClient.getClusterStateProvider();
  34. }
  35. public void close() throws Exception {
  36. if (null != cloudSolrClient) {
  37. cloudSolrClient.close();
  38. }
  39. }
  40. private List<String> findCollection(String aliasName, long start, long end) {
  41. List<String> collections = new ArrayList<>();
  42. if (start > end) {
  43. return collections;
  44. }
  45. //基于[start, end]寻找合适的collection
  46. if (clusterStateProvider.getState(aliasName) == null) {
  47. // 拿到当前aliasName对应的所有collection列表
  48. // 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
  49. List<String> aliasedCollections = clusterStateProvider.resolveAlias(aliasName);
  50. // 从collection名称中提取出时间日期
  51. // 2020-03-04T00:00:00Z=test_router_alias_2020-03-04,
  52. // 2020-02-26T00:00:00Z=test_router_alias_2020-02-26,
  53. // 2020-02-19T00:00:00Z=test_router_alias_2020-02-19,
  54. // 2020-02-12T00:00:00Z=test_router_alias_2020-02-12,
  55. // 2020-02-05T00:00:00Z=test_router_alias_2020-02-05
  56. List<Map.Entry<Instant, String>> collectionsInstant = new ArrayList<>(aliasedCollections.size());
  57. for (String collectionName : aliasedCollections) {
  58. String dateTimePart = collectionName.substring(aliasName.length() + 1);
  59. Instant instant = DATE_TIME_FORMATTER.parse(dateTimePart, Instant::from);
  60. collectionsInstant.add(new AbstractMap.SimpleImmutableEntry<>(instant, collectionName));
  61. }
  62. // 根据查询时间找出合理的collection
  63. Instant startI = Instant.ofEpochMilli(start);
  64. Instant endI = Instant.ofEpochMilli(end);
  65. for (Map.Entry<Instant, String> entry : collectionsInstant) {
  66. Instant colStartTime = entry.getKey();
  67. if (!endI.isBefore(colStartTime)) {
  68. collections.add(entry.getValue());
  69. System.out.println("find collection: " + entry.getValue());
  70. if (!startI.isBefore(colStartTime)) {
  71. break;
  72. }
  73. }
  74. }
  75. } else {
  76. collections.add(aliasName);
  77. }
  78. System.out.println("query " + collections);
  79. return collections;
  80. }
  81. public void run() throws Exception {
  82. try {
  83. // [2020-03-07 2020-03-10]
  84. long start = 1583538686312L;
  85. long end = 1583797886000L;
  86. String aliasName = "test_router_alias";
  87. String collections = StrUtils.join(findCollection(aliasName, start, end), ',');
  88. QueryResponse res = cloudSolrClient.query(collections, new SolrQuery("*:*"));
  89. for (SolrDocument sd : res.getResults()) {
  90. System.out.println(sd.get("id") + " " + sd.get("gmtCreate_l"));
  91. }
  92. } finally {
  93. cloudSolrClient.close();
  94. }
  95. }
  96. public static void main(String[] args) throws Exception {
  97. SolrDemo solrDemo = new SolrDemo();
  98. solrDemo.run();
  99. solrDemo.close();
  100. }
  101. }