Elasticsearch
Elasticsearch 是什么
Elasticsearch 是一个基于 Lucene 的搜索服务器. 它提供了一个分布式多用户能力的全文搜索引擎, 基于 RESTful Web 接口. Elasticsearch 是用 Java 语言开发的, 并作为 Apache 许可条款下的开放源码发布, 是一种流行的企业级搜索引擎. Elasticsearch 用于云计算中, 能够达到实时搜索, 稳定, 可靠, 快速, 安装使用方便. 官方客户端在 Java,.NET(C#),PHP,Python,Apache Groovy,Ruby 和许多其他语言中都是可用的. 根据 DB-Engines 的排名显示, Elasticsearch 是最受欢迎的企业搜索引擎, 其次是 Apache Solr, 也是基于 Lucene.
当然, Elasticsearch 不仅仅是 Lucene, 并且也不仅仅只是一个全文搜索引擎. 它可以被下面这样准确地形容:
一个分布式的实时文档存储, 每个字段可以被索引与搜索;
一个分布式实时分析搜索引擎;
能胜任上百个服务节点的扩展, 并支持 PB 级别的结构化或者非结构化数据;
主要用于全文搜索, 结构化搜索, 分析.
全文检索: 将非结构化数据中的一部分信息提取出来, 重新组织, 使其变得具有一定的结构, 然后对此有一定结构的数据进行搜索, 从而达到搜索相对较快的目的.
结构化检索: 传统 SQL 就是结构化检索.
由于 Elasticsearch 的功能强大和使用简单, 维基百科, 卫报, Stack Overflow,GitHub 等都纷纷采用它来做搜索. 现在, Elasticsearch 已成为全文搜索领域的主流软件之一.
0 Elasticsearch 和 MySQL 的对比
Mysql | ElasticSearch |
---|---|
database(数据库) | index(索引库) |
table(表) | type(类型) |
row(一行记录) | document(文档) |
column(列) | field(字段) |
schema(约束) | mapping(映射) |
Index
索引包含一堆有相似结构的文档数据, 比如商品索引, 订单索引. 可以理解为数据库,
一个 index 包含很多 document.
Type
每个索引都可以有一个或者多个 type,type 是 index 中的一个逻辑数据分类, 一个 type 下的 document, 都具有相同的 field, 可以理解为数据库中的表, 比如一个商品 index 下有生鲜商品 type, 日用品 type, 这两个 type 就具有不同的字段.
Document
文档是 es 中的最小数据单元, 一个 document 就是一条数据, 可以理解为表里的一行数据.
Field
列是 es 中的最小单位, 可以理解为表里的数据字段.
Mapping
数据如何存放到索引对象上, 需要有一个映射配置, 包括: 数据类型, 是否存储, 是否分词等.
索引区域和数据区域
逻辑上在 type 中存在索引区域和数据区域.
数据在存入 es 时, 先找到对应的 index(数据库), 再找到对应的 type(表), 将数据进行切词, 词语对应 document 的 id 号放入索引区域, 数据放入数据区域.
搜索时通过倒排索引, 在索引区域找到对应词语的 documentID, 去数据区域拿取数据进行返回.
1 Elasticsearch 安装
下载地址: https://github.com/elastic/elasticsearch/releases
百度网盘下载地址: https://pan.baidu.com/s/1yCH-DX9z2U3smW0_73Yozg
提取码: qh4m
推荐使用 7.4.2 版本, 下文使用 7.3.1 版本讲解安装
注意事项:
需要 jdk 环境 11 以上(使用 12)
只允许普通用户操作, 不允许 root 用户否则会抛出如下异常:
org.Elasticsearch.Bootstrap.StartupException: java.lang.RuntimeException: can not run Elasticsearch as root
安装准备
创建新用户 el
- # 添加用户
- useradd es
- # 修改密码
- passwd es
修改环境
- VIM /etc/security/limits.conf
- # 添加如下内容
- * soft nofile 65536
- * hard nofile 131072
- * soft nproc 4096
- * hard nproc 4096
- # 没报内存问题暂时不需要
- #* hard memlock unlimited
- #* soft memlock unlimited
- VIM /etc/security/limits.d/20-nproc.conf
- # 修改如下内容
- * soft nproc 4096
- VIM /etc/sysctl.conf
- # 在末尾添加
- vm.max_map_count=655360
- sysctl -p(输入该命令使配置生效)
- VIM /etc/systemd/system.conf
- DefaultLimitNOFILE=65536
- DefaultLimitNPROC=32000
- DefaultLimitMEMLOCK=infinity
reboot 重启系统
1.1 Linux 安装 java
百度网盘地址 https://pan.baidu.com/s/14Ew1cQwf0q72B_JsQ64wcw
提取码: ruqy
- #1 修改配置文件, 加入如下配置
- VIM /etc/profile
- #jdk
- export JAVA_HOME=/usr/local/src/jdk12/jdk-12.0.2
- export PATH=$JAVA_HOME/bin:$PATH
- export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
- #2 更新配置文件
- source /etc/profile
- #3 检查 java 版本
- java -version
1.2 ES 单机版安装
上传 tar 包到服务器
解压
tar -zxvf Elasticsearch-7.3.1-Linux-x86_64.tar.gz
修改解压后文件夹权限
chown -R es:es Elasticsearch-7.3.1
进入 conf 文件夹, 修改 Elasticsearch.YAML 文件
VIM Elasticsearch.YAML
- # 如下是修改及添加内容
- cluster.name: es-cluster #设置集群的名称
- node.name: master #修改当前节点的 hostname 名称
- path.data: /usr/local/src/Elasticsearch/Elasticsearch-7.3.1/data #修改数据路径
- path.logs: /usr/local/src/Elasticsearch/Elasticsearch-7.3.1/logs #修改日志路径
- Bootstrap.memory_lock: false #设置 ES 节点允许内存交换
- network.host: 192.168.74.10 #设置当前主机 IP
- discovery.seed_hosts: ["master"]
- # 如下两个配置为 es 服务器允许别的插件服务访问
- http.cors.enabled: true
- http.cors.allow-origin: "*"
进入 bin 目录, 切换之前建立的普通用户
- su es
- ./Elasticsearch
使用 restful 调用
curl -XGET http://192.168.192.11:9200
使用浏览器调用
1.3 ES 集群版安装
上传 tar 包到集群服务器
解压
tar -zxvf Elasticsearch-7.3.1-Linux-x86_64.tar.gz
修改解压后文件夹权限
chown -R es:es Elasticsearch-7.3.1
进入 conf 文件夹, 修改 Elasticsearch.YAML 文件(VIM 输入: set paste 在粘贴)
主节点
- # 设置集群的名称
- cluster.name: es-cluster
- # 当前节点的 hostname 名称
- node.name: master
- # 设置是否能成为主节点, false 是永远不可能成为主节点, true: 表示有可能成为主节点, 并不一定. 跟指定的参 数 cluster.initial_master_nodes 有关系. 当所有 node 都可为主节点时, 如果主节点宕机, 其他节点会再次选举一个新的主节点.
- node.master: true
- # 当前节点是否用于存储数据
- node.data: true
- # 修改索引存放路径
- path.data: /usr/local/src/Elasticsearch/Elasticsearch-7.3.1/data
- #修改日志存放路径
- path.logs: /usr/local/src/Elasticsearch/Elasticsearch-7.3.1/logs
- # 是否锁住 ES 节点内存交换
- Bootstrap.memory_lock: true
- # 监听 IP, 用于访问 ES
- network.host: 192.168.192.10
- #ES 对外提供的 http 监听端口
- http.port: 9200
- # TCP 的默认监听端口, 默认 9300
- transport.tcp.port: 9300
- # 设置这个参数来保证集群中的节点可以知道其它 N 个有 master 资格的节点. 默认为 1, 对于大的集群来说, 可以设置大一点的值(2-4)
- discovery.zen.minimum_master_nodes: 2
- #es7.x 之后新增的配置, 写入候选主节点的设备地址, 在开启服务后可以被选为主节点
- discovery.seed_hosts: ["master:9300","slave1:9300","slave2:9300"]
- discovery.zen.fd.ping_timeout: 1m
- discovery.zen.fd.ping_retries: 5
- # es7.x 之后新增的配置, 初始化一个新的集群时需要此配置来选举 master
- cluster.initial_master_nodes: ["master","slave1","slave2"] #主节点配置
- # 是否支持跨域, 是: true, 在使用 head 插件时需要此配置,"*" 表示支持所有域名
- http.cors.enabled: true
- http.cors.allow-origin: "*"
- action.destructive_requires_name: true
- action.auto_create_index: .security,.monitoring*,.watches,.triggered_watches,.watcher-history*
- xpack.security.enabled: false
- xpack.monitoring.enabled: true
- xpack.graph.enabled: false
- xpack.watcher.enabled: false
- xpack.ml.enabled: false
从节点 1
- # 设置集群的名称
- cluster.name: es-cluster
- # 修改当前节点的 hostname 名称
- node.name: slave1
- # 设置是否能成为主节点, false 是永远不可能成为主节点, true: 表示有可能成为主节点, 并不一定. 跟指定的参 数 cluster.initial_master_nodes 有关系. 当所有 node 都可为主节点时, 如果主节点宕机, 其他节点会再次选举一个新的主节点.
- node.master: true
- # 当前节点是否用于存储数据
- node.data: true
- # 修改索引存放路径
- path.data: /usr/local/src/Elasticsearch/Elasticsearch-7.3.1/data
- #修改日志存放路径
- path.logs: /usr/local/src/Elasticsearch/Elasticsearch-7.3.1/logs
- # 是否锁住 ES 节点内存交换
- Bootstrap.memory_lock: true
- # 监听 IP, 用于访问 ES
- network.host: 192.168.192.11
- #ES 对外提供的 http 监听端口
- http.port: 9200
- # TCP 的默认监听端口, 默认 9300
- transport.tcp.port: 9300
- # 设置这个参数来保证集群中的节点可以知道其它 N 个有 master 资格的节点. 默认为 1, 对于大的集群来说, 可以设置大一点的值(2-4)
- discovery.zen.minimum_master_nodes: 2
- #es7.x 之后新增的配置, 写入候选主节点的设备地址, 在开启服务后可以被选为主节点
- discovery.seed_hosts: ["master:9300","slave1:9300","slave2:9300"]
- discovery.zen.fd.ping_timeout: 1m
- discovery.zen.fd.ping_retries: 5
- # es7.x 之后新增的配置, 初始化一个新的集群时需要此配置来选举 master
- cluster.initial_master_nodes: ["master","slave1","slave2"] #主节点配置
- # 是否支持跨域, 是: true, 在使用 head 插件时需要此配置,"*" 表示支持所有域名
- http.cors.enabled: true
- http.cors.allow-origin: "*"
- action.destructive_requires_name: true
- action.auto_create_index: .security,.monitoring*,.watches,.triggered_watches,.watcher-history*
- xpack.security.enabled: false
- xpack.monitoring.enabled: true
- xpack.graph.enabled: false
- xpack.watcher.enabled: false
- xpack.ml.enabled: false
从节点 2
- # 设置集群的名称
- cluster.name: es-cluster
- # 修改当前节点的 hostname 名称
- node.name: slave2
- # 设置是否能成为主节点, false 是永远不可能成为主节点, true: 表示有可能成为主节点, 并不一定. 跟指定的参 数 cluster.initial_master_nodes 有关系. 当所有 node 都可为主节点时, 如果主节点宕机, 其他节点会再次选举一个新的主节点.
- node.master: true
- # 当前节点是否用于存储数据
- node.data: true
- # 修改索引存放路径
- path.data: /usr/local/src/Elasticsearch/Elasticsearch-7.3.1/data
- #修改日志存放路径
- path.logs: /usr/local/src/Elasticsearch/Elasticsearch-7.3.1/logs
- # 是否锁住 ES 节点内存交换
- Bootstrap.memory_lock: true
- # 监听 IP, 用于访问 ES
- network.host: 192.168.192.12
- #ES 对外提供的 http 监听端口
- http.port: 9200
- # TCP 的默认监听端口, 默认 9300
- transport.tcp.port: 9300
- # 设置这个参数来保证集群中的节点可以知道其它 N 个有 master 资格的节点. 默认为 1, 对于大的集群来说, 可以设置大一点的值(2-4)
- discovery.zen.minimum_master_nodes: 2
- #es7.x 之后新增的配置, 写入候选主节点的设备地址, 在开启服务后可以被选为主节点
- discovery.seed_hosts: ["master:9300","slave1:9300","slave2:9300"]
- discovery.zen.fd.ping_timeout: 1m
- discovery.zen.fd.ping_retries: 5
- # es7.x 之后新增的配置, 初始化一个新的集群时需要此配置来选举 master
- cluster.initial_master_nodes: ["master","slave1","slave2"] #主节点配置
- # 是否支持跨域, 是: true, 在使用 head 插件时需要此配置,"*" 表示支持所有域名
- http.cors.enabled: true
- http.cors.allow-origin: "*"
- action.destructive_requires_name: true
- action.auto_create_index: .security,.monitoring*,.watches,.triggered_watches,.watcher-history*
- xpack.security.enabled: false
- xpack.monitoring.enabled: true
- xpack.graph.enabled: false
- xpack.watcher.enabled: false
- xpack.ml.enabled: false
集群所有节点进入 bin 目录, 切换之前建立的普通用户
- su es
- ./Elasticsearch
成功如下
使用 restful 调用
curl -XGET http://192.168.192.10:9200
使用浏览器调用
1.4 Elasticsearch Chrome 插件
BIgDesk 插件用于监控 es 集群
kibana 用于读取 es 中索引库的 type 信息, 并使用可视化图表的方式展示出来.
cerebro 漂亮的 es 查询工具
1.5 kibana 安装
下载地址: https://www.elastic.co/cn/downloads/kibana
百度网盘下载地址: https://pan.baidu.com/s/1YmFdSOysZ9QxlO15avZraA
提取码: 9oio
下载解压
- tar -zxvf kibana-7.3.1-Linux-x86_64.tar.gz
- # 修改配置文件, 添加如下内容
- VIM kibana-7.3.1-Linux-x86_64/config/kibana.YAML
- server.host: "192.168.192.10"
- Elasticsearch.hosts: ["http://192.168.192.10:9200","http://192.168.192.11:9200","http://192.168.192.12:9200"]
启动(kibana 不建议以 root 用户启动, 如果用 root 启动, 需要加 --allow-root)
./bin/kibana --allow-root
访问 5601 端口
可以选择试试数据样本 或者 直接开始探索
这里选择 Try our sample data, 将数据导入 ES 中, 方便后面学习使用.
可以选择添加仪表盘的各种视图, 玩玩吧.
1.6 Open Distro For Elasticsearch
Open Distro for Elasticsearch 使您可以使用熟悉的 SQL 查询语法从 Elasticsearch 中提取见解. 使用聚合, 分组依据和 where 子句来调查数据. 以 JSON 文档或 CSV 表的形式读取数据, 因此您可以灵活地使用最适合自己的格式, 也就是抛弃 DSL, 多好啊!
1.6.1 Open Distro For Elasticsearch 搭建
注意查看对应 es 版本, 由于这里采用 es7.4.2, 所以 sql 插件选择 1.4.0.0
太慢可以点这里下载解压
- tar -zxvf sql-1.4.0.0.tar.gz
- cd sql-1.4.0.0
- #1 修改 build.gradle
- VIM build.gradle
- # 将(有两个地方)
- repositories {
- mavenCentral()
- }
- # 修改为
- repositories {
- maven { url 'https://maven.aliyun.com/repository/central' }
- maven { url 'https://maven.aliyun.com/repository/apache-snapshots' }
- maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
- maven { url 'https://maven.aliyun.com/repository/google' }
- maven { url 'https://maven.aliyun.com/repository/jcenter' }
- maven { url 'https://maven.aliyun.com/repository/publics' }
- maven { url 'http://repo1.maven.org/maven2' }
- maven { url 'http://repo2.maven.org/maven2' }
- mavenCentral()
- }
- #2 修改 gradle.properties
- VIM gradle.properties
- org.gradle.daemon=true // 开启线程守护, 第一次编译时开线程, 之后就不会再开了
- org.gradle.parallel=true // 开启并行编译, 相当于多条线程再走
org.gradle.configureondemand=true 启用新的孵化模式
- #3 构建
- ./gradlew build
编译过后, sql-1.4.0.0/build/distributions/opendistro_sql-1.4.0.0.zip 会有一个这个包
需要 VPN, 如果编译不了可以在这里下载
所有节点安装插件
/usr/local/src/Elasticsearch/Elasticsearch-7.4.2/bin/Elasticsearch-plugin install file:///usr/local/src/sql-1.4.0.0/build/distributions/opendistro_sql-1.4.0.0.zip
重启 ES 集群
1.6.2 Elasticsearch-sql 使用
参照
- GET /_opendistro/_sql?sql=SELECT * FROM ecommerce WHERE order_id>'600000'
- POST _opendistro/_sql
- {
- "query": "SELECT sum(products.base_price) FROM ecommerce WHERE order_id>'600000'"
- }
2 Elasticsearch 数据类型
2.1 文本类型
2.1.1 text 文本数据类型
用于索引全文值的字段. 使用文本数据类型的字段, 它们会被分词, 在索引之前将字符串转换为单个术语的列表(倒排索引), 分词过程允许 ES 搜索每个全文字段中的单个单词. 文本字段不用于排序, 很少用于聚合(重要的术语聚合是一个例外). 什么情况适合使用 text datatype, 只要不具备唯一性的字符串一般都可以使用 text, 例如: 电子邮件正文, 商品介绍, 个人简介
1analyzer: 指明该字段用于索引时和搜索时的分析字符串的分词器(使用 search_analyzer 可覆盖它). 默认为索引分析器或标准分词器
2fielddata: 指明该字段是否可以使用内存中的 fielddata 进行排序, 聚合或脚本编写? 默认值为 false, 可取值 true 或 false.(排序, 分组需要指定为 true)
3fields:text 类型字段会被分词搜索, 不能用于排序, 而当字段既要能被搜索, 又要能够排序, 就要设置 fields 为 keyword 进行聚合排序.
4index: 设置该字段是否可以用于搜索. 默认为 true, 表示可以用于搜索.
5search_analyzer: 设置在搜索时, 用于分析该字段的分析器, 默认是[analyzer] 参数的值.
6search_quote_analyzer: 设置在遇到短语搜索时, 用于分析该字段的分析器, 默认是[search_analyzer] 参数的值.
- PUT test_index
- {
- "mappings": {
- "doc":{
- "properties": {
- "title":{
- "type": "text"
- }
- }
- }
- }
- }
- PUT test_index
- {
- "mappings": {
- "properties": {
- "title":{
- "type": "text",
- "analyzer": "standard",
- "fielddata": true,
- "fields": {
- "sort":{
- "type": "keyword"
- }
- },
- "index": true,
- "search_analyzer": "standard"
- }
- }
- }
- }
- # 插入两条数据, 测试一下
- PUT test_index/_doc/1
- {
- "title": "York"
- }
- PUT test_index/_doc/2
- {
- "title": "NEW York"
- }
- # 查询测试
- GET test_index/_search
- {
- "query": {
- "match": {
- "title": "york"
- }
- },
- "sort": {
- "title.sort": "asc"
- }
- }
2.1.2 keyword 关键字数据类型
keyword datatype, 关键字数据类型, 用于索引结构化内容的字段. 使用 keyword 类型的字段, 其不会被分析, 给什么值就原封不动地按照这个值索引, 所以关键字字段只能按其确切值进行搜索, 通常用于过滤, 排序和聚合. 什么情况下使用 keyword datatype, 具有唯一性的字符串, 例如: 电子邮件地址, Mac 地址, 身份证号, 状态代码...
1eager_global_ordinals: 指明该字段是否加载全局序数? 默认为 false, 不加载. 对于经常用于术语聚合的字段, 启用此功能是个好主意.
2fields: 指明能以不同的方式索引该字段相同的字符串值, 例如用于搜索的一个字段和用于排序和聚合的多字段
3ignore_above: 不要索引长于此值的任何字符串. 默认为 2147483647, 以便接受所有值
4index: 指明该字段是否可以被搜索, 默认为 true, 表示可以被搜索
5index_options: 指定该字段应将哪些信息存储在索引中, 以便用于评分. 默认为 docs, 但也可以设置为 freqs, 这样可以在计算分数时考虑术语频率
6norms: 在进行查询评分时, 是否需要考虑字段长度, 默认为 false, 不考虑
- PUT test_index
- {
- "mappings": {
- "properties": {
- "title":{
- "type": "keyword",
- "eager_global_ordinals": true,
- "fields": {
- "sort":{
- "type":"text"
- }
- },
- "ignore_above": 1024,
- "index": true,
- "index_options": "freqs",
- "norms": true
- }
- }
- }
- }
2.2 数字类型
数字类型, 这类数据类型都是以确切值索引的, 可以使用 "term" 查询精确匹配.
ES 支持的数字类型有
long | 带符号的 64 位整数,最小值 - 263,最大值 263-1 |
---|---|
integer | 带符号的 32 位整数,最小值 - 231,最大值 231-1 |
short | 带符号的 16 位整数,最小值 - 32768,最大值 32767 |
byte | 带符号的 8 位整数,最小值 - 128,最小值 127 |
double | 双精度 64 位 IEEE 754 浮点数 |
float | 单精度 32 位 IEEE 754 浮点数 |
half_float | 半精度 16 位 IEEE 754 浮点数 |
scaled_float | 带有缩放因子的缩放类型浮点数 |
1coerce: 是否尝试将字符串转换为整数并截断整数的分数, 默认为 true, 是
2ignore_malformed: 如果为 true, 则忽略格式错误的数字; 如果为 false 则格式错误的数字会抛出异常并拒绝整个文档, 默认 false
3index: 指明该字段是否被搜索. 默认为 true, 表示可以被搜索
4null_value: 指明一个与该字段相同类型的值去替换掉该字段中的 null. 默认为 null, 表示该字段被视为缺失
注意点
double,float,half_float 这 3 种浮点型数据类型, 认为 - 0.0 和 0.0 是不同的值. 这意味着在 - 0.0 上进行查询 (match or term) 匹配不到 0.0, 反之亦然; 范围查询也是如此: 如果上限为 - 0.0 则 0.0 将不匹配, 如果下限为 0.0 则 - 0.0 将不匹配.
就整数类型 (byte,short,integer 和 long) 而言, 要选择满足实际需求的最小类型, 这有助于索引和搜索. 但请注意: ES 存储数据是根据存储的实际值进行优化的, 因此选择一种类型而不是另一种类型, 将不会影响存储要求.
scaled_float 类型需要格外注意: 必须指定缩放因子 scaling_factor.ES 索引时, 原始值会乘以该缩放因子并四舍五入得到新值, ES 内部储存的是这个新值, 但返回结果仍是原始值. 例如: scale_factor 为 10 的 scaled_float 字段将在内部存储 2.34 为 23, 查询时, ES 都会将查询参数 * 10 再四舍五入得到的值与 23 匹配, 若能匹配到返回结果为 2.34
- PUT test_index
- {
- "mappings": {
- "properties": {
- "number1":{
- "type": "long"
- },
- "number2":{
- "type": "integer"
- },
- "number3":{
- "type": "short"
- },
- "number4":{
- "type": "byte"
- },
- "number5":{
- "type": "double"
- },
- "number6":{
- "type": "float"
- },
- "number7":{
- "type": "half_float"
- },
- "number8":{
- "type": "scaled_float",
- "scaling_factor": 100
- }
- }
- }
- }
- # 添加两条数据
- #21.2121*100 四舍五入得到 2121 储存结果
- #21.2155*100 四舍五入得到 2122 储存结果
- PUT test_index/_doc/1
- {
- "number8": 21.2121
- }
- PUT test_index/_doc/2
- {
- "number8": 21.2155
- }
- # 查询测试
- #21.2133*100 四舍五入得到 2121 匹配上面插入的_id=1 的数据
- #21.2199*100 四舍五入得到 2122 匹配上面插入的_id=2 的数据
- GET test_index/_search
- {
- "query": {
- "match": {
- "number8": 21.2133
- }
- }
- }
- GET test_index/_search
- {
- "query": {
- "match": {
- "number8": 21.2199
- }
- }
- }
2.3 日期类型
日期数据类型.
由于 JSON 中没有表示日期的数据类型, 所以 ES 中的日期可以表示为:
日期格式化后的字符串, 如:"2018-01-01" 或 "2018/01/01 11:11:11"
long 类型值表示自纪元以来的毫秒数
integer 类型值表示自纪元以来的秒数
如果指定了时区, ES 将日期转换为 UTC, 然后再存储为自纪元以来的毫秒数(long 类型).
当字段被设置为 date 类型时, 可以自定义日期格式, 但如果未指定格式, 则使用默认格式:"strict_date_optional_time||epoch_millis"
若未指定自定义日期格式, 在保存日期的时候容易出错. 若未指定日期格式, ES 采用默认日期格式: 严格日期格式或者时间戳, 例如:
- 2020-01-01 ---- yes
- 2020/01/01 ---- no
- 2020-01-1 ---- no
- 1577808000000 ---- yes
在使用 date 类型字段进行排序时, 返回的排序值都是以毫秒为单位
1format: 自定义的日期格式, 默认: strict_date_optional_time || epoch_millis
2ignore_malformed: 若为 true, 则忽略格式错误的数字; 若为 false, 则格式错误的数字会抛出异常并拒绝整个文档. 默认 false
3index: 指明该字段是否可以被搜索, true 为可以, 默认 true
4null_value: 接受其中一个配置格式的日期值作为替换任何显式空值的字段. 默认为 null, 表示该字段被视为缺失
- PUT test_index
- {
- "mappings": {
- "properties": {
- "date1":{
- "type": "date"
- },
- "date2":{
- "type": "date",
- "format": "yyyy-MM-dd HH:mm:ss"
- },
- "date3":{
- "type": "date",
- "format": "yyyy-MM-dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
- }
- }
- }
- }
- # 添加 2 条数据
- PUT test_index/_doc/1
- {
- "date1": 1577808000000,
- "date2": "2020-01-01 00:00:00",
- "date3": "2020/01/01"
- }
- PUT test_index/_doc/2
- {
- "date1": 1577808000001,
- "date2": "2020-01-01 00:00:01",
- "date3": "2020/01/01"
- }
- # 查询测试
- GET test_index/_search
- {
- "sort": [
- {
- "date2": {
- "order": "desc"
- }
- }
- ]
- }
2.4 范围类型
范围数据类型. 具有大小关系的一个值区间, 所以会用到 gt,gte,lt,lte.. 等逻辑表示符.
ES 支持下面 6 种范围数据类型
integer_range, 带符号的 32 位整数区间, 最小值 - 231, 最大值 231-1
long_range, 带符号的 64 位整数区间, 最小值 - 263, 最小值 263-1
float_range, 单精度 32 位 IEEE 754 浮点数区间
double_range, 双精度 64 位 IEEE 754 浮点数区间
date_range, 日期值范围, 表示为系统纪元以来经过的无符号 64 位整数毫秒
ip_range, 支持 IPv4 或 IPv6(或混合)地址 ip 值范围
- PUT test_index
- {
- "mappings": {
- "properties": {
- "data1":{
- "type": "integer_range"
- },
- "data2":{
- "type": "float_range"
- },
- "data3":{
- "type": "date_range",
- "format": "yyyy-MM-dd HH:mm:ss"
- },
- "data4":{
- "type": "ip_range"
- }
- }
- }
- }
- # 添加数据
- PUT test_index/_doc/1
- {
- "date1": {
- "gte": 100,
- "lte": 200
- },
- "date2": {
- "gte": 21.21,
- "lte": 22.00
- },
- "date3": {
- "gte": "2020-01-01 00:00:00",
- "lte": "2020-01-02 00:00:00"
- },
- "date4": {
- "gte": "192.168.192.10",
- "lte": "192.168.192.11"
- }
- }
- # 查询测试
- GET test_index/_search
2.5 数组类型
数组类型. 默认情况下, 任何字段都可以包含零个或多个值, 当包含多个值时, 它就表示 array datatype 了. 但是, 数组中的所有值必须具有相同的数据类型(要么同为字符串, 要么同为整型, 不能数组中一些值为字符串, 另一些值为整型)
当数组里面放的是对象(object datatype), 即对象数组时, 要改为使用 nested datatype.
在动态添加字段时, ES 从数组中的第一个值确定字段类型, 所有后续值必须具有相同的数据类型, 或者必须至少可以将后续值强制转换为相同的数据类型.
使用 array datatype, 不需要预先配置, 它们是开箱即用的.(不像整型, 它需要手动定义 type: "integer")
使用_mapping 查看索引的映射类型时, array datatype 不会被写出来, 还是以数组的元素的基本类型来表示.
- PUT test_index
- {
- "mappings": {
- "properties": {
- "data1":{
- "type": "long"
- },
- "data2":{
- "type": "text"
- }
- }
- }
- }
- # 添加数据
- PUT test_index/_doc/1
- {
- "date1": [1,2,3],
- "date2": ["a","b","c"]
- }
- # 查询测试
- GET test_index/_search
2.6 对象类型
即对象类型. 一个文档的一个属性可以是一个内部对象, 而且, 这个内部对象, 可以再包含一个内部对象..(可以有多层嵌套)
整个外部文档是一个 JSON 对象
在 ES 内部, 这种文档会被索引成一种简单平坦的键值对列表(平铺)
- PUT test_index
- {
- "mappings": {
- "properties": {
- "persion":{
- "type": "object"
- }
- }
- }
- }
- PUT test_index/_doc/1
- {
- "persion": [
- {
- "first": "li",
- "last": "si"
- },
- {
- "first": "李",
- "last": "四"
- }
- ]
- }
- PUT test_index/_doc/2
- {
- "persion": [
- {
- "first": "li",
- "last": "四"
- },
- {
- "first": "李",
- "last": "si"
- }
- ]
- }
- # 查询测试 1
- GET test_index/_search
无法做到对象中数组独立索引和查询
- # 查询测试
- GET test_index/_search
- {
- "query": {
- "bool": {
- "must": [
- { "match": { "persion.first": "li" }},
- { "match": { "persion.last": "si" }}
- ]
- }
- }
- }
出现问题, 不正确的匹配. 使用 nested 嵌套对象类型解决
原因: 对象类型里的数组类型会将字段平铺为多值字段, 因此 li si 之间的关联关系会消失, 会将上面的文档转换成如下格式:
- {
- persion.first:["li 李"]
- persion.last:["四 si"]
- }
2.7 嵌套对象类型
嵌套数据类型, 是 object datatype 的专用版本, 在文档属性是一个对象数组时使用, 它允许对象数组彼此独立地编制索引和查询
- PUT test_index
- {
- "mappings": {
- "properties": {
- "persion":{
- "type": "nested"
- }
- }
- }
- }
- # 添加数据
- PUT test_index/_doc/1
- {
- "persion": [
- {
- "first": "li",
- "last": "si"
- },
- {
- "first": "李",
- "last": "四"
- }
- ]
- }
- PUT test_index/_doc/2
- {
- "persion": [
- {
- "first": "li",
- "last": "四"
- },
- {
- "first": "李",
- "last": "si"
- }
- ]
- }
- # 查询测试
- GET test_index/_search
- {
- "query": {
- "nested": {
- "path": "persion",
- "query": {
- "bool": {
- "must": [
- { "match": { "persion.first": "li" }},
- { "match": { "persion.last": "si" }}
- ]
- }
- }
- }
- }
- }
2.8 地理类型
地址位置数据类型, 可以用来表示经纬度.
- PUT test_index
- {
- "mappings": {
- "properties": {
- "add":{
- "type": "geo_point"
- }
- }
- }
- }
地理点表示为一个对象, lat 属性表示维度, lon 属性表示经度
- PUT test_index/_doc/1
- {
- "add":{
- "lat": 22.22,
- "lon": -22.22
- }
- }
地理点表示为一个字符串, 格式为:"lat,lon"
- PUT test_index/_doc/2
- {
- "add": "22.22,-22.22"
- }
地理点表示为一个 geohash 字符串
- PUT test_index/_doc/3
- {
- "add": "eebnqm5bukpn"
- }
地理点表示为一个数组, 格式为[lon,lat], 注意经纬度顺序字符串标识相反
- PUT test_index/_doc/4
- {
- "add": [-22.22,22.22]
- }
3 Elasticsearch 基础操作
注意:
索引库名称必须要全部小写, 不能以下划线开头, 也不能包括逗号.
如果没有明确指定索引数据的 ID, 那么 es 会自动生成一个随机的 ID, 需要使用 POST 参数
PUT 和 POST 进行更新时, 是全局更新(可以理解为删除旧的, 然后建立一个 ID 相同的新 document)
POST 和 PUT 都可以进行添加和更新操作, 但是 PUT 是幂等方法, POST 不是, 所以 PUT 用于更新, POST 用于新增比较合适.(幂等: 无论多少次操作, 最终结果一致)
3.1 创建索引库
- # 创建索引库(只能使用 put)
- curl -XPUT 'http://192.168.192.10:9200/twitter?pretty'
插件创建索引
3.2 添加索引内容
- curl -XPOST 'http://192.168.192.10:9200/twitter/_doc/1?pretty' -H 'Content-Type: application/json' -d '
- {
- "user": "kimchy",
- "post_date": "2009-11-15T13:12:00",
- "message": "Trying out Elasticsearch, so far so good?"
- }'curl -XPUT'http://192.168.192.10:9200/twitter/_doc/2?pretty'-H'Content-Type: application/JSON'-d'
- {
- "user": "kimchy",
- "post_date": "2009-11-15T14:12:12",
- "message": "Another tweet, will it be indexed?"
- }'
- # 不指定唯一 ID 标识, 会自动生成, 但是必须使用 POST 添加
- curl -XPOST 'http://192.168.192.10:9200/twitter/_doc?pretty' -H 'Content-Type: application/json' -d '
- {
- "user": "kimchy",
- "post_date": "2009-11-15T14:12:12",
- "message": "Another tweet, will it be indexed?"
- }'
插件添加索引内容
3.3 更新索引
3.3.1 全局更新
post 更新
- curl -XPOST 'http://192.168.192.10:9200/twitter/_doc/1?pretty' -H 'Content-Type: application/json' -d '
- {
- "user": "kimchy-post"
- }'
put 更新
- curl -XPUT 'http://192.168.192.10:9200/twitter/_doc/2?pretty' -H 'Content-Type: application/json' -d '
- {
- "user": "kimchy-put"
- }'
从上面的结果我们看出, 不想被更新的字段也被删除了
3.3.2 局部更新
需要使用_update 动作命令
- # 必须使用 POST
- curl -XPOST 'http://192.168.192.10:9200/twitter/_doc/fl4Te3ABJGbFfeWwGLCX/_update?pretty' -H 'Content-Type: application/json' -d '
- {
- "doc":{
- "user": "kimchy-update"
- }
- }'
从上面看出做到了局部更新
3.4 查询索引
根据 ID 查看
curl -XGET 'http://192.168.192.10:9200/twitter/_doc/1?pretty' -H 'Content-Type: application/json'
查询指定字段(_source)
curl -XGET 'http://192.168.192.10:9200/twitter/_doc/fl4Te3ABJGbFfeWwGLCX?_source=user,message&pretty' -H 'Content-Type: application/json'
只获取 source 数据
curl -XGET 'http://192.168.192.10:9200/twitter/_doc/fl4Te3ABJGbFfeWwGLCX/_source?pretty' -H 'Content-Type: application/json'
查询所有(_search)
curl -XGET 'http://192.168.192.10:9200/twitter/_doc/_search?pretty' -H 'Content-Type: application/json'
还有很多高级用法, 请参考 3.7 的拓展
3.5 删除索引
curl -XDELETE 'http://192.168.192.10:9200/twitter/_doc/1?pretty' -H 'Content-Type: application/json'
3.6 批处理 bulk
bulk 好处就是可以 在一个文件里面执行 create update delete 等操作
如下面要进行什么操作, 遵守什么格式就可以了.
3.6.1 新增案例
创建批处理文件 test.JSON
- {
- "index":{
- "_id":"3"
- }
- }
- {
- "user":"kimchy3","post_date":"2009-11-15T13:12:00","message":"Trying out Elasticsearch, so far so good?"
- }
- {
- "index":{
- "_id":"4"
- }
- }
- {
- "user":"kimchy4","post_date":"2009-11-15T13:12:00","message":"Trying out Elasticsearch, so far so good?"
- }
- {
- "index":{
- "_id":"5"
- }
- }
- {
- "user":"kimchy5","post_date":"2009-11-15T13:12:00","message":"Trying out Elasticsearch, so far so good?"
- }
执行命令
curl -XPOST 'http://192.168.192.10:9200/twitter/_doc/_bulk?pretty' -H 'Content-Type: application/json' --data-binary '@/root/test/test.json'
3.6.2 删除操作
创建批处理文件 test.JSON
- {
- "delete":{
- "_id" : 2
- }
- }
- {
- "delete":{
- "_id" : 3
- }
- }
执行命令
curl -XPOST 'http://192.168.192.10:9200/twitter/_doc/_bulk?pretty' -H 'Content-Type: application/json' --data-binary '@/root/test/test.json'
3.6.3 更新操作
创建批处理文件 test.JSON
- {
- "index":{
- "_id":"4"
- }
- }
- {
- "user":"test4"
- }
- {
- "index":{
- "_id":"5"
- }
- }
- {
- "user":"test5"
- }
执行命令
curl -XPOST 'http://192.168.192.10:9200/twitter/_doc/_bulk/_update?pretty' -H 'Content-Type: application/json' --data-binary '@/root/test/test.json'
3.7 kibana 插件使用拓展
3.7.1 CRUD 基础操作
查看所有索引 GET _cat/indices
添加索引 PUT Twitter
查询 Twitter 内容 GET Twitter/_search
新增索引内容
- POST Twitter/_doc/1
- {
- "user": "kimchy",
- "post_date": "2009-11-15T13:12:00",
- "message": "Trying out Elasticsearch, so far so good?"
- }
查看具体某条记录 GET Twitter/_doc/1
批量操作
- POST Twitter/_bulk
- {
- "index":{
- "_id":"3"
- }
- }
- {
- "user":"kimchy3","post_date":"2009-11-15T13:12:00","message":"Trying out Elasticsearch, so far so good?"
- }
- {
- "index":{
- "_id":"4"
- }
- }
- {
- "user":"kimchy4","post_date":"2009-11-15T13:12:00","message":"Trying out Elasticsearch, so far so good?"
- }
更新操作
- POST Twitter/_update/1
- {
- "doc":{
- "user": "kimchy-update01"
- }
- }
3.7.2 URI 查询操作拓展
3.7.2.1 泛查询
GET kibana_sample_data_ecommerce/_search?q=38
3.7.2.2 字段匹配查询
索引名字太长, 新建别名
普通字段匹配查询 GET ecommerce/_search?q=customer_id:38
数组字段匹配查询 GET ecommerce/_search?q=category:Men's
数组对象字段匹配查询 GET ecommerce/_search?q=category:Men's
3.7.2.3 字段多条件匹配
查询分类中有 '男士' 或 '服装' 的结果(||)
- GET ecommerce/_search?q=category:Men's Clothing
- GET ecommerce/_search?q=category:(+Men's +Clothing)
推荐写法: GET ecommerce/_search?q=category:(Men's Clothing)
查询分类中有 '男士' 且没有 '服装' 的结果
GET ecommerce/_search?q=category:(+Men's -Clothing)
查询分类中有 '男士' 且有 '服装' 的结果(&&)
GET ecommerce/_search?q=category:(Men's AND Clothing)
AND 必须大写
查询商品价格在 20.00 到 30.00 之间的结果(范围查询)
GET ecommerce/_search?q=products.base_price:(>=20.00 AND <=30.00)
3.7.2.4 分页查询
从索引为 0 处查询分类中有 '男士' 的结果, 只显示一条(分页)
GET ecommerce/_search?q=category:(Men's)&from=0&size=1
3.7.3 DSL 查询
ES 支持一种 JSON 格式的查询, 叫做 DSL,domain specific language.
3.7.3.1 match 匹配查询
查询分类包含 "男性" 或 "服装" 的结果
- GET ecommerce/_search
- {
- "query":{
- "match":{
- "category":"Men's Clothing"
- }
- }
- }
3.7.3.2 query_string 匹配查询
查询分类包含 "男性服装" 的结果
- # 写法一
- GET ecommerce/_search
- {
- "query": {
- "query_string": {
- "default_field": "category",
- "query": "Men's AND Clothing"
- }
- }
- }
- # 写法二(条件 OR AND)
- GET ecommerce/_search
- {
- "query": {
- "query_string": {
- "default_field": "category",
- "query": "Men's Clothing",
- "default_operator": "AND"
- }
- }
- }
3.7.3.3 match_phrase 强匹配查询
查询包含 "男性服装" 的整体结果
- GET ecommerce/_search
- {
- "query":{
- "match_phrase":{
- "category":"Men's Clothing"
- }
- }
- }
3.7.3.4 range 范围查询
查询包含 "订单>=584677 <=584678" 的整体结果("gt" "lt" "gte" "lte")
- GET ecommerce/_search
- {
- "query":{
- "range":{
- "order_id":{
- "gte":584677,
- "lte": 584678
- }
- }
- }
- }
3.7.3.5 _source 查询结果过滤
查询 "订单 ID 和 货币" 的结果
- GET ecommerce/_search
- {
- "_source": ["order_id","currency"]
- }
3.7.3.6 sort 排序
查询包含 "订单>=584677 <=584678" 的包含 "订单 ID 和 货币" 结果, 并倒序排列
- GET ecommerce/_search
- {
- "_source": ["order_id","currency"],
- "query":{
- "range":{
- "order_id":{
- "gte":584677,
- "lte": 584678
- }
- }
- },
- "sort": [
- {
- "order_id": {
- "order": "desc"
- }
- }
- ]
- }
3.7.3.7 multi_match 多列匹配查询
type 类型:
best_fields(默认): 在某一字段中匹配的越多, 排名越靠前
most_fields: 在多字段中匹配的越多, 排名越靠前
cross_fields: 查询越分散, 排名越靠前
查询 customer_first_name,customer_full_name 中包含 "Eddie" 的结果
- GET ecommerce/_search
- {
- "_source": ["customer_first_name", "customer_full_name"],
- "query": {
- "multi_match": {
- "query": "Eddie",
- "fields": ["customer_first_name","customer_full_name"],
- "type": "best_fields"
- }
- }
- }
3.7.3.8 bool 多字段条件查询
必须 must
查询分类中包含 "Men's"或"Clothing", 并且 20<=customer_id<=50 的结果
- GET ecommerce/_search
- {
- "query": {
- "bool": {
- "must": [
- {
- "match": {
- "category":"Men's Clothing"
- }
- },{
- "range": {
- "customer_id": {
- "gte": 20,
- "lte": 50
- }
- }
- }
- ]
- }
- }
- }
必须不 must_not
查询分类中不包含 "Men's"或"Clothing", 并且 40<=customer_id<=50 的结果
- GET ecommerce/_search
- {
- "query": {
- "bool": {
- "must_not": [
- {
- "match": {
- "category":"Men's Clothing"
- }
- }
- ],
- "must": [
- {
- "range": {
- "customer_id": {
- "gte": 40,
- "lte": 50
- }
- }
- }
- ]
- }
- }
- }
或者 should
查询分类中包含 "Men's"或"Clothing", 或者 20<=customer_id<=50 的结果, 按照 customer_id 倒序排列, 从第 0 个索引位置展示 10 条, 只显示 category,customer_id 字段
- GET ecommerce/_search
- {
- "_source": ["category","customer_id"],
- "query": {
- "bool": {
- "should": [
- {
- "match": {
- "category":"Men's Clothing"
- }
- },{
- "range": {
- "customer_id": {
- "gte": 20,
- "lte": 50
- }
- }
- }
- ]
- }
- },
- "sort": [
- {
- "customer_id": {
- "order": "desc"
- }
- }
- ],
- "from": 0,
- "size": 10
- }
精确查找 term
查询分类中包含 "men's" 的结果
注意不能大写, 因为精确查找其实找的是索引库, 在我们的 document 存放的时候会将大写转为小写.
GET ecommerce/_search { "query": { "term": { "category": { "value": "men's" } } } }
缓存查找 filter
查询分类中包含 "men's" 的结果
注意不能大写, 因为精确查找其实找的是索引库, 在我们的 document 存放的时候会将大写转为小写.
GET ecommerce/_search { "query": { "term": { "category": { "value": "men's" } } } }
5 Elasticsearch 基本概念
cluster
代表一个集群, 集群中有多个节点, 其中有一个为主节点, 这个主节点是可以通过选举产生的, 主从节点是对于集群内部来说的. es 的一个概念就是去中心化, 字面上理解就是无中心节点, 这是对于集群外部来说的, 因为从外部来看 es 集群, 在逻辑上是个整体, 你与任何一个节点的通信和与整个 es 集群通信是等价的.
shards
代表索引分片, es 可以把一个完整的索引分成多个分片, 这样的好处是可以把一个大的索引拆分成多个, 分布到不同的节点上. 构成分布式搜索. 分片的数量只能在索引创建前指定, 并且索引创建后不能更改.
replicas
代表索引副本, es 可以设置多个索引的副本, 副本的作用一是提高系统的容错性, 当某个节点某个分片损坏或丢失时可以从副本中恢复. 二是提高 es 的查询效率, es 会自动对搜索请求进行负载均衡.
recovery
代表数据恢复或叫数据重新分布, es 在有节点加入或退出时会根据机器的负载对索引分片进行重新分配, 挂掉的节点重新启动时也会进行数据恢复.
river
代表 es 的一个数据源, 也是其它存储方式 (如: 数据库) 同步数据到 es 的一个方法. 它是以插件方式存在的一个 es 服务, 通过读取 river 中的数据并把它索引到 es 中, 官方的 river 有 couchDB 的, RabbitMQ 的, Twitter 的, Wikipedia 的.
gateway
代表 es 索引快照的存储方式, es 默认是先把索引存放到内存中, 当内存满了时再持久化到本地硬盘. gateway 对索引快照进行存储, 当这个 es 集群关闭再重新启动时就会从 gateway 中读取索引备份数据. es 支持多种类型的 gateway, 有本地文件系统(默认), 分布式文件系统, Hadoop 的 HDFS 和 Amazon 的 s3 云存储服务.
discovery.zen
代表 es 的自动发现节点机制, es 是一个基于 p2p 的系统, 它先通过广播寻找存在的节点, 再通过多播协议来进行节点之间的通信, 同时也支持点对点的交互.
Transport
代表 es 内部节点或集群与客户端的交互方式, 默认内部是使用 tcp 协议进行交互, 同时它支持 http 协议(JSON 格式),thrift,servlet,Memcached,zeroMQ 等的传输协议(通过插件方式集成).
6 分词器
6.1 分词介绍
新建索引库(中英文测试)
PUT /participles_en
添加查询所引
PUT /participles_en/_doc/1 { "msg":"Eating a banana day keeps!" } POST /participles_en/_search { "query": { "match": { "msg": "eat" } } }
如上图, es 返回结果为 0, 并没有被分成我们想要的词语.
搜索引擎的核心是倒排索引, 而倒排索引的基础就是分词. 所谓分词可以简单理解为将一个完整的句子切割为一个个单词的过程.
读时分词发生在用户查询时, ES 会即时地对用户输入的关键词进行分词, 分词结果只存在内存中, 当查询结束时, 分词结果也会随即消失.
写时分词发生在文档写入时, ES 会对文档进行分词后, 将结果存入倒排索引, 该部分最终会以文件的形式存储于磁盘上, 不会因查询结束或者 ES 重启而丢失.
使用_analyze 可以查询分词结果
ES 中处理分词的部分被称作分词器, 英文是 Analyzer, 它决定了分词的规则. ES 自带了很多默认的分词器, 比如
分词器名称 | 处理过程 |
---|---|
Standard Analyzer(默认) | 默认的分词器,按词切分,小写处理 |
Simple Analyzer | 按照非字母切分 (符号被过滤),小写处理 |
Stop Analyzer | 小写处理,停用词过滤 (the, a, this) |
Whitespace Analyzer | 按照空格切分,不转小写 |
Keyword Analyzer | 不分词,直接将输入当做输出 |
Pattern Analyzer | 正则表达式,默认是 \ W+(非字符串分隔) |
English Analyzer | 英文单词切分 |
当我们在读时或者写时分词时可以指定要使用的分词器, 如下图使用 English Analyzer 对
"Eating a banana day keeps!" 进行分词
更多案例
#A. Standard Analyzer GET _analyze { "analyzer": "standard", "text": "2 Running quick brown-foxes leap over lazy dog in the summer evening" } #B. Simple Analyzer GET _analyze { "analyzer": "simple", "text": "2 Running quick brown-foxes leap over lazy dog in the summer evening" } #C. Stop Analyzer GET _analyze { "analyzer": "stop", "text": "2 Running quick brown-foxes leap over lazy dog in the summer evening" } #D. Whitespace Analyzer GET _analyze { "analyzer": "whitespace", "text": "2 Running quick brown-foxes leap over lazy dog in the summer evening" } #E. Keyword Analyzer GET _analyze { "analyzer": "keyword", "text": "2 Running quick brown-foxes leap over lazy dog in the summer evening" } #F. Pattern Analyzer GET _analyze { "analyzer": "pattern", "text": "2 Running quick brown-foxes leap over lazy dog in the summer evening" }
6.2 写时分词器指定
写时分词器需要在 mappings 中指定, 而且一经指定就不能再修改, 若要修改必须新建索引.
构建索引指定分词器
PUT participles_en_english { "mappings": { "properties": { "msg_en":{ "type": "text", "analyzer": "english" }, "msg":{ "type": "text" } } } }
查询索引分词设置
GET participles_en_english/_mapping
插入数据
PUT /participles_en_english/_doc/1 { "msg":"Eating a banana day keeps!", "msg_en":"Eating a banana day keeps!" }
查询是否真的被分词
POST /participles_en_english/_search { "query": { "match": { "msg": "eat" } } } POST /participles_en_english/_search { "query": { "match": { "msg_en": "eat" } } }
6.3 读时分词器指定
由于读时分词器默认与写时分词器默认保持一致, 你搜索 msg 字段, 那么读时分词器为 Standard, 搜索 msg_en 时分词器则为 english.
查询需要插入字段分词结果
GET /participles_en_english/_analyze { "field": "msg_en", "text": "Eating a banana day keeps!" } GET /participles_en_english/_analyze { "field": "msg", "text": "Eating a banana day keeps!" }
6.4 自定义分词器
添加索引 my_analyze, 设置自定义分词器
PUT /my_analyze { "settings": { "analysis": { "char_filter": { "&_to_and":{ "type":"mapping", "mappings":["&=> and"] } }, "filter": { "my_stopwords":{ "type":"stop", "stopwords":["a","the","is"] } }, "analyzer": { "my_analyzer":{ "type":"custom", "char_filter":["html_strip","&_to_and"], "tokenizer":"standard", "filter":["lowercase","my_stopwords"] } } } } }
查看分词效果
GET /my_analyze/_analyze { "text": "<a> Tom & Jerry is a good </a> & </br> Friends", "analyzer": "my_analyzer" }
6.5 IK 分词器
IK Analyzer 是一个开源的, 基于 java 语言开发的轻量级的中文分词工具包. 从 2006 年 12 月推出 1.0 版开始, IKAnalyzer 已经推出了 4 个大版本. 最初, 它是以开源项目 Luence 为应用主体的, 结合词典分词和文法分析算法的中文分词组件. 从 3.0 版本开始, IK 发展为面向 Java 的公用分词组件, 独立于 Lucene 项目, 同时提供了对 Lucene 的默认优化实现. 在 2012 版本中, IK 实现了简单的分词歧义排除算法, 标志着 IK 分词器从单纯的词典分词向模拟语义分词衍化
由于 es 是 7.4.2, 故选择 ik7.4.2
6.5.1 安装
下载地址 1 https://github.com/medcl/elasticsearch-analysis-ik
下载地址 2
新建 ik 目录, 上传服务器(如果是集群, 需要安装所有节点)
使用 es 插件安装(需要 file:/// 协议头)
/usr/local/src/Elasticsearch/Elasticsearch-7.4.2/bin/Elasticsearch-plugin install file:///usr/local/src/ik/Elasticsearch-analysis-ik-7.4.2.zip
重启 es 即可
6.5.2 使用
未使用 IK 分词器
使用 ik_smart
POST _analyze { "analyzer": "ik_smart", "text": "南京市长江大桥" }
使用 ik_max_word
POST _analyze { "analyzer": "ik_max_word", "text": "南京市长江大桥" }
比较两个分词器对同一句中文的分词结果, ik_max_word 比 ik_smart 得到的中文词更多, 但这样也带来一个问题, 使用 ik_max_word 会占用更多的存储空间
6.5.3 ik 自定义词典
k 提供了自定义词典的功能, 也就是用户可以自己定义一些词汇, 这样 ik 就会把它们当作词典中的内容来处理. 其实就是上面中文能分成那么多词是有一套别人写好的词典.
如上面例子中,"江大桥'是个人, 而且火了, 我们现在先把江大桥也索引出来.
在 Elasticsearch-7.4.2/config/analysis-ik 目录下新建 my.dic 文件, 写入 "江大桥"
在 Elasticsearch-7.4.2/config/analysis-ik 目录下修改 IKAnalyzer.cfg.xml 文件
VIM IKAnalyzer.cfg.xml
# 修改如下内容, 指定拓展文件 <entry key="ext_dict">my.dic</entry>
重启 es 生效, 发现针对 ik_max_word 生效
6.5.4 ik 分词测试
创建索引
PUT participles_ik { "mappings": { "properties": { "msg_ch":{ "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" }, "msg":{ "type": "text", "analyzer": "ik_smart", "search_analyzer": "ik_smart" } } } }
插入数据
PUT /participles_ik/_doc/1 { "msg":"南京市长江大桥", "msg_ch":"南京市长江大桥" }
获取数据
POST /participles_ik/_search { "query": { "query_string": { "default_field": "msg", "query": "江大桥" } } }
POST /participles_ik/_search { "query": { "query_string": { "default_field": "msg_ch", "query": "江大桥" } } }
7 索引设计
索引设计重要性
好的索引设计在整个集群规划中占据举足轻重的作用, 索引的设计直接影响集群设计的好坏和复杂度.
好的索引设计应该是充分结合业务场景的时间维度和空间维度, 结合业务场景充分考量增, 删, 改, 查等全维度设计的.
好的索引设计是完全基于 "设计先行, 编码在后" 的原则, 前期会花很长时间, 为的是后期工作更加顺畅, 避免不必要的返工.
7.1 PB 级别的大索引如何设计?
7.1.1 存储大小限制维度
单个分片 (Shard) 实际是 Lucene 的索引, 单分片能存储的最大文档数是: 2,147,483,519 (= Integer.MAX_VALUE - 128), 可以使用'GET _cat/shards'命令查询全部索引的分隔分片的文档大小.
7.1.2 性能维度
索引很大的话, 数据写入和查询性能都会变差.
而高效检索体现在: 基于日期的检索可以直接检索对应日期的索引, 无形中缩减了很大的数据规模.
比如一开始我们的订单索引设计为 order, 要检索某一天的数据会是在一个月甚至更大体量的索引中进行.
如果索引设计为 "业务_yyyy-MM-dd" 形式, 现在直接检索 "order_2020-02-28" 的索引, 效率提升好几倍.
7.1.3 风险维度
一旦一个大索引出现故障, 相关的数据都会受到影响. 而分成滚动索引的话, 相当于做了物理隔离.
7.1.4 方案
方案一: rollover + curator + crontab(增量索引的管理模板)
方案二(推荐):Index Lifecycle Management(6.6 版本的新特性: 索引生命周期管理)
目的: 统一管理索引, 相关索引字段完全一致.
7.1.4.1 方案一: Rollver 增量管理索引
场景: 每天数据量很少, 但是又需要保存很久的数据, 或者每天数据量极大, 每天一个索引已经不能容纳了, 这个时候我们就需要考虑一个机制, 将索引 rollover.
按照日期, 文档数, 文档存储大小三个维度进行更新索引
#1 新建订单索引 PUT order #2 对 ordr 建立别名 POST _aliases { "actions": [ { "add": { "index": "order", "alias": "test_order", "is_write_index" : true } } ] } #3 手动 Rollover # 结合 alias, 我们可以实现客户端写 alias, 在需要时将 alias 指向一个新的索引, 就可以自由地控制数据的写入了 # 创建一个新索引 order1 PUT order1 #4 再将 alias 指向新的索引并移除旧的 alias POST _aliases { "actions": [ { "remove": { "index": "order", "alias": "test_order" } }, { "add": { "index": "order1", "alias": "test_order", "is_write_index" : true } } ] } # 4.1 自动 Rollover # 我们手动 Rollover 了一个索引, 在运行过程中, 我们需要不断的获取 ES 中索引的情况, 然后判断是否进行 Rollover. 这里, 我们可以用 ES 自带的 Rollover 接口替代, # 已经存在一个 order 索引, 和一个 test_order 别名指向 order, 测试是否可以_rollover POST test_order/_rollover/order2 { "conditions": { "max_age": "7d", "max_docs": 1, "max_size": "5gb" } }
#4.2 写入一个 document PUT test_order/_doc/1 { "msg":"value1" } #4.3 执行 4.1 的操作 POST test_order/_rollover/order2 { "conditions": { "max_age": "7d", "max_docs": 1, "max_size": "5gb" } }
索引更新的时机是: 当原始索引满足设置条件的三个中的一个的时候, 就会更新为新的索引. 为保证业务的全索引检索, 一般采用别名机制.
在索引模板设计阶段, 模板定义一个全局别名: 用途是全局检索, 如图所示的别名: indexall. 每次更新到新的索引后, 新索引指向一个用于实时新数据写入的别名, 如图所示的别名: indexlatest. 同时将旧索引的别名 index_latest 移除.
7.1.4.2 方案一: 使用 curator 高效清理历史数据
目的: 按照日期定期删除, 归档历史数据.
解决问题:
一个大索引的数据删除方式只能使用 delete_by_query, 由于 ES 中使用更新版本机制. 删除索引后, 由于没有物理删除, 磁盘存储信息会不减反增. 有同学就反馈 500GB+ 的索引 delete_by_query 导致负载增高的情况
如果没有基于时间创建索引, 单一索引借助 delete_by_query 结合时间戳, 会越删磁盘空间越紧张, 以至于对自己都产生了怀疑?
是否还在通过复杂的脚本管理索引?
1 个增量 rollover 动态更新脚本,
1 个定期 delete 脚本,
1 个定期 force_merge 脚本,
1 个定期 shrink 脚本,
1 个定期快照脚本.
索引多了或者集群规模大了, 脚本的维护是一笔不菲的开销.
而按照日期划分索引后, 不需要的历史数据可以做如下的处理.
删除 -- 对应 delete 索引操作.
压缩 -- 对应 shrink 操作.
段合并 -- 对应 force_merge 操作.
而这一切, 可以借助: curator 工具通过简单的配置文件结合定义任务 crontab 一键实现.
curator 最早被称为 clearESindices.py. 它的唯一功能是删除索引,
而后重命名: logstash_index_cleaner.py. 它在 logstash 存储库下作用: 过期日志清理.
此后不久, 原作者加入 Elastic, 它成为了 Elasticsearch Curator,
Git 地址: https://github.com/elastic/curator
文档地址: https://curator.readthedocs.io/en/latest/
7.1.4.3 方案二: 使用 ILM 索引生命周期管理
index lifecycle management
只需要在 kibana 内简单配置, 就可以管理以前我们不得不设置 cronjob 去删除 index 的工作.
ES 索引生命周期管理分为 4 个阶段: hot,warm,cold,delete, 其中 hot 主要负责对索引进行 rollover 操作, warm,cold,delete 分别对 rollover 后的数据进一步处理(前提是配置了 hot).
phases | desc |
---|---|
hot | 主要处理时序数据的实时写入 |
warm | 可以用来查询,但是不再写入 |
cold | 索引不再有更新操作,并且查询也会很少 |
delete | 数据将被删除 |
案例
建立简单的 ILM 策略
使用命令创建 policy, 其意思如下:
如果一个 index 的大小超过 50GB, 那么自动 rollover
如果一个 index 日期已在 30 天前创建索引后, 那么自动 rollover
如果一个 index 的文档数超过 2, 那么也会自动 rollover
当一个 index 创建的时间超过 90 天, 那么也自动删除
PUT _ilm/policy/datastream_policy { "policy": { "phases": { "hot": { "min_age": "0ms", "actions": { "rollover": { "max_age": "30d", "max_size": "50gb", "max_docs": 2 }, "set_priority": { "priority": 100 } } }, "delete": { "min_age": "90d", "actions": { "delete": {} } } } } }
或者使用 kibana 创建
定义 Index 模板, 使用策略
意思如下:
index_patterns 所有以 logs 开头的 index 都需要遵循这个规律.
rollover_alias 别名为 "logs".
PUT _template/datastream_template { "index_patterns": ["logs-*"], "order" : 0, "settings": { "number_of_shards": 1, "number_of_replicas": 1, "index.lifecycle.name": "datastream_policy", "index.lifecycle.rollover_alias": "logs" } }
或者使用 kibana 创建
定义 Index 别名
意思如下:
在这里定义了一个别名叫做 logs, 它指向 logs-00001 索引.
is_write_index 为 true. 如果有 rollover 发生时, 这个 alias 会自动指向最新 rollover 的 index
PUT logs-000001 { "aliases": { "logs": { "is_write_index": true } } }
添加数据前后查看索引策略
GET logs-*/_ilm/explain
生产数据, 查看效果
PUT logs/_doc/1 { "msg":"value1" } PUT logs/_doc/2 { "msg":"value2" } PUT logs/_doc/3 { "msg":"value3" }
补充索引创建日期滚动方式
#PUT /<logs-{now/d}-000001> with URI encoding: 效果 logs-2020.03.10-000001 # 特殊字符都应进行 URI 编码 PUT /{ "aliases": { "logs": { "is_write_index": true } } }
如上面操作后效果
转码表
< | < |
---|---|
> | > |
/ | / |
{ | { |
} | } |
| | | |
+ | + |
: | : |
,(逗号) | , |
例子(以 2020 年 3 月 10 日为例)
需要形式 | 格式 | 转码表 |
---|---|---|
logs-2020.03.10 | <logstash-{now/d}> | |
logs-2020.03.01 | <logstash-{now/M}> | |
logs-2020.03 | <logstash-{now/M{YYYY.MM}}> | |
logs-2020.02 | <logstash-{now/M-1M{YYYY.MM}}> |
7.2 分片数和副本数如何设计?
7.2.1 分片 / 副本认知
分片: 分片本身都是一个功能齐全且独立的 "索引", 可以托管在集群中的任何节点上.
数据切分分片的主要目的:
(1)水平分割 / 缩放内容量 .
(2)跨分片 (可能在多个节点上) 分布和并行化操作, 提高性能 / 吞吐量.
注意: 分片一旦创建, 不可以修改大小.
副本: 它在分片 / 节点出现故障时提供高可用性.
副本的好处: 因为可以在所有副本上并行执行搜索 -- 因此扩展了搜索量 / 吞吐量.
注意: 副本分片与主分片存储在集群中不同的节点. 副本的大小可以通过: number_of_replicas 动态修改.
分片副本例子
# 使用模板控制 kibana_开头的索引为 2 个分片 0 个副本 PUT _template/shards_and_replicas_template { "index_patterns": ["kibana_*"], "order" : 0, "settings": { "number_of_shards": 2, "number_of_replicas": 0 } } # 创建 kibana_logs 索引 PUT kibana_logs
插入一组数据
# 数据导入_reindex(3 个并行且执行刷新), 下图中可以看出是分了 3 组数据一起插入 POST /_reindex?slices=3&refresh { "source": { "index": "kibana_sample_data_logs" }, "dest": { "index": "kibana_logs" } }
# 查询 2 个分片的数据 GET kibana_logs/_search?preference=_shards:0 GET kibana_logs/_search?preference=_shards:1
分片数据移动例子
从上图中可以看出索引 kibana_logs 的分片 0 在 slave2 上, 分片 1 在 master 上, 现在要将分片 1 的数据全部移动到 slave1 上.
POST _cluster/reroute { "commands": [ { "move": { "index": "kibana_logs", "shard": 1, "from_node": "master", "to_node": "slave1" } } ] }
7.2.2 分片和副本设计
索引设置多少分片?
Shard 大小官方推荐值为 20-40GB, 具体原理呢? Elasticsearch 员工 Medcl 曾经讨论如下:
Lucene 底层没有这个大小的限制, 20-40GB 的这个区间范围本身就比较大, 经验值有时候就是拍脑袋, 不一定都好使.
Elasticsearch 对数据的隔离和迁移是以分片为单位进行的, 分片太大, 会加大迁移成本.
一个分片就是一个 Lucene 的库, 一个 Lucene 目录里面包含很多 Segment, 每个 Segment 有文档数的上限, Segment 内部的文档 ID 目前使用的是 Java 的整型, 也就是 2 的 31 次方, 所以能够表示的总的文档数为 Integer.MAXVALUE - 128 = 2^31 - 128 = 2147483647 - 1 = 2,147,483,519, 也就是 21.4 亿条.
同样, 如果你不 forcemerge 成一个 Segment, 单个 shard 的文档数能超过这个数.
单个 Lucene 越大, 索引会越大, 查询的操作成本自然要越高, IO 压力越大, 自然会影响查询体验.
具体一个分片多少数据合适, 还是需要结合实际的业务数据和实际的查询来进行测试以进行评估
综合实战 + 网上各种经验分享, 梳理如下:
第一步: 预估一下数据量的规模. 一共要存储多久的数据, 每天新增多少数据? 两者的乘积就是总数据量.
第二步: 预估分多少个索引存储. 索引的划分可以根据业务需要.
第三步: 考虑和衡量可扩展性, 预估需要搭建几台机器的集群. 存储主要看磁盘空间, 假设每台机器 2TB, 可用: 2TB*0.85(磁盘实际利用率)0.85(ES 警戒水位线).
第四步: 单分片的大小建议最大设置为 30GB. 此处如果是增量索引, 可以结合大索引的设计部分的实现一起规划.
前三步能得出一个索引的大小. 分片数考虑维度:
1)分片数 = 索引大小 / 分片大小经验值 30GB .
2)分片数建议和节点数一致.
设计的时候 1),2)两者权衡考虑 + rollover 动态更新索引结合.
每个 shard 大小是按照经验值 30G 到 50G, 因为在这个范围内查询和写入性能较好.
索引设置多少副本?
对于集群数据节点>=2 的场景: 建议副本至少设置为 1.
对于集群数据节点>=3 的场景: 建议副本至少设置为 2.
多副本带来的就是多落地磁盘, 也就会慢.
单节点的机器设置了副本也不会生效的.
对于数据安全性要求非常高的业务场景, 建议做好: 增强备份(结合 ES 官方备份方案).
8 JAVA API
项目地址: https://github.com/70416450/Bigdata-Util
修改配置文件中的信息即可使用, 看效果吧.
来源: https://www.cnblogs.com/ttzzyy/p/12469726.html