Elasticsearch 企业级实战 01:Painless 脚本如何调试?

共 8568字,需浏览 18分钟

 ·

2024-07-16 07:30

在企业级应用中,Elasticsearch 常常被用来处理复杂的数据查询和操作。

Painless 是 Elasticsearch 的内置脚本语言,虽然强大,但调试起来并不容易。

本文将详细介绍如何在实战中有效调试 Painless 脚本,以提高开发和运维效率。

本文所有实现均在 Elasticsearch 8.11 dev-tool 环境充分验证,建议放大图片查看结果。

1、 抛出问题

在使用 Elasticsearch 的过程中,咱们开发者经常需要编写和调试 Painless 脚本,例如在查询、更新文档或定义复杂的预处理条件时。

由于 Painless 没有 REPL 环境(实时代码测试工具),调试嵌入在 Elasticsearch 中的脚本变得更加困难。

开发者无法直接在交互式环境中输入和测试 Painless 脚本,必须依赖诸如 Kibana 的 Painless Lab 或其他工具来间接调试和验证脚本。

这增加了调试的复杂性和开发周期。

2、脚本调试方式分类

通过大量的调研工作,其实核心就分两类。

2.1 调试方案 1:Elasticsearch  Debug.Explain 调试

Painless 提供的调试工具,可以在脚本中插入 Debug.explain 方法,通过抛出异常的方式输出变量信息。

参见官网:https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-debugging.html

2.2 调试方案 2:Kibana Painless Lab 工具调试

Elasticsearch 7.13 引入的实验性功能 Painless Lab,是一个交互式代码编辑器,可以实时测试和调试 Painless 脚本。

参见如下图,相信你和我一样,看过这幅图,但没有真正用过。下一篇我们多花笔墨解读这一部分的用法。

3、Debug.Explain 调试实战案例

依然以官方示例作为范例解读,参见:

https://www.elastic.co/guide/en/elasticsearch/painless/current/painless-debugging.html#_debug_explain

3.1 官方样例解读

假设我们有一个包含球员数据的索引,文档如下:

DELETE hockey
PUT /hockey/_doc/1?refresh
{
"first""johnny",
"last""gaudreau",
"goals": [9, 27, 1],
"assists": [17, 46, 0],
"gp": [26, 82, 1]
}

我们可以使用以下脚本来查看 goals 字段的类型:

POST /hockey/_explain/1
{
  "query": {
    "script": {
      "script""Debug.explain(doc.goals)"
    }
  }
}

执行结果如下:

当看到上面一堆输出的时候,相信你和我的表情一致:“这是啥?”、“错了吧?”、“就这”......

结合上文定义:“通过抛出异常的方式输出变量信息”,本质上是抛出异常了。

3.2 延伸详细解读

我们一点点剖析一下,如下内容官网没有提供。


我们使用脚本的本质,我延展一下:

3.2.1 脚本过滤检索

POST /hockey/_search
{
  "query": {
    "bool": {
      "filter": {
        "script": {
          "script""""
          def goals = doc['goals'];
          
          // 计算总和
          def sum = 0;
          for (def goal : goals) {
            sum += goal;
          }
          
          return sum > 30;
          """

        }
      }
    }
  }
}

过滤查询出总和大于 30 的数据。结果符合预期,如下图所示:


那,如何调试呢?

3.2.2 explain API 调试文档是否满足条件

极简单的方式,可以借助:explain 解读。也就是说:使用 _explain API 来探究并调试一个脚本查询。

细节参见:

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html

执行命令如下:

POST /hockey/_explain/1
{
  "query": {
    "bool": {
      "filter": {
        "script": {
          "script""""
          def goals = doc['goals'];
          //Debug.explain(goals);
          // 计算总和
          def sum = 0;
          for (def goal : goals) {
            sum += goal;
          }
          
          return sum > 30;
          """

        }
      }
    }
  }
}

之前咱们讲过 explain 可以详尽展开评分计算细节。

而此处还展示了:matched与否标记,如果条件满足则返回 true;如果不满足则返回 false。

显然,咱们的文档1符合查询条件。

3.2.3 Debug.explain 使用细节调试

还不够,doc['goals'] 到底来自哪里呢?

如下 DSL 仅在 3.2.2 基础上加了  Debug.explain 。

POST /hockey/_explain/1
{
  "query": {
    "bool": {
      "filter": {
        "script": {
          "script""""
          def goals = doc['goals'];
          Debug.explain(doc['goals']);
          // 计算总和
          def sum = 0;
          for (def goal : goals) {
            sum += goal;
          }
          
          return sum > 30;
          """

        }
      }
    }
  }
}

执行结果截图如下:

该错误指出在调用 Debug.explain(doc['goals']); 时发生了运行时错误。

Debug.explain 是一个调试方法,用于在脚本中输出变量的信息。然而,这种方法在某些上下文中可能不被允许,或者 doc['goals'] 字段的类型 ScriptDocValues.Longs 导致了这个问题。

其实,我们还能得到如下有价值信息:

  • (1):"to_string": "[1, 9, 27]" 显示了 doc['goals'] 字段的值,即一个包含 [1, 9, 27] 的数组。

  • (2):"painless_class": "org.elasticsearch.index.fielddata.ScriptDocValues.Longs" 指出导致错误的类是 ScriptDocValues.Longs。这是一个表示长整型字段值的类。

关于这个类的官方文档,可以参见:

根据:org.elasticsearch.index.fielddata.ScriptDocValues.Longs

找到 fielddata 子模块,进而找到文档,参见下图。


https://www.elastic.co/guide/en/elasticsearch/painless/8.11/painless-api-reference-shared-org-elasticsearch-index-fielddata.html#painless-api-reference-shared-ScriptDocValues-Longs

其实,这些 API 就是我们使用脚本的依据和参考。

这里,往往也是被问最多的地方:Elasticsearch 脚本细节运算的 API 在哪里查?支持哪些方法?

有了它,我们进一步可以执行脚本了,举例:sort 使用如下:

POST /hockey/_search
{
  "script_fields": {
    "sorted_goals": {
      "script": {
        "lang""painless",
        "source""""
          // 获取 goals 数组,并复制到一个新的列表中
          def goals = new ArrayList(doc['goals']);

          // 定义排序比较器,从大到小排序
          goals.sort((a, b) -> b.compareTo(a));

          // 返回排序后的数组
          return goals;
        """

      }
    }
  }
}

goals.sort((a, b) -> b.compareTo(a)); ——语法的核心是使用 Java 8 的 lambda 表达式和 Comparator 接口来定义排序规则。

b.compareTo(a) 是对 b 和 a 进行比较的方法调用。compareTo 方法返回一个整数,用于指示元素的顺序:

  • 如果返回负数,则表示 b 小于 a。
  • 如果返回零,则表示 b 等于 a。
  • 如果返回正数,则表示 b 大于 a。仔细看来,这是意外的收获!

4、小结

篇幅原因,本文只给出了Painless 脚本的第一种调试方式:Debug.explain 的详尽解读。

相信对你的脚本调试也会有帮助,如果你有脚本调试疑问,欢迎留言交流哈。

关于 Kibana Painless Lab 工具调试 ,且听下回分解。

探究 | Elasticsearch Painless 脚本 ctx、doc、_source 的区别是什么?

Elasticsearch 脚本安全使用指南

干货 | Elasticsearch7.X Scripting脚本使用详解

新时代写作与互动:《一本书讲透 Elasticsearch》读者群的创新之路


短时间快习得多干货!

和全球2000+ Elastic 爱好者一起精进!

http://elastic6.cn——ElasticStack进阶助手


抢先一步学习进阶干货

浏览 59
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报