Elasticsearch入门篇-基本概念&中文分词器IK

学习资料汇总

Overview

开源的 Elasticsearch(以下简称 Elastic)是目前全文搜索引擎的首选。它可以快速地储存、搜索和分析海量数据,维基百科、Stack Overflow、Github 都采用它。

Elastic 的底层是开源库 Lucene。但是,你没法直接用 Lucene,必须自己写代码去调用它的接口。ElasticLucene 的封装,提供了 REST API 的操作接口,开箱即用。

FAQ

分词器和ES版本不匹配

执行 elasticsearch 启动 ES 时遇到如下错误,提示分词器版本和ES版本不匹配。

Plugin [analysis-ik] was built for Elasticsearch version 7.6.1 but version 7.9.1 is running
复制代码

解决办法为重新安装版本匹配的分词器即可。参考 elasticsearch-analysis-ik | github

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.9.1/elasticsearch-analysis-ik-7.9.1.zip
复制代码

ES 安装配置

  1. 使用 Homebrew 安装 ElasticSearch
brew tap elastic/tap

brew install elastic/tap/elasticsearch-full
复制代码
  1. 默认的 ES 安装目录
Type Description Default Location Setting
home Elasticsearch home directory or $ES_HOME /usr/local/var/homebrew/linked/elasticsearch-full
conf Configuration files including elasticsearch.yml /usr/local/etc/elasticsearch ES_PATH_CONF
data /usr/local/var/lib/elasticsearch path.data
logs /usr/local/var/log/elasticsearch path.logs
plugins /usr/local/var/homebrew/linked/elasticsearch/plugins
  1. 至此,ES 测试环境已经搭建完毕,在进入正式开发前,需要做以下的额外步骤
  1. 下面进行简单的运行,验证配置是否成功
  • 在终端执行 elasticsearch,运行ES。如果遇到 max virtual memory areas vm.maxmapcount [65530] is too low 报错,可以执行下述命令
sudo sysctl -w vm.max_map_count=262144
复制代码
  • 如果一切正常,Elastic 就会在默认的 9200 端口运行。这时,打开另一个命令行窗口,请求该端口,会得到说明信息
ZBMAC-b286c5fb6:~ liubaoshuai1$ curl localhost:9200
{
  "name" : "ZBMAC-b286c5fb6",
  "cluster_name" : "elasticsearch_liubaoshuai1",
  "cluster_uuid" : "v6A5SuX2RI2Clgs30qhq7g",
  "version" : {
    "number" : "7.6.2",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f",
    "build_date" : "2020-03-26T06:34:37.794943Z",
    "build_snapshot" : false,
    "lucene_version" : "8.4.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}
复制代码
  • 上面代码中,请求 9200 端口,Elastic 返回一个 JSON 对象,包含当前节点、集群、版本等信息
  • 按下 Ctrl + C,Elastic 就会停止运行
  • 默认情况下,Elastic 只允许本机访问,如果需要远程访问,可以修改 Elastic 安装目录的config/elasticsearch.yml 文件,去掉 network.host 的注释,将它的值改成 0.0.0.0,然后重新启动 Elastic。设成 0.0.0.0 表示允许任何人都可以访问。线上服务不要这样设置,要设成具体的 IP
network.host: 0.0.0.0
复制代码
  1. cat 命令可以帮助开发者快速查询 Elasticsearch 的相关信息
  • 使用 _cat 可以查看支持的命令
ZBMAC-b286c5fb6:~ liubaoshuai1$ curl localhost:9200/_cat
=^.^=
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes
/_cat/tasks
/_cat/indices
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/thread_pool/{thread_pools}
/_cat/plugins
/_cat/fielddata
/_cat/fielddata/{fields}
/_cat/nodeattrs
/_cat/repositories
/_cat/snapshots/{repository}
/_cat/templates
复制代码
  • 每个命令都支持使用 ?v 参数,来显示详细的信息
ZBMAC-b286c5fb6:~ liubaoshuai1$ curl localhost:9200/_cat/master?v
id                     host      ip        node
KqsnRZTeRbeKimt8XfGAoQ 127.0.0.1 127.0.0.1 ZBMAC-b286c5fb6
复制代码
  • 每个命令都支持使用 help 参数,来输出可以显示的列
ZBMAC-b286c5fb6:~ liubaoshuai1$ curl localhost:9200/_cat/master?help
id   |   | node id    
host | h | host name  
ip   |   | ip address 
node | n | node name 
复制代码
  • 通过 h 参数,可以指定输出的字段
ZBMAC-b286c5fb6:~ liubaoshuai1$ curl localhost:9200/_cat/master?v
id                     host      ip        node
KqsnRZTeRbeKimt8XfGAoQ 127.0.0.1 127.0.0.1 ZBMAC-b286c5fb6
ZBMAC-b286c5fb6:~ liubaoshuai1$ curl localhost:9200/_cat/master?h=host,ip,node
127.0.0.1 127.0.0.1 ZBMAC-b286c5fb6
复制代码
  • 数字类型的格式化:很多的命令都支持返回可读性的大小数字,比如使用 mb 或者 kb 来表示
ZBMAC-b286c5fb6:~ liubaoshuai1$ curl localhost:9200/_cat/indices?v
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
复制代码

ES基本概念

ES中概念和关系型数据库的概念类比关系如下表所示。

Relational Database Elasticsearch
Database Index
Table Type
Row Document
Column Field
Scheme Mapping
Index Everything is indexed
SQL Query DSL
SELECT * FROM ... GET http://...
UPDATE table_name SET ... PUT http://...

Tip: ES中一个 index 下最好只创建一个 type,不要创建多个type。

Node 与 Cluster

Elastic 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个 Elastic 实例。

单个 Elastic 实例称为一个节点(node)。一组节点构成一个集群(cluster)。

Index

Elastic 会索引所有字段,经过处理后写入一个反向索引(Inverted Index)。查找数据的时候,直接查找该索引。

所以,Elastic 数据管理的顶层单位就叫做 Index(索引)。它是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写。

下面的命令可以查看当前节点的所有 Index。

$ curl -X GET 'http://localhost:9200/_cat/indices?v'
复制代码

Document

Index 里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。

Document 使用 JSON 格式表示,下面是一个例子。

{
  "user": "张三",
  "title": "工程师",
  "desc": "数据库管理"
}
复制代码

同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。

Type

Document 可以分组,比如 weather 这个 Index 里面,可以按城市分组(北京和上海),也可以按气候分组(晴天和雨天)。这种分组就叫做 Type它是虚拟的逻辑分组,用来过滤 Document

不同的 Type 应该有相似的结构(schema),举例来说,id 字段不能在这个组是字符串,在另一个组是数值。这是与关系型数据库的表的一个区别。性质完全不同的数据(比如 productslogs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。

下面的命令可以列出每个 Index 所包含的 Type。

$ curl 'localhost:9200/_mapping?pretty=true'
复制代码

根据规划,Elastic 6.x 版只允许每个 Index 包含一个 Type,7.x 版将会彻底移除 Type。

elasticsearch-head 可视化插件

A web front end for an Elasticsearch cluster

elasticsearch-head 的安装步骤如下

  1. 下载源码并安装依赖项
git clone git://github.com/mobz/elasticsearch-head.git

cd elasticsearch-head

npm install

npm run start
复制代码
  1. 【推荐使用该方式】访问 http://localhost:9100/

需要注意的是,也可以通过 Chrome 插件方式进行安装,参考 elasticsearch-head | github

Kibana 可视化平台

Kibana 是一个针对 Elasticsearch 的开源分析及可视化平台,用来搜索、查看交互存储在 Elasticsearch 索引中的数据。

  1. 使用 Homebrew 安装 Kibana
brew tap elastic/tap

brew install elastic/tap/kibana-full
复制代码
lbsMacBook-Pro:~ lbs$ brew install elastic/tap/kibana-full
==> Installing kibana-full from elastic/tap
==> Downloading https://artifacts.elastic.co/downloads/kibana/kibana-7.6.2-darwin-x86_64.tar.gz?tap=elastic/homebrew-tap
######################################################################## 100.0%
==> Caveats
Config: /usr/local/etc/kibana/
If you wish to preserve your plugins upon upgrade, make a copy of
/usr/local/opt/kibana-full/plugins before upgrading, and copy it into the
new keg location after upgrading.

To have launchd start elastic/tap/kibana-full now and restart at login:
  brew services start elastic/tap/kibana-full
Or, if you don't want/need a background service you can just run:
  kibana
==> Summary
🍺  /usr/local/Cellar/kibana-full/7.6.2: 99,457 files, 689.8MB, built in 7 minutes 17 seconds
复制代码
  1. 默认的 Kibana 安装路径如下表所示
Type Description Default Location Setting
home Kibana home directory or $KIBANA_HOME /usr/local/var/homebrew/linked/kibana-full
bin Binary scripts including kibana to start a node and kibana-plugin to install plugins /usr/local/var/homebrew/linked/kibana-full/bin
conf Configuration files including kibana.yml /usr/local/etc/kibana
data The location of the data files of each index / shard allocated on the node. Can hold multiple locations. /usr/local/var/lib/kibana path.data
logs Log files location. /usr/local/var/log/kibana path.logs
plugins Plugin files location. Each plugin will be contained in a subdirectory. /usr/local/var/homebrew/linked/kibana-full/plugins
  1. 启动 elastissearch
  2. 启动 kibana
lbsMacBook-Pro:~ lbs$ elasticsearch
lbsMacBook-Pro:~ lbs$ kibana

//后台启动
nohup nice kibana &  
复制代码
  1. 访问 http://localhost:5601/ 可以看到 Kibana 面板
  2. 访问 http://localhost:5601/status 可以查看 Kibana 状态

Index 新建/删除

新建 Index,可以直接向 Elastic 服务器发出 PUT 请求。下面的例子是新建一个名叫 weather 的 Index。

$ curl -X PUT 'localhost:9200/weather'
复制代码

服务器返回一个 JSON 对象,里面的 acknowledged 字段表示操作成功。

{
  "acknowledged":true,
  "shards_acknowledged":true
}
复制代码

然后,我们发出 DELETE 请求,删除这个 Index。

$ curl -X DELETE 'localhost:9200/weather'

{
   "acknowledged":true
}
复制代码

数据操作

FAQ

在进行数据操作时,若遇到下述报错,可以参考如上链接解决。

elasticsearch6.x {"error":"Content-Type header [application/x-www-form-urlencoded] is not supported"
复制代码

The Elasticsearch engineering team is busy working on features for Elasticsearch 6.0. One of the changes that is coming in Elasticsearch 6.0 is strict content-type checking.

Starting from Elasticsearch 6.0, all REST requests that include a body must also provide the correct content-type for that body. --- elasticsearch6.x {"error":"Content-Type header [application/x-www-form-urlencoded] is not supported" - 官方Fix

在 ES 6.0之后,采用了严格 content-type 校验,需要添加 -H'Content-Type: application/json' 参数在命令行中。比如

curl -X PUT 'localhost:9200/accounts/person/1' -d '
{
  //...
}' 
复制代码

需要更新为

curl -H'Content-Type: application/json' -X PUT 'localhost:9200/accounts/person/1' -d '
{
  //...
}' 
复制代码

新增记录

向指定的 /Index/Type 发送 PUT 请求,就可以在 Index 里面新增一条记录。比如,向 /accounts/person 发送请求,就可以新增一条人员记录。

ZBMAC-b286c5fb6:~ liubaoshuai1$ curl -H "Content-Type: application/json" -X PUT 'localhost:9200/accounts/person/1' -d '
> {
>   "user": "张三",
>   "title": "工程师",
>   "desc": "数据库管理"
> }'
复制代码

服务器返回的 JSON 对象,会给出 Index、Type、Id、Version 等信息。

{           
    "_index":"accounts",
    "_type":"person",
    "_id":"1",
    "_version":1,
    "result":"created",
    "_shards":{"total":2,"successful":1,"failed":0},
    "_seq_no":0,
    "_primary_term":1
 }
复制代码

仔细看,会发现请求路径是 /accounts/person/1,最后的 1 是该条记录的 Id。它不一定是数字,任意字符串(比如abc)都可以。

新增记录的时候,也可以不指定 Id,这时要改成 POST 请求。

$ curl -H "Content-Type: application/json" -X POST 'localhost:9200/accounts/person' -d '
{
  "user": "李四",
  "title": "工程师",
  "desc": "系统管理"
}'
复制代码

这个时候,服务器返回的 JSON 对象里面,_id 字段就是一个随机字符串。

{
  "_index":"accounts",
  "_type":"person",
  "_id":"A9QxNXEBLRNcEQlvnE1_",
  "_version":1,
  "result":"created",
  "_shards":{"total":2,"successful":1,"failed":0},
  "_seq_no":2,
  "_primary_term":2
}
复制代码

查看记录

/Index/Type/Id 发出 GET 请求,就可以查看这条记录。

URL 的参数 pretty=true 表示以易读的格式返回。

返回的数据中,found 字段表示查询成功,_source 字段返回原始记录。

$ curl 'localhost:9200/accounts/person/1?pretty=true'

{
  "_index" : "accounts",
  "_type" : "person",
  "_id" : "1",
  "_version" : 2,
  "_seq_no" : 1,
  "_primary_term" : 2,
  "found" : true,
  "_source" : {
    "user" : "张三",
    "title" : "工程师",
    "desc" : "数据库管理"
  }
}
复制代码

删除记录

删除记录就是发出 DELETE 请求。

$ curl -X DELETE 'localhost:9200/accounts/person/1'
复制代码

更新记录

更新记录就是使用 PUT 请求,重新发送一次数据。

$ curl -H "Content-Type: application/json" -X PUT 'localhost:9200/accounts/person/1' -d '
{
    "user" : "张三",
    "title" : "工程师",
    "desc" : "数据库管理,软件开发"
}' 
复制代码

可以发现,返回的JSON对象中,记录的 Id 没变,但是版本(version)变化了,操作类型(result)从 created 变成 updatedcreated 字段变成 false,因为这次不是新建记录。

{
  "_index":"accounts",
  "_type":"person",
  "_id":"1",
  "_version":3,
  "result":"updated",
  "_shards":{"total":2,"successful":1,"failed":0},
  "_seq_no":3,
  "_primary_term":2
}
复制代码

额外补充的是,若要对返回的 JSON 对象格式化,可以执行下述步骤

  1. NPM 全局安装jsonnpm install -g json
  2. 在上述命令参数后面加上 | json
  3. 若不想显示 curl 的统计信息,可以添加 -s 参数(参考-Curl不显示统计信息% Total % Received % | 简书
$ curl -H "Content-Type: application/json" -X PUT 'localhost:9200/accounts/person/1' -d '
{
    "user" : "张三",
    "title" : "工程师",
    "desc" : "数据库管理,软件开发"
}'  |json
复制代码

数据查询

返回所有记录

使用 GET 方法,直接请求 /Index/Type/_search,就会返回所有记录。

$ curl 'localhost:9200/accounts/person/_search'

{
    "took": 629,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 1,
        "hits": [
            {
                "_index": "accounts",
                "_type": "person",
                "_id": "A9QxNXEBLRNcEQlvnE1_",
                "_score": 1,
                "_source": {
                    "user": "李四",
                    "title": "工程师",
                    "desc": "系统管理"
                }
            },
            {
                "_index": "accounts",
                "_type": "person",
                "_id": "1",
                "_score": 1,
                "_source": {
                    "user": "张三",
                    "title": "工程师",
                    "desc": "数据库管理,软件开发"
                }
            }
        ]
    }
}
复制代码

上面代码中,返回结果的 took 字段表示该操作的耗时(单位为毫秒),timed_out 字段表示是否超时,hits 字段表示命中的记录,里面子字段的含义如下

  • total:返回记录数
  • max_score:最高的匹配程度
  • hits:返回的记录组成的数组

全文搜索

Elastic 的查询非常特别,使用自己的查询语法,要求 GET 请求带有数据体。

$ curl -H "Content-Type: application/json" 'localhost:9200/accounts/person/_search'  -d '
{
  "query" : { "match" : { "desc" : "软件" }}
}'
复制代码

上面代码使用 Match 查询,指定的匹配条件是 desc 字段里面包含"软件"这个词。返回结果如下。

{
    "took": 33,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 1.1978253,
        "hits": [
            {
                "_index": "accounts",
                "_type": "person",
                "_id": "1",
                "_score": 1.1978253,
                "_source": {
                    "user": "张三",
                    "title": "工程师",
                    "desc": "数据库管理,软件开发"
                }
            }
        ]
    }
}
复制代码

Elastic 默认一次返回10条结果,可以通过 size 字段改变这个设置。还可以通过 from 字段指定位移。

$ curl -H "Content-Type: application/json" 'localhost:9200/accounts/person/_search'  -d '
{
  "query" : { "match" : { "desc" : "管理" }},
  "size": 20   //定义size
  “from": 1,   // 从位置1开始(默认是从位置0开始)
}'
复制代码

逻辑运算

如果有多个搜索关键字, Elastic 认为它们是或 (or) 关系。

$ curl -H "Content-Type: application/json"  'localhost:9200/accounts/person/_search'  -d '
{
  "query" : { "match" : { "desc" : "软件 系统" }}
}'
复制代码

上面代码搜索的是 软件 or 系统

如果要执行多个关键词的 and 搜索,必须使用布尔查询

$ curl -H "Content-Type: application/json" 'localhost:9200/accounts/person/_search'  -d '
{
  "query": {
    "bool": {
      "must": [
        { "match": { "desc": "软件" } },
        { "match": { "desc": "系统" } }
      ]
    }
  }
}'
复制代码

中文分词设置

IK 在线分词器

分词基本概念

当一个文档被存储时,ES 会使用分词器从文档中提取出若干词元(token)来支持索引的存储和搜索。ES 内置了很多分词器,但内置的分词器对中文的处理不好。下面通过例子来看内置分词器的处理。在 web 客户端发起如下的一个 REST 请求,对英文语句进行分词

curl -H "Content-Type: application/json" -PUT 'localhost:9200/_analyze' -d '
{  
    "text": "hello world"  
}' | json

// |json 表示对返回结果进行json化展示
复制代码

返回结果如下

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   213  100   179  100    34  44750   8500 --:--:-- --:--:-- --:--:-- 53250
{
  "tokens": [
    {
      "token": "hello",
      "start_offset": 0,
      "end_offset": 5,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "world",
      "start_offset": 6,
      "end_offset": 11,
      "type": "<ALPHANUM>",
      "position": 1
    }
  ]
}
复制代码

上面结果显示 "hello world" 语句被分为两个单词,因为英文天生以空格分隔,自然就以空格来分词,这没有任何问题。

下面我们看一个中文的语句例子,请求 REST 如下

curl -H "Content-Type: application/json" -PUT 'localhost:9200/_analyze' -d '
{  
    "text": "我爱编程"  
}' | json

复制代码

操作成功后,响应的内容如下

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   383  100   348  100    35  69600   7000 --:--:-- --:--:-- --:--:-- 76600
{
  "tokens": [
    {
      "token": "我",
      "start_offset": 0,
      "end_offset": 1,
      "type": "<IDEOGRAPHIC>",
      "position": 0
    },
    {
      "token": "爱",
      "start_offset": 1,
      "end_offset": 2,
      "type": "<IDEOGRAPHIC>",
      "position": 1
    },
    {
      "token": "编",
      "start_offset": 2,
      "end_offset": 3,
      "type": "<IDEOGRAPHIC>",
      "position": 2
    },
    {
      "token": "程",
      "start_offset": 3,
      "end_offset": 4,
      "type": "<IDEOGRAPHIC>",
      "position": 3
    }
  ]
}
复制代码

从结果可以看出,这种分词把每个汉字都独立分开来了,这对中文分词就没有意义了,所以 ES 默认的分词器对中文处理是有问题的。好在有很多不错的第三方的中文分词器,可以很好地和 ES 结合起来使用。在 ES 中,每种分词器(包括内置的、第三方的)都会有个名称。上面默认的操作,其实用的分词器的名称是 standard。下面的请求与前面介绍的请求是等价的,如:

curl -H "Content-Type: application/json" -PUT 'localhost:9200/_analyze' -d '
{  
    "analyzer": "standard",
    "text": "我爱编程"  
}' | json

复制代码

当我们换一个分词器处理分词时,只需将 "analyzer" 字段设置相应的分词器名称即可。

ES通过安装插件的方式来支持第三方分词器,对于第三方的中文分词器,比较常用的是中科院 ICTCLAS 的 smartcn 和 IKAnanlyzer 分词器。

下面,对 IKAnanlyzer 分词器(下面简称为 ik)进行介绍和使用。

ik分词器的安装

ES提供了一个脚本 elasticsearch-plugin( windows 下为 elasticsearch-plugin.bat)来安装插件,脚本位于 ES 安装目录的 bin 目录下。

elasticsearch-plugin 脚本可以有3种命令,通过参数区分


// install
// 安装指定的插件到当前ES节点中。
elasticsearch-plugin install 插件地址

// list
// 显示当前ES节点已经安装的插件列表
elasticsearch-plugin list


// remove
// 删除已安装的插件
elasticsearch-plugin remove 插件名称
复制代码

使用 elasticsearch-plugin install 安装插件时,插件地址既可以是一个远程文件地址(在线安装),也可以是下载到本地的文件。不管是远程文件或本地文件,对于ik插件来说都是一个 zip 文件。

注意,ik 的版本要与 ES 的版本一致。此处,安装的 ES 版本为 7.6.2,所以 ik 也安装 7.6.2 版本。

远程文件安装命令如下

elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.2/elasticsearch-analysis-ik-7.6.2.zip
复制代码

需要注意的是,在Linux下安装本地路径的 .zip 包时,文件路径前面需要添加 file:/// 前缀,比如

elasticsearch-plugin  install 
file:///home/hadoop/elasticsearch-analysis-ik-7.6.2.zip
复制代码

安装完毕后

  • 发现在 ES 的安装目录下的 plugins 目录下多了一个 analysis-ik 目录(内容是 ikzip 包解压后根目录下的所有文件,一共是 5 个 jar 文件和 1 个 properties 配置文件)
  • 另外 ES 的安装目录下的 config 目录下多了一个 analysis-ik 目录(内容是 ikzip 包解压后根目录下的 config 目录下所有文件,用于放置 ik 的自定义词库)

参考网上资料,会介绍在安装 ik 插件后,需要在 ES 的配置文件 elasticsearch.yml 中加上如下一行内容(/usr/local/etc/elasticsearch/elasticsearch.yml

index.analysis.analyzer.ik.type: "ik"
复制代码

可是,实际情况是加上这句话后,ES 会启动失败。

这是因为,在 ES 新版本中,index.analysis.analyzer.ik.type 已经不需要了,添加了启动时反而会报错。ES 5.X 版本不再通过 elasitcsearch.yml 配置设置分词规则,而是在创建索引时指定。

需要注意的是,ik 安装完成后,需要重启 ES,才可以进行后续的 ik 使用。

下面再介绍一个坑点,参考网上给出的使用 ik 的例子,请求命令如下

curl -H "Content-Type: application/json" -PUT 'localhost:9200/_analyze' -d '
{  
    "analyzer": "ik",
    "text": "我爱编程"  
}' | json
复制代码

执行上述命令,总会返回如下错误信息

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   264  100   207  100    57  25875   7125 --:--:-- --:--:-- --:--:-- 33000
{
  "error": {
    "root_cause": [
      {
        "type": "illegal_argument_exception",
        "reason": "failed to find global analyzer [ik]"
      }
    ],
    "type": "illegal_argument_exception",
    "reason": "failed to find global analyzer [ik]"
  },
  "status": 400
}
复制代码

错误信息还是很明确的,找不到分词器 ik

这是因为,在新版本 ES 中,分词器的名称变了,不再是 ik 了。新版本的 ik 提供了两个分词器,分别是 ik_max_wordik_smart,用任何一个替换 ik,就没问题了。

curl -H "Content-Type: application/json" -PUT 'localhost:9200/_analyze' -d '
{  
    "analyzer": "ik_smart",
    "text": "我爱编程"  
}' | json



  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   312  100   249  100    63  83000  21000 --:--:-- --:--:-- --:--:--  101k
{
  "tokens": [
    {
      "token": "我",
      "start_offset": 0,
      "end_offset": 1,
      "type": "CN_CHAR",
      "position": 0
    },
    {
      "token": "爱",
      "start_offset": 1,
      "end_offset": 2,
      "type": "CN_CHAR",
      "position": 1
    },
    {
      "token": "编程",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 2
    }
  ]
}
复制代码

下面给出一个使用 elasticsearch-head 进行检索的 Demo。

ik中文分词器的使用

下面进行 ik_max_wordik_smart 分词效果的对比。

可以发现,对中文“世界如此之大”进行分词,ik_max_wordik_smart 得到的中文词更多,但这样也带来一个问题,使用 ik_max_word 会占用更多的存储空间。

  • ik_max_word
curl -H "Content-Type: application/json" -PUT 'localhost:9200/_analyze' -d '
{
    "analyzer": "ik_max_word",
    "text": "世界如此之大"
}' | json

//echo

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   407  100   339  100    68  12107   2428 --:--:-- --:--:-- --:--:-- 14535
{
  "tokens": [
    {
      "token": "世界",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "如此之",
      "start_offset": 2,
      "end_offset": 5,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "如此",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "之大",
      "start_offset": 4,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 3
    }
  ]
}
复制代码
  • ik_smart
curl -H "Content-Type: application/json" -PUT 'localhost:9200/_analyze' -d '
{
    "analyzer": "ik_smart",
    "text": "世界如此之大"
}' | json


//echo

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   324  100   255  100    69  51000  13800 --:--:-- --:--:-- --:--:-- 64800
{
  "tokens": [
    {
      "token": "世界",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "如此",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "之大",
      "start_offset": 4,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 2
    }
  ]
}
复制代码

ik的自定义词典

有时,可能 ik 自身提供的分词词典无法满足特定的一些需求(如专用名词等),ik 提供了自定义词典的功能,也就是用户可以自己定义一些词汇,这样ik就会把它们当作词典中的内容来处理。

举个例子,对于上面例子中的 “世界如此之大” 这个中文语句,ik 词库中不会有 “界如此” 这样一个单词,假设 “界如此” 就是一个专用名词,我们希望 ik 能识别出来。这时就可自定义ik的词典。具体方法是如下

  1. 步骤1:新建扩展名为 dic 的文本文件,文件中写入想增加的词条,每个词条单独一行,如文件名是 test.dic,文件内容如下
界如此
高潜
复制代码

上面例子中有两个自定义词条。

  1. 步骤2:将上面的 dic 文件保存到 ES 安装目录(/usr/local/var/homebrew/linked/elasticsearch-full)的 config 目录下的 analysis-ik 目录下,可以建立子目录,放在子目录下。比如文件的路径为 /usr/local/etc/elasticsearch/analysis-ik/mydict/test.dic

  2. 步骤3:修改 ik 的配置文件 IKAnalyzer.cfg.xml(位于 config/analysis-ik 目录下),在配置文件中增加如下条目

<!--用户可以在这里配置自己的扩展字典 -->
<!-- <entry key="ext_dict"></entry> -->
<entry key="ext_dict">mydict/test.dic</entry>
复制代码

这样就将自定义的字典文件加到ik的字典中了。

  1. 步骤4:重启ES,使配置生效。并发起请求查看结果
curl -H "Content-Type: application/json" -PUT 'localhost:9200/_analyze' -d '
{
    "analyzer": "ik_max_word",
    "text": "世界如此之大"
}' | json


//echo

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   491  100   423  100    68  70500  11333 --:--:-- --:--:-- --:--:-- 81833
{
  "tokens": [
    {
      "token": "世界",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "界如此",
      "start_offset": 1,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "如此之",
      "start_offset": 2,
      "end_offset": 5,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "如此",
      "start_offset": 2,
      "end_offset": 4,
      "type": "CN_WORD",
      "position": 3
    },
    {
      "token": "之大",
      "start_offset": 4,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 4
    }
  ]
}

复制代码

可以看出,自定义的 “界如此” 词条被分词出来了。不过如果我们将 analyzer 改为 ik_smart 却发现 “界如此” 词条没能被识别出来。

文档的中文分词使用

前面的介绍只是简单举例介绍了 ik 的使用,下面我们来通过一个更完整的例子介绍分词。

ES的分词在创建索引(index)后,可以通过 REST 命令来设置,这样后续插入到该索引的数据都会被相应的分词器进行处理。

为了比较 ik 的 ik_smartik_max_word 这两个分词器及默认的分词器 standard,我们创建 3 个索引来分别使用这 3 个分词器。

  1. 创建索引
curl -H 'Content-Type: application/json' -X PUT 'localhost:9200/index_ik_s' |json
curl -H 'Content-Type: application/json' -X PUT 'localhost:9200/index_ik_m' |json
curl -H 'Content-Type: application/json' -X PUT 'localhost:9200/index_stan' |json
复制代码
  1. 设置分词器
curl -H "Content-Type: application/json" -PUT 'localhost:9200/index_ik_s/_mapping' -d '
{
        "properties": {
            "content": {
                "type": "text",
                "analyzer": "ik_smart",
                "search_analyzer": "ik_smart"
            }
        }
}' | json

//echo

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   220  100    21  100   199    132   1251 --:--:-- --:--:-- --:--:--  1383
{
  "acknowledged": true
}
复制代码
curl -H "Content-Type: application/json" -PUT 'localhost:9200/index_ik_m/_mapping' -d '
{
        "properties": {
            "content": {
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_max_word"
            }
        }
}' | json
复制代码
curl -H "Content-Type: application/json" -PUT 'localhost:9200/index_stan/_mapping' -d '
{
        "properties": {
            "content": {
                "type": "text",
                "analyzer": "standard",
                "search_analyzer": "standard"
            }
        }
}' | json
复制代码
  1. 插入数据

为了批量插入,我们使用了 linux 的 curl 命令来执行 REST 操作。

curl -XPOST http://localhost:9200/index_ik_s/_create/1 -H 'Content-Type:application/json' -d'
{"content":"美国留给伊拉克的是个烂摊子吗"}' |json

curl -XPOST http://localhost:9200/index_ik_s/_create/2 -H 'Content-Type:application/json' -d'
{"content":"公安部:各地校车将享最高路权"}' |json

curl -XPOST http://localhost:9200/index_ik_s/_create/3 -H 'Content-Type:application/json' -d'
{"content":"中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"}' |json

curl -XPOST http://localhost:9200/index_ik_s/_create/4 -H 'Content-Type:application/json' -d'
{"content":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"}' |json
复制代码
curl -XPOST http://localhost:9200/index_ik_m/_create/1 -H 'Content-Type:application/json' -d'
{"content":"美国留给伊拉克的是个烂摊子吗"}' |json

curl -XPOST http://localhost:9200/index_ik_m/_create/2 -H 'Content-Type:application/json' -d'
{"content":"公安部:各地校车将享最高路权"}' |json

curl -XPOST http://localhost:9200/index_ik_m/_create/3 -H 'Content-Type:application/json' -d'
{"content":"中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"}' |json

curl -XPOST http://localhost:9200/index_ik_m/_create/4 -H 'Content-Type:application/json' -d'
{"content":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"}' |json
复制代码
curl -XPOST http://localhost:9200/index_stan/_create/1 -H 'Content-Type:application/json' -d'
{"content":"美国留给伊拉克的是个烂摊子吗"}' |json

curl -XPOST http://localhost:9200/index_stan/_create/2 -H 'Content-Type:application/json' -d'
{"content":"公安部:各地校车将享最高路权"}' |json

curl -XPOST http://localhost:9200/index_stan/_create/3 -H 'Content-Type:application/json' -d'
{"content":"中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"}' |json

curl -XPOST http://localhost:9200/index_stan/_create/4 -H 'Content-Type:application/json' -d'
{"content":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"}' |json
复制代码
  1. 简单查询示例

下面给出一个简单的查询示例。

curl -XPOST http://localhost:9200/index_ik_s/_search  -H 'Content-Type:application/json' -d'
{
    "query" : { "match" : { "content" : "中国" }},
    "highlight" : {
        "pre_tags" : ["<tag1>", "<tag2>"],
        "post_tags" : ["</tag1>", "</tag2>"],
        "fields" : {
            "content" : {}
        }
    }
}' |json

//echo

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   928  100   697  100   231   1671    553 --:--:-- --:--:-- --:--:--  2225
{
  "took": 413,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 0.66590554,
    "hits": [
      {
        "_index": "index_ik_s",
        "_type": "_doc",
        "_id": "4",
        "_score": 0.66590554,
        "_source": {
          "content": "中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
        },
        "highlight": {
          "content": [
            "<tag1>中国</tag1>驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
          ]
        }
      },
      {
        "_index": "index_ik_s",
        "_type": "_doc",
        "_id": "3",
        "_score": 0.61737806,
        "_source": {
          "content": "中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"
        },
        "highlight": {
          "content": [
            "中韩渔警冲突调查:韩警平均每天扣1艘<tag1>中国</tag1>渔船"
          ]
        }
      }
    ]
  }
}
复制代码
  1. 测试验证和对比

先测下它们对中文标准单词的支持,查询 “中国”,3种索引效果都一样的,都能胜任。请求命令如下

curl -XPOST http://localhost:9200/index_ik_s/_search  -H 'Content-Type:application/json' -d'
{
    "query" : { "match" : { "content" : "中国" }},
    "highlight" : {
        "pre_tags" : ["<tag1>", "<tag2>"],
        "post_tags" : ["</tag1>", "</tag2>"],
        "fields" : {
            "content" : {}
        }
    }
}' |json

curl -XPOST http://localhost:9200/index_ik_m/_search  -H 'Content-Type:application/json' -d'
{
    "query" : { "match" : { "content" : "中国" }},
    "highlight" : {
        "pre_tags" : ["<tag1>", "<tag2>"],
        "post_tags" : ["</tag1>", "</tag2>"],
        "fields" : {
            "content" : {}
        }
    }
}' |json

curl -XPOST http://localhost:9200/index_stan/_search  -H 'Content-Type:application/json' -d'
{
    "query" : { "match" : { "content" : "中国" }},
    "highlight" : {
        "pre_tags" : ["<tag1>", "<tag2>"],
        "post_tags" : ["</tag1>", "</tag2>"],
        "fields" : {
            "content" : {}
        }
    }
}' |json
复制代码

下面进行极端的非标准搜索,搜索关键字“均每”,可以发现, ik_smartik_max_word 这两个分词器均无法检索出结果,默认的分词器 standard 可以检出到内容。这是因为默认的分词器 standard 在搜索时,会将每个字都拆成一个检索条件。

curl -XPOST http://localhost:9200/index_stan/_search  -H 'Content-Type:application/json' -d'
{
    "query" : { "match" : { "content" : "均每" }},
    "highlight" : {
        "pre_tags" : ["<tag1>", "<tag2>"],
        "post_tags" : ["</tag1>", "</tag2>"],
        "fields" : {
            "content" : {}
        }
    }
}' |json

//echo

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   672  100   442  100   230  22100  11500 --:--:-- --:--:-- --:--:-- 33600
{
  "took": 16,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 2.2112894,
    "hits": [
      {
        "_index": "index_stan",
        "_type": "_doc",
        "_id": "3",
        "_score": 2.2112894,
        "_source": {
          "content": "中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"
        },
        "highlight": {
          "content": [
            "中韩渔警冲突调查:韩警平<tag1>均</tag1><tag1>每</tag1>天扣1艘中国渔船"
          ]
        }
      }
    ]
  }
}
复制代码

最后给出一个经验性的结论

  • ik_smart 既能满足英文的要求,又更智能更轻量,占用存储最小,所以首推 ik_smart
  • standard 对英语支持是最好的,但是对中文是简单暴力每个字建一个反向索引,浪费存储空间而且效果很c差
  • ik_max_wordik_smart 对中文的支持更全面,但是存储上的开销实在太大,不建议使用