假设当前有5个文档:

PUT /hotel
{
  "mappings": {
    "properties": {
      "city":{
        "type": "keyword"
      },
      "full_room":{
        "type": "boolean"
      }
    }
  }
}


PUT /hotel/_doc/1
{
  "city":"北京",
  "full_room":true
}

PUT /hotel/_doc/2
{
  "city":"天津",
  "full_room":true
}

PUT /hotel/_doc/3
{
  "city":"上海",
  "full_room":false
}

PUT /hotel/_doc/4
{
  "city":"天津",
  "full_room":false
}

PUT /hotel/_doc/5
{
  "city":"北京",
  "full_room":true
}

Elasticsearch中的 filter bitset 是一种用于快速过滤文档的数据结构。它的原理是将每个文档表示为一个位集合(bitset),其中每个位表示一个特定的过滤条件是否匹配该文档。例如,如果一个过滤条件是“age>30”,则对于每个文档,该条件的位将被设置为1或0,表示该文档是否符合该条件。

当执行查询时,Elasticsearch会将查询转换为一个或多个过滤器,并将这些过滤器应用于所有文档。对于每个过滤器,Elasticsearch会使用 filter bitset 来快速确定哪些文档符合该过滤器。这是通过将过滤器的位集合与每个文档的位集合进行位运算来实现的。如果结果为1,则表示该文档符合过滤器,否则不符合。

ElasticSearch对于 city 和 full_room 字段的倒排索引结构:

elk TraceId过滤 elasticsearch 过滤器_elasticsearch

当ES执行过滤条件时,会查询缓存中是否有 city 字段值为“北京”对应的 bitset 数据。如果查询缓存中有对应的 bitset 数据,则取出备用;如果缓存中没有 bitset 数据,则ES在查询数据后会对查询条件进行 bitset 的构建并将其放入缓存中。同时,ES也会考察满房字段为false是否有对应的bitset数据。如果有,则取出备用;如果缓存中没有,ES也会进行bitset的构建。

假设 city 字段值为“北京”,缓存中没有对应的 bitset 数据,则 bitset 构建的过程如下:

① ES在倒排索引中查找字段city值为“北京”字符串的文档,这里为doc1和doc5。

② 所有文档构建 bitset 数组,数组中每个元素的值用来表示对应位置的文档是否和查询条件匹配,0表示未匹配,1表示匹配。 doc1和doc5匹配“北京”,对应位置的值为1;doc2、doc3、doc4不匹配,对应位置的值为0。最终,本例的bitset数组为[1,0,0,0,1]。之所以用 bitset 表示文档和query的匹配结果,是因为该结构不仅节省空间而且后续进行操作时也能节省时间。如果 full_room 字段缓存中没有对应的 bitset 数据,ES构建满房字段为 true 对应 bitset 的过程也是类似的,full_room字段的 bitset 数组为[1,1,0,0,1]

elk TraceId过滤 elasticsearch 过滤器_spring boot_02

③ ES会遍历查询条件的 bitset 数组,按照文档命中与否进行文档过滤。当一个请求中有多个 filte r查询条件时,ES会构建多个bitset 数组。为提升效率,ES会从最稀疏的数组开始遍历,因为遍历稀疏的数组可以过滤掉更多的文档。此时,city 为“北京”对应的 bitset 比 full_room 为 true 的 bitset 更加稀疏,因此先遍历城市为“北京”的bitset,再遍历 full_room 为 true 的 bitset。遍历的过程中也进行了位运算,每次运算的结果都逐渐接近符合条件的结果。遍历计算完这两个bitset后,得到匹配所有过滤条件的文档,即doc1和doc5。

正如上面的介绍,如果查询内包含 filter,那么ES首先就从缓存中搜索这个 filter 条件是否有执行记录,是否有对应的bitset缓存可查询。如果有,则从缓存中查询;如果没有,则为 filter 中的每个查询项新建 bitset,并且缓存该 bitset,以供后续其他带有 filter的查询可以先在缓存中查询。也就是说,ES对于 bitset 是可重用的,这种重用的机制叫作 filter cache(过滤器缓存)。

filter cache会跟踪每一个filter查询,ES筛选一部分 filter 查询的 bitset 进行缓存。首先,这些过滤条件要在最近256个查询中出现过;其次,这些过滤条件的次数必须超过某个阈值。另外,filter cache 是有自动更新机制的,即如果有新增文档或者文档被修改过,那么 filter cache 对应的过滤条件中的 bitset 将被更新。例如城市为“北京”过滤条件对应的 bitset 为[1,0,0,0,1],如果文档4的城市被修改为“北京”,则“北京”过滤条件对应的 bitset 会被自动更新为[1,0,0,1,1]。

filter 查询带来的效益远不止这些,使用 filter 查询的子句是不计算分数的,这可以减少不小的时间开销。为提升查询效率,对于简单的 term 级别匹配查询,应该根据自己的实际业务场景选择合适的查询语句,需要确定这些查询项是否都需要进行打分操作,如果某些匹配条件不需要打分操作的话,那么应该把这些查询全部改成 filter 形式,让查询更高效。