一止长渊

ElasticSearch

N 人看过
字数:6.5k字 | 预计阅读时长:28分钟

Elastcisearch 是分布式的文档存储。它能以实时的方式存储和检索复杂的数据结构——序列化成为JSON 文档。换句话说,一旦一个文档被存储在 Elasticsearch 中,它就是可以被集群中的任意节点检索到。
尽管现存的 NoSQL 解决方案允许我们以文档的形式存储对象,但是他们仍旧需要我们思考如何查询我们的数据,以及确定哪些字段需要被索引以加快数据检索。在 Elasticsearch 中, 每个字段的所有数据都是默认被索引的 。 即每个字段都有为了快速检索设置的专用倒排索引。而且,不像其他多数的数据库,它能在   同一个查询中   使用所有这些倒排索引,并以惊人的速度返回结果。
ES 的底层是开源库 Lucene,ES 是对 Lucene 的封装,对外提供了REST API的操作接口,外部调用就是向 ES 发送请求即可,做到开箱即用。

注意!!!ES7.x 已经移除 types(类似于 MySQL 中的表)的支持,8.x 将不再支持
注意 kibana、ES、IK 分词插件版本要对应,本文使用版本均为 6.8.4

一、基础概念

截屏2021-04-12 22.02.21.png

1、Index(索引)

动词:相当于 MySQL 中的 insert
名词:相当于 MySQL 中的 database

2、Type(类型)

在 Index(索引中),可以定义一个或多个类型。
类似于 MySQL 中的 Table,每一种类型的数据放在一起

3、Document(文档)

保存在某个索引(Index)下,某种类型(Type)的一个数据(Document)。ES 中每条记录是以 JSON 文档的格式存储的,相当于 MySQL 中的一条记录

4、倒排索引

就是每个分词对应于哪些文档的 id
插入一条记录,就会将该数据进行分词,然后每个分词都有对应一个记录,该记录记下该分词出现在哪些文档中
截屏2021-04-12 22.03.34.png
查询时也会将查询的语句进行分词,然后统计哪些记录命中的分词数多计算相关性得分,返回评分的结果列表。

二、Docker 安装

1、下载镜像文件

docker pull elasticsearch:7.4.2 # 存储和检索数据
docker pull kibana:7.4.2  # 可视化界面:方便可视化检索数据

2、创建 ES 实例

# 将docker中ES容器文件挂在到外部
mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
mkdir -p /mydata/elasticsearch/plugins
chmod -R 777 /mydata
# 将ES配置为可以被远程的任何机器访问
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml

# 9200端口为外面向ES发送REST API端口,9300为ES集群间相互通信端口
# discovery.type=single-node ES以单节点运行
# -Xms64m -Xmx128m 非常重要,不设置初始堆和最大堆的大小,ES会默认抢占所有内存,会造成卡死
# -v 将ES容器的文件关联到外面文件下,实现外面修改容器里面也同时修改,plugins实现以后在外面安装插件重启就可以了
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:6.8.4

# 服务如果没启动,查看启动日志
docker logs -f -t --tail 20 elasticsearch
# 如果是Caused by: java.nio.file.AccessDeniedException: /usr/share/elasticsearch/data/nodes
# 则使用以下语句授权
chmod 777 -R /mydata/elasticsearch/

# 自动重启
docker update elasticsearch --restart=always

浏览器访问虚拟机 IP+9200 端口出现以下界面即成功截屏2021-04-14 21.21.18.png

3、安装 Kibana 实例(可视化界面)

# 这里的ip填写为虚拟机的ip 192.168.2.200,端口为前面ES容器内9200映射到宿主机的端口(前提是虚拟机关闭了防火墙,并且配置了桥接网络)
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.2.200:9200 -p 5601:5601 -d kibana:6.8.4
docker update kibana --restart=always

浏览器访问http://192.168.2.200:5601(这里为虚拟机的ip)
截屏2021-04-14 21.23.23.png

三、ES 实践

ES 是以 REST API 提供服务,外界向接口发送请求即可
下面的地址默认前缀都为http://**192.168.2.200:9200**

1、查看结点信息

  • GET /_cat/nodes 查看 ES 集群所有结点信息
  • GET /_cat/health 查看 ES 集群健康
  • GET /_cat/master 查看 ES 集群的主节点信息
  • GET /_cat/indices 查看 ES 集群的所有索引信息(相当于 MySQL 中的 show databases)

2、索引一个文档(保存)

保存一个数据:保存在哪个索引的哪个类型下,指定用哪一个唯一标识,对应的数据以 JSON 形式放置在请求 Body。
请求方法:PUT 或 POST 都可以,PUT 时需要指定唯一标识(否则出错),POST 时可以指定唯一标识也可以不指定(ES 会分配一个唯一标识)

  • PUT customer/external/1
{
  "name": "John Doe"
}

响应:

{
  "_index": "customer", // 索引:对应MySQL中的数据库
  "_type": "external", // 类型:对应MySQL中的表
  "_id": "1", // 标识
  "_version": 1, // 版本号:发送多次则是一个更新操作,版本号会逐渐加1递增
  "result": "created", // 结果:第一次发送为created,第二次及以后则是更新updated,与Map相似
  "_shards": {
    // 分片:集群时会用到
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 0,
  "_primary_term": 1
}
保存/更新 携带 id 不携带 id
PUT 必须项,更新保存二合一 报错
POST 非必须,更新保存二合一 新增操作

3、查询数据

GET /customer/external/1
响应:

{
  "_index": "customer",
  "_type": "external",
  "_id": "1",
  "_version": 5,
  "_seq_no": 5, //并发控制字段,序列号(每次更新自动加1),与_primary_term搭配可以作为乐观锁
  "_primary_term": 1, // 主分片,主分片重新分配,如重启,就会变化
  "found": true,
  "_source": {
    // 数据真正内容在_source中
    "name": "John Doe"
  }
}

更新操作(乐观锁)携带上 ?if_seq_no=#{当前的版本号}&if_primary_term=1

乐观锁(CAS)举例:

例如两个线程多时发起 PUT 操作同一个文档,两个请求都先发起查询,发现当前文档版本号_seq_no 为 5,故同时发起 PUT 请求,仅在版本号为 1 时才 PUT 更新。
当第一个请求先达到 ES,发现版本号为 5 与自己预料的是正确的的,则将 name 修改为了 1,此时版本号_seq_no 会自动加一个随机数,改为了 8;
第二个请求后随后到达,准备将 name 修改 2,但是发现此时的版本号为 8 与自己预料的版本号 5 对不上,则该 PUT 操作就不会执行,会显示 409 错误码。

  • PUT customer/external/1?if_seq_no=5&if_primary_term=1 {“name”:”1”}
  • PUT customer/external/1?if_seq_no=5&if_primary_term=1 {“name”:”2”}

截屏2021-04-13 00.13.25.png

4、更新数据

  • POST customer/external/1/_update

请求体为:
{
“doc”:{
“name”: “John”
}
}

  • POST customer/external/1

请求体为:
{
“name”: “John”
}

  • PUT customer/external/1

     请求体为
    

    {
    “name”: “John”
    }
    不同点:
    1.POST 发送请求携带上 update,请求体需要“doc”字段,doc 下才是文档数据
    2.POST 发送请求携带上 update,会对比要更新的数据,

  • 如果不存在则会将请求体数据创建

  • 如果存在

    • 对比数据相同:则什么也不做,version、seq_no 都不会发生变化
    • 对比数据不相同:发生更新操作,此时版本号才会发生变化(只有对比更新的数据有不同,才会进行更新)

    3.POST 不携带 update 和 PUT 请求,原来数据存在的话,都会发生更新,不会去进行对比,不存在就会创建
    共同点: 1.都可以在原来的基础上,更新增加属性
    截屏2021-04-13 10.47.17.png截屏2021-04-13 10.47.48.png

5、删除文档&索引

ES 中没有直接删除类型 Type

  • DELETE /customer/external/1 删除文档
  • DELETE /customer 删除索引

6、bulk 批量 API

在 kibana 中批量操作形式为:

  • 其中 action 可选为 index/create/update/delete
  • 其中 metadata 可选为_index,_type,_id
  • 其中 requestbody 为数据

create 和 index 的区别:
如果数据存在,使用 create 操作失败,会提示文档已经存在,使用 index 则可以执行成功,如果存在会进行覆盖
bulk 批量操作,一个操作失败不会影响后续的操作

{"action": {metedta}}
{requestbody}
{"action": {metedta}}
{requestbody}

举例:

  • POST customer/external/_bulk
{"index": {"_id": 1}}
{"name": "1"}
{"index": {"_id": 2}}
{"name": "2"}
  • POST _bulk
{"delete": {"_index":"website", "_type":"blog","_id":"123"}} // 删除数据,初次会提示404,但不影响后续的操作
{"create": {"_index":"website", "_type":"blog","_id":123}} // 创建数据
{"title":"My first blog post"}
{"index": {"_index":"website","_type":"blog"}} // 插入数据
{"title": "My second blog post"}
{"update":{"_index":"website", "_type":"blog","_id":"123"}} // 更新数据
{"doc":{"title":"My updated blog post"}}

四、复杂检索

首先批量导入数据 POST /bank/account/_bulk
https://raw.githubusercontent.com/elastic/elasticsearch/master/docs/src/test/resources/accounts.json
支持两种方式:

  • 将请求检索参数防止在 GET 请求路径背后 例如:
    • GET /bank/_search?q=*&sort=account_number:sort
  • 将请求检索参数放置在请求体中
    • GET bank/_search
{
  "query": {
    "match_all":{}
  },
  "sort":[
    {
      "account_number": "asc"
    }
    ]
}

1)基本语法格式

ES 提供了一个可以执行查询的 json 风格的 DSL(domain-specific language 领域特定语言),也被称为 Query DSL。
1、全记录分页排序查询

# 分页查询 from和size相当于MySQL中的limit
GET bank/_search
{
  "query": {
    "match_all":{}
  },
  "sort": [
    {
      "balance": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 5
}

2、特定字段分页排序查询

# 查询特定字段的分页查询,_source指定查询字段
GET bank/_search
{
  "query": {
    "match_all":{}
  },
  "sort": [
    {
      "balance": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 5,
  "_source": ["balance","firstname"]
}

3、match 查询、term 查询
term 查询,和 match 一样匹配某个属性的值,区别在于:

  • term 查询只能用于查询非文本字段,查询文本字段会始终显示结果数为 0;或者 term 与 keyword 搭配,查询全量字符串
  • 文本字段全文检索字段用 match

官方推荐:term 用于查询非文本字段,match 用于查询文本字段

# match用在字段为数字上相当于精确查询
GET bank/_search
{
  "query": {
    "match": {
      "account_number": 20
    }
  }
}

# match模糊检索,会对检索条件进行分词匹配,不指定排序的话,结果会按照评分进行排序
GET bank/_search
{
  "query": {
    "match": {
      "address": "mill lane"
    }
  }
}

# match全文检索,指定了排序则会按照排序字段进行排序
GET bank/_search
{
  "query": {
    "match": {
      "address": "mill lane"
    }
  },
  "sort": [
    {
      "balance": {
        "order": "desc"
      }
    }
  ]
}

# term查询,和match一样匹配某个属性的值
# 只能用于查询非文本字段,查询文本字段会始终显示结果数为0
# 全文检索字段用match,其他非text字段用term
# 官方推荐:term用于查询非文本字段,match用于查询文本字段
GET bank/_search
{
  "query": {
    "term": {
      "balance": 32838
    }
  }
}

# 或者term与keyword搭配,查询全量字符串
GET bank/_search
{
  "query": {
    "term": {
      "lastname.keyword": "Bates"
    }
  }
}

4、match_phrase 短语匹配查询、keyword 全量精确查询
match_phrase 和 keyword 区别

  • match_phrase 是将查询词不可拆分进行查询,只有完整包含了这个短语就可以得到匹配
  • keyword 则是精确查询,要求查询字段只能是该短语,而不是仅仅的包含关系
# match_pharse 短语匹配,将查询条件当成不可分割的短语进行匹配查询
# 包含了完成的短语mill lane会检索出来
GET bank/_search
{
  "query": {
    "match_phrase": {
      "address": "789 Madison"
    }
  }
}

# 对于有一条记录为"address" : "789 Madison Street"
# match_phrase可以查到一条记录,而keyword差不到该条记录,是精确查询
# keyword全量精确匹配
GET bank/_search
{
  "query": {
    "match": {
      "address.keyword" : "789 Madison"
    }
  }
}

5、multi_match 多字段匹配查询

# multi_match 多字段查询,query的词也会进行分词,两个字段有任意一个字段包含分的词检索出来,是or关系
GET /bank/_search
{
  "query": {
    "multi_match": {
      "query": "mill lane",
      "fields": ["state", "address"]
    }
  }
}

6、bool 多重条件复合查询
bool 多重查询可以用上的条件交并集:must、must_not、should、filter
must 和 filter 配合 range 都可以进行过滤,区别在于:must 会贡献评分,但是 filter 不会贡献评分

  • must:数组内的条件必须都满足
# bool中可以有多个查询条件,这里的must含义就是must数组中的条件都必须满足
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {"match":
          {
            "gender": "F"
          }
        },
        {
          "match": {
            "address": "mill"
          }
        }
      ]
    }
  }
}
  • must_not:数组内的条件必须都不满足
# 复合查询,must:必须满足,must_not:必须不满足
# 查找出gender为M,address中有mill且age不是38的记录
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {"match":
          {
            "gender": "M"
          }
        },
        {
          "match": {
            "address": "mill"
          }
        }
      ],
      "must_not": [
        {
          "match":
          {
            "age": 38
          }
        }
      ]
    }
  }
}
  • should:数组内的条件最好满足,满足会有匹配度加分
# 复合查询,其中的should表示满足最好,不满足也可以,是加分项,满足了评分会更高
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {"match":
          {
            "gender": "M"
          }
        },
        {
          "match": {
            "address": "mill"
          }
        }
      ],
      "must_not": [
        {
          "match":
          {
            "age": 18
          }
        }
      ],
      "should": [
        {
          "match":
          {
          "lastname": "Wallace"
          }
        }
      ]
    }
  }
}
  • filter:过滤
# must和filter配合range都可以进行过滤
# 区别在于:must会贡献评分,但是filter不会贡献评分
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "age": {
              "gte": 18,
              "lte": 30
            }
          }
        }
      ]
    }
  }
}

GET bank/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "age": {
            "gte": 18,
            "lte": 30
          }
        }
      }
    }
  }
}

7、aggragations 聚合查询
聚合提供了从数据中分组和提取数据的能力,最简单的聚合方法大致等于 SQL 中的 group by 和 SQL 聚合函数。ES 中,您有执行搜索返回 hits(命中结果),并且同时返回聚合结果,将一个响应中的所有 hits(命中结果)隔开的能力,可以执行查询和多个聚合,并且在一次使用中得到各自的返回结果,使用一次简洁和简化的 API 来避免网络往返
聚合语法:

"aggregations" : {
    "<aggregation_name>" : {        # 这次聚合的名字,自定义方便展示在结果集中
        "<aggregation_type>" : {    # 聚合类型
            <aggregation_body>      # 聚合体
        }
        [,"meta" : {  [<meta_data_body>] } ]?
        [,"aggregations" : { [<sub_aggregation>]+ } ]?   # 聚合内嵌套子聚合,是在第一个查询的结果集上再次进行子聚合查询
    }
    [,"<aggregation_name_2>" : { ... } ]* # 第二个聚合,和第一聚合是相互独立的
}
 1.搜索address中包含mill的所有人的年龄分布以及平均年龄
 GET bank/_search
{
  "query": {
    "match": {
      "address": "mill"
    }
  },
  "aggs": {
    "aggAge": {
      "terms": {   # terms相当于SQL中的count + group by,这里是先年龄分组计算每个分组总数
        "field": "age",
        "size": 10
      }
    },
    "ageAvg":{
      "avg": {   # 第二个聚合:计算平均年龄
        "field": "age"
      }
    }
  }
}

2.按照年龄聚合,并且请求这些年龄段这些人的平均薪资
# 子查询,类似于MySQL中group by 然后avg
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "aggAge": {
      "terms": {
        "field": "age",
        "size": 10
      },
      "aggs": {   # 子查询,在上一次聚合的结果中再次查询
        "aggAvg": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

3.查出所有年龄分布,并且这些年龄中的性别为M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "aggAge": {
      "terms": {
        "field": "age", # 首先按照年龄进行聚合,terms计算count
        "size": 100
      },
      "aggs": {
        "avgGender": {
          "terms": {
            "field": "gender.keyword"  # 在年龄聚合的桶内再用性别进行聚合
          },
          "aggs": {
            "aggBalance": {
              "avg": {
                "field": "balance"   # 在性别聚合的桶内在计算桶内的平均薪资
              }
            }
          }
        },
        "avgBalanceOnAge":{
          "avg": {
            "field": "balance" # 在年龄聚合的桶内计算平均薪资
          }
        }
      }
    }
  }
}

2)Mapping 映射

Mapping 是一个定义文档和所包含的字段如何保存和索引的,举例,使用 mapping 可以用来定义:

  • 哪个字符串字段应该被当成全文检索字段(full text fields)
  • 哪个字段包含数字、日期、或者地理标志
  • 日期的格式
  • 自定义映射规则来执行动态添加属性
1.查询映射关系
PUT /my_index
{
  "mappings": {
    "properties": {
      "age": {"type": "Long"},
      "email": {"type": "keyword"},
      "name": {"type": "text"}
    }
  }
}

2.建立索引的mapping映射
# ES7.x不再支持Types(类似MySQL中的表),所以这里没有在my_index在指定Types
# keyword会进行精确匹配,而不再分词全文匹配
PUT /my_index/_mapping
{
  "properties": {
    "employee_id": {
      "type": "keyword",
      "index": false
    }
  }
}

3.修改指定索引的映射
3.1 增加新的字段
PUT /my_index/_mapping
{
  "properties": {
    "employee_id": {
      "type": "keyword",
      "index": false   # index表示employee_id不再可以被查询。(Fields that are not indexed are not queryable.)
    }
  }
}
3.2 修改已有的字段映射
建立了映射后只能新增字段,而不能修改以前的字段属性,因为修改已存在的字段会让已经存在的数据失效。
如果你确实需要修改某个字段的映射,你需要重新建立一个映射关系的索引,然后将你的数据reindex到你新建的索引中

3.3 数据迁移到新的索引
3.3.1 首先建立新的索引mapping关系
PUT /newbank
{
  "mappings": {
    "properties": {
      "account_number": {
        "type": "long"
      },
      "address": {
        "type": "text"
      },
      "age": {
        "type": "integer"
      },
      "balance": {
        "type": "long"
      },
      "city": {
        "type": "keyword"
      },
      "email": {
        "type": "keyword"
      },
      "employer": {
        "type": "keyword"
      },
      "firstname": {
        "type": "text"
      },
      "gender": {
        "type": "keyword"
      },
      "lastname": {
        "type": "text"
      },
      "state": {
        "type": "keyword"
      }
    }
  }
}

3.3.2 数据迁移reindex
【固定写法】对于没有建立类型的索引之间相互迁移数据
POST _reindex
{
  "source": {
    "index": "twitter"
  },
  "dest": {
    "index": "tweet  "
  }
}

将旧索引的types下的数据进行迁移
因为我们旧索引建立了accout类型,索引迁移需要将索引和类型指定迁移到哪个索引下
POST _reindex
{
  "source": {
    "index": "bank",
    "type": "account"
  },
  "dest": {
    "index": "newbank"
  }
}

# 可以看到数据迁移了过来,并且没有了type
# 以后都不用type,老的数据都可以通过reindex迁移过来
GET newbank/_search

五、分词

一个 tokenizer(分词器)接受一个字符流,将之分隔为独立的 tokens(词元,通常是独立的单词),然后输出 tokens 流。
例如:whitespace tokenizer 遇到空白字符时分隔文本,它会将文本”Quick brown fox!”分隔为【Quick,brown,fox!】
该 tokenizer(分词器)还负责记录各个 term(词条)的顺序或 position 位置(用于 phrase 短语和 word proximity 词近邻查询),以及 term(词条)所代表的原始 word(单词)的 start(起始)和 end(结束)的 character offsets(字符偏移量)用于高亮显示搜索的内容,ElasticSearch 提供了很多内置的分词器,可以用来构建 custom analyzers(自定义分词器)

# 使用标准特定的分词器来进行分词
POST _analyze
{
  "analyzer": "standard",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

1、安装 ik 分词器

# 进入容器挂载到外部的目录或未挂载进入容器内的目录
cd /mydata/elasticsearch/plugins
mkdir ik
cd ik
# 下载对应ES版本的ik分词器,这里选择7.4.2版本
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
unzip elasticsearch-analysis-ik-6.8.4.zip
cd ../..
chmod -R 777 plugins/
docker exec -it elasticsearch /bin/bash
cd /usr/share/elasticsearch/plugins/
# 查看安装好的插件
elasticsearch-plugin
# 重启让插件生效
docker restart elasticsearch

2、测试 ik 分词器

ik 分词器提供两种:ik_smart 和 ik_max_word
ik_smart:智能分词,将输入词进行拆分
ik_max_word:保留所有可能搭配的词组组合

POST _analyze
{
  "analyzer": "ik_smart",
  "text": "我是中国人"
}

3、自定义词库

ik 分词器有很多网络热词无法进行分词,所以这里我们可以自定义词库
我们可以指定一个远程的词库,让 ik 分词器可以去远程拉取最新的单词作为新的词元进行分解,这里建立远程的词库有两种方式:

  • 写一个项目来处理 ik 发送的请求,来返回新的单词
  • 安装 Nginx,让 ik 分词器给 Nginx 发送请求,Ngix 将这个静态资源返回

更改 ES 栈大小,之前是-Xmx128m,有点小,这里改为 512m,方法为将之前的 ES 容器删掉,因为之前是将容器内的数据挂载到了 centos 下的目录,所以数据不会丢失,新建一个 ES 容器再次挂载到这些目录即可

docker rm -f elasticsearch
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

4、安装 Ngnix

# 首先随便创建一个nginx实例,主要是将其中的配置复制出来
cd /mydata
mkdir nginx
docker pull nginx:1.10
docker run -p 80:80 --name nginx -d nginx:1.10
# 将nginx中的配置复制出来
docker container cp nginx:/etc/nginx .
cd nginx
ls # 就可以查看复制出来的文件
cd ..
mv nginx conf  # 将nginx配置移动到conf文件夹下
mkdir nginx   # 新建nginx文件夹
mv conf nginx/  # 将整个conf文件夹移动到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

浏览器访问虚拟机 ip 地址http://192.168.2.200/即成功
截屏2021-04-15 16.31.43.png

cd /ngnix/html
echo '<h1>hello nginx</h1>' >> index.html

截屏2021-04-14 21.29.41.png

5、在 Nginx 下自定义静态资源词库

# 在html下创建es文件夹,这里放入自定义分词器的词库内容
mkdir es
cd es
vi fenci.txt
# 输入你的词元,例如以下

image.png

# 修改ik分词器请求分词词库地址
cd elasticsearch/plugins/ik
cd config
vi IKAnalyzer.cfg.xml

填充上 nginx 代理的静态资源地址(如果设置了 centos 的 ip 地址为宿主机 ip 地址同一个网段,则可以填写 centos 的地址,这里填写 192.168.2.200)
截屏2021-04-14 21.35.37.png

# 让分词器生效
docker restart elasticsearch
docker update nginx --restart=always

地址栏访问 kibana,可以看到尚硅谷成为了一个词元,没有再被拆开为【尚,硅,谷】,说明自定义词库已经生效
(kibana 显示 not ready yet 的,注意因为我们新建了 ES 容器,对应容器的 ip 地址也发生了变化,所以新建 kibana 容器,指定好新的 ES 地址就可以访问了,上面步骤也都是有的)

截屏2021-04-15 16.38.59.png

五、java 操作 ES

java 操作 ES 有多重选择
1)9300:TCP

  • spring-data-elasticsearch:transport-api.jar
    • springboot 版本不同,transport-api.jar 不同,不能适配 es 高版本
    • 7.x 已经不建议使用,8 以后就要废弃
  • spring-boot-starter-data-elasticsearch

请注意 springboot 版本以及 elasticsearch 版本,是对官方 restClient 更简化的封装,推荐
2)9200:HTTP

  • JestClient:非官方,更新慢
  • RestTemplate:模拟发送 HTTP 请求,ES 很多 DSL 都要自己封装,麻烦
  • HttpClient:同上
  • Ok-Http:同上
  • Elasticsearch-Rest-Client:官方 RestClient,封装了 ES 操作,上手比较繁琐

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 (CC BY-NC-ND 4.0) 进行许可。