侧边栏壁纸
博主头像
LYMTICS

海纳百川,有容乃大

  • 累计撰写 45 篇文章
  • 累计创建 37 个标签
  • 累计收到 19 条评论

目 录CONTENT

文章目录

ElasticSearch(1)入门:安装与基本操作

LYMTICS
2022-04-12 / 0 评论 / 1 点赞 / 116 阅读 / 11,637 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-04-12,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

ElasticSearch(1)入门:安装与基本操作

介绍

倒排索引

倒排索引的概念是基于MySQL这样的正向索引而言的。

创建倒排索引是对正向索引的一种特殊处理,流程如下:

  • 将每一个文档的数据利用算法分词,得到一个个词条
  • 创建表,每行数据包括词条、词条所在文档id、位置等信息
  • 因为词条唯一性,可以给词条创建索引,例如hash表结构索引

pximage

倒排索引中有两个非常重要的概念:

  • 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息
  • 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我是中国人,就可以分为:我、是、中国人、中国、国人这样的几个词条

倒排索引的搜索流程如下(以搜索"华为手机"为例):

1)用户输入条件"华为手机"进行搜索。

2)对用户输入内容分词,得到词条:华为手机

3)拿着词条在倒排索引中查找,可以得到包含词条的文档id:1、2、3。

4)拿着文档id到正向索引中查找具体文档。

pximage

ElasticSearch

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

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

pximage

而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。

elasticsearch底层是基于lucene来实现的。

Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。官网地址:https://lucene.apache.org/

与MySQL对比

MySQL Elasticsearch 说明
Table Index 索引(index),就是文档的集合,类似数据库的表(table)
Row Document 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
Column Field 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)
Schema Mapping Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)
SQL DSL DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD

是不是说,我们学习了elasticsearch就不再需要mysql了呢?

并不是如此,两者各自有自己的擅长支出:

  • Mysql:擅长事务类型操作,可以确保数据的安全和一致性

  • Elasticsearch:擅长海量数据的搜索、分析、计算

因此在企业中,往往是两者结合使用:

  • 对安全性要求较高的写操作,使用mysql实现
  • 对查询性能要求较高的搜索需求,使用elasticsearch实现
  • 两者再基于某种方式,实现数据的同步,保证一致性

安装

部署单点ES

Install Guide

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

docker network create es-net

拉取 elasticSearch 镜像

docker pull docker.elastic.co/elasticsearch/elasticsearch:8.1.2

运行

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:8.1.2

命令解释:

  • -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:端口映射配置

修改一下配置文件,不然主机可能无法访问:

参考:Empty reply from server

docker exec -it es /bin/bash

vi /conf/elasticsearch.yml

访问虚拟机的9200端口,即可看到如下内容:

{
  "name" : "0e3b26f6bd8d",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "--7l3qiNT2SgSZ3zeCpuAQ",
  "version" : {
    "number" : "8.1.2",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "31df9689e80bad366ac20176aa7f2371ea5eb4c1",
    "build_date" : "2022-03-29T21:18:59.991429448Z",
    "build_snapshot" : false,
    "lucene_version" : "9.0.0",
    "minimum_wire_compatibility_version" : "7.17.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "You Know, for Search"
}

部署Kibana

docker pull docker.elastic.co/kibana/kibana:8.1.2
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601  \
kibana:8.1.2
  • --network es-net :加入一个名为es-net的网络中,与elasticsearch在同一个网络中
  • -e ELASTICSEARCH_HOSTS=http://es:9200":设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch
  • -p 5601:5601:端口映射配置

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

docker logs -f kibana

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

pximage

访问虚拟机的5601端口,即可看到如下内容:

pximage

可以在左侧菜单找到DevTools进行编码

安装IK分词器

pximage

如图所示,默认的分词器对中文的效果不理想,我们需要安装中文分词器

查看数据卷位置:

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"
    }
]

上传解压缩后的ik分词器到该目录(下载地址:Releases

然后重启容器

docker restart es

测试:

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

pximage

字典扩展与禁用

扩展一些词汇,比如奥里给、白嫖等等

修改配置文件 ik/config/IKAnalyzer.cfg.xml

(假设你已经在该文件夹下放置一个字典xxx.dic)

pximage

索引库操作

索引库就类似数据库表,mapping映射就类似表的结构。

我们要向es中存储数据,必须先创建“库”和“表”。

mapping映射属性

pximage

pximage

索引库的CRUD

创建索引库:

PUT /heima
{
  "mappings": {
    "properties": {
      "info":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "email":{
        "type": "keyword",
        "index": "falsae"
      },
      "name":{
        "properties": {
          "firstName": {
            "type": "keyword"
          }
        }
      },
      // ... 略
    }
  }
}

查询索引库:

GET /索引库名

修改索引库:

倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping

但是可以添加新的字段:

PUT /索引库名/_mapping
{
  "properties": {
    "新字段名":{
      "type": "integer"
    }
  }
}

删除索引库:

DELETE /索引库名

文档操作

新增文档

语法:

POST /索引库名/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子属性1": "值3",
        "子属性2": "值4"
    },
    // ...
}

示例:

POST /heima/_doc/1
{
    "info": "黑马程序员Java讲师",
    "email": "zy@itcast.cn",
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

响应:

pximage

查询文档

根据rest风格,新增是post,查询应该是get,不过查询一般都需要条件,这里我们把文档id带上。

语法:

GET /{索引库名称}/_doc/{id}

通过kibana查看数据:

GET /heima/_doc/1

删除文档

删除使用DELETE请求,同样,需要根据id进行删除:

语法:

DELETE /{索引库名}/_doc/id值

示例:

# 根据id删除数据
DELETE /heima/_doc/1

修改文档

全量修改:直接覆盖原来的文档

语法:

PUT /{索引库名}/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    // ... 略
}

示例:

PUT /heima/_doc/1
{
    "info": "黑马程序员高级Java讲师",
    "email": "zy@itcast.cn",
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

增量修改:只修改指定id匹配的文档中的部分字段

语法:

POST /{索引库名}/_update/文档id
{
    "doc": {
         "字段名": "新的值",
    }
}

示例:

POST /heima/_update/1
{
  "doc": {
    "email": "ZhaoYun@itcast.cn"
  }
}

RestClient

根据一个酒店项目进行学习

导入项目

SQL 创建语句(数据略):

CREATE TABLE `tb_hotel` (
  `id` bigint(20) NOT NULL COMMENT '酒店id',
  `name` varchar(255) NOT NULL COMMENT '酒店名称;例:7天酒店',
  `address` varchar(255) NOT NULL COMMENT '酒店地址;例:航头路',
  `price` int(10) NOT NULL COMMENT '酒店价格;例:329',
  `score` int(2) NOT NULL COMMENT '酒店评分;例:45,就是4.5分',
  `brand` varchar(32) NOT NULL COMMENT '酒店品牌;例:如家',
  `city` varchar(32) NOT NULL COMMENT '所在城市;例:上海',
  `star_name` varchar(16) DEFAULT NULL COMMENT '酒店星级,从低到高分别是:1星到5星,1钻到5钻',
  `business` varchar(255) DEFAULT NULL COMMENT '商圈;例:虹桥',
  `latitude` varchar(32) NOT NULL COMMENT '纬度;例:31.2497',
  `longitude` varchar(32) NOT NULL COMMENT '经度;例:120.3925',
  `pic` varchar(255) DEFAULT NULL COMMENT '酒店图片;例:/img/1.jpg',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

创建索引库:

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",
        "copy_to": "all"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

说明:

  • location:地理坐标,里面包含精度、纬度
    pximage
  • all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索(all 只是一个名称,叫别的也行)

导入Maven坐标:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

注意到SpringBoot父工程的版本可能不正确,所以要覆盖一下:

<properties>
    <elasticsearch.version>7.12.1</elasticsearch.version>
</properties>

索引操作

pximage

创建一个测试类:

@SpringBootTest
class HotelIndexTest {

    private RestHighLevelClient client;

    @BeforeEach
    void setUp() {
        client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.137.112:9200")
        ));
    }

    @AfterEach
    void tearDown() throws IOException {
        client.close();
    }
}

测试添加索引库:

@Test
void testCreateIndex() throws IOException {
    // 1.准备Request      PUT /hotel
    CreateIndexRequest request = new CreateIndexRequest("hotel");
    // 2.准备请求参数
    request.source(MAPPING_TEMPLATE, XContentType.JSON);
    // 3.发送请求
    client.indices().create(request, RequestOptions.DEFAULT);
}

测试删除索引库:

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

测试是否存在索引:

@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 ? "存在" : "不存在");
}

文档操作

pximage

创建一个测试类:

@SpringBootTest
class HotelDocumentTest {

    private RestHighLevelClient client;

    @Autowired
    private IHotelService hotelService;

    @BeforeEach
    void setUp() {
        client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://192.168.137.112:9200")
        ));
    }

    @AfterEach
    void tearDown() throws IOException {
        client.close();
    }
}

新增文档:

@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);
}

说明:

如果出现如下错误:

pximage

这是由于版本不一致导致的,参考:parsing - Elasticsearch java RestHighLevelClient “Unable to parse response body” IllegalArgumentException

删除文档:

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

查询文档:

@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);
}

修改文档:

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

批量导入数据:

@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);
}

通过 GET /hotel/_search 查询结果

参考

史上最全面的springcloud微服务技术栈课程 讲师:虎翼老师

(这个老师PPT做的实在是太棒了)

1

评论区