Elasticsearch(高性能分布式搜索引擎)-上篇

发布于:2024-08-08 ⋅ 阅读:(92) ⋅ 点赞:(0)

Elasticsearch(高性能分布式搜索引擎)


黑马商城作为一个电商项目,商品的搜索肯定是访问频率最高的页面之一。目前搜索功能是基于数据库的模糊搜索来实现的,存在很多问题。

首先,查询效率较低。

由于数据库模糊查询不走索引,在数据量较大的时候,查询性能很差。黑马商城的商品表中仅仅有不到9万条数据,基于数据库查询时,搜索接口的表现如图:

在这里插入图片描述

改为基于搜索引擎后,查询表现如下:

需要注意的是,数据库模糊查询随着表数据量的增多,查询性能的下降会非常明显,而搜索引擎的性能则不会随着数据增多而下降太多。目前仅10万不到的数据量差距就如此明显,如果数据量达到百万、千万、甚至上亿级别,这个性能差距会非常夸张。

其次,功能单一

数据库的模糊搜索功能单一,匹配条件非常苛刻,必须恰好包含用户搜索的关键字。而在搜索引擎中,用户输入出现个别错字,或者用拼音搜索、同义词搜索都能正确匹配到数据。

综上,在面临海量数据的搜索 ,或者有一些复杂搜索需求的时候,推荐使用专门的搜索引擎来实现搜索功能。

目前全球的搜索引擎技术排名如下:

在这里插入图片描述

1 初识elasticsearch

1.1 认识和安装

elasticsearch结合kibana、Logstash、Beats,是一整套技术栈,被叫做ELK。被广泛应用在日志数据分析、实时监控等领域。

1.2 倒排索引

传统数据库(如MySQL)采用正向索引,例如给下表(tb goods)中的id创建索引:

在这里插入图片描述

elasticsearch采用倒排索引:

  • 文档(document):每条数据就是一个文档
  • 词条(term):文档按照语义分成的词语

在这里插入图片描述

1.3 IK分词器

中文分词往往需要根据语义分析,比较复杂,这就需要用到中文分词器,例如IK分词器。IK分词器是林良益在2006年开源发布的,其采用的正向迭代最细粒度切分算法一直沿用至今。

在Kibana的DevTools中可以使用下面的语法来测试IK分词器:

语法说明:

  • POST:请求方式
  • /_analyze:请求路径,这里省略了http://虚拟机ip:9200 有kibana帮我们补充
  • 请求参数,json风格:
    • analyzer:分词器类型,这里默认是standard分词器
    • text:要分词的内容

在这里插入图片描述

1.4 基础概念

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

在这里插入图片描述

索引(index): 相同类型的文档的集合,可以称为数据库

映射(mapping): 索引中文档的字段约束信息,类似表的结构约束

在这里插入图片描述

1.4.1 elasticsearch与数据库对比

在这里插入图片描述

2 索引库的操作

2.1 Mapping映射属性

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

  • type:字段数据类型,常见的简单类型有:
    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
    • 数值:long、integer、short、byte、double、float
    • 布尔:boolean
    • 日期:date
    • 对象:object
  • index:是否创建索引,默认为true(如果字段不参与索引、不参与排序,则可以不用索引)
  • analyzer:使用哪种分词器(一般只有可分词的文本才需要分词器)
  • properties:该字段的子字段

例如下面的json文档:

{
    "age": 21,
    "weight": 52.1,
    "isMarried": false,
    "info": "黑马程序员Java讲师",
    "email": "zy@itcast.cn",
    "score": [99.1, 99.5, 98.9],
    "name": {
        "firstName": "云",
        "lastName": "赵"
    }
}

对应的每个字段映射(Mapping):

字段名 字段类型 类型说明 是否 参与搜索 是否参与分词 分词器
age integer 整数 ——
weight float 浮点数 ——
isMarried boolean 布尔 ——
info text 字符串,但需要分词 IK
email keyword 字符串,但是不分词 ——
score float 只看数组中元素类型 ——
firstName keyword 字符串,但是不分词 ——
lastName keyword 字符串,但是不分词 ——

name是个object类型,有两个properties,每个子字段都需要单独指定类型

2.2 索引库操作

Elasticsearch提供的所有API都是Restful的接口,遵循Restful的基本规范:

在这里插入图片描述

创建索引库和mapping的请求语法如下:

在这里插入图片描述
在这里插入图片描述

#创建索引库并设置mapping映射
PUT /heima
{
  "mappings": {
    "properties": {
      "info":{
        "type": "text",
        "analyzer": "ik_smart",
        "index": true
      },
      "age":{
        "type": "byte"
      },
      "email":{
        "type": "keyword",
        "index": false
      },
      "name":{
        "type": "object",
        "properties": {
          "firstName": {
            "type": "keyword"
          },
          "lastName":{
            "type": "keyword"
          }
        }
      }
    }
  }
}

#查询索引库
GET /heima

#删除索引库
DELETE /heima

索引库和mapping一旦创建无法修改,但是可以添加新的字段,语法如下:

在这里插入图片描述
在这里插入图片描述

3 文档操作

3.1 文档CRUD

新增文档的请求格式如下:

在这里插入图片描述

查看文档请求格式:

删除索引库的请求格式:

# 新增文档
POST /heima/_doc/1
{
  "info": "黑马程序员Java讲师",
  "age": 35,
  "email": "zy@itcast.cn",
  "name": {
    "first": "云",
    "lastname": "赵"
  }
}

# 查询文档
GET /heima/_doc/1

# 删除文档
DELETE /heima/_doc/1

修改索引库:

  • 方式一:全量修改,会删除旧文档,添加新文档

    # 全量修改
    PUT /heima/_doc/1
    {
        "info": "黑马程序员JAVA讲师",
      "age": 40,
      "email": "ZY@itcast.cn",
      "name": {
        "first": "云",
        "lastname": "赵"
      }
    }
    

    如果修改的id不存在,则会直接创建一个此id的新文档

  • 方式二:增量修改,修改指定字段值

    # 增量修改
    POST /heima/_update/1
    {
      "doc": {
        "email": "ZhaoYun@itcast.cn"
      }
    }
    

3.2 批量处理

Elasticsearch中允许通过一次请求中携带多次文档操作,也就是批量处理,语法格式如下:

# 批量新增
POST /_bulk
{"index":{"_index":"heima","_id":"3"}}
{"info":"黑马程序员C++讲师","email":"ww@itcast.cn","name":{"firstName":"五","lastName":"王"}}
{"index":{"_index":"heima","_id":"4"}}
{"info":"黑马程序员前端讲师","email":"zhangsan@itcast.cn","name":{"firstName":"三","lastName":"张"}}

# 批量删除
POST /_bulk
{"delete":{"_index":"heima","_id":"3"}}
{"delete":{"_index":"heima","_id":"4"}}

4 JavaRestClient

4.1 客户端初始化

Elasticsearch目前最新版本是8.0,其]ava客户端有很大变化。不过大多数企业使用的还是8以下版本,所以我们选择使用早期的)avaRestClient客户端来学习

>

  1. 引入es的RestHighLevelClient依赖:

    D

  2. 因为SpringBoot默认的ES版本是7.17.0,所以需要覆盖默认的ES版本:

  3. 初始化RestHighLevelClient:

在这里插入图片描述

4.2 商品表Mapping映射

要实现商品搜索,那么索引库的字段肯定要满足页面搜索的需求:

在这里插入图片描述

#商品索引库
PUT /hmall
{
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "price":{
        "type": "integer"
      },
      "image":{
        "type": "keyword",
        "index":false
      },
      "category":{
        "type":"keyword"
      },
      "brand":{
        "type": "keyword"
      },
      "sold":{
        "type": "integer"
      },
      "commentCount":{
        "type": "integer",
        "index": false
      },
      "isAD":{
        "type": "boolean"
      },
      "updateTime":{
        "type": "date"
      }
    }
  }
}

4.3 索引库操作

创建索引库 的Java与Restful接口API对比:

删除索引库:

查询索引库信息:

public class ElasticTest {

    private RestHighLevelClient client;

    @Test
    void testConnection(){
        System.out.println("client=" + client);
    }

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

    @Test
    void testGetIndex() throws IOException {
        //1. 准备Request对象
        GetIndexRequest request = new GetIndexRequest("items");
        //2. 发送请求
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);
    }

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

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

    @AfterEach
    void tearDown() throws IOException {
        if(client != null){
            client.close();
        }
    }

    private static final String MAPPING_TEMPLATE = "{\n" +
            "  \"mappings\": {\n" +
            "    \"properties\": {\n" +
            "      \"id\": {\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"name\": {\n" +
            "        \"type\": \"text\",\n" +
            "        \"analyzer\": \"ik_smart\"\n" +
            "      },\n" +
            "      \"price\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"image\":{\n" +
            "        \"type\": \"keyword\",\n" +
            "        \"index\":false\n" +
            "      },\n" +
            "      \"category\":{\n" +
            "        \"type\":\"keyword\"\n" +
            "      },\n" +
            "      \"brand\":{\n" +
            "        \"type\": \"keyword\"\n" +
            "      },\n" +
            "      \"sold\":{\n" +
            "        \"type\": \"integer\"\n" +
            "      },\n" +
            "      \"commentCount\":{\n" +
            "        \"type\": \"integer\",\n" +
            "        \"index\": false\n" +
            "      },\n" +
            "      \"isAD\":{\n" +
            "        \"type\": \"boolean\"\n" +
            "      },\n" +
            "      \"updateTime\":{\n" +
            "        \"type\": \"date\"\n" +
            "      }\n" +
            "    }\n" +
            "  }\n" +
            "}";
}

在这里插入图片描述

4.4 文档操作

新增文档 的javaAPI如下:

删除文档 的JavaAPI如下:

查询文档 包含查询和解析响应结果两部分,对应的JavaAPI如下:

**文档操作: **

修改文档那个数据有两种方式:

  • 方式一:全量更新,再次写入id一样的文档,就会删除旧文档,添加新文档。与新增的JavaAPI一致
  • 方式二:局部更新。只更新指定部分字段

@SpringBootTest(properties = "spring.profiles.active=local")
public class ElasticDocumentTest {

    private RestHighLevelClient client;

    @Autowired
    private IItemService iItemService;

    @Test
    void testIndexDoc() throws IOException {
        //0. 准备文档数据
        //0.1 根据id查询数据库数据
        Item item = iItemService.getById(100000011127L);
        //0.2 把数据库数据转为文档数据
        ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);

        //设置此项,则会修改索引库中对应文档的数据,相当于修改操作
        itemDoc.setPrice(29900);

        //1. 准备Request
        IndexRequest request = new IndexRequest("items").id(itemDoc.getId());
        //2. 准备请求参数
        request.source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON);
        //3. 发送请求
        client.index(request, RequestOptions.DEFAULT);

    }

    @Test
    void testUpdateDoc() throws IOException{
        //1. 准备Request
        UpdateRequest request = new UpdateRequest("items", "100000011127");
        //2. 准备请求的参数
        request.doc(
                "price",25600,
                "sold",45000
        );
        //2. 发送请求
        client.update(request,RequestOptions.DEFAULT);
    }

    @Test
    void testGetDoc() throws IOException {
        //1. 准备Request
        GetRequest request = new GetRequest("items","100000011127");
        //2. 发送请求
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        //3. 解析响应结果
        String json = response.getSourceAsString();
        ItemDoc doc = JSONUtil.toBean(json, ItemDoc.class);
        System.out.println("doc = " + doc);
    }

    @Test
    void testDeleteDoc() throws IOException {
        //1. 准备Request
        DeleteRequest request = new DeleteRequest("items","100000011127");
        //2. 发送请求
        client.delete(request, RequestOptions.DEFAULT);
    }

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

    @AfterEach
    void tearDown() throws IOException {
        if (client != null) {
            client.close();
        }
    }

}

4.5 批处理

批处理代码流程与之前类似,只不过构建请求会用到一个名为BulkRequest来封装普通的CRUD请求:

批处理的API实例:

    @Test
    void testBulkDoc() throws IOException {
        int pageNo = 1, pageSize = 500;
        //1. 准备文档数据
        Page<Item> page = iItemService.lambdaQuery()
                .eq(Item::getStatus, 1)
                .page(Page.of(pageNo, pageSize));
        List<Item> records = page.getRecords();
        if(records == null || records.isEmpty()){
            return;
        }
        //1. 准备Request
        BulkRequest request = new BulkRequest();
        //2. 准备请求参数
        for (Item item : records) {
            request.add(new IndexRequest("items")
                    .id(item.getId().toString())
                    .source(JSONUtil.toJsonStr(BeanUtil.copyProperties(item,ItemDoc.class)),XContentType.JSON));
        }
//        request.add(new DeleteRequest("items").id("1"));
        //3. 发送请求
        client.bulk(request, RequestOptions.DEFAULT);
    }

网站公告

今日签到

点亮在社区的每一天
去签到