ElasticSearch 存储系统中的实体叫做文档, document 如果用关系型数据库来类比的话, 一个文档相当于数据库中的一行记录 ElasticSearch 中的文档有个特点, 相同字段必须是相同的类型, 也就是说所有包含 title 字段的文档, title 字段类型都必须一样, 要么同为 string, 要么同为 int
文档由多个字段组成, 每个字段的类型可以是, 文本, 数值, 日期, 还可以是字符串数组这种复杂的类型字段类型在 ElasticSearch 中非常重要, 它涉及到各种分析和排序操作如何被执行的信息 Elastic 官方推荐我们使用 Mapping 映射来干预字段的类型与关系型数据库不同, ElasticSearch 不需要有固定的结构, 每个文档可以有不同的字段, 此外, 在程序开发期间, 不必确定有哪些字段
文档的类型
在 ElasticSearch 中, 文档类型可以让程序员轻松的区分单个索引中的不同对象每个文档可以有不同的结构, 但在实际生产环境中我们还是推荐将文档中的类型详细化, 这样对以后的开发会有很大的帮助
文档类型的映射
上面提到的映射, 指的是 ElasticSearch 在映射中存储有关字段的信息, 这种类型信息就是映射 Mapping 每个文档类型都有自己的映射, 即使在初始化时没有提前定义在涉及到全文搜索和倒排索引的内容中, 会有对文档分析的过程, 在这个过程中每个字段都必须根据不同类型作相应的分析举例来说, 对数值字段和文本字段的分析肯定是不同的分析过程, 数字的分析就不应该是按照字母的排序来分析
使用 ElasticSearch 的 ResultAPI 来新建文档
在 ElasticSearch 中, 所有文档都是数据, 所有数据都有定义好的索引和类型现在我们通过一个比较常见的例子来建立文档:
上面操作的意思是, 我们建立了一个名为 article 的索引和名为 computer 的类型, 文档的标示符为 1
如果一切正常, 那么这种使用 RESTfulAPI 的创建方式会返回一个 JSON 响应, 与如下输出类似:
前面的相应包含了操作状态的信息, 显示了创建的文档放在什么地方, 还包含了文档的唯一标示符 **_id 和当前版本_version** 的信息每次 ElasticSearch 的更新版本都会自动递增
而且 ElasticSearch 在创建文档时, 如果没指定文档标示符, 那么这个文档的标示符会被自动创建
这都是怎么做到的呢? 我们会在下一节从源码的角度解释
ElasticSearch 源码如何新建文档
在以前文章中强调的 Node 实例化的过程中, 加载了 ActionModule 这个模块, 这个模块是接收客户端发送的 RESTful 请求的的模块, ActionModule 的加载如下:
ActionModule actionModule = new ActionModule(false, settings, clusterModule.getIndexNameExpressionResolver(), settingsModule.getIndexScopedSettings(), settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(), threadPool, pluginsService.filterPlugins(ActionPlugin.class), client, circuitBreakerService, usageService);
在加载完了 ActionModule 后, 会通过 ActionModule 的方法 **initRestHandlers()** 来初始化 HTTP 处理程序, 这个 handler 就能解析客户端通过 http 协议发送到 ElasticSearch 集群中的 RESTful 请求
加载 RestIndexActionindex 处理器,
registerHandler.accept(new RestIndexAction(settings, restController))
如下图所示, 注册不同的 REST 处理程序路径, 以用来不同的匹配请求
可以看到控制器匹配路径中, 有 index,type 和 id, 如果不指定 id, 则 id 会被自动创建, 而且不指定 id 必须用 POST 方法来发送请求
因为 ElasticSearch 中的 Controller 底层都是 Netty 实现的所以在端口绑定后, Netty4HttpChannel 会去监听端口收到的 http 请求在 ElasticSearch 的 Controller 接收到 Netty4HttpChannel 转发的请求后, 会调用 RestIndexAction 中的方法 prepareRequest()该方法返回 RestChannelConsumer 类型的实例, 该实例是虚拟类 BaseRestHandler 中的 Functional 接口阅读这个接口的定义的方法, 可以知道 ElasticSearch 中的 REST 请求是通过准备一个表示通道的请求执行的通道消费者 (a channel consumer) 来处理的
接收到请求后开始构建 IndexRequest, 这个实例作用是将 JSON 类型的文档转换为一个特定的和可搜索的索引
IndexRequest 回首先取得 RestRequest 中的三个构造实例必须的参数:
index: 文档的索引
type: 文档的类型
id: 文档指定的标识
然后在依次取得一些附加参数:
routing: 控制分片的路由请求使用这个值来哈希的分片, 而不是 id
parent: 设置 document 的父 id
pipeline: 在执行索引 document 前, 设置摄取管道(ingest pipeline)
source: 设置 document 索引的字节形式
timeout: 超时时间
refresh: 解析刷新策略
version_type: 设置版本类型
op_type: 字符串, 用来表示是索引数据还是新建数据
参数详情如下图:
这参数都是 NodeClient 在索引文档时候需要用到的数据, NodeClient 在 Node 初始化时候就加载完成, 他是用来在本地节点上执行操作的模拟客户端
方法 prepareRequest 最后返回
channel -> client.index(indexRequest, new RestStatusToXContentListener<>(channel, r -> r.getLocation(indexRequest.routing())))
, 因为该方法需要返回 RestChannelConsumer 类型的返回值, 所以改写成 jdk7 版本易于理解的代码版本如下图所示:
该段代码中最重要的就是 NodeClient 的 index()方法, 此方法的关键是新建了一个 Task, 这个 Task 包含了 id,type,action,description,parentTask,startTime 等信息
该 task 在老版本会被 TransportIndexAction 处理, 但是 6.0 版本后 TransportBulkAction 已经取代了 TransportIndexActiontask 会被当做参数送入 TransportBulkAction 的 doExecute 方法中, 另外两个参数是 BulkRequest 和 ActionListener
void doExecute(Task task, BulkRequest bulkRequest, ActionListener<BulkResponse> listener)
BulkRequest 中包含了该文档存储的信息, 而 ActionListener 则用来监听 action 的响应或失败, 用以做回调操作
doExecute 方法主要做了以下操作:
收集请求中的所有索引
过滤掉不存在的索引, 同时建立一个我们无法创建的索引图判断不存在的索引和无法创建的索引主要是看索引是否有别名
如果有遗漏的索引, 则创建缺少的所有索引注意在所有的创建完成后开始批量处理数据
然后执行 TransportBulkAction 类的 executeBulk 方法, 完成数据的落地
来源: https://juejin.im/post/5a93d808f265da4e92684c05