elasticsearch

本文详细介绍了Elasticsearch的正向与倒排索引原理,对比了与MySQL的适用场景,并提供了单点及集群部署的Docker实践。同时,讲解了IK分词器的安装、扩展词典和停用词的配置,以及如何通过JavaRestClient进行索引库操作。最后,展示了DSL查询、数据聚合和搜索结果处理的方法。
摘要由CSDN通过智能技术生成

一、简介

 

1.   

elasticsearch是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。

结合kibana,Logstash,Beats,也就是elastic stack(ELK),被广泛用于日志数据分析,实时监控等领域。

 Elasticsearch是核心,其他的可以用自己的方式进行替换。

2.  正向索引和倒排索引

2.1 正向索引:从数据库中一条一条取出数据,进行比较,和搜索匹配的放入结果集,和搜索不匹配的直接丢弃。可以得到完整的结果,但是这是基于逐行搜索的方式,效率非常低下。

2.2 倒排索引(ES):分为文档和词条两个概念

***文档:每条数据都是一个文档,可以比喻为一个表中的一行数据

                文档数据会被序列化为json格式存储到ES中

***词条:将文档按照语义分成不同的词语,每个词语都是一个词条

用ES会为每个表创建一个新表,用来存储词条和该词条的id

 如:小米手机可以分为 小米和手机两个词条,将词条存入词条表中,表中没有的会存入,并记录该词条的id,例如小米是1,手机也是1。当华为手机时,差分为华为和手机,华为表中没有,新增一行加入  华为 : 2     。  而华为分出的手机已经在表中存在,不需要加入新的行,只需要在手机哪一行加入手机(华为)对应的id即可,看上表。

3.Mysql和ES的比较

MySQL:擅长事务类型操作,可以确保数据的安全性和一致性。适合例如商品下单的功能

ES:擅长海量数据的搜索和分析和计算,适用于商品的查询和搜索。

 写操作直接进入mysql,读操作就进入ES,分工明确,类似于mysql的主从复制。

二、ES和kibana的安装

1.单点部署ES

1.1 创建网络

因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络。

docker  network  create es-net

1.2 加载镜像

这里我们采用elasticsearch的7.12.1版本的镜像,这个镜像体积非常大,接近1G,不建议大家自己pull。提前备好该镜像的tar包。

 将其上传到虚拟机上,进行命令加载即可

docker  load  -i  es.tar

 同样的kibana的tar包也重复上述操作即可。

1.3.运行

运行docker命令,部署单点es:

docker run -d \
	--name es \
    -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
    -e "discovery.type=single-node" \
    -v es-data:/usr/share/elasticsearch/data \
    -v es-plugins:/usr/share/elasticsearch/plugins \
    --privileged \
    --network es-net \
    -p 9200:9200 \
    -p 9300:9300 \
elasticsearch:7.12.1

命令解释:

  • -e "cluster.name=es-docker-cluster":设置集群名称

  • -e "http.host=0.0.0.0":监听的地址,可以外网访问

  • -e "ES_JAVA_OPTS=-Xms512m -Xmx512m":内存大小

  • -e "discovery.type=single-node":非集群模式

  • -v es-data:/usr/share/elasticsearch/data:挂载逻辑卷,绑定es的数据目录

  • -v es-logs:/usr/share/elasticsearch/logs:挂载逻辑卷,绑定es的日志目录

  • -v es-plugins:/usr/share/elasticsearch/plugins:挂载逻辑卷,绑定es的插件目录

  • --privileged:授予逻辑卷访问权

  • --network es-net :加入一个名为es-net的网络中

  • -p 9200:9200:端口映射配置

 通过浏览器访问,能看到返回这些json数据,证明es已经准备好了

2.部署kibana

kibana可以给我们提供一个elasticsearch的可视化界面,便于我们学习。

2.1.部署

运行docker命令,部署kibana

docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601  \
kibana:7.12.1
  • --network es-net :加入一个名为es-net的网络中,与elasticsearch在同一个网络中

  • -e ELASTICSEARCH_HOSTS=http://es:9200":设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch

  • -p 5601:5601:端口映射配置

kibana启动一般比较慢,需要多等待一会,可以通过命令:

docker logs -f kibana

查看运行日志,当查看到下面的日志,说明成功:

此时,在浏览器输入地址访问:http://ip:5601,即可看到结果

3.安装IK分词器

3.1.在线安装ik插件(较慢)

# 进入容器内部
docker exec -it elasticsearch /bin/bash
​
# 在线下载并安装
./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
​
#退出
exit
#重启容器
docker restart elasticsearch

3.2.离线安装ik插件(推荐)

1)查看数据卷目录

安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看:

docker volume inspect es-plugins

显示结果:

[
    {
        "CreatedAt": "2022-05-06T10:06:34+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
        "Name": "es-plugins",
        "Options": null,
        "Scope": "local"
    }
]

说明plugins目录被挂载到了:/var/lib/docker/volumes/es-plugins/_data这个目录中。

2)解压缩分词器安装包

下面我们需要把课前资料中的ik分词器解压缩,重命名为ik

3)上传到es容器的插件数据卷中

也就是/var/lib/docker/volumes/es-plugins/_data

4)重启容器

# 4、重启容器
docker restart es
# 查看es日志
docker logs -f es

5)测试:

IK分词器包含两种模式:

  • ik_smart:最少切分

  • ik_max_word:最细切分

GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "学习java太棒了"
}

结果:

 

3.3 扩展词词典

随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:“奥力给” 等。

所以我们的词汇也需要不断的更新,IK分词器提供了扩展词汇的功能。

1)打开IK分词器config目录:

2)在IKAnalyzer.cfg.xml配置文件内容添加:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 *** 添加扩展词典-->
        <entry key="ext_dict">ext.dic</entry>
</properties>

3)新建一个 ext.dic,可以参考config目录下复制一个配置文件进行修改

奥力给

4)重启elasticsearch

docker restart es
​
# 查看 日志
docker logs -f elasticsearch

日志中已经成功加载ext.dic配置文件

5)测试效果:

GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "90%,奥力给!"
}

注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑

3.4 停用词词典

在互联网项目中,在网络间传输的速度很快,所以很多语言是不允许在网络上传递的,如:关于宗教、政治等敏感词语,那么我们在搜索时也应该忽略当前词汇。

IK分词器也提供了强大的停用词功能,让我们在索引时就直接忽略当前的停用词汇表中的内容。

1)IKAnalyzer.cfg.xml配置文件内容添加:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典-->
        <entry key="ext_dict">ext.dic</entry>
         <!--用户可以在这里配置自己的扩展停止词字典  *** 添加停用词词典-->
        <entry key="ext_stopwords">stopword.dic</entry>
</properties>

3)在 stopword.dic 添加停用词

八嘎

4)重启elasticsearch

# 重启服务
docker restart elasticsearch
docker restart kibana
​
# 查看 日志
docker logs -f elasticsearch

日志中已经成功加载stopword.dic配置文件

5)测试效果:

GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "Java就业率超过95%,都点赞,奥力给!"
}

注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑

4.部署es集群

部署es集群可以直接使用docker-compose来完成,不过要求你的Linux虚拟机至少有4G的内存空间

首先编写一个docker-compose文件,内容如下:

version: '2.2'
services:
  es01:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
    container_name: es01
    environment:
      - node.name=es01
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es02,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data01:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
    networks:
      - elastic
  es02:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
    container_name: es02
    environment:
      - node.name=es02
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data02:/usr/share/elasticsearch/data
    networks:
      - elastic
  es03:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.12.1
    container_name: es03
    environment:
      - node.name=es03
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data03:/usr/share/elasticsearch/data
    networks:
      - elastic
​
volumes:
  data01:
    driver: local
  data02:
    driver: local
  data03:
    driver: local
​
networks:
  elastic:
    driver: bridge

Run docker-compose to bring up the cluster:

<br class="Apple-interchange-newline"><div></div>

 
docker-compose up

三、索引库操作

1. mapping属性

mapping是对索引库文档的约束,常见的mapping属性包括:

分词器只有在字符串类型的字段时才会使用。properties就是指图片中name的子属性,firstName,可以自己为name添加properti子属性。 

四、java客户端操作ES======》rest client

1.什么是restclient:

官方为java语言提供的一个操作es的客户端。

2.入门案例了解restclient的用法

2.1  导入数据库资料,创建一个hotel表:

 2.2  导入hotel-demo工程

2.3  编写mapping

PUT /_hotel
{
  "mappings":{
    "properties":{
      "id":{
        "type":"keyword"
      },
      "name":{
        "type":"text",
        "analyzer":"ik_max_word",
        "copy_to":"all"
      },
      "address":{
        "type":"keyword",
        "index":false
      },
      "price":{
        "type":"integer"
      },
      "score":{
        "type":"integer"
      },
      "brand":{
        "type":"keyword",
        "copy_to":"all"
      },
      "city":{
        "type":"keyword"
      },
      "starName":{
        "type":"keyword"
      },
      "business":{
        "type":"keyword",
        "copy_to":"all"
      },
      "lcoation":{
        "type":"geo_point"
      },
      "pic":{
        "type":"keyword",
        "index":false
      },
      "all":{
        "type":"text",
        "analyzer":"ik_max_word"
      }
    }
  }
  
}

其中copy_to表示将其整合到一个字段里面,相当于多条联查。

2.4  初始化JavaRestClient

 1.导入依赖

=================================》依赖:
<dependency>
     <groupId>org.elasticsearch.client</groupId>
     <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

 2.初始化RestHighLeveClient:

 用完需要让客户端关闭

 

 3.编写客户端创建索引库代码

3.1   创建索引库

 其中Mapping_TEMPLATE就是我们之前在kibana上写好的DSL语句。

测试:

 可以看到可以查询到,证明索引库创建成功。

3.2  查看索引库是否存在



    @Test
    void testExistsIndex() throws IOException {
        // 1.准备Request
        GetIndexRequest request = new GetIndexRequest("hotel");
        // 3.发送请求
        boolean isExists = client.indices().exists(request, RequestOptions.DEFAULT);

        System.out.println(isExists ? "存在" : "不存在");
    }

3.3 删除索引库


    @Test
    void testDeleteIndex() throws IOException {
        // 1.准备Request
        DeleteIndexRequest request = new DeleteIndexRequest("hotel");
        // 3.发送请求
        client.indices().delete(request, RequestOptions.DEFAULT);
    }

4.  restclient 操作文档

4.1  添加文档

@Test
    void testAddDocument() throws IOException {
        // 1.查询数据库hotel数据
        Hotel hotel = hotelService.getById(61083L);
        // 2.转换为HotelDoc
        HotelDoc hotelDoc = new HotelDoc(hotel);
        // 3.转JSON
        String json = JSON.toJSONString(hotelDoc);

        // 1.准备Request
        IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
        // 2.准备请求参数DSL,其实就是文档的JSON字符串
        request.source(json, XContentType.JSON);
        // 3.发送请求
        client.index(request, RequestOptions.DEFAULT);
    }

4.2  查看文档

@Test
    void testGetDocumentById() throws IOException {
        // 1.准备Request      // GET /hotel/_doc/{id}
        GetRequest request = new GetRequest("hotel", "61083");
        // 2.发送请求
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        // 3.解析响应结果
        String json = response.getSourceAsString();

        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
        System.out.println("hotelDoc = " + hotelDoc);
    }

测试:

 4.3 删除文档

 @Test
    void testDeleteDocumentById() throws IOException {
        // 1.准备Request      // DELETE /hotel/_doc/{id}
        DeleteRequest request = new DeleteRequest("hotel", "61083");
        // 2.发送请求
        client.delete(request, RequestOptions.DEFAULT);
    }

4.4 修改文档

@Test
    void testUpdateById() throws IOException {
        // 1.准备Request
        UpdateRequest request = new UpdateRequest("hotel", "61083");
        // 2.准备参数
        request.doc(
                "price", "870"
        );
        // 3.发送请求
        client.update(request, RequestOptions.DEFAULT);
    }

4.5 批量添加文档

@Test
    void testBulkRequest() throws IOException {
        // 查询所有的酒店数据
        List<Hotel> list = hotelService.list();

        // 1.准备Request
        BulkRequest request = new BulkRequest();
        // 2.准备参数
        for (Hotel hotel : list) {
            // 2.1.转为HotelDoc
            HotelDoc hotelDoc = new HotelDoc(hotel);
            // 2.2.转json
            String json = JSON.toJSONString(hotelDoc);
            // 2.3.添加请求
            request.add(new IndexRequest("hotel").id(hotel.getId().toString()).source(json, XContentType.JSON));
        }

        // 3.发送请求
        client.bulk(request, RequestOptions.DEFAULT);
    }

五、分布式搜索引擎

1.DSL查询文档

1.1 DSL Query的分类

*

//查询所有
GET /hotel/_search
{
  "query": {
    "match_all":{}
  }
}

* multi_match查询是根据多个字段查询,查询字段越多,性能越差. 

 * term  精确搜索  根据一个字段的值 精确搜索,一般搜索keyword类型,数值类型,布尔类型,日期类型字段。

 * range  根据数值范围查询,可以是数值,日期的范围

 * distance  附近距离的查询  距离-------中心点

 * function score  复合查询 ,算分函数查询,可以控制文档相关性算分,控制文档排名,例如百度竞价

 *  bool查询

2.搜索结果处理

2.1 排序

 

 2.2 高亮

就是在搜索结果中把搜索关键字突出显示

 3.RestClient查询文档

3.1 match_all 查询

@Test
    void testMatchAll() throws IOException {
        SearchRequest searchRequest = new SearchRequest("hotel");
        searchRequest.source().query(QueryBuilders.matchAllQuery());
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println(JSON.toJSONString(response));
    }

3.2 match查询

@Test
    void testMatchAll() throws IOException {
        SearchRequest searchRequest = new SearchRequest("hotel");
        searchRequest.source().query(QueryBuilders.matchQuery("all","如家"));
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = response.getHits();
        long totalHits = hits.getTotalHits().value;
        System.out.println("共查到:"+totalHits+"条数据~");
        SearchHit[] hits1 = hits.getHits();
        for (SearchHit hit : hits1){
            String sourceAsString = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
            System.out.println(hotelDoc);
        }
    }

 3.3 term查询(精确查询)

@Test
    void testMatchAll() throws IOException {
        SearchRequest searchRequest = new SearchRequest("hotel");
        searchRequest.source().query(QueryBuilders.termQuery("city","上海"));
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = response.getHits();
        long totalHits = hits.getTotalHits().value;
        System.out.println("共查到:"+totalHits+"条数据~");
        SearchHit[] hits1 = hits.getHits();
        for (SearchHit hit : hits1){
            String sourceAsString = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
            System.out.println(hotelDoc);
        }
    }

 3.4 range组合查询

@Test
    void testMatchAll() throws IOException {
        SearchRequest searchRequest = new SearchRequest("hotel");
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(QueryBuilders.termQuery("city","上海"));
        boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(250));
        searchRequest.source().query(boolQueryBuilder);
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = response.getHits();
        long totalHits = hits.getTotalHits().value;
        System.out.println("共查到:"+totalHits+"条数据~");
        SearchHit[] hits1 = hits.getHits();
        for (SearchHit hit : hits1){
            String sourceAsString = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
            System.out.println(hotelDoc);
        }
    }

3.5 分页和结果排序

@Test
    void testPageAndSort() throws IOException {
        SearchRequest searchRequest = new SearchRequest("hotel");
        MatchAllQueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();
        searchRequest.source().query(matchAllQuery);
        searchRequest.source().sort("price", SortOrder.ASC).from(0).size(5);
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = response.getHits();
        long totalHits = hits.getTotalHits().value;
        System.out.println("共查到:"+totalHits+"条数据~");
        SearchHit[] hits1 = hits.getHits();
        for (SearchHit hit : hits1){
            String sourceAsString = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
            System.out.println(hotelDoc);
        }
    }

六、数据聚合

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员Mapy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值