- Elasticsearch的java客户端
- 节点客户端作为一个非数据节点加入到本地集群中。
- 轻量级的传输客户端可以将请求发送到远程集群。它本身不加入集群
- Java 客户端都是通过9300 端口并使用本地 Elasticsearch 传输 协议和集群交互。
- 所有其他语言可以使用 RESTful API 通过端口 9200 和 Elasticsearch 进行通信
- 结构化搜索(Structured search)、全文搜索(Full text search)、相关性得分(score)
- 聚合与 SQL 中的 GROUP BY 类似但更强大。
- 聚合并非预先统计,而是从匹配当前查询的文档中即时生成
- 聚合还支持分级汇总 。比如,查询特定兴趣爱好员工(aggs 1)的平均年龄(aggs 2)
- 分布式相关概念:
- 集群Cluster 、节点node
- 复制(replication),
- 副本(replica)提供数据冗余,
- 支持故障转移(fail over),提高可用性(availability)
- 主从(master-slave、primary-replica)写同步
- 读写分离
- 负载均衡(load balance)
- 轮询(round-robin)
- 副本(replica)提供数据冗余,
- 分片(shard),
- 计算分片id(hash散列算法、分片数量、求余),
- 路由(route)
- 提供弹性(elastic)水平横向扩展(scale)机制
- 计算分片id(hash散列算法、分片数量、求余),
- fielddata,访问field value
- search不需要访问field value。。
- sorting、aggs、 accessing field values in scripts则需要
- field value保存在硬盘中。
- 访问则需要加载到内存,从而占用大量内存
- 访问方式:
- text field需要通过fielddata( a query-time in-memory data structure )访问
- Fielddata is disabled on text fields by default
- Enabling fielddata on text fields
- 其他field可以通过 doc_values 来访问。
- Doc Values 是一种列式存储结构
- 它将所有单字段的值存储在单数据列,像关系数据库的列一样。便于sort、aggs等
- text field需要通过fielddata( a query-time in-memory data structure )访问
- search不需要访问field value。。
- Elasticsearch 透明分布式支持
- 默认配置、自动机制尽可能地屏蔽了分布式系统的复杂性
- 当一个节点被选举成为 主 节点(master)时,
- 它将负责管理集群范围(cluster-wide)的变更,
- 例如增加、删除索引,或者增加、删除节点等。
- 而主节点并不需要涉及到文档级别的变更和搜索等操作,
- 它将负责管理集群范围(cluster-wide)的变更,
- primary shard负责写操作,然后并行复制到replica shard上
- 如果副本分片所在节点收到写操作请求,会转发给primary shard。
- 所有分片都可以执行读操作
- 为了实现负载均衡,每次请求会轮询文档所在的所有副本分片。
- 作为用户,我们可以将请求发送到 集群中的任何节点 ,包括主节点。
- 接收请求的节点,即协调节点(coordinating node) ,发散收集:
- 将我们的请求直接转发到存储我们所需文档的节点。
- 从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端
- 当发送请求的时候, 为了扩展负载,
- 更好的做法是轮询集群中所有的节点。
- 接收请求的节点,即协调节点(coordinating node) ,发散收集:
- health status
- green、yellow(有副本分片不ok)、red(有主分片不ok)
- 索引实际上是指向一个或者多个物理分片 的 逻辑命名空间 。
- 一个分片是一个 Lucene 的实例,
- 它本身就是一个完整的搜索引擎
- 分片是文档的容器
- Elasticsearch 通过在各节点中迁移分片,使得数据仍然均匀分布在集群里。
- 一个主分片最大能够存储 Integer.MAX_VALUE - 128 个文档
- 一个副本分片只是一个主分片的拷贝。并且只提供读操作服务。
- 在索引建立的时候就已经确定了主分片数(static),但是副本分片数可以随时修改(dynamic)。
- Split Index(index拆分)
- Shrink Index(index压缩)
- 一个分片是一个 Lucene 的实例,
- 同一个节点上,不允许分配同一个primary shard的多个copy(包括original)
- 在同一个节点上保存多个副本是没有意义的,
- 因为一旦失去了那个节点,我们也将丢失该节点上的所有副本数据。
- 如果没有足够多的节点来分配副本分片,则分片的状态将是 unassigned。
- 一旦有新的节点加入cluster,则立即分配(allocated)
- 在同一个节点上保存多个副本是没有意义的,
- 多个node,组成一个cluster:
- 认识Development vs. production mode,
- 多个node加入cluster,Elasticsearch将切换到Production mode
- Production mode下, Bootstrap Checks失败将导致node无法启动。
- 届时,根据log打印的bootstrap check失败项,参考文档修正即可。
- 设置node要绑定的non-loopback address: network.host
- 打开端口:
- 集群中的节点彼此通信的 Transport 9300端口
- 提供REST API服务的HTTP 9200端口
- 在同一台机器上启动了多个节点时,
- 只需要再次运行/bin/elasticsearch即可,
- 共享配置选项。HTTP端口和Transport端口,会默认从9200/9300开始依次加1
- 默认同一台机器上只允许运行一个节点
- 即配置选项node.max_local_storage_nodes默认为1
- 在不同机器上启动节点时,
- 需要配置单播主机列表,discovery.zen.ping.unicast.hosts
- 单播列表不需要包含你的集群中的所有节点,
- 它只是需要足够的节点,节点间可以通过【Discovery】来发现所有节点。
- 认识Development vs. production mode,
- Elasticsearch会Publish HTTP服务 和 Transport服务
- HTTP服务,用于提供REST API服务
- Transport服务,用于节点之间的通信。
- 注意:Elasticsearch会bind到一个address上。
- 由于本地存在至少两个以上的network interface(网卡接口),
- 一个是lo本地回环接口,一个是ens33。
- 一个网卡接口就是一个独立的网络节点。有相应的ip等
- Rebalancing/水平扩容
- 本质上,是基于分片的重新平衡分配
- 最大扩容场景,是每个node,拥有一个shard
- 分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力
- 本质上,是基于分片的重新平衡分配
- 应对故障:
- 如果主节点缺失,则选举一个新的主节点
- 如果主分片缺失,则将副本分片提升为主分片
- 面向对象编程语言如此流行的原因之一
- 是对象帮我们表示和处理现实世界具有潜在的复杂的数据结构的实体
- 我们以行和列的形式存储数据到关系型数据库中,相当于使用电子表格。
- 正因为我们使用了这种不灵活的存储媒介导致所有我们使用对象的灵活性都丢失了
- 将 对象按对象的方式来存储,
- 基于json的文档数据库,应运而生。。
- 所以,MongoDB就是文档数据库界的MySQL。。
- 对象的json序列化和json反序列化
- 索引名,必须小写
- Mapping type大小写都行
- 不能以【_】开头
- 以【.】开头的index,都是系统添加的index。
- Endpoint和元数据(meta_data),都是以【_】开头
- 返回文档的一部分
- _source 参数,指定要返回的字段
- _source 端点,只返回 _source 字段
- 检查文档是否存在
- curl -i -XHEAD http://localhost:9200/website/blog/123
- 如果文档存在, Elasticsearch 将返回一个 200 ok 的状态码:
- 注意:使用【-i】选项,让curl命令的输出结果包含response header
- curl -i -XHEAD http://localhost:9200/website/blog/123
- 文档是不可变的:
- 他们不能被修改,只能被替换,
- 更新文档,是将旧文档标记为已删除,并增加一个全新的文档
- 文档是标记删除。不是立即删除。
- 随着你不断的索引更多的数据,Elasticsearch 将会在后台清理标记为已删除的文档。
- DELETE文档,即使不存在( Found 是 false ), _version 值仍然会增加。
- 并发控制
- 悲观锁
- 悲观假设资源会被修改,使用互斥锁,锁住共享资源
- 乐观锁
- 乐观假设资源不会修改,使用一定机制(比如version)检测是否已被修改,并采取一定措施(比如重试)处理资源已被修改的情况
- 悲观锁
- 外部版本号(version_type=external)的处理方式和内部版本号不同,
- 内部版本号,Elasticsearch 检查当前 _version 和version参数指定的版本号 是否相同,
- 外部版本号,则是检查当前 _version 是否 小于 version参数指定的版本号 。
- 如果请求成功,外部的版本号作为文档的新 _version 进行存储。
- _update API
- doc 参数
- 覆盖现有的字段,增加新的字段
- script参数
- 使用脚本更新。参考Scripting
- params参数,提供脚本可引用的外部变量
- upsert 参数
- 指定如果文档不存在就应该先创建它
- 并发控制
- update API内部执行过程
- 先get文档,标记删除旧文档,修改后再index新文档
- 内部使用version进行乐观锁控制,
- 可以指定 retry_on_conflict参数,来指定发生冲突后,重试的次数。
- 默认值是0
- 可以指定 retry_on_conflict参数,来指定发生冲突后,重试的次数。
- update API内部执行过程
- doc 参数
- 只创建新文档
- 默认PUT index API,存在则覆盖
- 只创建文档,否则报错
- _create端点 或者 op_type=create
- 每个子请求都是独立执行。单个请求失败与否不会影响其他请求
- URI中的index、type,可以在各个子请求的Metadata中,覆盖
- 批量请求的大小有一个最佳值,
- 大于这个值,性能将不再提升,甚至会下降
- 找到这个 最佳点 :通过批量索引典型文档,并不断增加批量大小进行尝试
- 路由一个文档到一个分片中
- 文档要存储到的shard 编号计算公式: shard = hash(routing) % number_of_primary_shards
- routing默认是文档的 _id ,可以通过routing参数自定义
- 这就解释了为什么我们要在创建索引的时候就确定好主分片的数量 并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。
- 文档要存储到的shard 编号计算公式: shard = hash(routing) % number_of_primary_shards
- 文档变更相关的document API,要求有足够的副本可用
- consistency参数
- 默认值为 quorum ,即规定数量( int( (primary + number_of_replicas) / 2 ) + 1)
- 即必须有quorum数量的副本(含primary)分片可用,才能执行。
- 否则等待 timeout参数的时间(默认一分钟),超时后,即返回失败
- consistency参数
- 基于文档的复制
- 当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。
- 基于协调节点/发散收集、以及分片搜索的工作方式
- 单索引,单类型搜索的性能跟多索引,多类型基本等价
- 性能取决于分页大小、查询复杂度、聚合等因素
- 一个请求经常跨越多个分片,每个分片都产生自己的排序结果,这些结果需要进行集中排序以保证整体顺序是正确的
- 假设在一个有 5 个主分片的索引中搜索。 当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给 协调节点 ,协调节点对 50 个结果排序得到全部结果的前 10 个。
- 在分布式系统中深度分页的问题
- 现在假设我们请求第 1000 页--结果从 10001 到 10010 。所有都以相同的方式工作除了每个分片不得不产生前10010个结果以外。 然后协调节点对全部 50050 个结果排序最后丢弃掉这些结果中的 50040 个结果。
- 这就是 web 搜索引擎对任何查询都不要返回超过 1000 个结果的原因。
- query string 需要的 百分比编码 (即URL编码)
- _all field
- Elasticsearch 可以在文档所有字段中查找结果
- 当索引一个文档的时候,Elasticsearch 取出所有字段的值拼接成一个大的字符串,作为 _all 字段进行索引。
- _all may no longer be enabled for indices created in 6.0+, use a custom field and the mapping copy_to parameter
- 获取Mapping(类似数据库表结构 schema定义)
- GET /index/_mapping/type
- Elasticsearch 中的数据可以概括的分为两类:精确值和全文。
- 精确值很容易查询。结果是二进制的:要么匹配查询,要么不匹配
- 全文域(full text field)的查询,一般涉及相关性
- 还希望搜索能够理解我们的 意图 (比如同义词),由Analyzer实现
- 字段搜索方式基于其Mapping 而不同:
- 结构化搜索(针对结构化类型的field,如keyword、date、number等)
- 精确查询、范围查询
- 可以用于sort、aggs等操作
- 注意: Exact values (like numbers, dates, and keywords) have the exact value specified in the field added to the inverted index in order to make them searchable.
- 全文搜索,针对text类型的field
- 由Analyzer分析成tokens(terms),然后加入倒排索引
- Multi-fields
- 可以对同一个字段,指定多个Mapping
- 结构化搜索(针对结构化类型的field,如keyword、date、number等)
- 这意味着如果你通过引号( "123" )索引一个数字,它会被映射为 string 类型,而不是 long。
- 但是,如果这个域已经映射为 long ,那么 Elasticsearch 会尝试将这个字符串转化为 long ,
- string类型已经deprecated,
- 全文string,应定义为text类型
- 使用index参数,来控制当前字段是否被索引(Analysis)
- 精确值string,应定义为keyword类型
- 全文string,应定义为text类型
- 多值域(multi-value fields)
- there is no dedicated array type.
- Any field can contain zero or more values by default,
- which means , all values in the array must be of the same datatype
- 空域(null 值或者zero个值 )将不会被索引
- there is no dedicated array type.
- 内部对象(Json嵌套对象)的映射
- Elasticsearch 会Dynamic Mapping内部对象为 object(type属性) ,并在同级的 properties 属性下列出内部域
- 内部对象 扁平化处理 来实现索引
- Lucene 不理解内部对象。 Lucene 文档是由一组键值对列表组成的。
- 为了能让 Elasticsearch 有效地索引内部类,它把我们的文档转化成【内部域全路径名】-【内部域值】这样的key-value形式
- 内部对象数组,扁平化 加 multi-value 形式处理。
- 默认使用_score(相关性得分)排序
- 如果指定 sort 参数排序,
- _score 不被计算(都是 null ), 因为它并没有用于排序。
- 如果无论如何你都要计算 _score , 你可以将track_scores 参数设置为 true 。
- 在每个结果中有一个新的名为 sort 的元素,它包含了我们用于排序的值
- _score 不被计算(都是 null ), 因为它并没有用于排序。
- 如果指定 sort 参数排序,
- multi-valued字段的排序:
- 这些值并没有固有的顺序
- 使用 mode 参数,指定聚合方式( min 、 max 、 avg 或是 sum)
- Put Mapping API形式:
- PUT index_name/_mapping/type_name {“properties”:{...}}
- allows you to add a new type to an existing index 或者update mapping type
- 区分, Create Index API中 create mapping的情况
- Elasticsearch 的相似度算法
- 检索词频率(TF)
- 检索词在该字段出现的频率越高,相关性也越高
- 反向文档频率(IDF)
- 检索词 在倒排索引上所有文档的 `tweet` 字段中出现的次数越高,相关性越低。(即常用词的权重低)
- 字段长度准则( fieldNorm)
- 检索词占所在字段长度比例,占比越高,相关性越高
- 检索词频率(TF)
- 单个查询可以联合使用 TF/IDF 和其他方式,
- 比如短语查询中检索词的距离或模糊查询里的检索词相似度
- 复合查询语句 ,
- 每个查询子句计算得出的评分会被合并到总的相关性评分中
- _search API explain 参数
- 返回详细的评分计算信息
- JSON 形式的 explain 描述是难以阅读的, 但是转成 YAML 会好很多,只需要在参数中加上 format=yaml 。
- explain api
- 请求路径为 /index/type/id/_explain
- 有了一个 description 元素,提示匹配失败的原因
- 缺省的搜索类型 query then fetch
- query 过程就是协调节点,
- 转发搜索请求到目标索引的所有分片的某个副本中
- 分别在各个分片内执行搜索,排序产生分片级优先队列
- 一个 优先队列 是一个存有 top-n 匹配文档的有序列表
- 优先队列的大小取决于分页参数 from 和 size
- 每个分片返回各自优先队列中所有文档的 ID 和排序值给协调节点,然后合并产生全局优先队列
- 一个 优先队列 是一个存有 top-n 匹配文档的有序列表
- fetch 过程就是协调节点
- 根据全局优先队列和分页参数 from 和 size 确定要fetch的文档,
- 然后转发 multi-get 请求获取文档内容。
- 最后收集后返回结果
- query 过程就是协调节点,
- 实际上, “深分页” 很少符合人的行为。
- 当2到3页过去以后,人会停止翻页,并且改变搜索标准。
- 会不知疲倦地一页一页的获取网页直到你的服务崩溃的罪魁祸首一般是机器人或者web spider。
- preference (偏好)参数
- 允许 用来控制由哪些分片或节点来处理搜索请求
- 设置 preference 参数为用户会话ID,来解决 bouncing results 问题(结果跳跃)
- 想象一下有两个文档有同样值的timestamp 字段,搜索结果用 timestamp 字段来排序。
- 由于搜索请求是在所有有效的分片副本间轮询的,那就有可能发生主分片处理请求时,这两个文档是一种顺序, 而副本分片处理请求时又是另一种顺序。
- 就会造成,每次用户刷新页面,搜索结果表现是不同的顺序
- timeout参数:
- 应当注意的是 timeout 不是停止执行查询,它仅仅是告知正在协调的节点返回到目前为止收集的结果并且关闭连接。
- 搜索的返回结果会用属性 timed_out 标明分片是否返回的是部分结果
- 很可能查询会超过设定的超时时间。
- 在查询文档前需要做文档评估,
- 这个阶段并不考虑超时设置,所以太长的评估时间会导致超过超时时间
- 超时检查是基于每文档做的
- 一次长时间查询在单个文档上执行并且在下个文档被评估之前不会超时
- 这也意味着差的脚本(比如带无限循环的脚本)将会永远执行下去。
- 在查询文档前需要做文档评估,
- 通过指定几个 routing 值来限定只搜索几个相关的分片
- scroll query(游标查询)
- 游标查询分两个过程:
- the initial search request( 查询初始化)
- 除了指定 scroll=1m 参数来保持游标窗口外,其他都是原来的搜索选项
- 查询初始化 会取当前时间点的快照数据
- 使用 scroll API 来 retrieve the next batch of results.
- POST或GET /_search/scroll
- scroll=1m 和 scroll_id ( retrieved from previous scroll request)
- POST或GET /_search/scroll
- the initial search request( 查询初始化)
- 使用 _doc 排序来提升性能
- 假如不考虑排序,可以使用 "sort" : ["_doc"]
- 这个指令让 Elasticsearch 仅仅从还有结果的分片返回下一批结果
- 游标查询分两个过程:
- setting、mapping的update API
- Endpoint都是_setting、_mapping/type
- mapping是type级别的(先前版本,可以有多个type),需指定type
- 创建的时候,都是使用的 Create Index API
- Endpoint都是_setting、_mapping/type
- 不允许通过指定 _all 或通配符来删除
- action.destructive_requires_name: true
- 自定义index级别的Analyzer
- 可以在 analysis 下 自定义index级别的 character filters、tokenizers、token filters ,或者使用内置的
- 用于定义接下来的 analyzer
- 然后在mapping中,对相关field指定Analyzer
- 可以在 analysis 下 自定义index级别的 character filters、tokenizers、token filters ,或者使用内置的
- 但是不能添加新的分析器或者对现有的字段做改动。
- 如果你那么做的话,结果就是那些已经被索引的数据就不正确, 搜索也不能正常工作。
- 所以需要重建索引: Reindex API
- 索引 别名( Index Aliases)
- 就像一个快捷方式或软连接,可以指向一个或多个索引
- 可以解决, 重建索引时必须更新索引名称 的问题
- 搜索是 近 实时的
- 文档的 CRUD (创建-读取-更新-删除) 操作是 实时 的
- 一个Lucene索引,即Elasticsearch的一个分片。
- 一个Lucene索引由若干个segment和一个commit point构成
- 每一 段(segment) 本身都是一个倒排索引
- 倒排索引被写入磁盘后是 不可改变 的,即segment是不可改变的
- commit point是一个记录当前索引下,所有segment的文件
- 包含一个 .del 文件,文件中会列出这些被标记删除的文档
- 一个Lucene索引由若干个segment和一个commit point构成
- 新的segment的产生:
- 新文档(index、update)被收集到In-Memory Buffer
- 将In-Memory Buffer中的内容会首先写入可被搜索但还没有提交的segment(新段),
- 这个过程叫做refresh
- 默认 refresh_interval 是1s
- 所以Elasticsearch search是近实时的(Nearly Real Time)
- 可以手动刷新 Refresh
- refresh_interval 是Dynamic setting。
- 可以动态关闭refresh(设置-1)
- 新段提交,commit point文件记录新段
- 提交(Commiting)一个新段到磁盘需要一个 fsync 来确保段被物理性地写入磁盘,这样在断电的时候就不会丢失数据。
- 但是 fsync 操作代价很大
- 为防止两次提交之间的更新操作丢失,
- Translog文件 有多安全
- 文件被 fsync 到磁盘前,被写入的文件在重启之后就会丢失
- 默认Translog在每次写请求之后,同步执行一次fsync
- (即 "index.translog.durability": "request")
- 使用异步的 fsync ,例如每5s异步执行一次fsync
- 存在风险: 可能丢失掉 5s内的数据
- 默认Translog在每次写请求之后,同步执行一次fsync
- 文件被 fsync 到磁盘前,被写入的文件在重启之后就会丢失
- 段合并
- segment是不可变的。
- 每refresh一次就会建立一个新segment
- segment越多,搜索性能越差 。
- Elasticsearch内部存在【段合并】进程,
- 自动将小段合并成大段
- 段合并的时候标记删除的文档将 从文件系统中清除。
- optimize API用于手动 强制段合并
- 它会将一个分片强制合并到 max_num_segments 参数指定大小的段数目
- (通常减少到一个)
- 注意:用 optimize API 触发段合并的操作一点也不会受到任何资源上的限制。
- 这可能会消耗掉你节点上过多的资源, 而无法处理搜索请求
- segment是不可变的。