java与es8实战之三:Java API Client有关的知识点串讲

打印 上一主题 下一主题

主题 914|帖子 914|积分 2742

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览


  • 本篇是《java与es8实战》系列的第三篇,将一些重要的知识点在这里梳理清楚,为后面的实践奠定基础
  • 一共有七个与Java API Client有关的重要知识点

  • 关于namespace:每个feature都有自己的package
  • 命名规则:介绍Java API Client中对象的方法的命名规则
  • 集合不为空:Java API Client中对象返回的集合,到底要不要做判空?
  • variant type:繁多的场景和对象,可以通过variant type进行简化
  • 通过JSON字符串创建API对象:通过builder创建复杂的对象,会导致代码也很复杂,这里提供了一种更简单的方式
  • 关于异常:有哪些异常类型,各自会在什么场景抛出


  • 接下来逐个去看
命名空间


  • 在REST API文档中,数量众多API是按照特性(feature)来分组的,如下图

  • 在ES的Java库Java API Client中,上图中的各种feature被称为namespace
  • 在ES的Java库Java API Client中,与REST API对应的的类和接口都在统一的包名co.elastic.clients.elasticsearch之下,然后再通过下一级package进行分类,这个分类与上图的feature相对应,例如索引相关的,在REST API中的feature是Index APIs,那么在Java API Client中,完整的package就是co.elastic.clients.elasticsearch.indices,这里面有索引操作所需的请求、响应、服务等各种类,如下图

  • 每一个namespace(也就是REST API中的feature),都有自己的client,例如索引相关的操作都有索引专用的client类负责,实例代码如下,client.indices()返回的是ElasticsearchIndicesClient对象,这是索引操作专用的实例
  1. ElasticsearchClient client = ...
  2. client.indices().create(c -> c.index("products"));
复制代码

  • 展开上述代码的indices()方法,看看其内部实现,如下所示,每次调用indices方法,都会创建一个ElasticsearchIndicesClient对象,对于其他namespace,例如ingest、license亦是如此,都会创建新的实例

  • 看到这里,经验丰富的您应该发现了问题:在大量并发频繁执行各种namespace操作时,会创建大量client对象,这样会影响系统性能吗?
  • es预判了咱们的预判,如下图,官方说这是轻量级对象(very lightweight),所以,理论上可以放心创建,不必担心其对系统造成的压力

  • 尽管每个namespace都有自己的client,但也有例外,就是searchdocument,它们的代码不在search或者document这样的package下面,而是在core下面,而且可以通过ElasticsearchClient来操作,如下图

命名规则


  • Java API Client是个库,也是个java工程,工程里有自己的内部设计,这算是Java API Client自己的框架部分(framework),另一部分就是专门为使用者提供的大量API
  • 对于API部分,方法的命名规则都是驼峰式(camelCaseNaming),例如查询请求ElasticsearchClient.search()、查询结果的最高评分SearchResponse.maxScore()
  • 对于framework部分,方法命令是下划线作为前缀,例如获取查询类型Query._kind()
五种对象


  • 官方将Java API Client中的对象分为五种

  • Object mapper:序列化和反序列化工具,这类对象是线程安全、无状态的,通常是单例模式存在于应用中,常在启动时创建
  • Transport:传输工具,此类对象线程安全,借助底层HTTP客户端工具维护着网络资源,例如负责与es服务端建立连接,在需要关闭连接的时候负责释放所有底层网络资源
  • Clients:实际处理每个namespace的客户端类,例如负责索引的是ElasticsearchIndicesClient,它们的特点:不可变对象、无状态、线程安全、轻量级(类似于普通bean的资源开销),之所以轻量级,是因为其结构实际上就是对一些API endpoint的包装
  • Builders:这个在《开篇》中已经详细说明了,就不多赘述,用过builder的您应该会发现,builder当然是可变类,至于是否线程安全似乎和builder没什么关系,因为每创建一次实例时,都要创建一个builder实例,而且,一旦执行完build方法后,这个builder实例就没用了
  • Requests & other API objects:和请求相关的对象,都是不可变的、线程安全的
集合不会为空


  • 对于单值属性,我们在使用的时候判断是否为空是个常规操作,这样是为了避免直接使用时可能出现的空指针异常
  • 而对于集合,Java API Client 已经确保了API返回的集合非空,我们只需要检查集合中是否有内容,而不必担心集合自身是否等于null的问题,官方给出的演示代码如下
  1. NodeStatistics stats = NodeStatistics.of(b -> b
  2.     .total(1)
  3.     .failed(0)
  4.     .successful(1)
  5. );
  6. // The `failures` list was not provided.
  7. // - it's not null
  8. assertNotNull(stats.failures());
复制代码

  • 出于好奇,去看看NodeStatistics源码,构造方法如下,failures来自ApiTypeHelper.unmodifiable
  1.         private NodeStatistics(Builder builder) {
  2.                 this.failures = ApiTypeHelper.unmodifiable(builder.failures);
  3.                 this.total = ApiTypeHelper.requireNonNull(builder.total, this, "total");
  4.                 this.successful = ApiTypeHelper.requireNonNull(builder.successful, this, "successful");
  5.                 this.failed = ApiTypeHelper.requireNonNull(builder.failed, this, "failed");
  6.         }
复制代码

  • 再去看ApiTypeHelper.unmodifiable,如下,已确保了failures不为空
  1.     public static <T> List<T> unmodifiable(@Nullable List<T> list) {
  2.         if (list == null) {
  3.             return undefinedList();
  4.         }
  5.         if (list == UNDEFINED_LIST) {
  6.             return list;
  7.         }
  8.         return Collections.unmodifiableList(list);
  9.     }
复制代码

  • 因此,再使用API返回的集合时,集合对象自身始终非空
variant type


  • variant type是Java API Client中常见的对象类型,这个该如何翻译呢,个人觉得是不确定类型的意思,不专业,期待您的指正
  • 举个例子,查询是最常见的操作了,下面列举三种查询,第一个是普通的不分词查询
  1. {
  2.   "query":{"term":{ "interests":"youyong"}}
  3. }
复制代码

  • 分词查询
  1. {
  2.   "query":{"match":{"interests": "changge"}}
  3. }
复制代码

  • 以及复杂的全文本查询
  1. {
  2.   "query": {
  3.     "intervals" : {
  4.       "my_text" : {
  5.         "all_of" : {
  6.           "ordered" : false,
  7.           "intervals" : [
  8.             {
  9.               "match" : {
  10.                 "query" : "my favorite books",
  11.                 "max_gaps" : 0,
  12.                 "ordered" : true
  13.               }
  14.             },
  15.             {
  16.               "any_of" : {
  17.                 "intervals" : [
  18.                   { "match" : { "query" : "java tutorials" } },
  19.                   { "match" : { "query" : "cold porridge" } }
  20.                 ]
  21.               }
  22.             }
  23.           ]
  24.         }
  25.       }
  26.     }
  27.   }
  28. }
复制代码

  • 对查询来说,在Java API Client中,有个Query对象代表了查询行为,这就是个典型的variant type,至于对应的真实query是哪种,可以在builder时指定,例如下面指定了类型是term
  1. Query query = new Query.Builder()
  2.     .term(t -> t                          
  3.         .field("name")                    
  4.         .value(v -> v.stringValue("foo"))
  5.     )
  6.     .build();  
复制代码

  • 上述query有对应的方法返回其值,例如上面的value可以这样获取
  1. query.term().value().stringValue()
复制代码

  • 如果在设置的时候,并非用stringValue方法,而是其他类型,那么上面的代码在获取String类型的值时会抛出IllegalStateException异常
  • variant type配有对应的isXXX方法返回其是否属于某个类型,例如Query就有query.isTerm()表示自己是不是term查询
  • 还可以用_kind()返回当前类型,下面是示例
  1. switch(query._kind()) {
  2.     case Term:
  3.         doSomething(query.term());
  4.         break;
  5.     case Intervals:
  6.         doSomething(query.intervals());
  7.         break;
  8.     default:
  9.         doSomething(query._kind(), query._get());
  10. }
复制代码

  • 可见有了variant type,在 queries, aggregations, field mappings, analyzers等多种场景下,我们不需要使用各种具体的类,只要用最抽象的variant type,再配置builder pattern即可,这对服务提供者和服务消费者都是有效的简化
通过JSON字符串创建API对象


  • 下面是kibana页面上,用JSON创建索引的操作截图

  • 如果要在代码中实现上述效果,该如何做呢?一层一层的创建mapping、proterties、field对象?那可真是麻烦...
  • 在Java API Client中,可以通过json字符串反序列化为API对象,首先,将上述JSON放入名为some-index.json的文件中,然后执行以下代码,即可用json文件创建req对象
  1. InputStream input = this.getClass()
  2.     .getResourceAsStream("some-index.json");
  3. CreateIndexRequest req = CreateIndexRequest.of(b -> b
  4.     .index("some-index")
  5.     .withJson(input)
  6. );
  7. boolean created = client.indices().create(req).acknowledged();
复制代码

  • 再来段更复杂的,一个API对象,既通过JSON反序列化生成,同时又能调用对象的方法设置一些属性
  1. Reader queryJson = new StringReader(
  2.     "{" +
  3.     "  "query": {" +
  4.     "    "range": {" +
  5.     "      "@timestamp": {" +
  6.     "        "gt": "now-1w"" +
  7.     "      }" +
  8.     "    }" +
  9.     "  }," +
  10.     "  "size": 100" +
  11.     "}");
  12. Reader aggregationJson = new StringReader(
  13.     "{" +
  14.     "  "size": 0, " +
  15.     "  "aggregations": {" +
  16.     "    "hours": {" +
  17.     "      "date_histogram": {" +
  18.     "        "field": "@timestamp"," +
  19.     "        "interval": "hour"" +
  20.     "      }," +
  21.     "      "aggregations": {" +
  22.     "        "max-cpu": {" +
  23.     "          "max": {" +
  24.     "            "field": "host.cpu.usage"" +
  25.     "          }" +
  26.     "        }" +
  27.     "      }" +
  28.     "    }" +
  29.     "  }" +
  30.     "}");
  31. SearchRequest aggRequest = SearchRequest.of(b -> b
  32.     .withJson(queryJson)
  33.     .withJson(aggregationJson)
  34.     .ignoreUnavailable(true)
  35. );
  36. Map<String, Aggregate> aggs = client
  37.     .search(aggRequest, Void.class)
  38.     .aggregations();
复制代码
关于异常


  • 在Java API Client中一共有两大类异常

  • 第一类是由es服务端返回的错误引发的,例如es服务端的校验未通过,或者es服务端自己内部出现异常等,这些情况下抛出的异常是ElasticsearchException
  • 第二类是因为请求未能成功到达es服务端而引发的,例如网络故障,es服务不可用等,这些情况下抛出的异常是TransportException,这些是lower-level implementation抛出的,有个例外:如果这些问题发生在RestClientTransport对象的方法中,那么抛出的异常类型是ResponseException


  • 以上就是Java API Client相关的重要知识点,在写代码之前先了解它们算是打好基础,然后,接下来精彩的实战篇即将开幕
欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

麻花痒

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表