ES-分片路由(routing)

ES-分片路由

        • ES分片
        • 为什么要使用分片路由
        • 使用前的思考
        • 具体方案实践
            • 需求分析与分片设计
            • 上代码


ES分片

    ElaticSearch 分片路由是个比较高级的功能了,一般情况下,我们可能会给es做个分片,这样可以把数据按照默认文档_id来分到各个分片上。这样es每次查询会分别去各个分片上查数据,然后聚合到一起返回给客户端。
    配置也很简单,只需要在配置mapping的时候设置下number_of_shards参数。如下:

{
"settings": { "number_of_shards": 6, "number_of_replicas": 2 },
"mapping":{....}
}

需要注意的是,一旦创建了分片,后续想要直接修改可就不行了,解决方案是:重新创建新索引mapping,然后把数据迁移进去,删掉原有的索引,重命名新索引,数据量比较多的情况下还是比较麻烦的。

    es分片路由的规则是这样的:
            shard_num = hash(_routing) % num_primary_shards

  • _routing字段的取值,默认是_id字段。
  • num_primary_shards表示索引有多少个分片。

为什么要使用分片路由

    实际上上线的业务由于扩张较快,数据量上去了,很容易让es查询速度变慢,这时候我们就想提高点效率啊。 往往我们会发现,我实际需求的数据在每个分片中只是离散的一小部分,而es每次需要查找所有的分片,扫描所有数据。如果我找到某个规则去把数据分配到对于的分片上,然后查询的时候只查对应分片,这样应该可以节省不少资源。

使用前的思考

    对于分配规则不同业务不太一致,比如:有些使用es统计的数据每次只需要查询一天的数据,我们就可以把每一天的数据放在一个分片上,而路由分片的功能就可以使用routing参数。亦或是根据某个字段如用户id(uuid)的前缀来划分,手机号的尾号来划分等等。当然也不要钻牛角尖,对于查询频率很少的业务,让他扫描下全分片也没啥。

那么需要考虑的问题是:

  • 应该按照什么规则分片?要保正分片比较均匀。
  • 业务代码需要根据自定义routing查询。

具体方案实践

下面给出个例子具体描述下:

需求分析与分片设计

    一较大数据量的业务,es中仅存储约90天数据,过期删除。现在需要查询这些数据,业务方支持每次查询最多查询一天。

    那么我们决定按日期分片,也就是把同一个日期的数据放在一个分片中。(当然你也可以把两天的数据放一个分片里)。先来个90个分片。路由字段选择为日期createDate。
那么,我该怎么通过日期找到对应的分片呢?我们必须选择一个基准日期(
比如unix时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数)我们选择2021年7月16号作为初始日期,这样就可以得到数据创建时间到基准时间 天数差 .

上代码
/**
* 从2021年7月16号到查询日期的天数差
* @param curDate
* @return
*/
public static int getDateDiff(Date curDate) {
   long curTime = curDate.getTime();
   //  Constants.UNIX_TIME_STAMP_MILLISECONDS是2021年7月16号的时间戳int值
   return (int)((curTime - Constants.UNIX_TIME_STAMP_MILLISECONDS) / (1000 * 60 * 60 * 24));
}
public static int getRouting(Date curDate) {
   return getDateDiff(curDate) % 90;
}

注意:这里getRouting方法也可以不做%90操作。这里为了保证routing值在0-90范围内方便读写。

  • 由于es内部使用了hash散列函数,那么就会导致我这90天的数据不会一对一的映射到90个分片上,有的分片是空的,有的分片又存了多天的数据。但这个无关紧要,因为我们的目标已经达成了:减少了数据扫描范围。那么如果我们希望数据分布的更均衡点,我们可以扩大分片数。经过测试我把分片数设成200.可以保证我大体上90天数据均匀分开,当然也导致了有120个空分片(空就空着,也无关紧要)。

    查询、插入数据的时候需要指定参数routing即可,总体看开发还是挺简单的。

Search search = builder
.addIndex(EsConstant.INDEX)
.addType("_doc")
.setParameter("routing",String.valueOf(getRouting(new Date())))
.build();

    有时候可能业务代码里忘记加路由或者后来开发人员没有加可以设定下_routing:{"required": true},这样没有根据路由查询的会报错。当然你想放开也没关系。

PUT my_index_name
{
  "mappings": {
    "my_type": {
      "_routing": {
        "required": true
      }
    }
  }
}

除此之外,一般分片可以跟据某个uuid字段去随机分, 如下:

 public static String getRouting(String str) {
        int shardCount = 10;
        BigInteger hashKey = new BigInteger(hash(str.hashCode()) + "").abs();
        int result = (hashKey.mod(BigInteger.valueOf(shardCount))).intValue() + 1;
        return result + "";
    }
  public static int hash(int key) {
        // key = (key << 21) - key - 1;
        key = (~key) + (key << 21);
        key = key ^ (key >> 24);
        // key * 265
        key = (key + (key << 3)) + (key << 8);
        key = key ^ (key >> 14);
        // key * 21
        key = (key + (key << 2)) + (key << 4);
        key = key ^ (key >> 28);
        key = key + (key << 31);
        return key;
    }

这里我自己做了一次hash和取模,是为了让routing值处于分片数区间。这边比较方便手动根据routing查,如果routing直接放uuid,那么使用kibana跟据routing查的时候就很头疼了。对于我这个处理后的routing,es内部还会去做hash(routing)%shardNums,读者们不要误解成我去实现了分片逻辑哈。routing值和分片也并非一一对应的哈。

没了没了,莫莫绵溜了溜了。


水平有限,如果你觉得上述有任何疑问、不足、错误的地方,欢迎在评论区指正讨论。

你可能感兴趣的:(Elasticsearch,大数据,es,routing,搜索引擎优化,ElaticSearch)