
2.5 客户端API概览
Elasticsearch提供了丰富的客户端API操作和访问文档,支持Java、JavaScript、Groovy、.NET、PHP、Perl、Python、Ruby等多种语言。但Elasticsearch最基本的访问方式还是通过REST接口,以HTTP协议的形式操作文档数据。除此之外,Elasticsearch还内置一种称为Painless的脚本语言,可以在API中使用。本节将从总体上介绍REST接口和Painless脚本语言,同时也会简要介绍一下基于Java语言的API。
2.5.1 REST接口
在前述章节的讲解中,大部分示例代码中使用的就是Elasticsearch提供的REST接口。REST有自己独特的规范和约定,Elasticsearch的REST接口基本遵从了这些规范和约定,所以了解REST的一些基础知识,对于理解Elasticsearch客户端接口是有一定帮助的。
REST可以理解为一种架构风格,它核心的思想是认为一切都是资源,而应用程序则是通过更改资源的状态实现具体功能。REST中的资源可以理解为数据,并且以URI为惟一标识。所以构建REST风格的URI,一个重要原则就是它们应该都是名词。Elasticsearch的主要资源就是索引、映射类型和文档,它们在Elasticsearch定义的URI中是有次序的。例如在“/users/_doc/1”这个URI中,users为索引名,_doc为类型名,而1则是文档的ID。在版本7中,映射类型已经被提升为一种内置资源,用户不能再自定义映射类型名称。内置资源要么以下划线“_”开头,要么以“.”开头,一般都是一些具有特别用途的资源。除了映射类型_doc以外,Elasticsearch提供的所有接口也都是以下划线“_”开头的,例如_search、_cat、_cluster等。
REST这种将URI定义为名词的作法,与早期软件系统定义的URI有很大区别。例如,对于查看用户来说,以往定义的URI一般是“listUsers.do”。因为它即包含了资源users,又包含了对资源的操作list,所以不是REST风格的URI。在REST中对资源的操作是通过HTTP的七种请求方法完成的,即POST、PUT、GET、DELETE、HEAD、OPTIONS、TRACE。但常用的请求方法一般只有4种,即POST、PUT、GET、DELETE。简单来说,POST可以理解为新增资源,PUT为新增或修改资源,GET为查询资源,DELETE为删除资源。具体到Elasticsearch中,创建一个索引可以使用PUT请求,如PUT test代表创建一个名为test的索引,“PUT test/_doc/1”则代表在test索引的_doc类型下增加ID为1的文档。除此之外,Elasticsearch还支持HEAD请求,用于做存在性查询,即查看一个资源是否存在。
这里要特别说明一下POST和PUT的区别。PUT新增资源是直接通过URI标明资源是哪一个,所以对同一个URI多次PUT请求时,只有第一次请求是执行新增操作;之后所有的PUT请求都可以理解为对资源的更新。所以一般说PUT请求是幂等的,即对同一资源请求多次的结果是一样的。而POST请求则不同,它的URI一般是要操作资源的父资源,所以对同一URI请求多次会一直新增资源。举例来说,多次请求“PUT test/_doc/1”相当于一直在更新ID为1的文档,文档的_id字段值为1。而使用POST请求时应该为“POST test/_doc/”,多次请求时会不断向test索引中添加新文档,而每次文档的_id字段都由Elasticsearch自动产生。在Elasticsearch中可以同时使用POST和PUT请求更新一份文档,但通过映射类型添加文档则只能使用POST请求。
本书后续章节在讲解Elasticsearch具体操作、检索接口时都会使用REST接口,读者在调用时可以使用Kibana控制台,也可以使用curl命令。在调用这些REST接口时有一些比较通用的规则,下面将分别介绍。
1.多索引
REST接口的URI一般都对应一个资源,但Elasticsearch在操作索引时可以在URI中指定多个,也可以使用通配符匹配多个索引。具体来说,多个索引使用逗号分隔开出现在URI中,可以在索引名称中使用星号匹配任意字符,还可以在URI中使用_all代表所有索引,如示例2-19所示:

示例2-19 多索引
需要注意的是,并不是所有Elaticsearch接口都支持多索引,在不支持多索引的接口中使用上述格式的URI将会报错。
2.通用参数
有一些参数对于所有Elasticsearch接口都是有效的,它们一般用于定义接口返回结果的内容和格式定义。比如在接口请求中添加“?pretty=true”,可以让返回的JSON结果以更易读的格式展示出来;添加“?format=yaml”则可以让返回结果以YAML格式展示出来;而添加“?human=true”则会让返回结果字段以更易于人类理解的方式展示。
除了以上参数,还有一个比较有用的参数filter_path。这个参数用于过滤返回结果中的字段,参数值是希望在返回结果中展示的字段路径。例如,如果只想查看返回结果中的_source字段值,那么就可以使用“hits.hits._source”。这是因为在使用_search接口检索文档时,_source字段是在返回结果的hits.hits字段。本书第6章在讲解聚集查询时,在所有示例中都会使用filter_path参数以过滤聚集结果。
2.5.2 Painless脚本
在Elasticsearch的许多接口中都可以使用脚本做动态运算,支持包括Painless、expression、mustache和Java在内的4种脚本语言。在这些语言中,Java语言虽然通用,但没有提供内置支持,需要用户自行开发插件;而Painless则是Elasticsearch支持最好的一种脚本语言,所以本小节将简单介绍一下这种语言。
Painless是Elasticsearch在5.0版本之后提供的一种简单安全且高性能的脚本语言,它的名称也反映出它在使用上让人更愉悦。Painless以Java语言为基础,最终会被编译为字节码并运行在JVM上。所以Painless在语法上与Java语言基本相同,并添加了动态类型、Map和List快捷访问等新功能。
为了便于学习和测试Painless脚本,Elasticsearch还专门提供了一个脚本执行接口。接口地址为_scripts/painless/_execute,支持使用GET或POST方法调用。脚本执行接口的script参数接收Painless脚本,其中它的子参数source用于编写脚本本身,而params则用于给脚本传递参数。例如示例2-20中的脚本片段会将列表list中的元素累加起来并返回:

示例2-20 Painless脚本执行接口
通过示例2-20代码可以看出,Painless语言与Java语言非常相似。但为了讲解Painless语言特性,代码中特意添加了三处Painless特有语法,否则它会与Java代码完全相同。在这三处特有语法中,一个是在第5行中List初始化使用的“[...]”的格式,另一个是在第7行for循环中使用了def动态类型声明变量,还有一个就是第10行脚本最后一句没有用return。在Painless中,“[...]”格式不仅可以初始化List,还可以初始化Map,只是在括号中的元素需要用冒号分隔为键和值两部分。例如:

示例2-21 初始化Map
这种初始化方式只是一种语法糖,在编写时完全可以按Java语法的形式初始化它们,只不过要麻烦一些。实际上def也是一种语法糖,其实在示例2-20中的list、result都可以使用def来声明。所以,如果不想在Painless语法上花费太多精力,可以完全按Java语法编写脚本。
如果希望Painless脚本返回一个具体数值,则需要在最后一句使用return语句返回值,或者像示例2-20中那样只写一个变量或表达式。在实际应用中,Painless脚本往往就是一句简单的表达式,用于从Elasticsearch的对象中求解某个值。这就牵出另外一个问题,即Painless脚本如何访问Elasticsearch中的对象,比如如何从文档中读取某一字段值。
Painless为每段脚本的执行定义了一个上下文,而在这个上下文中则保存了脚本需要的大部分环境数据。但由于Painless执行的上下文有多种情况,所以它们包含的数据也不相同。本节就不在此一一列举,而会在后续章节中具体用到Painless脚本的接口中介绍它们。
Painless可以使用Java风格的注释语句,支持分支、循环等控制结构,所以Painless有一些关键字不能用于声明标识符。Painless关键字比Java少很多,一共只有15个。表2-5列出了所有可用关键字,从中可以看出Painless都支持哪些语句类型。
表2-5 Painless关键字

通过表2-5可以看出,Painless支持除switch语句以外的所有控制语句。有关Painless语言就暂时介绍这么多,对于有Java语言基础的用户来说,完全可以按Java语法来编写脚本。本书后续章节在具体应用到Painless脚本时还会再详细介绍,这里就不再赘述。
2.5.3 Java API
由于REST接口基于HTTP协议而与客户端语言无关,所以使用Java调用Elasticsearch完全可以使用Apache HttpClient这样的开源框架实现。例如示例2-22中的代码就是直接使用HttpClient调用Elasticsearch:


示例2-22 使用HttpClient调用Elasticsearch
Elasticsearch官方提供的Java客户端API底层其实也是基于HttpClient框架,它在此基础上做封装并提供了两种解决方案。第一种解决方案封装的比较简单,开放出来的接口还是REST调用形式,但使用的客户端、请求、响应等对象已经被封装为Elasticsearch相关的类型,所以被称为低级REST客户端(Low Level REST Client)。使用Java低级REST客户端访问Elasticsearch需要在pom文件中添加如下依赖:

示例2-23 低级REST客户端Maven依赖
Java低级REST客户端使用RestClient对象连接Elasticsearch,在创建RestClient对象时可以指定多个节点地址以实现负载均衡。RestClient的performRequest可以执行REST请求,在使用结束后需要调用close方法释放RestClient占用的资源。Request对象代表需要执行的REST请求,在创建时接收两个参数:一个是请求方法;另一个是请求的地址。如果有请求体需要传递可使用setEntity或setJsonEntity设置,前者可使用HttpClient中的HttpEntity设置,而后者则可以直接使用字符串设置。Response对象代表REST请求执行的结果,通过调用getEntity方法可以得到响应体,类型也是HttpEntity。示例2-24展示了使用这几个对象访问Elasticsearch中test索引的方法:

示例2-24 低级REST客户端调用
Elasticsearch提供的第二种解决方案建立在低级REST客户端基础之上,它将几乎所有Elasticsearch访问接口都封装为对象和方法,在调用过程中已经没有明显的REST语法形式了。由于这种解决方案面向对象级别更高,所以被称为高级REST客户端(High Level REST Client)。高级REST客户端连接Elasticsearch的对象是RestHighLevelClient,它基于RestClient在使用结束后也需要调用close方法释放资源。由于高级REST客户端中将接口请求和响应都做了封装,所以请求和响应的种类非常多。比如,同样是获取索引信息接口,在高级REST客户端中对应的请求和响应分别为GetIndexRequest和GetIndexResponse。示例2-25展示了使用高级REST客户端访问test索引的代码片段:

示例2-25 高级REST客户端调用
无论是使用哪一种方式,REST接口都是调用Elasticsearch的基础。所以本书后续章节在介绍Elasticsearch接口时将全部以REST接口的形式讲解,而有关它们在各种编程语言中的接口请读者根据REST接口自行查找。