springcloud篇7-ElasticSearch分布式搜索

一、初识elasticsearch

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

例如,搜索商品:

google浏览器搜索:

打车软件搜索附近车辆:

1.1 了解ES

elasticsearch是elastic stack(ELK)的核心,elastic stack还包括kibana、Logstash、Beats,被广泛应用于 日志数据分析、实时监控 等领域。

(1)elasticsearch 负责存储、搜索和分析数据。

(2)logstash和beats 负责数据抓取(可替代,可以写java代码进行搜索)。

(3)kibana 负责数据可视化(可替代,可视化的工具有很多,不一定要用kibana)。

ElasticSearch的底层实现是Lucene技术,Lucene是一个java语言的搜索引擎类库 ,是Apache公司的顶级项目。

官方网址:https://lucene.apache.org/

Lucene的优势:

(1)易扩展

(2)高性能(基于倒排索引)

缺点:

(1)只限于Java语言开发

(2)学习曲线陡峭(API设计复杂)

(3)不支持水平扩展(类库只考虑如何实现搜索,面对高并发场景不支持集群扩展,要想实现需要进行二次开发)
ElasticSearch解决了Lucene的问题,支持分布式,可水平扩展;提供Restful接口,可被任何语言调用。

1.2 倒排索引

正向索引和倒排索引

传统数据库(如MySQL)采用正向索引,例如给tb_goods表的字段id创建索引,而对于没加索引的字段title,根据该字段查询时需要遍历所有记录。

倒排索引:

倒排索引创建时会建立一个新的表,包括文档(document)和词条(term)。
文档:每条数据就是一个文档
词条:文档按照语义分成的词语


词条字段不能重复,给词条创建索引,根据词条查找id,再在原表里根据id查找,速度很快

1.3 ES与MySQL对比

(1)文档

elasticsearch是面向文档存储的,文档数据会被序列化为json格式后存储在elasticsearch中。

(2)索引

索引是相同类型的文档的集合

映射:索引中文档的字段约束信息,类似表的结构约束(约束索引中文档的数据格式相同)。

对比:

(1)MySQL擅长事务类型操作,可以确保数据的安全和一致性。

(2)Elasticsearch:擅长海量数据的搜索、分析和计算。

1.4 安装ES和Kibana

1.4.1 创建网络

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

bash 复制代码
docker network create es-net

1.4.2 加载镜像

将资料中的两个jar包导入(镜像太大,docker pull时间会很长)。

bash 复制代码
docker load -i es.tar
docker load -i kibana.tar

1.4.3 运行(创建容器)

(1)运行elasticsearch

bash 复制代码
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:端口映射配置


    (2)运行kibana
bash 复制代码
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 ps可以查看到,但启动速度很慢,会产生各种数据和日志。可以通过docker logs -f kibana查看。

bash 复制代码
docker logs -f kibana

通过浏览器访问:

点击"Explore on my own":


DSL语句的本质就是发送一个Restful请求到ES中。

1.5 安装KI分词器

es在创建倒排索引时 需要对文档分词;在搜索时,需要对用户输入内容分词 。但默认的分词规则对中文处理并不友好。

处理分词时,一般会使用IK分词器。

bash 复制代码
docker volume inspect es-plugins

将解压的ik文件夹里的东西上传到查询到的插件目录/var/lib/docker/volumes/es-plugins/_data。直接上传可能打不开这个目录,可以先上传到一个目录再用mv指令。

重启docker服务的es:

bash 复制代码
docker restart es

IK分词器包含两种模式:

  • ik_smart:最少切分
  • ik_max_word:最细切分

测试:

GET /_analyze { "analyzer": "ik_max_word", "text": "黑马程序员学习java太棒了" }

补充:IK分词器的拓展和停用字典

IK分词器的分词是基于一个词库,词库里有的词就会切分,但有些词词库里没有,无法准确切分,此时需要拓展。

例如:

最新的网络用语"白嫖"、"欧力给"没办法切割出来。

要拓展IK分词器的词库,只需要修改一个IK分词器目录中的config目录中的IKAnalyzer.cfg.xml文件:

也可以加一些停用词(比如"的","地","了"等):

文件里新增地ext.dic和stopword.dic是文件,需要新建并在文件中添加词。




测试:

二、索引库操作

mapping映射是对索引库中文档的约束。

可以查看官方文档:https://www.elastic.co/

2.1 mapping属性

(一)type(字段数据类型)

(1)字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、IP地址)

(2)数值:long、Integer、short、byte、double、float

(3)布尔:boolean

(4)日期:date

(5)对象:object

(二)index(是否创建索引,默认为true)

(三)analyzer(使用哪种分词器)

结合text类型用,值为ik_smart、ik_max_word

(四)properties:该字段的子字段

2.2 创建索引库

ES通过Restful请求操作索引库、文档。请求内容用DSL语句来表示,创建索引库和mapping的DSL语法如下:

一个示例:

操作实例:

创建索引库

bash 复制代码
PUT /heima
{
  "mappings": {
    "properties": {
      "info":{
        "type":"text",
        "analyzer": "ik_smart"
      },
      "email":{
        "type":"keyword",
        "index":false
      },
      "name":{
        "type":"object",
        "properties":{
          "firstName":{
            "type":"keyword"
          },
          "lastName":{
            "type":"keyword"
          }
        }
      }
    }
  }
}

2.3 查看、删除索引库

查看索引库语法:
GET /索引库名

删除索引库语法:
DELETE /索引库名

注意:ES中索引库是不允许修改的,但是可以新增新字段。

bash 复制代码
# 查询
GET /heima
bash 复制代码
# 修改索引库
PUT /heima/_mapping
{
  "properties":{
    "age":{
      "type":"integer"
    }
  }
}
bash 复制代码
# 删除
DELETE /heima

三、文档操作

3.1 新增文档

一个例子:

bash 复制代码
# 插入一个文档
POST /heima/_doc/1
{
  "info":"黑马程序员Java讲师",
  "email":"zy@itcast.cn",
  "name":{
    "firstName":"云",
    "lastName":"赵"
  }
}

3.2 查询、删除文档




3.3 修改文档

3.3.1 方式一、全量修改

注意:改方式如果文档id没有对应的就文档,则相当于执行"新增"操作。

例如:

bash 复制代码
PUT /heima/_doc/1
{
  "info":"黑马程序员Java讲师",
  "email":"ZhaoYun@itcast.cn",
  "name":{
    "firstName":"云",
    "lastName":"赵"
  }
}

3.3.2 局部修改(修改指定字段)



四、RestClient

ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。

地址:

https://www.elastic.co/guide/en/elasticsearch/client/index.html

通过一个案例学习。

4.1 导入代码和SQL



注意修改数据库连接。

4.2 分析数据结构

mappging要考虑的问题:

字段名、数据类型、是否参与搜索、是否分词、如果分词分词器是什么。

表结构如下:

新增酒店索引的DSL语句如下:

bash 复制代码
# 酒店的mapping
PUT /hotel
{
  "mapping":{
    "properties":{
      "id":{
        "type":"keyword" # id一般是字符串类型
      },
      "name":{
        "type":"text",
        "analyzer":"ik_max_word"
      },
      "address":{
        "type":"keyword",
        "index":false
      },
      "price":{
        "type":"integer"
      },
      "score":{
        "type":"integer"
      },
      "brand":{
        "type":"keyword"
      },
      "city":{
        "type":"keyword"
      },
      "starName":{
        "type":"keyword"
      },
      "business":{
        "type":"keyword"
      },
      "location":{
        "type":"geo_point"
      },
      "pic":{
        "type":"keyword",
        "index":false
      }
    }
  }
}

另外,需要实现多个字段(包括酒店名称、酒店品牌、酒店星级、酒店商圈等)都能搜索的功能,但是分别创建 索引和分词比较麻烦,ES可以把这些字段的值汇总到一个字段,使用copy_to属性。

bash 复制代码
# 酒店的mapping
PUT /hotel
{
  "mapping":{
    "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"
      },
      "location":{
        "type":"geo_point"
      },
      "pic":{
        "type":"keyword",
        "index":false
      },
      "all":{
        "type":"text",
        "analyzer":"ik_max_word"
      }
    }
  }
}

4.3 初始化JavaRestClient


1.引入es的RestHighLevelClient依赖

2.覆盖springboot默认的es版本

见上图。
3.初始化RestHighLevelClient

java 复制代码
public class HotelIndexTest {
    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://10.38.48.12:9200")));
    }
    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    @Test
    void testInit(){
        System.out.println(client);
    }
}

测试结果:

4.4 RestClient操作索引库

4.4.1 创建索引库

就是写java代码来组装之前写的DSL语句。

java 复制代码
    @Test
    void createHotelIndex() throws IOException {
        //1.创建Request对象
        CreateIndexRequest request=new CreateIndexRequest("hotel");
        //2.准备请求的参数,DSL语句
        request.source(MAPPING_TEMPLATE, XContentType.JSON);
        //3.发送请求
        client.indices().create(request, RequestOptions.DEFAULT);
    }

注意上图中的MAPPING_TEMPLATEZ字段,它是另一个类的静态字段。

java 复制代码
public class HotelConstants {
    public static final String MAPPING_TEMPLATE="{\n" +
            "  \"mapping\":{\n" +
            "    \"properties\":{\n" +
            "      \"id\":{\n" +
            "        \"type\":\"keyword\"\n" +
            "      },\n" +
            "      \"name\":{\n" +
            "        \"type\":\"text\",\n" +
            "        \"analyzer\":\"ik_max_word\",\n" +
            "        \"copy_to\":\"all\"\n" +
            "      },\n" +
            "      \"address\":{\n" +
            "        \"type\":\"keyword\",\n" +
            "        \"index\":false\n" +
            "      },\n" +
            "      \"price\":{\n" +
            "        \"type\":\"integer\"\n" +
            "      },\n" +
            "      \"score\":{\n" +
            "        \"type\":\"integer\"\n" +
            "      },\n" +
            "      \"brand\":{\n" +
            "        \"type\":\"keyword\",\n" +
            "        \"copy_to\":\"all\"\n" +
            "      },\n" +
            "      \"city\":{\n" +
            "        \"type\":\"keyword\"\n" +
            "      },\n" +
            "      \"starName\":{\n" +
            "        \"type\":\"keyword\"\n" +
            "      },\n" +
            "      \"business\":{\n" +
            "        \"type\":\"keyword\",\n" +
            "        \"copy_to\":\"all\"\n" +
            "      },\n" +
            "      \"location\":{\n" +
            "        \"type\":\"geo_point\"\n" +
            "      },\n" +
            "      \"pic\":{\n" +
            "        \"type\":\"keyword\",\n" +
            "        \"index\":false\n" +
            "      },\n" +
            "      \"all\":{\n" +
            "        \"type\":\"text\",\n" +
            "        \"analyzer\":\"ik_max_word\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
}


4.4.2 删除和判断索引


注意区分删除和判断是否存在于创建的区别。

java 复制代码
    @Test
    void deleteHotelIndex() throws IOException {
        //1.创建Request对象
        DeleteIndexRequest request=new DeleteIndexRequest("hotel");
        //2.发送请求
        client.indices().delete(request, RequestOptions.DEFAULT);
    }

    @Test
    void existsHotelIndex() throws IOException {
        //1.创建Request对象
        GetIndexRequest request=new GetIndexRequest("hotel");
        //2.发送请求
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        //3.输出
        System.out.println(exists?"索引库已经存在":"索引库不存在");
    }


4.5 RestClient操作文档

4.5.1 初始化JavaRestClient


4.5.2 添加酒店数据到数据库


还需要注意一个问题,前面在elastic中创建Hotel索引库时,里面的字段和MySQL数据库中表的字段不完全一致,longitude和latitude合成了location。为了方便,可以再建议一个HotelDoc.java来进行转换:


java 复制代码
@SpringBootTest
public class HotelDocumentTest {
    @Autowired
    private IHotelService  hotelService;

    private RestHighLevelClient client;
    @BeforeEach
    void setUp() {
        this.client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://10.38.48.12:9200")));
    }
    @AfterEach
    void tearDown() throws IOException {
        this.client.close();
    }

    @Test
    void addDocument() throws IOException {
        //根据id查询酒店数据
        Hotel hotel = hotelService.getById(61083L);
        //转为文档类型
        HotelDoc hotelDoc = new HotelDoc(hotel);
        //1.创建Request对象
        IndexRequest request=new IndexRequest("hotel").id(hotel.getId().toString());
        //2.准备json文档
        request.source(JSON.toJSONString(hotelDoc),XContentType.JSON);
        //3.发送请求
        client.index(request,RequestOptions.DEFAULT);
    }
}


4.5.3 根据ID查询酒店文档

注意,如上图,从elastic中查出的数据除了包括原始值"_source"字段的值,还有其他一些字段。需要使用getSourceAsString()方法获取数据。

java 复制代码
@Test
    void getDocumentById() throws IOException {

        //1.创建Request对象
        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);
    }

4.5.4 根据id修改酒店数据

全量更新和新增代码一样。

局部更新代码:

java 复制代码
    @Test
    void updateDocumentById() throws IOException {

        //1.创建Request对象
        UpdateRequest request=new UpdateRequest("hotel","61083");
        //2.准备请求参数
        request.doc(
                "price","952",
                "starName","四钻"
        );
        //3.发送请求
        client.update(request,RequestOptions.DEFAULT);
    }


4.5.5 根据id删除酒店数据


java 复制代码
    @Test
    void deleteDocumentById() throws IOException {

        //1.创建Request对象
        DeleteRequest request=new DeleteRequest("hotel","61083");
        //2.发送请求
        client.delete(request,RequestOptions.DEFAULT);
    }


4.5.6 批量导入文档

一条一条记录新增效率很慢,需要批量导入。

java 复制代码
@Test
    void testBulkRequest() throws IOException {
        //批量查询酒店数据
        List<Hotel> hotels = hotelService.list();

        //1.创建Request对象
        BulkRequest request=new BulkRequest();
        for (Hotel hotel : hotels) {
            //转化为文档类型
            HotelDoc hotelDoc = new HotelDoc(hotel);
            //2.准备参数,添加多个新增的Request
            request.add(new IndexRequest("hotel")
                    .id(hotel.getId().toString())
                    .source(JSON.toJSONString(hotelDoc),XContentType.JSON));
        }
        // 3.发送请求
        client.bulk(request,RequestOptions.DEFAULT);
    }


五、DSL查询和RestClient进阶

相关推荐
Elastic 中国社区官方博客1 小时前
Elasticsearch 中的文档级基于属性的访问控制 - ABAC
大数据·数据库·elasticsearch·搜索引擎·全文检索
IT机器猫2 小时前
ES基础一
大数据·elasticsearch·搜索引擎
Wang's Blog2 小时前
Elastic Stack梳理: 聚合分析核心技术深度解析与最佳实践
elasticsearch·搜索引擎·es·elastic search
Elastic 中国社区官方博客2 小时前
EDB EPAS 通过 PostgreSQL 连接器同步数据到 Elasticsearch
大数据·数据库·人工智能·elasticsearch·搜索引擎·postgresql·全文检索
摇滚侠3 小时前
2025最新 SpringCloud 教程,Gateway-断言-Query,笔记56
笔记·spring cloud·gateway
pp-周子晗(努力赶上课程进度版)4 小时前
Docker入门学习笔记
spring cloud·docker·容器
武子康5 小时前
大数据-174 Elasticsearch 查询 DSL 实战:match/match_phrase/query_string/multi_match 全解析
大数据·后端·elasticsearch
摇滚侠5 小时前
2025最新 SpringCloud 教程,Gateway-断言-长短写法,笔记55
笔记·spring cloud·gateway
jiayong236 小时前
Elasticsearch 核心概念详解:Index、Document、Field
大数据·elasticsearch·jenkins