一文上手ElasticSearch

1,520 阅读9分钟

最近花了几天时间撸了下 ElasticSearch 的文档,这是篇学习记忆存档,不涉及到原理相关的东西,希望能看完这篇文章后,用最短的时间来较快速全面的上手 ElasticSearch。如果有错误欢迎大家指正修改。

学习一个东西,最快的方式就是使用它

部署

ES的部署

ES 的部署十分简单,直接下载归档包,解压后拆包即用,可以在官网下载自己需要的版本,这里用的是 7.9 作为例子。

wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.9.3-linux-x86_64.tar.gz
tar -xzvf elasticsearch-7.9.3-linux-x86_64.tar.gz
mv elasticsearch-7.9.3-linux-x86_64.tar.gz <target_dir>
# es 不支持root用户启动,所以要新建个用户
groupadd es && useradd -r es -g es
chown -R es:es <target_dir>

中文分词器

这里用最常用的中文分词器ik举例

安装

elasticsearch-plugin  install \
https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.9.3/elasticsearch-analysis-ik-7.9.3.zip

设置

  • 通过 mapping 映射来选择哪些字段使用ik分词
  • 通过 POST analyze 可以指定分词器获取分词结果
curl -x POST /_analyze -d
{
    "analyzer": "ik_smart",
    "text": "铝合金营养不粘微压锅6L" 
}

自定义词库

cd <es_dir>/analysis-ik/
create my.dic
vim <es_dir>/analysis-ik/IKAnalyzer.cfg.xml

配置

ES 只需很少的配置就可使用,如果仅仅是出于学习的目的,甚至可以只使用默认配置。 高可用是 ES 在设计时着重考虑的一个特性,所以 ES 支持集群部署,仅需增加几项配置即可完成。 ES 也支持在运行中动态配置。

配置文件的位置

使用归档安装方式,默认配置文件目录在config文件夹内,可以通过设置环境变量ES_PATH_CONF=/path/to/my/config来自定义配置目录

  • elasticsearch.yml ES的配置文件
  • jvm.options ES jvm 配置
  • log4j2.properties 日志配置

常用配置项

# 集群名称,只有同样的集群名称的节点,才能构建集群,默认 elasticsearch
cluster.name: my-es
cluster.initial_master_nodes: ["node1"] # 初始master节点,集群初始化时用来引导

node.name: node1 # 当前节点的名称

path.data: /path/to/es/data # 数据的存放路径
path.logs: /path/to/es/log  # 日志的存放路径

# 哪些ip可以访问,默认 _local_
# _local_  127.0.0.1
# _site_   内网可访问
# _global_ 全局可访问
network.host: _site_ 

http.port: 9200 # REST端口,默认9200

transport.port: 9300     # 节点间内部通信端口
transport.compress: true # 启用节点间压缩,默认false

# 集群ips
discovery.zen.ping.unicast.hosts: 
    - "192.168.0.1"
    - "192.168.0.2:9300"
    - "my.els.com"
# 最小主节点数,为了防止 脑裂 集群节点数最少为 半数+1
# 节点集群最好大于3个,2个有可能会脑裂。。。
discovery.zen.minimum_master_nodes: 2 

启动异常及修复

  1. max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535]
    vim /etc/security/limits.conf
    * soft nofile 65536
    * hard nofile 65536

    # 保存后重新登录用户
    # 使用 ulimit -S -n/ulimit -H -n 查看是否生效
  1. max number of threads [1024] for user [elasticsearch] is too low, increase to at least [4096]
    vim /etc/security/limits.d/90-nproc.conf
    *          soft    nproc     4096
  1. max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
    vim /etc/sysctl.conf
    vm.max_map_count=262144 
    sysctl -p
  1. system call filters failed to install; check the logs and fix your configuration or disable system call filters at your own risk
    # 这是在因为Centos6不支持SecComp,
    # 而ES5.2.0默认bootstrap.system_call_filter为true进行检测,
    # 所以导致检测失败,失败后直接导致ES不能启动
    # 解决:
    # 在elasticsearch.yml中配置bootstrap.system_call_filter为false
    # 注意要在Memory下面:

    bootstrap.memory_lock: false
    bootstrap.system_call_filter: false

文档的CURD操作

旧版本中(5.0之前),ES 的术语和 MySQL 对应关系为:

index => 库;type => 表;doc => 行。

在 7.0 之后的版本中,移除了type的概念,实际上index可以理解为数据的集合,也就是表的概念了,doc仍然对应具体的数据行。

提到 ES 的索引,有个很重要的概念倒排索引,倒排索引是 ES 能够快速搜索和排序的重要原因,ES 会给文档的每个需要查询的字段建立索引(除非在建立索引映射时指明了不需要搜索)。倒排索引的原理其他博客已经说得很详细了,可以参考官方文档或者其他文章。

创建索引

有两种方式可以创建索引:

  1. 显式通过mappingAPI创建,后面具体讲
  2. 首次创建文档时,ES 会自动创建索引,会依据文档值,推断生成索引的字段类型。

我们基于一个虚拟的用户信息user来进行示例,所以要创建的索引就是user。 ES 提供了 RESTful API,参数接收 JSON 格式

单个文档的 CURD 都可以通过 _doc API 完成,即: /{index}/_doc/{id}

创建文档

# pretty 增加pretty参数,会以宜读的格式返回
curl -x PUT /user/_doc/1?pretty -d
{
	"name": "王老五",
    "intro": "上海五套房,杭州3套房,北京俩别墅,孤独寂寞喜欢诗",
    "gender": "M",
    "age": 26,
    "created": "2020-11-11 10:23:34"
}

# 返回以下数据就创建成功了
{
    "_index": "user",
    "_type": "_doc",
    "_id": "1",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 2,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

创建文档是PUT类型的请求,此时必须要指明文档的id,也可以通过POST请求不指定文档id来完成创建

curl -x POST /user/_doc?pretty -d
{
	"name": "王老六",
    "intro": "王老五的弟弟,还在上学",
    "gender": "M",
    "age": 21,
    "created": "2020-11-11 10:30:34"
}

# 返回
{
    "_index": "user",
    "_type": "_doc",
    "_id": "S6vfznUBBA-Q0zh_ydNu", // 此时的id为系统生成
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 2,
        "failed": 0
    },
    "_seq_no": 1,
    "_primary_term": 1
}

可以通过查看索引列表API看到刚刚创建的索引

curl -x GET /_cat/indices?pretty

# 返回
green open user       Me4Qr0m6SWybZqE2W6O8NA 1 1    1 0 12.5kb 6.2kb

获取文档

使用GET请求doc API,可以获取指定id的文档

curl -x GET /user/_doc/1?pretty
# 返回
{
    "_index": "user",
    "_type": "_doc",
    "_id": "1",
    "_version": 1,
    "_seq_no": 0,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "name": "王老五",
        "intro": "上海五套房,杭州3套房,北京俩别墅,孤独寂寞喜欢诗",
        "gender": "M",
        "age": 26,
        "created": "2020-11-11 10:23:34"
    }
}

第一列表示数据的同步状态,green数据已同步到全部节点,yellow数据已同步到主节点,单未同步到备份节点,red数据同步未完成。

更新文档

有两种方式可以更新文档

  1. POST _update API
  2. 再次调用创建文档API

第一种方式可以只更新文档的部分字段,但无论哪种方式,实际上都是替换原有的文档并重建索引。

curl -x POST /user/_update/1?pretty -d
{
	"doc": {
        "name": "王老五先生"
    }
}

删除文档

curl -x DELETE /user/_doc/2?pretty
# 返回
{
    "_index": "user",
    "_type": "_doc",
    "_id": "1",
    "_version": 3,
    "result": "deleted",
    "_shards": {
        "total": 2,
        "successful": 2,
        "failed": 0
    },
    "_seq_no": 4,
    "_primary_term": 1
}

搜索

DSL查询语法

ES 最核心的功能就是搜索,搜索查询有很强大的DSL查询语法,而且支持聚合查询,ES 也内置了用 SQL 语句来完成搜索

用一个例子来全面描述最常用的DSL查询语法:

{
    // query 部分描述了查询条件
    "query": {
        "match_all":{}, // 空条件,即搜索全部记录,和下面的条件互斥

        /* 查询条件 */
        /* 查询条件分为基础查询条件(单项条件查询)和组合查询条件(bool查询,如And(must)等) */
        // 基础查询条件,在query中不允许存在多个,如果想要组合查询,需要配合bool中使用
        
        // 字段是否为空
        "exists": { 
            "field": "field_name", // or ["field_names", ...]
        },
        
        // 全文索引
        // 两种方式,一种是直接指定字段k:v
        "match": { 
            "field_name": "value value2" // 多个值用空格隔开,默认同 or
        },
        // 另一种是可以设置额外属性
        "match": {
            "field_name": {
                "query": "keyword",
                "operator": "or", //操作符,定义字段值间的查询关系,默认or,可变更为and
                "boost": 1.0, // 查询语句积分权重,默认1.0
                'minimum_should_match' => 1,// 最小匹配参数, 数字或百分比,操作符为or时,最少需要匹配几个条件
            },
        },
        
        // 精准匹配
        "term": {"field_name": "keyword"}, // 单值精准匹配
        "terms": {"field_name": ["keywords"]},  // 多值精准匹配
        
        // 范围查询,一般会嵌套在filter中使用
        // 这个查询等价于 field_name >=1 && field_name < 10
        // range需要通过额外设置search.allow_expensive_queries来支持text或keyword字段类型
        "range": {  
            "field_name": {
                "lt": 10,
                "gte": 1,
            }
        },
        
        // 多字段匹配
        "multi_match": { 
            "query": "keyword keyword2", //字段值,多个值用空格隔开
            "type": "best_fields",       //best_fields(默认)、 most_fields 和 cross_fields (最佳字段、多数字段、跨字段)
            "operator": "or",            //操作符,默认or,可变更为and
            "boost": 1,                  //语句权重,用于计算分值
            "minimum_should_match": 1,   //or时,最少匹配条件数
        },
        
        /* 布尔查询即组合嵌套查询 */
        // bool 查询子项可以嵌套组合任意基础查询和bool查询
        // 如果需要用到组合查询的话,可以用bool完成
        "bool": { 
            "must": [],     // AND
            "must_not": [], // NOT  不参与评分
            "should": [],   // OR
            "filter": [],   // 过滤器,对查询结果进行过滤 不参与评分
        }
    },
    
    /* limit */
    "from": 0,
    "size": 10,
    
    /* sort 排序 */
    // 默认排序会通过查询语句计算相关度 _score,通过相关度排序
    // 如果制定了排序字段,同时不指定相关度参与排序,查询结果将不会计算相关度评分
    "sort": {
        "num": "asc",
        "_score": "desc"
    },
    
    // 选择返回的字段
    "_source": ["field1", "field2"]
    // TODO 聚合
}

用SQL语法查询

最新版的 ES 内置了 SQL 语法查询xpack包,内部会将 SQL 语句翻译成 DSL 查询,而且支持全文索引和聚合语法。需要注意的是SQL仅支持查询,不支持插入、更新、删除。

  1. SQL语句不带分号结尾
  2. SQL语句不支持返回数组字段,也不支持指定返回对象字段(select object from table)
curl -x POST /_xpack/sql?format=txt -d
{
	"query": "DESC user"
}
# 返回
    column     |     type      |    mapping    
---------------+---------------+---------------
age            |BIGINT         |long           
created        |VARCHAR        |text           
created.keyword|VARCHAR        |keyword        
gender         |VARCHAR        |text           
gender.keyword |VARCHAR        |keyword        
intro          |VARCHAR        |text           
intro.keyword  |VARCHAR        |keyword        
name           |VARCHAR        |text           
name.keyword   |VARCHAR        |keyword        

# 使用全文索引
# MATCH(field[s], text, [options])
SELECT * FROM user WHERE MATCH(intro, "孤独 房子")
SELECT * FROM user WHERE MATCH(name, '王', 'operator=or;cutoff_frequency=0.2')

# 同样支持使用query
# QUERY(expr, [options])
SELECT * FROM user WHERE QUERY('name:王')

ES 也提供了将 SQ L语句翻译成 DSL 的翻译API:

curl -x POST /_sql/translate -d
{
	"query": "SELECT * FROM user"
}

索引映射

索引映射可以配置索引的文档字段类型以及全文索引的分词解析器等,调用映射接口会创建索引,所以重建索引需要删掉旧索引(这里暂不涉及到平滑重建索引)

删除索引

curl -x DELETE /user

映射索引

curl -x PUT /user -d
{
	"mappings": {
    	"properties": {
            // 普通字段
            "name": {
                "type": "text" // 字段类型下面会详细讲解
                "index": true, // 是否可检索,默认是
                
                // 仅text类型可用属性,text会被全文索引
                "analyzer": "ik_smart", // 建立索引时的分词器,默认default英文分词器
                "search_analyser": "ik_max_word" // 搜索时的分词器
                
                // 多元索引
                // 一个字段可以创建不同的索引,name既可以是text全文检索,也可以是keyword用来精准查询
                "fields: {
                    "raw": { // name.raw 即可使用这个字段
               	        "type": "keyword"
                        // 嵌套设置项 ...
                    }
                }
            },
            // 数组或对象
            "field2_array_or_object": {
            	"dynamic": false, 是否支持动态将新字段建立索引,默认false
                "properties": {
                    "field2.item1": {} // 同普通字段配置
                    // ...
                }
            }
        }
    }
}

常用映射字段类型

详细的字段文档可以看这里

  • keyword关键字类型,一般用来存储id、状态、标签等不需要解析的值

    什么时候需要用关键字类型?

    • 不打算使用范围查询来搜索标识符数据
    • 需要快速检索。关键词字段上的术语查询搜索通常比数字字段上的术语搜索快。
    • 如果不确定使用方式,可以尝试使用多索引类型↓
  • text 用于分析的文本类型,可以全文检索

    analyzer 设置分词器,如 ik_smart ik_max_word,默认英文分词器

  • boolean 布尔类型

  • numbers 数字类型

    常用数字类型

    • long => int64
    • integer => int32
    • double => float64
    • float => float32
  • date 日期类型

{
    "created": {
        "type": "date",
        // format 字段可以指定可解析的类型
        // 如下可解析 2020-11-11 10:23:34 和 时间戳
        // 只有格式化的日期支持1970年以前
        // epoch_seconds 支持精确到秒的时间戳
        "format": "yyyy-MM-dd HH:mm:ss||epoch_seconds",
    }
}
  • alias 其他字段的别名
{
    "field1": {
        "type": "long"
    },
    "field1_alias": {
        "type": "alias",
        "path": "field1"
    }
}
  • object json对象类型

    • 对象的索引类型,实际是对象的深层单个字段
    • object如果字段过多可能会引起‘索引爆炸’,这时可以用flattened来索引整个对象
  • array 数组类型

    • 数组类型的所有元素都应该是同样的类型
    • 数组元素支持对象
    • 数组的搜索可能不是预期结果,可以参考nested类型,优化数组搜索