# elasticsearch notes **Repository Path**: xsu/elasticsearch-notes ## Basic Information - **Project Name**: elasticsearch notes - **Description**: elasticsearch 学习笔记 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2 - **Created**: 2020-11-16 - **Last Updated**: 2023-09-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 学习 [Elasticsearch核心技术与实战](https://time.geekbang.org/course/detail/100030501-104929), 做的简单的笔记, 加上后期翻文档理解的 # 安装上手 ## 前期准备 编辑器使用vscode, 并安装好以下插件 - Docker - Elasticsearch for VSCode - Markdown All in One - TabNine - Remote SSH/WSL 如果时远程开发的话 其实 elasticsearch 的调试可以使用 `kibana dev tool`, 也可以用 vscode 的 `Elasticsearch for VSCode` 目录说明 - abcDev 里面存放例程 - elasticsearch ES的dockerfile目录 - kibana dockerfile - logstash dockerfile - sample 存放 .es 文件, 可以用vscode(装好Elasticsearch for VSCode插件)执行, 里面有注释和详细的操作流程 ## 在 Docker 容器中运行 Elasticsearch,Kibana 和 Logstash - elasticsearch: - ik分词器, - 动态同义词 - 备份所需的配置 等 - kibana: - 已经配置为中文 - logstash: - beats, file, jdbc, - json处理, ruby处理, geoip处理, 字段修改处理, csv处理 第一次启动创建网络和磁盘 ``` docker network create app docker volume create nginx-logs ``` 启动ELK环境 ``` docker-compose up ``` docker pull镜像慢的话, 有两种方案 - 设置国内镜像 - 设置国外代理 我这里使用国外代理 ``` sudo mkdir -p /usr/lib/systemd/system/docker.service.d cd /usr/lib/systemd/system/docker.service.d ``` ``` sudo vim http-proxy.conf ``` 填入以下内容 ``` [Service] Environment="HTTP_PROXY=192.168.100.188:7890" ``` ``` sudo vim https-proxy.conf ``` 填入一下内容 ``` [Service] Environment="HTTPS_PROXY=192.168.100.188:7890" ``` 根据自己的需求改ip和host 查看结果 ``` > systemctl show --property=Environment docker Environment=HTTP_PROXY=192.168.100.188:7890 HTTPS_PROXY=192.168.100.188:7890 ``` # elasticsearch 入门 ## 基本概念:索引,文档和REST API es新版本已经把 Type 标记为 弃用 索引 -> table (es支持sql查询, sql中就是把索引当作表来用的) 文档 -> row 虽然并不是, 但是sql模块支持使用sql查询 [index.es](./sample/index.es) 其他: - 告别类型。迎接无类型 https://www.elastic.co/cn/blog/moving-from-types-to-typeless-apis-in-elasticsearch-7-0 - cat APIs: https://www.elastic.co/guide/en/elasticsearch/reference/current/cat.html ## 基本概念:节点,集群,分片及副本 ES节点: 运行的ES实例 ES集群: 由cluster-name相同的若干节点组成 常用节点类型: - master节点 - data节点 - ingest节点 - coordinating节点 分片, 副本 - 分片: 把一个索引分成几片, 主分片 - 副本: 主分片的备份, 提高吞吐量, 提高容错率 其他 - Node: https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html ## 文档的基本CRUD和批量操作 [crud.es](./sample/crud.es) 文档: - Document APIs https://www.elastic.co/guide/en/elasticsearch/reference/current/docs.html 在导入时, 可以参考 ``` curl -X POST "elasticsearch:9200/_bulk" -H 'Content-Type: application/json' --data-binary @data.json ``` ## 倒排索引和正排索引 倒排索引: 使用词(term或者token)来索引文档id 正排索引: 使用文档id来索引词 文档: - 倒排索引 https://www.elastic.co/guide/cn/elasticsearch/guide/current/inverted-index.html ## search 搜索时用户和搜索引擎的对话 相关性 - Precision 查准率, 尽可能返回较少的无关文档 - Recall 查全率, 可怜返回较多的相关文档 - Ranking, 是否能够按照相关度进行排序 es的查询和相关的参数改善搜索的Precision和Recall ### URI search ``` GET /movies/_search?q=2012&df=title&sort=year:desc&from=0&size=20&timeout=1s { "profile": true } ``` - q 指定查询语句, 使用Query string syntax - df 默认字段, 不指定时, 会对所有字段进行查询 - sort排序 - from 和 size 分页 - profile, 可以查看查询时如何被执行的 语法 - 指定字段和泛查询: q=title:2012 / q=2012 - term和phrase: beautiful mind等效于 beautiful OR mind / "beautiful mind" 等效于 beautiful and mind, 并且前后顺序保持一致 - 分组与引号: title:(beautiful and mind) / title:"beautiful mind" - 布尔操作: AND OR NOT或者 && || !, 必须大写 - 分组: +表示must -表示must_not: title:(+beautiful -mind) - 范围查询:[]闭区间, {}开区间, year:{2010 TO 2020], year:[* TO 2020] - 算数符号: year:>2010, year:(>2010 && <=2020), year:(+>2010 && +<=2020) // 在vscode里面, uri上有空格, 执行不了, 需要复制到 `kibana dev tool` 里面执行 [search with uri](./sample/search_with_uri.es) ### DSL [search](./sample/search.es) ## 文本分析 Analysis: 通过分析器进行分词 `Analysis` 是把文本转换为词(`term/token`)的过程, text->term/token, 即分词 `Analysis` 具体是通过 `Analyzer` 来实现的, text -> `Analyzer` -> term/token `Analyzer` 由3部分组成, - [Character filters](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html) - [Tokenizer](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html) - [Token filter](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html) text --> `Character filters`(预处理, 最少0个) --> `Tokenizer`(分词, 1个) --> `Token filter`(后处理, 最少0个) --> term/token Token filter后处理: 大小写转换, 去掉停用词(a, and, the, 中文中的感叹词等), 增加同义词, 增加pinyin 等 内置分析器(都是由内置的Character filters, Tokenizer, Token filter组成) - Standard Analyzer 默认分词器,按词切分,小写处理 - Simple Analyzer – 按照非字母切分(符号被过滤),小写处理 - Stop Analyzer – 小写处理,停用词过滤(the,a,is) - Whitespace Analyzer – 按照空格切分,不转小写 - Keyword Analyzer – 不分词,直接将输入当作输出 - Patter Analyzer – 正则表达式,默认 \W+ (非字符分隔) - Language – 提供了30多种常见语言的分词器 - Customer Analyzer 自定义分词器 中文分词器 - [ik](https://github.com/medcl/elasticsearch-analysis-ik) ik提供两种Analyzer - ik_smart - ik_max_word ik安装, 增加中文词, 和同义词 ``` elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.9.2/elasticsearch-analysis-ik-7.9.2.zip ``` 配置远程词库 找到 `IKAnalyzer.cfg.xml` (elasticsearch/config/analysis-ik/或者elasticsearch/plugins/analysis-ik/config) 配置好本地或者远程的词库, 我这里配置远程词库 remote_ext_dict为 http://es-update/ext.dic [analyzer](./sample/analyzer.es) 文档: - 文字分析 https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis.html ## Mapping 类似数据库的 schema, 定义字段的类型和使用analyzer 如果直接写入文档, 不手动定义mapping, es会 `自动识别`, 但是为了让`检索更加高效`, 最好还是定义好mapping或者mapping模板 类型自动识别 | JSON | ES类型 | |--------|----------------------------------------------------------------------| | 字符串 | 匹配日期date -> 匹配float或long(默认关闭) -> text并增加keyword子字段 | | 布尔值 | boolean | | 浮点数 | float | | 整数 | long | | 对象 | object | | 数组 | 由第一个非空数值的类型决定 | | 空值 | 忽略 | 是否能动态增加新的字段到索引: index.mappings.dynamic - true, 可以, 默认值 - false, 不可以, 但是字段仍然会被存储, 出现在 `_source` 中 - strict, 严格模式, 直接报错 任何对类型的修改, 必须reindex 精确值excat values和全文本full text - 数字 日期 具体的字符串text.keyword, 不分词 - es中的text, 分词 ### 字段类型 支持的类型较多, 这里只列出常用的, 在设置索引的时候 https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html #### Common types 通用类型 - binary: base64编码后的字段, 默认不存储和不可搜索 - boolean: true(true, "true"), false(false, "false", "") - keywords - keyword - numbers - 整数: long, integer, short, byte - 浮点数: double, float, half_float, scaled_float - dates - date 支持自定义格式, 支持多种格式, 支持毫秒级long/秒级int 时间戳 - date nanos - alias: 给字段取别名, 但是不能给别名取别名 #### Object and relational types 对象和关联类型 - object: json对象 - flattened: 把json对象拉平来查询 - nested: 对象数组(每个对象必须有的字段一致) - join #### Structured data types 结构化数据类型 - range: integer_range, float_range, long_range, double_range, date_range, ip_range - ip: 支持v4和v6 #### Aggregate data types 聚合数据类型 #### Text search types 文本搜索类型 - text: 会分词 #### Spatial data types 空间数据类型 - geo_point - geo_shape - point - shape #### Arrays 数组内部所有的类型必须一致 #### Multi-fields 多字段类型 以不同的方式来索引相同的字段, 比如把一个text, 先以text方式分词, 以方便全文索引, 然后又以keyword的方式来排序或者聚合 比如厂商名字实现精确匹配 使用不同的analyzer - 不同语言 - pinyin字段搜索 - 还支持为搜索和和索引指定不同的 analyzer > null 是missing field, 空数组也是 missing field ### 索引参数 https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-params.html 这里只列出常用的索引参数 #### fields 建立Multi-fields, 以不同的方式来索引相同的字段 ``` PUT my_index { "mappings": { "properties": { "name": { # 针对 name 字段,设置为text分词 "type": "text", "fields": { # 针对 name.keyword 字段,设置为 keyword 不分词 "keyword": { "type": "keyword", "ignore_above": 256 } } } } } ``` #### analyzer search_analyzer 设置字段的 `analyzer` ``` PUT my_index { "mappings": { "properties": { "content": { # 针对 content 字段,使用 ik_smart "type": "text", "analyzer": "ik_smart" } } } ``` 设置字段的 `search_analyzer` ``` PUT my_index { "mappings": { "properties": { "content": { # 搜索的时候使用 `ik_smart`, 索引的时候使用 `ik_max_word` "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart" } } } ``` 设置默认 `analyzer` ``` PUT my_index { "settings": { "analysis": { "analyzer": { "default": { "type": "simple" }, "default_search": { "type": "simple" } } } } } ``` #### boost 增加字段权重, boost>1 -> 增加权重, 0< boost < 1,减小权重, boost < 0, 负相关 #### dynamic 是否能动态增加新的字段到索引 - true, 可以, 默认值 - false, 不可以, 但是字段仍然会被存储, 出现在 `_source` 中 - strict, 严格模式, 直接报错 ``` PUT my-index-000001 { "mappings": { "dynamic": false, "properties": { "user": { "properties": { "name": { "type": "text" }, "social_networks": { "dynamic": true, "properties": {} } } } } } } ``` #### enabled 默认true, 只存储, 不做搜索和聚合分析, 只能用于顶级字段映射和对象字段 ``` PUT my-index-000001 { "mappings": { "properties": { "session_data": { "type": "object", "enabled": false } } } } ``` #### index 默认true, 是否构建倒排索引, false的话字段不可被搜索, 可以节省磁盘额存储空间, 因为倒排索引不会被创建 ``` PUT my-index-000001 { "mappings": { "properties": { "session_data": { "type": "object", "index": false } } } } ``` #### index_options 控制倒排索引记录的内容, 有4个级别: - docs, 只记录doc id - freqs, 记录 doc id term frequency - positions, 记录 doc id, term frequency, term position - offsets, 记录 doc id, term frequency, term position, character offset text 默认positions, 其他默认docs 记录内容越多, 占用空间越大 #### norms 默认true, 是否存储归一化的相关参数, 如果字段仅用于过滤和聚合分析, 可关闭 #### null_value 默认null为missing, 但是需要对null值进行搜索 #### doc_values 用于聚合排序分析, 默认为true #### fielddata 默认true, 为text类型启动fielddata, 以实现排序和聚合 #### store 默认false, 是否存储该字段值 #### copy_to 可以把多个字段合并为一个新的字段, 但是不出现在 _source 中 比如, 在合成姓名的时候, 合成商品name(名称 + 销售参数 等) ``` PUT my-index-000001 { "mappings": { "properties": { "first_name": { "type": "text", "copy_to": "full_name" }, "last_name": { "type": "text", "copy_to": "full_name" }, "full_name": { "type": "text" } } } } ``` ### Dynamic Mapping 动态映射 就是因为这个特性, 可以不必先创建索引, 定义映射类型和字段, 而直接开撸, 会动态映射 es内部默认的模板, 会匹配那些是text, date, 数字, 当然也可以自定义模板 #### 动态映射模板 这是经常用的, 把匹配到的long类型, 映射为 `integer`, 把匹配到的string类型, 映射为text类型, 并且建立一个 `multi-fields`, 不分词, 最大长度256 [dynamic mapping](./sample/dynamic_mapping.es) mapping建议: 为了减少输入的工作量, 减少出错的概率: 1. 创建一个临时的index, 写入样本输入 2. 访问mapping API获取临时文件的动态mapping定义 3. 根据实际情况修改, 使用改配置文件创建索引 4. 删除临时索引 文档: - mapping: https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html ## Index Template 索引模板 索引模板是告诉es在创建索引时如何配置索引的一种方式, 日志类索引, 比如 `log-*-*` - 索引模板仅仅在被创建时才会产生作用, 修改模板不会影响已创建的模板 - 可以设置多个模板, 这些设置会被merge, 冲突的会根据 priority 来决定使用那个(越大优先级越高) 一个索引模板 `_index_template` 可以由最小的积木 `_component_template` 组成 模板仿真 由于一个索引模板, 可以由多个组件模板组成, 并且索引模板包括自身的设置, 这里面可能有覆盖现象, 可以使用模版仿真, 得到最终的索引, 重复的部分将会放到返回的overlapping字段内 例子 [index template](./sample/index_template.es) 文档: - Index Template: https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html - Index Module: https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html # 搜索 - 基于term的搜索: term是表达语义的最小单位, 不分词, 精确匹配 - 基于全文本的查找: 会分词, 算分 term搜索可以转换为`constant_score`把, 查询转换为一个filter, 避免算分, 并利用缓存, 提高查询性能 文档 - https://www.elastic.co/guide/en/elasticsearch/reference/current/term-level-queries.html ## 结构化搜索 [structured search](./sample/structured_search.es) ## 搜索的相关性算分 相关性, 相关性算分 词频TF(Term Frequency) - 检索词在一篇文档中出现的频率: 检索词出现的次数/文档的总字数 - 度量一条查询和结果文档相关性的简单算法就是将搜索的每一个词的TF进行相加 - 停止词Stop Word, 停止词在文档中出现 很多次, 但对相关度几乎没有作用, 不考虑他的TF 逆文档频率IDF(Inverse Document Frequency) - DF: 检索词在所有文档中出现的频率 - IDF: log(全部文档数/检索词出现过的文档总数) - TF-IDF就是将TF求和变成加权求和: 区块链的应用 -> TF(区块链)*IDF(区块链) + TF(应用)*IDF(应用) | | 出现的文档数 | 总文档数 | IDF | |--------|--------------|---------|-----------------| | 区块链 | 200W | 10亿 | log(500) = 8.96 | | 的 | 10亿 | 10亿 | log(1) = 0 | | 应用 | 5亿 | 10亿 | log(2) = 1 | 出现在越多的文档中, 加权越高 lucene评分公式 score(q, d) = coord(q,d) * queryNorm(q) * $\displaystyle \sum^{}_{t\ in \ q}(tf(t\ in\ d) * idf(t)^2 * boost(t) * norm(t,d))$ - score相关性打分函数 - q: 查询语句 - d: 匹配的文档 - t: 分词后的词项 - tf: tf计算 - idf: idf计算 - boost: boosting提升 - norm: field length norm计算, 文档越短, 相关性越高 es5开始, 默认算法改为BM25 和经典的TF-IDF相比, 当TF无限增加, BM25算法会趋于一个数值 [test score](./sample/testscore.es) ## query context 和 filter context query context -> How well does this document match this query clause? filter context -> Does this document match this query clause? bool 查询 | 子句 | desc | |----------|----------------------------| | must | 必须匹配, 贡献算分 | | should | 选择性匹配, 贡献算分 | | must_not | filter context, 必须不匹配 | | filter | filter context, 必须匹配 | constant score filter 聚合 ## 单字符串多字段查询: Disjunction Max Query [dis max query](./sample/disjunction_max_query.es) https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-dis-max-query.html ## 单字符串多字段查询: multi match - 最佳字段(best fields): 字段之间相互竞争又相互关联. 例如title和body这样的字段. 评分来自最匹配的字段 - 多数字段(most fields): 处理英文内容时: 一种常见的手段时在主字段(English Analyzer)抽取词干, 加入同义词, 以匹配更多的文档. 相同的文本加入子字段(standard Analyzer),以提供更加精确的匹配. 其他字段作为 匹配文档提高相关度的信号. 匹配字段越多越好 - 混合字段(cross field): 对于某些实体, 例如人名, 地址, 图书信息, 需要在某个字段中确定信息, 单个字段只能作为整体的一部分, 希望在任何这些列出的字段中尽可能多的词, 比如 firstname lastname 在两个字段中, 但是要搜索这个人的名字, 可以在定义mapping 时使用copy_to, 但这样会占用额外的空间 [multi match](./sample/multi_match.es) https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html ## 综合排序: Function Score Query 优化算分 在查询结束后, 对每一个匹配的文档进行一系列的重新算分, 根据新生成的分数进行排序 提供几种默认的计算分值的函数: - weight: 为每个文档设置一个简单而不被规范化的权重 - Field Value Factor: 使用改数值来修改 _score, 例如 将热度和点赞数作为算分的参考因素 - Random Score: 一致性随机函数, 为每一个用户使用一个不同的, 随机的算分结果 - 衰减函数: 以某个字段的值作为标准, 离这个值越近, 分数越高 - script score: 自定义脚本完全控制所需逻辑 [Function score query](./sample/function_score_query.es) https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html ## search模板 开发工程师, 性能工程师, 测试工程师解耦 [search template](./sample/search_template.es) 文档 - https://www.elastic.co/guide/en/elasticsearch/reference/current/search-template.html ## Term & Phrase Suggester [suggester](./sample/suggester.es) ## 自动补全和基于上下文的提示 Completion Suggester 提供 自动完成 auto complete 功能, 用户没输入一个字符, 就需要即时发送一个查询到后端查找匹配项 对性能苛刻, es采用了不同的数据结构, 并非通过倒排索引, 而是将analyze的数据编码成FST和索引存放在一起, FST会被ES整个加载进内存, 速度很快, 但是FST只能用于前缀查找 [auto complete](./sample/auto_complete.es) https://www.elastic.co/guide/en/elasticsearch/reference/current/search-suggesters.html ## sql 数据定义 | sql | elasticsearch | description | |------------------|---------------------|-------------------| | column | field | | | row | document | | | table | index | | | schema | / | es 没有对应的概念 | | database/catalog | cluster instance | | | cluster | cluster (federated) | | [sql](./sample/sql.es) ## 总结 # 聚合 近实时 数据分散在 多个分片上, Coordinating Node无法获取数据全貌 - 数据年不大时, 设置Primary Shard为1, 实现精准性 - 在分布式数据上, 设置shard_size, 提高精确度 聚合的范围是query查询出来的内容 # 备份 [backup](./sample/backup.es) # 索引及其生命周期管理 - Open/Close Index - Shrink Index: 缩小主分片数 会使用和源索引相同的配置(主分片数除外)创建新的索引 - 源分片数必须时目标分片数的倍数, 如果源分片数时素数, 那么目标分片数只能为1 - 如果文件系统支持硬链接, 会将segments硬链接奥目标索引, 性能更好 适用 - 索引保存的数据量比较小, 需要重新设定主分片数 - 索引从hot移动到warm, 需要降低主分片数 完成后可以删除源索引 限制: - 分片必须只读 - 所有分片必须在同一节点上 - 集群健康状态为green - Splite Index: 扩大主分片数 - - Rollup Index: 对数据进行处理, 重新写入, 减少数据量 一个时间序列索引的实际场景 Rollover Index: 索引尺寸或者时间操作一定值后, 创建新的 当满足一系列的条件, rollover api支持将一个alias指向一个新的index: - 最大建立时间 - 最大尺寸 - 最大数量 [rollover api](./sample/index.rollover.es) Index APIs: https://www.elastic.co/guide/en/elasticsearch/reference/current/indices.html ## 生命周期管理 Hot -> Warm -> Cold -> Delete - Hot 存在大量读写操作 - Warm 不存在写, 有被查询的需要 - Cold 数据不存在写, 读操作也不多 - Delete 索引不再被需要, 可以安全的删除 Elasticsearch Curator eBay Lifecycle Management Tool Index Lifecycle Management / ILM Hot -> Warm -> Cold -> Delete # 集群 ## 常见的集群部署方法 master eligible 低配置CPU, RAM, 磁盘 data node 高配置CPU, RAM, 磁盘 ingest node 高配置CPU, 中配置RAM, 低配置磁盘 coordinating node: - load balancers, 降低master和data node负载 - 负责gather和reduce master node 一般在生产环境配置3台 一个集群只有1太活跃的主节点(负责分片管理, 索引创建, 集群管理等操作) 如果master node 和数据节点或者coordinate节点混合部署 - 数据节点有相对大的内存占用 - coordinate节点有时候有开销很高的查询, 导致OOM 这些有可能影响master节点, 导致集群不稳定 ### 基本部署 增加节点, 水平扩展 - 磁盘容量无法满足需求或者磁盘读写压力大 -> 增加data node - 有大量的复杂查询和聚合, 需要提高查询性能 -> 增加coordinating节点, 并且在coordinating节点前面增加一个LB 读写分离 部署kibana 部署到coordinating节点 异地多活部署 数据多写, GTM分发读请求 ## Hot & Warm architechture - 数据通常不会有update操作, 适用与time based索引数据(生命周期管理), 同时数据量比较大 - 引入warm节点, 低配置大容量的机器存放老数据, 以降低部署成本 两类数据节点, 不同的硬件配置 - hot节点, 索引有新的文档写入, 通常使用ssd - warm节点, 索引不存在新数据写入, 同时也不存在大量的数据查询, 通常使用hdd [hot warm](./sample/hot_warm.es) ## 分片设计和管理 单个索引, 单个分片 不能实现 水平扩展 当分片数 > 节点数: - 一旦集群有新的数据节点加入, 分片就可以自动进行分配 - 分片在重新分配时, 系统不会有downtime 多分片的好处: 一个索引分布在不同的节点, 多个节点可以并行执行 - 查询可以并行执行 - 数据写入分散到多个机器, 提高数据写入的吞吐量 ### 例子 案例1: 每天1GB数据, 一个索引一个主分片, 一个副本分片, 需要保留半年数据, 接近360GB的数据量 案例2: 5个不同的日志, 每天创建一个日志索引, 每个日志创建10个主分片, 保留半年数据, 5 * 10 * 30 * 6 = 9000个分片 shard时es实现集群水平扩展的最小单位 过多的分片问题: - 每个分片时一个lucene的索引, 会使用机器资源. 过多的分片会导致额外的性能开销 - lucene indices / File descriptors / RAM / CPU - 每次搜索的请求, 需要从每个分片上获取数据 - 分片的meta信息由master节点维护, 过多会增加管理负担, 经验值 分片总数控制在10w以内 如果确定主分片数: 从出储的物理角度看: - 日志类应用, 单个分片不要大于50GB - 搜索类应用, 单个分片不要超过20GB 为什么要控制分片存储大小: - 提高update性能 - merge时, 减少所需资源 - 丢失节点后具备更快的恢复速度, 便于在集群内rebalancing 副本分片: - 提高系统可用性: 相应的查询请求, 防止数据丢失 - 需要占用和主分片一样的资源 副本分片对性能的影响: - 副本会降低数据的索引速度, 有几份副本就会有几倍的CPU资源消耗在索引上 - 会减缓对主分片的查询压力, 但会消耗同样的内存资源 - 如果机器资源充足, 提高副本数, 可以提高整体的查询的QPS 调整分片总数设定, 避免分配不均衡, 否则扩容的新节点没有数据, 导致新索引集中在新的节点, 热数据过于集中, 可能产生性能问题: - es分片策略会尽量保证节点上的分片数大致相同 - index.routing.allocation.total_shareds_per_node - cluster.routing.allocation.total_shareds_per_node ## 集群的容量规划 常见用例: - 搜索类: 基本固定大小的数据集 - 搜索的数据集增长相对缓慢, 可以网上的时候数据做下同步 - 日志类: 基于时间序列的数据 - 数据每天不断写入, 增长速度较快 - 可以搭配hot&warm架构做数据老化处理 硬件配置: - 选择合理的硬件, `数据节点` 尽可能使用SSD - 搜索等性能较高的场景, 使用SSD, 按照1:10的比例配置内存和硬盘 - 日志类和查询并发低的场景, 可以考虑使用HDD, 按照1:50的比例配置内存和硬盘 - 单节点数据控制在2TB以内, 最大不建议超过5TB - JVM配置机器内存的一半, JVM内存配置不建议超过32GB 部署方式: - 选择合理的部署方式, 合理部署data node, coordinating node, master node, ingest node - 如果考虑可靠性高可用, 建议部署3台 master eligible 节点 - 如果考虑复杂的查询和聚合, 考虑coordinating节点 案例: 固定的大小的数据集 - 一些案例L信息库, 产品信息 - 特征 - 被搜索的数据集大, 增长慢, 更关心搜索和聚合的读取性能 - 数据的重要性和时间无关, 更加关注搜索的相关度 - 估算索引的数据量, 然后确定分片 - 单个分片的数据不要超过20GB - 可以通过增加副本分片, 增加查询的吞吐量 基于时间 序列的数据 - 案例: 日志, 指标, 安全相关的events, 舆情分析 - 特征 - 每条数据都有时间戳, 文档基本不会被更新(日志和指标数据) - 更多的是查询新的数据, 对旧的数据查询较少 - 对数据的写入性能要求较高 - 创建 time-based索引 - 在索引的名称中增加时间信息, 按照每天,每周,每月的方式划分 - 好处: 更加合理的组织索引, 随着时间的推移, 便于对索引做老化处理 - 利用hot&warm架构, 备份和删除的效率高, (相对于delete by query 执行速度慢, 底层也不会立刻释放空间, merge由耗费资源) 写入时间序列的数据: - 基于Data Math方式 - 容易使用, 但是时间发生变化, 需要重新部署代码 ``` # PUT / es不擅长处理关联关系 es处理关联的方法: - 对象类型 - 嵌套对象 - 父子关联关系 - 应用端关联 ## update by query & reindex reindex 支持异步操作 数据重建: update by query , reindex - 索引mapping变更: 字段类型更改, 分词器和字典更新 - 索引setting变更: 如索引主分片变化 - 数据迁移 es不允许对已有字段的类型做修改: 正确做法是新建一个索引, 然后reindex reindex场景: - 修改索引主分片数 - 修改字段的mapping类型 - 集群内和集群之间迁移数据 - 注意: 1 必须enable `_source` 字段, 必须先设置新的索引的mapping op_type reindex的dest索引已经存在相同的文档, `dest` 索引的`op_type`设置为`create`, 就只会创建文档 ``` POST _reindex { "source": {"index": "old_index"}, "dest": {"index": "new_index", "op_type": "create"}, } ``` 跨集群reindex ## ingest pipeline & painless script 数据预处理 [ingest.es](./sample/ingest.es) ingest node 和 logstash 对比 ||logstash|ES| |---|---|---| |数据输入输出|支持从不同的数据源读取, 并写入不同的数据源|rest api读取, 写入es| |数据缓冲|简单的数据队列, 支持重写|不支持数据缓冲| |数据处理|支持大量插件, 支持定制开发|内置插件, 可以开发插件, 更新插件需要重启es| |配置和使用|增加了架构的复杂度|无需额外部署| https://cloud.tencent.com/developer/article/1354027 https://elasticstack.blog.csdn.net/article/details/105403249 ## painless [painless](./sample/painless.es) - 对文档字段进行加工 - 更新或者删除字段, 对你数据聚合操作 - script field: 对返回的字段提前进行计算 - function score: 对文档进行算分 - ingest pipeline总执行脚本 - reindex api, update_by_query时对数据进行处理 |上下文|语法| |--|--| |ingest|ctx.field_name| |update|ctx._source.field_name| |search & aggregation|doc["field_name"]| 脚本是会被缓存下来的, 不管是在执行的时候指定source, 还是用_script保存下来 文档 https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html ## 建模 如何建模 字段类型 -> 是否需要搜索及分词 -> 是否集合和排序 -> 是否需要额外存储 字段类型: 文本 - Text: 全文本字段, 会被analyzer分词, 默认不支持聚合分析和排序, 需要设置fielddata为true - Keyword: 不分词, 适用与 精确匹配, sorting 聚合 - 设置多字段类型, 默认会为文本类型设置多字段 字段类型: 结构化数据 - 数值: 能小尽量小, 能用byte不要用long - 枚举: 设置为keyword, 即使是数字也设置为keyword, 这样有更好的性能 - 其他: 日期,布尔,地理信息 检索 - 不需要检索, 排序和聚合分析: enable设置为false - 不需要检索, index设置为false - 需要检索, 可以设置存储粒度, 通过index_option, norms 聚合和排序 - 不需要检索, 排序和聚合分析: enable设置为false - 不需要排序或者聚合分析: doc_values / fielddata 设置为false - 更新频繁, 聚合查询频繁的keyword类型字段 推荐把 eager_global_ordinals 设置为true [model_sample](./sample/model_sample.es) ## 最佳实践 ### 如何处理关联关系 - object: 优先考虑 - nested: 数据包含多数值对象, 同时有查询需求 - child/parent: 关联文档更新非常频繁 > kibana 暂时不支持nested和parent/child, 如果利用kibana做数据分析时, 在建模时需要对此做取舍 ### 避免过多的字段 - 一个文档避免大量的字段(对象是扁平存储的) - 过多的的字段数不容易维护 - mapping信息保存在cluster state中, 数据量过大, 对集群性能有影响 - 删除或者修改数据需要reindex - 默认的最大字段数是1000, 可以设置index.mapping.total_fields.limit来修正 - 什么原因会导致文档有成百上千的字段? - dynamic(生产环境今年不要打开) - true: 未知字段自动加入 - false: 新字段不会被索引, 但是会保存到_source - strict: 写入失败 - strict - 可以控制字段级别 这个例子, 就是转换为nested [model_sample](./sample/model_sample2.es) 使用nested 可以有效的减少字段数量, 解决cluster state中保存过多meta信息问题, 但是导致查询语句复杂度增加, 且不利于在kibana中实现可视化分析 ### 避免使用正则查询 问题: - 正则, 通配符查询, 前缀查询属于term查询, 但是 `性能不够好`, 特别是通配符放在开头的 案例 文档中某个字段包含es版本信息, 例如 version: "7.9" 搜索所有的bug fix的版本? 每个主要版本好所关联的文档 这个例子, 就是把字符串转换为对象 [model_sample](./sample/model_sample3.es) ### 避免空值引起的聚合不准 例子 [null_aggs](./sample/null_agg.es) ### 为索引的mapping加入meta信息, 并且用git进行管理 mapping的设置非常重要, 需要从两个维度进行考虑: - 功能: 搜索 聚合 排序 - 性能: 存储的开销 内存的开销 搜索的性能 mapping设置是一个迭代的过程 - 加入新的字段很容易(必要时update_by_query) - 更新删除字段不允许(需要reindex重建) - 最好能对mappings加入meta信息, 更好的进行版本管理 迭代 - 把mapping文件上传到git进行管理 相关阅读 https://www.elastic.co/guide/en/elasticsearch/reference/current/general-recommendations.html https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-disk-usage.html https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html