ElasticSearch:介绍
先放官方中文网站镇楼:
https://www.elastic.co/cn/
按照本文的流程去学习,保证一定有收获。如果暂时没时间学,不妨点个收藏点个赞,找个时间慢慢学。那么我们接下来就开始ES的旅程吧!
首先,ElasticSearch是什么?
ElasticSearch是基于RESTful web接口的全文搜索引擎 ,简单说就是可以对存储的数据进行快速搜索的强大引擎。
MySQL同样可以存储数据,为什么一定要用ES?
mysql存储用作持久化存储,而ES的存储用作检索分析。
ElasticSearch中的各种名词
1. index索引
动词:相当于mysql的insert
名词:相当于mysql的db
2. Type类型
在index中,可以定义一个或多个类型
类似于mysql的table,每一种类型的数据放在一起
3. Document文档
保存在某个index下,某种type的一个数据document,文档是json格式的,
document就像是mysql中的某个table里面的内容。每一行对应的列叫属性
其中index库 > type表 > document文档
为什么ES搜索快?
因为ES使用了倒排索引。
什么是倒排索引?
举个例子:ES对存入数据进行分词存储,比如红海行动,存储为红海、行动。探索红海行动存储为探索、红海、行动。用户搜索红海,则对红海进行索引搜索,搜索出红海行动,探索红海行动。
ElasticSearch:安装
基本介绍完毕,接下来我们来学学如何安装ES和Kibana,ES和kibana的关系可以看成是好基友的关系,kibana可以更方便地操作ES。
这里使用的是docker进行安装,方便,快捷,好使。
使用docker进行安装:
1、下载ElasticSearch(存储检索)和Kibana(可视化检索),两个版本要一致
docker pull elasticsearch:7.4.2
docker pull kibana:7.4.2
2、配置
#docker里的目录挂载到linux的/mydata目录中
mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
#es可以被远程任何机器访问
echo "http.host: 0.0.0.0" >/mydata/elasticsearch/config/elasticsearch.yml
#更改权限
chmod -R 777 /mydata/elasticsearch/
3、启动ElasticSearch
#9200是用户交互端口 9300是集群心跳端口
#-e指定是单阶段运行
#-e指定占用的内存大小,生产时可以设置32G
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2
#设置开机启动elasticsearch
docker update elasticsearch --restart=always
访问:
http://xxxx:9200/
4、启动kibana
#kibana指定了了ES交互端口9200 # 5600位kibana主页端口
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://47.97.26.154:9200 -p 5601:5601 -d kibana:7.4.2
#设置开机启动kibana
docker update kibana --restart=always
#查看kibana运行日志
docker logs 实例ID
访问5601,出现以下则表示成功:
ElasticSearch:入门使用
安装完成了,那我们来试试ES好不好使。
我们对ES的所有操作,都被封装成了RestAPI,所以我们只要发送请求就行了。
入门:
1、_cat
功能:查看ES的基本信息
使用postman或者浏览器加上地址查询,或者直接Kibana操作。
GET _cat/nodes:查看所有结点
GET _cat/health:查看es健康状况
GET _cat/master:查看主节点
GET _cat/indices:查看所有索引[类似于show databases]
2、索引一个文档(保存)
用mysql的话来说,就是保存一条记录
索引到哪个类型下 == 保存到哪张表中
如:
PUT http://xxxx:9200/customer/external/1
JSON:
{
"name":"john"
}
结果:
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
我们分析下都返回了些什么:
返回结果中,所有带有_的都被称为元数据,用来携带一些基本信息
"_index":"customer":数据存在索引customer下[mysql即哪个数据库下]
"_type": "external":数据存在类型external下[mysql即哪张表下]
"_id": "1":数据ID
"_version": 1:数据版本
"result": "created":执行结果[若执行两遍PUT,则此处变为update]
细节:
保存操作可以使用PUT或者POST,但是经过多次操作可以发现:
使用PUT进行保存数据时,携带ID会创建或者修改数据,不携带ID则报错。
使用POST保存数据时,携带ID会创建或者修改数据,不携带ID则会自动生成一个唯一ID,因此对于保存操作。
虽然PUT和POST请求都可,但是我们一般还是使用POST,可以减少容错,显得更专业。
3、查询文档
拿刚刚的操作举个栗子:
GET http://xxxx:9200/customer/external/1
语句解读:不带任何参数,使用GET对customer索引下的external类型下的id为1的数据进行查询
结果:
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 2,
"_seq_no": 2,
"_primary_term": 1,
"found": true,
"_source": {
"name": "john"
}
}
结果分析:
"_index": "customer",索引
"_type": "external",类型
"_id": "1",查询ID
"_version": 2,版本[被更新过几次,下标从1开始]
"_seq_no": 2,乐观锁[稍后说]
"_primary_term": 1,分片,用作乐观锁
"found": true,是否查询成功
"_source":数据查询的信息
我们模拟以下乐观锁操作,可以更有助于理解:
情境:A和B同时需要对同一条数据进行修改,于是两人各自开始操作:
A先查出数据如下:
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 2,
"_seq_no": 2,
"_primary_term": 1,
"found": true,
"_source": {
"name": "john"
}
}
A开始修改数据,B来到工位,一查,seq_no也为2,刚准备开始修改数据,突然接到个电话,于是暂停手上工作。
A修改如下:
PUT http://XXXX:9200/customer/external/1?if_seq_no=2&if_primary_term=1
{
"name":"yellow"
}
解读:加上了判断条件进行修改
修改结果:
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 3,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 3,
"_primary_term": 1
}
B打完电话,继续工作,由于原先已经查询了,只是还未来得及提交,于是B省得麻烦,直接提交。
PUT http://XXXX:9200/customer/external/1?if_seq_no=2&if_primary_term=1
{
"name":"red"
}
结果:
{
"error": {
"root_cause": [
{
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, required seqNo [2], primary term [1]. current document has seqNo [3] and primary term [1]",
"index_uuid": "kIKj5ZSlT32aH-ziXyQN2Q",
"shard": "0",
"index": "customer"
}
],
"type": "version_conflict_engine_exception",
"reason": "[1]: version conflict, required seqNo [2], primary term [1]. current document has seqNo [3] and primary term [1]",
"index_uuid": "kIKj5ZSlT32aH-ziXyQN2Q",
"shard": "0",
"index": "customer"
},
"status": 409
}
于是B只好重新先查询,再进行修改。
这就是乐观锁修改。
4、更新文档
POST customer/external/1/_update
{
"doc":{
"name":"blue"
}
}
或者
POST/PUT customer/external/1
{
"name":"blue"
}
各更新操作分析:
第一种更新:
我们继续使用上面的数据,首先对1号数据进行第一种更新操作:
POST customer/external/1/_update
{
"doc":{
"name":"john"
}
}
第一次结果:
"_version": 5,
"result": "updated"
第二次结果:
"_version": 5,
"result": "noop"
分析:使用这种更新操作进行重复更新时,若数据与原先一致,则不进行任何操作。
第二种更新:
POST customer/external/1
{
"name":"blue"
}
第一次结果:
"_version": 6,
"result": "updated"
第二次结果:
"_version": 7,
"result": "updated"
分析:
使用第二种更新操作时[即不带update],无论是否重复更新,数据是否一致,都会进行更新,version版本相应增加。因此若使用PUT方式,结果也一致。
5、删除文档&索引
DELETE http://XXXX:9200/customer/external/1
手动[啪的一下],数据没了:
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 9,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 10,
"_primary_term": 1
}
我们再试试能不能查到:
GET http://XXXX:9200/customer/external/1
结果:
{
"_index": "customer",
"_type": "external",
"_id": "1",
"found": false
}
数据可以删除,那类型和索引是否也可以删除,答案是能删,只能删一点点,ES只提供了直接删除索引的操作,并没有提供直接删除类型的操作,但是我们可以将类型下的所有文档清空,也相当于删除了整个类型。
6、bulk批量API
bulk是ES中可以给ES批量导入数据地语法。
如:
POST customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"John"}
{"index":{"id":"2"}}
{"name":"Jane"}
解析:
我们对customer索引下的external类型进行bulk的POST操作,批量index[保存]id为1的name为John和id为2的name为Jane的两条数据,可以看作每两行为一次操作,同样可以delete,get。
我们在postman中进行操作时发现,JSON数据报错,那这会kibana就派上用场了,我们进入kibana,找到Dev Tools:
在kibana中发送请求:
POST /customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"John"}
{"index":{"id":"2"}}
{"name":"Jane"}
结果成功。
接下来我们继续测试批量数据操作,官方为我们准备了一份样本数据:
https://www.elastic.co/guide/cn/kibana/current/tutorial-load-dataset.html 下载莎士比亚,将其中部分使用bulk批量导入
命令:
POST shakespeare/_bulk
{"index":{"_index":"shakespeare","_id":0}}
{"type":"act","line_id":1,"play_name":"Henry IV", "speech_number":"","line_number":"","speaker":"","text_entry":"ACT I"}
.....
结果若无误,OK,我们继续下一步操作。
ElasticSearch:进阶
进阶检索:
ES支持两种基本方式检索:
uri+检索参数检索
通过使用REST request URI 发送搜索参数(uri+检索参数)如:
GET shakespeare/_search?q=*
uri+请求体检索
通过使用REST request body进行发送(uri+请求体)如:
GET shakespeare/_search
{
"query": {
"match_all": {}
},
"sort": []
}
分析:
在以后我们可能更多采用第二种方式,因为可操作性更强,处理复杂请求时更得心应手
"query":"查询条件"
"match_all":"匹配所有"
"sort":"排序条件"
我们一个个试试:
uri+检索参数检索
即第一种请求参数方式检索
GET shakespeare/_search?q=*&sort=account_number:asc
分析:
q=* # 查询所有
sort # 排序字段
asc #升序
检索shakespeare下所有信息,包括type和docs
GET shakespeare/_search
查询结果:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 500,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "shakespeare",
"_type" : "account",
"_id" : "0",
"_score" : 1.0,
"_source" : {
"type" : "act",
"line_id" : 1,
"play_name" : "Henry IV",
"speech_number" : "",
"line_number" : "",
"speaker" : "",
"text_entry" : "ACT I"
}
},....
]
}
}
结果分析:
"took" – 花费多少ms搜索
"timed_out" – 是否超时
"_shards" – 多少分片被搜索了,以及多少成功/失败的搜索分片
"max_score" –文档相关性最高得分
"hits.total.value" - 多少匹配文档被找到
"hits.sort" - 结果的排序key(列),没有的话按照score排序
"hits._score" - 相关得分 (not applicable when using match_all)
uri+请求体检索
即第二种请求体方式检索
GET shakespeare/_search
{
"query": {
"match_all": {}
},
"sort": []
}
结果与上相同。
我们再进行更多测试,比如我们想只显示部分查询数据:
GET shakespeare/_search
{
"query": {
"match_all": {}
},
"sort": [],
"from": 0,
"size": 1
}
解析:查出的数据从"from"开始,长度为"size"进行显示,即显示从0开始的1条数据
结果:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 500,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "shakespeare",
"_type" : "account",
"_id" : "0",
"_score" : 1.0,
"_source" : {
"type" : "act",
"line_id" : 1,
"play_name" : "Henry IV",
"speech_number" : "",
"line_number" : "",
"speaker" : "",
"text_entry" : "ACT I"
}
}
]
}
}
继续测试,我们如果只想返回查询到的数据的某一个部分,那么可以用以下操作[上条件不变]:
GET shakespeare/_search
{
"query": {
"match_all": {}
},
"sort": [],
"from": 0,
"size": 1,
"_source": ["line_id","line_number"]
}
解析:"_source"作用是对返回数据进行显示,默认是全部显示
结果:
"_source" : {
"line_number" : "",
"line_id" : 1
}
可以看到只返回了我们指定的数据。
match全文检索
先来个简单的match检索[match的属性必须是_source中存在的]:
GET shakespeare/_search
{
"query": {
"match": {
"line_id": "2"
}
}
}
结果:
"_source" : {
"type" : "scene",
"line_id" : 2,
"play_name" : "Henry IV",
"speech_number" : "",
"line_number" : "",
"speaker" : "",
"text_entry" : "SCENE I. London. The palace."
}
对于属性的match,不仅可以全属性值match,还可以部分属性值match,功能强大,原因我们之后再说。继续举个栗子,我们对某属性的部分值进行检索:
GET shakespeare/_search
{
"query": {
"match": {
"text_entry": "London"
}
}
}
结果[方便起见,只放查询部分结果]:
"text_entry" : "SCENE I. London. The palace."
"text_entry" : "SCENE III. London. The palace."
"text_entry" : "SCENE II. London. An apartment of the Princes."
"text_entry" : "riding to London with fat purses: I have vizards"
再再再举个栗子,使用match分词进行全文匹配:
GET shakespeare/_search
{
"query": {
"match": {
"text_entry": "be of"
}
}
}
match条件是text_entry属性中存在be of的结果,无论be of是否在一起,是否相连,是否都存在,只要存在其中一个,就被match。
结果:
"text_entry" : "Of murderous subornation, shall it be,"
"text_entry" : "Than out of anger can be uttered."
"text_entry" : "thieves of the days beauty: let us be Dianas"
"text_entry" : "The scourge of greatness to be used on it;"
"text_entry" : "Of my young Harry. O that it could be proved"
....
为何ES能做到这么强大?原因是match进行了分词全文匹配,只要包含了匹配值的都会被查询出来,而ES底层维护了一张倒排索引表,以支持全文匹配和最大得分[匹配相似评比分],且查询结果会以最大得分排序。
match_phrase[短语匹配]
短语匹配不同于match的分词匹配,它必须组合在一起才能被match,举个栗子,使用match_phrase进行匹配查询:
GET shakespeare/_search
{
"query": {
"match_phrase": {
"text_entry": "be of"
}
}
}
结果:
"hits" : [ ]
还是同一时间,同一地点,结果却不同,这就是match_phrase与match的区别
multi_match[多字段匹配]
可以在多个字段中进行match匹配。
举个栗子,我们同时match匹配两个字段:
GET shakespeare/_search
{
"query": {
"multi_match": {
"query": 1,
"fields": ["line_number","speech_number"]
}
}
}
解析:
"query":查询匹配内容
"fields":被查询的属性字段
结果:
"speech_number" : 1,
"line_number" : "1.1.1",
"speech_number" : 1,
"line_number" : "1.1.2",
...
如果查询的内容是多词,同样满足match分词匹配条件,也会进行分词查询,最后得出最大分数进行排序。
bool[复合查询]
当我们要构造更复杂的查询时可以使用bool复合查询。其查询规格必须全部被满足。
我们举个栗子,使用bool进行复合查询:
GET shakespeare/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"play_name": "Henry"
}
},
{
"match": {
"speaker": "HENRY"
}
}
],
"must_not": [
{
"match": {
"text_entry": "of"
}
}
],
"should": [
{"match": {
"play_name": "Henry"
}}
]
}
}
}
分析:
"bool":表示使用bool查询
"must":必须执行
"must_not":必须不被执行
"should":如果满足其中条件,可以加分[即增加最大分数]
结果:
"play_name" : "Henry IV",
"speaker" : "PRINCE HENRY",
"text_entry" : "and unbuttoning thee after supper and sleeping upon"
....
结果过滤
filter可以理解为bool查询中的must_not,作为过滤条件存在。
举个栗子,查询属性text_entry中values中存在and after且line_id在 100 - 120之间的数据
GET shakespeare/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"text_entry": "and after"
}
}
],
"filter": {
"range": {
"line_id": {
"gte": "100",
"lte": "120"
}
}
}
}
}
}
分析:
"filter":过滤条件
"range":区间
结果:
"_score" : 4.313828
"_score" : 1.2873212
"_score" : 1.2208143
可以发现,其相关性得分各不相同。
但是filter在使用过程中,并不会计算相关性得分, 文档是否符合每个“must”或“should”子句中的标准,决定了文档的“相关性得分”。 得分越高,文档越符合搜索条件。
而must_not会影响文档是否包含在结果中, 但不影响文档的评分方式。
term查询
term查询用于查询非文本字段的值
首先我们先进行一段查询,match的keyword查询与match_phrase查询的区别:
GET shakespeare/_search
{
"query": {
"match": {
"text_entry.keyword": "Breathless and faint, leaning upon my sword,"
}
}
}
GET shakespeare/_search
{
"query": {
"match_phrase": {
"text_entry": "Breathless and faint, leaning upon my sword,"
}
}
}
结果[相同]:
"_source" : {
"type" : "line",
"line_id" : 361,
"play_name" : "Henry IV",
"speech_number" : 6,
"line_number" : "1.3.33",
"speaker" : "HOTSPUR",
"text_entry" : "Breathless and faint, leaning upon my sword,"
}
如果将keyword删去最后的逗号,再执行,发现没有结果,而match_phrase结果不变,于是我们可以发现,keyword的整句搜索就是该字段的全部值;而match_phrase则是短语匹配,只要该字段的全部值中包含搜索条件,则匹配成功。
这个作为一个小复习。
回到term,全文检索字段使用match,其它非text字段匹配用term。这是一个规范。
GET shakespeare/_search
{
"query": {
"term": {
"line_id":361
}
}
}
聚合分析
通常与查询一起使用,查询检索聚合一步到位,十分强大,类似mysql中的groupby等操作,因此也十分重要。
举个栗子,查询大小为10的地址中包含Mill的人的年龄区间分布,并查出其年龄平均值、收入平均值,并且不想看结果:
#分别为包含mill、,平均年龄、
GET bank/_search
{
"query": { # 查询出包含mill的
"match": {
"address": "Mill"
}
},
"aggs": { #基于查询聚合
"ageAgg": { # 聚合的名字,随便起
"terms": { # 看值的可能性分布
"field": "age",
"size": 10
}
},
"ageAvg": {
"avg": { # 看age值的平均
"field": "age"
}
},
"balanceAvg": {
"avg": { # 看balance的平均
"field": "balance"
}
}
},
"size": 0 # 不看详情
}
如上只是单个聚合,子聚合可以在父聚合的条件下继续聚合。使用起来十分强大。
如需详细了解,可移步官网深究。
ElasticSearch:映射
映射简单说就是定义文档如何被存储和检索的。
[以后不推荐使用类型映射,会降低检索效率,一般使用索引映射]。
查看映射:
GET shakespeare/_mapping
如果我们不满意默认的映射规则,可以自定义映射规则,如:
PUT /my_index
{
"mappings": {
"properties": {
"age":{"type": "integer"}
}
}
}
解析:按上面规定的映射规格,我们以后在my_index索引下存储的数据都以该映射规则存储和检索。
如果我们在创建索引映射时未创建完全,仍想继续在原索引上添加映射,那么可以这样做:
PUT /my_index/_mapping
{
"mappings": {
"properties": {
"name":{"type": "text"}
"phone":{"type":"keyword","index":false}
}
}
}
解析:如上的作用仅为添加映射,不能作为创建新的索引映射或者更新映射。在映射属性上添加index=false则表示不能使用phone来检索数据,在默认清空下index都为true。
更新映射
很遗憾,在ES的语法中,我们对于已经存在的映射字段,不能更新。但是可以通过创建新的索引进行数据迁移达到相同目的。
数据迁移
固定写法:
POST _reindex
{
"source":{
"index":"old_index[name]"
},
"dest":{
"index":"new_index[name]"
}
}
ElasticSearch:分词
分词是ES中做全文检索的核心,作用是将一个字符流分成一个个词元[tokens],然后利用各单词的相关性匹配完成检索,举个栗子:“I am a pig”,默认的标准分词器会将其分为"I",“am”,“a”,“pig”,然后再进行全文检索。
分词操作是分词器[tokenizer]完成的。默认我们使用的是标准分词器。举个栗子:
POST _analyze
{
"analyzer": "standard",
"text": "The 2 Brown-Foxes bone."
}
分词结果:
{
"tokens" : [
{
"token" : "the",
"start_offset" : 0,
"end_offset" : 3,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "2",
"start_offset" : 4,
"end_offset" : 5,
"type" : "<NUM>",
"position" : 1
},
{
"token" : "brown",
"start_offset" : 6,
"end_offset" : 11,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "foxes",
"start_offset" : 12,
"end_offset" : 17,
"type" : "<ALPHANUM>",
"position" : 3
},
{
"token" : "bone",
"start_offset" : 18,
"end_offset" : 22,
"type" : "<ALPHANUM>",
"position" : 4
}
]
}
可以看出标准分词器使用了空格作为标准之一进行了分词。
但是如果我们使用中文进行分词,会发现,每一个中文词都被分割了,而不是我们平常使用的按照词义分词,因此我们需要引入其它分词器。
一般而言,中文大都使用ik分词器:github注意分词器要和ES版本对应。
在前面安装的elasticsearch时,我们已经将elasticsearch容器的“/usr/share/elasticsearch/plugins”目录,映射到宿主机的“ /mydata/elasticsearch/plugins”目录下,所以比较方便的做法就是下载“/elasticsearch-analysis-ik-7.4.2.zip”文件,然后解压到该文件夹下即可。安装完毕后,需要重启elasticsearch容器。
这里操作其实还是推荐使用xftp软件,就一个字,方便。
进入映射目录:cd /mydata/elasticsearch/plugins
下载:wget 下载地址
解压:unzip xxx.zip /ik
卸磨杀驴:rm -rf *.zip
重启ES:docker restart xxxx
成功后,我们试试新的分词器[带有两种中文分词器,分别是ik_smart和ik_max_work]:
POST _analyze
{
"analyzer": "ik_smart",
"text": "我是中国人"
}
结果:
{
"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" : 5,
"type" : "CN_WORD",
"position" : 2
}
]
}
OK,到这里分词功能大功告成。
不过有些时候其自带库不能满足我们的需求【流行语等等…】,所以接下来我们来试试自定扩展词库:
这里我们使用nginx来实现自定义扩展词库方便点,如果没有nginx,先使用docker安装nginx:
docker run -p 80:80 --name nginx -d nginx:1.10
然后我们需要将容器中的配置文件拷贝到mydata目录下【记得先创建一个nginx文件夹,同时使用命令时结尾的.和空格需要相同】:
docker container cp nginx:/etc/nginx .
然后把容器中的nginx停掉并rm。
我们想将该文件夹中的内容移入nginx的conf文件夹中,因此我们来修改一下,先给nginx文件夹改个名:
mv nginx conf
然后再创建一个nginx文件夹:
mkdir nginx
把conf移入nginx:
mv conf nginx/
创建一个新的nginx,并执行以下命令【挂载】:
docker run -p 80:80 --name nginx \
-v /mydata/nginx/html:/usr/share/nginx/html \
-v /mydata/nginx/logs:/var/log/nginx \
-v /mydata/nginx/conf:/etc/nginx \
-d nginx:1.10
创建成功后,在nginx的conf目录下,我们可以看到三个文件夹,进入html文件夹中,创建一个es文件夹,用来存放我们自定义的词库:
cd mydata/nginx/html
mkdir es
cd es/
vi mycode.txt
nginx方面基本完成,接下来我们来到ES配置远程词库地址:
由于ES也是挂载了文件,因此我们找到ES中配置远程词库文件对应的Linux中的文件中:
cd mydata/elasticsearch/plugins/ik/config/
vi IKAnalyzer.cfg.xml
在这里进行远程词库配置:
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://XXX/es/mycode.txt</entry>
重启ES:
docker restart XXXX
试试效果,我在自定义中配置了"沙琪玛"和"好吃点":
POST _analyze
{
"analyzer": "ik_smart",
"text": "我爱沙琪玛"
}
结果:
{
"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" : 5,
"type" : "CN_WORD",
"position" : 2
}
]
}
成功!