天天看点

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 getDatestamp(Date curDate) {
   long curTime = curDate.getTime();
   return (int)((curTime - Constants.UNIX_TIME_STAMP_MILLISECONDS) / (1000 * 60 * 60 * 24));
}
           

然后通过取模得到路由数routing

public static int getRouting(Date curDate) {
   return getDatestamp(curDate) % 90;
}
           

这样我们就可以通过字段createDate拿到路由routing值啦。

这里注意,不知道你有没有思考到一个问题,

shard_num = hash(_routing) % num_primary_shards

分片号和路由值可能不是一一对应的关系。如果我设90个分片,那么不一定一个路由值分到一个分片,可能某个分片会有多个routing的数据,而有的分片啥也没有.

解决方案:我们可以把分片设置多点.尽量把数据分布开来。有人问,那有很多分片是空的咋办?回答是:有什么关系呢?我就空着你管我,哈哈哈哈。

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

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

    有时候可能业务代码里忘记加路由或者后来开发人员没有加可以设定下

_routing:{"required": true}

,这样没有根据路由查询的会报错。当然你想放开也没关系。

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

没了没了,

莫莫绵

溜了溜了。

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

继续阅读