SpringBoot集成Elasticsearch

什么是搜索?

  • 百度、Google:我们想寻找一个我们喜欢的电影或者书籍就会去百度或者Google搜索一下。

  • 互联网搜索:电商搜索商品,招聘网站搜索简历或者岗位

  • IT系统的搜索:员工管理搜索,会议管理搜索

用Mysql数据库做搜索会怎么样?

  • 如果表记录上千万上亿了,会有性能问题,另外一个如果有一个本文字段要在里面模糊配置,这个就会出现严重的性能问题;

  • 还不能将搜索词拆分开来,比如有个字段值“张三”,“张三丰”只能搜索以“张三”开头才有结果,如果想用“张小三”那是搜索不出来任何数据的。

总体来说,用数据库来实现搜索,是不太靠谱的,通常性能也会很差。

什么是全文检索、倒排索引和Lucene

举个简单的例子:比如最近上映的热剧(碟中谍6:全面瓦解),我们想搜索一下全面瓦解这个电视剧,可是在输入的过程,不小心输入了”全瓦解”,我们看看百度这个返回了什么,百度返回的结果确实是我想要找到的内容。

image

倒排索引就是将数据中的词拆分构建一个大表,将关键字拆出来,后面带上这个文章的documentid号,例如下图中间这个就是倒排索引了。

全文检索就比较好理解的,就是当我们输入“全瓦解”,会被拆分成”全”,“瓦解”2个此,用2个词去倒排索引里面去检索数据,检索到的数据返回。整个过程就叫做全文检索。
image

如果这个用数据库的思维来做的话,假如一共100W的记录,按照之前的思路就是扫描100W次,而且每次扫描,都需要匹配那个文本所有的字符,确认是否包含搜索的关键词,而且还不能将搜索词拆解来进行检索。

如果是利用倒排索引的话,假设还是100W,拆分出来的词语,假设有1000W个词语,那么在倒排索引中,就有1000W行。我们可能不需要检索1000W词,有可能检索1次,就能找到我们需要的数据,也有可能是100W次,也有可能是1000W次。

lucene就是一个jar包,里面包含了封装好的各种建立倒排索引,以及进行搜索的代码,包括各种算法。

ElasticSearch是什么?

Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java语言开发的,并作为Apache许可条款下的开放源码发布,是一种流行的企业级搜索引擎。

Lucene是单机的模式,如果你的数据量超过了一台物理机的容量,你需要扩容,将数据拆分成2份放在不同的集群,这个就是典型的分布式计算了。需要拷贝容错,机器宕机,数据一致性等复杂的场景,这个实现就比较复杂了。

ElasticSearch解决了哪些问题?

  • 自动维护数据的分布到多个节点的索引的建立,还有搜索请求分布到多个节点的执行

  • 自动维护数据的冗余副本,保证了一旦机器宕机,不会丢失数据

  • 封装了更多高级的功能,例如聚合分析的功能,基于地理位置的搜索

image

ElasticSearch基本概念

Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档。我们将 Elasticsearch 里存储文档数据和关系型数据库 MySQL 存储数据的概念进行一个类比如下图

Elasticsearch Mysql
Index(索引) Database(数据库)
Types(类型) Table(表)
Documents(文档) Row(行)
Fileds(字段) Column(列)

ElasticSearch有哪些功能?

1、分布式的搜索引擎和数据分析引擎

  • 搜索:网站的站内搜索,IT系统的检索
  • 数据分析:电商网站,统计销售排名前10的商家

2、全文检索,结构化检索,数据分析

  • 全文检索:我想搜索商品名称包含某个关键字的商品
  • 结构化检索:我想搜索商品分类为日化用品的商品都有哪些
  • 数据分析:我们分析每一个商品分类下有多少个商品

3、对海量数据进行近实时的处理

  • 分布式:ES自动可以将海量数据分散到多台服务器上去存储和检索
  • 海联数据的处理:分布式以后,就可以采用大量的服务器去存储和检索数据,自然而然就可以实现海量数据的处理了
  • 近实时:检索数据要花费1小时(这就不要近实时,离线批处理,batch-processing);在秒级别对数据进行搜索和分析

ElasticSearch的特点

  • 可以作为一个大型分布式集群(数百台服务器)技术,处理PB级数据,服务大公司;也可以运行在单机上,服务小公司

  • Elasticsearch不是什么新技术,主要是将全文检索、数据分析以及分布式技术,合并在了一起

  • 对用户而言,是开箱即用的,非常简单,作为中小型的应用,直接3分钟部署一下ES

  • Elasticsearch作为传统数据库的一个补充,比如全文检索,同义词处理,相关度排名,复杂数据分析,海量数据的近实时处理;

ElasticSearch的应用场景

  • 维基百科
  • The Guardian(国外新闻网站)
  • Stack Overflow(国外的程序异常讨论论坛)
  • GitHub(开源代码管理)
  • 电商网站
  • 日志数据分析
  • 商品价格监控网站
  • BI系统
  • 站内搜索

SpringBoot集成ES步骤

依赖引入

<!-- ElasticSearch依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

配置文件

es:
  port: 9200
  servers: localhost

配置Config类

此处为单机配置,集群模式再次基础上修改也行

@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {

    @Value("${es.clusterName}")
    private String clusterName;
    @Value("${es.servers}")
    private String servers;
    @Value("${es.port}")
    private int port;

    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {

        final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo(servers + ":" + port)
                .build();
        return RestClients.create(clientConfiguration).rest();
    }
}

定义数据类型

定义数据类型,类似于mysql的表,定义好字段,@Field()可以定义字段类型以及分词等。

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
//@Document(indexName = "es_architecture")
public class ArchitectureDto {

    @ApiModelProperty(value = "id", required = true)
    @Id
    private String id;

    @ApiModelProperty(value = "建筑名称", required = true)
    @Field(type = FieldType.Text,analyzer = "ik-max-word")
    private String name;

    @ApiModelProperty(value = "所在省份", required = true)
    @Field(type = FieldType.Text)
    private String province;

    @ApiModelProperty(value = "所在城市", required = true)
    @Field(type = FieldType.Text)
    private String city;

    @ApiModelProperty(value = "所在区", required = true)
    @Field(type = FieldType.Text)
    private String area;

    @ApiModelProperty(value = "详细街道地址", required = true)
    @Field(type = FieldType.Text)
    private String address;

    @ApiModelProperty(value = "经纬度", required = true)
    private LocationPo location;

    @ApiModelProperty(value = "描述", required = true)
    @Field(type = FieldType.Text)
    private String description;

    @ApiModelProperty(value = "评分", required = true)
    @Field(type = FieldType.Double)
    private double score;

    @ApiModelProperty(value = "门票价格", required = true)
    @Field(type = FieldType.Double)
    private double price;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LocationPo {

    @ApiModelProperty(value = "经度", required = true)
    private double lon;

    @ApiModelProperty(value = "纬度", required = true)
    private double lat;
}

增删改查代码

通过restHighLevelClient对象对ElasticSearch数据库进行操作,restHighLevelClient由springboot容器创建管理,用户不需要进行配置,使用的时候注入即可,本次使用的是测试类代码编写方式。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = EsApplication.class)
public class EsHandTest {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    // 测试文档的添加
    @Test
    public void testCreateDoc() throws IOException {
        // CreateDoc5()创建实体方法,可自己实现
        ArchitectureDto architectureDto = DocDemo.CreateDoc5();
        // 创建好index请求
        IndexRequest indexRequest = new IndexRequest("architecture_index");
        // 设置索引
        indexRequest.id("5");
        // 设置超时时间(默认)
        indexRequest.timeout(TimeValue.timeValueSeconds(5));
        // 往请求中添加数据
        indexRequest.source(JSON.toJSONString(architectureDto), XContentType.JSON);
        //执行添加请求
        IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        System.out.println(indexResponse);
    }

    @Test
    public void getDoc() throws IOException {
        //获得查询索引的请求对象
        GetRequest gerRequest = new GetRequest("architecture_index").id("2");
        //获得文档对象
        GetResponse doc = restHighLevelClient.get(gerRequest, RequestOptions.DEFAULT);
        //获得文档数据
        System.out.println(doc.getSourceAsString());
    }

    @Test
    public void delDoc() throws IOException {
        //获得删除的索引请求对象
        DeleteRequest delRequest = new DeleteRequest("architecture_index").id("1");
        //删除文档
        DeleteResponse delete = restHighLevelClient.delete(delRequest, RequestOptions.DEFAULT);
        System.out.println(delete.getIndex());
    }

    @Test
    public void delIndex() throws IOException {
        IndicesClient indices = restHighLevelClient.indices();
        DeleteIndexRequest delReq = new DeleteIndexRequest("architecture_index");
        AcknowledgedResponse delete = indices.delete(delReq, RequestOptions.DEFAULT);
        System.out.println(delete.isAcknowledged());
    }

    @Test
    public void contextLoads() throws IOException {
        //查询mysql中所有数据
        List<ArchitectureDto> architectures = new ArrayList<>();
        //创建批量处理对象
        BulkRequest bulkRequest = new BulkRequest();
        //循环添加新增处理请求
        for (ArchitectureDto architecture : architectures) {
            String architecturJson = JSON.toJSONString(architecture);
            IndexRequest indexRequest = new IndexRequest("architecture_index").id(architecture.getId() + "").source(architecturJson, XContentType.JSON);
            bulkRequest.add(indexRequest);
        }
        //提交批量处理对象
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        //查看添加状态
        System.out.println(bulk.status());
    }
}

高级查询代码

通过restHighLevelClient对象对ElasticSearch数据库进行高级查询操作

@RunWith(SpringRunner.class)
@SpringBootTest(classes = EsApplication.class)
public class EsSearchTest {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * 查询条件数据    匹配查询不到将字段类似设置为.keyword
     * @throws IOException
     */
    @Test
    public void searchAll() throws IOException {
        //定义请求对象
        SearchRequest request = new SearchRequest("architecture_index");
        //制定检索条件
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(QueryBuilders.matchAllQuery());   //查询所有
//        builder.query(QueryBuilders.termQuery("address","huahexi777"));    //非String 类型查询
//        builder.query(QueryBuilders.termQuery("location.lat",33.2));    //非String 类型查询
//        builder.query(QueryBuilders.matchPhraseQuery("area","高新区"));   //精准查询String
//        builder.query(QueryBuilders.matchQuery("area.keyword","高新区"));   // 不能匹配到
//        builder.query(QueryBuilders.termQuery("area.keyword","高新区"));
//        builder.sort("price", SortOrder.DESC);
        request.source(builder);
        //获得文档对象
        SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        //获得文档数据
        for (SearchHit hit : search.getHits().getHits()) {
            ArchitectureDto art = JSONObject.parseObject(hit.getSourceAsString(), ArchitectureDto.class);
            System.out.println(JSON.toJSONString(art));
        }
    }

    /**
     * 类似于数据库的 or 查询
     * @throws IOException
     */
    @Test
    public void searchByBolt() throws IOException {
        //定义请求对象
        SearchRequest request = new SearchRequest("architecture_index");
        //制定检索条件
        SearchSourceBuilder builder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.should(QueryBuilders.matchQuery("price",10));
        boolQueryBuilder.should(QueryBuilders.matchQuery("score",4.6).boost(10));
        builder.query(boolQueryBuilder);    //非String 类型查询
        request.source(builder);
        //获得文档对象
        SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        //获得文档数据
        for (SearchHit hit : search.getHits().getHits()) {
            ArchitectureDto art = JSONObject.parseObject(hit.getSourceAsString(), ArchitectureDto.class);
            System.out.println(JSON.toJSONString(art));
        }
    }

    /**
     * 查询部分字段
     * @throws IOException
     */
    @Test
    public void searchByParam() throws IOException {
        //定义请求对象
        SearchRequest request = new SearchRequest("architecture_index");
        //制定检索条件
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(QueryBuilders.matchAllQuery());   //查询所有
        String[] includes = {"name","address","price"};
        String[] excludes = {};
        /** 会多出一个score字段 默认值都为0 具体原因不详  */
        builder.fetchSource(includes,excludes);
        request.source(builder);
        //获得文档对象
        SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        //获得文档数据
        for (SearchHit hit : search.getHits().getHits()) {
            ArchitectureDto art = JSONObject.parseObject(hit.getSourceAsString(), ArchitectureDto.class);
            System.out.println(JSON.toJSONString(art));
        }
    }

    /**
     * 范围查询 大于小于
     * @throws IOException
     */
    @Test
    public void searchByFilter() throws IOException {
        //定义请求对象
        SearchRequest request = new SearchRequest("architecture_index");
        //制定检索条件
        SearchSourceBuilder builder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //定制查询条件
        boolQueryBuilder.filter (QueryBuilders.rangeQuery("price").gte(10).lte(30));
        builder.query(boolQueryBuilder);    //非String 类型查询
        request.source(builder);

        SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        for (SearchHit hit : search.getHits().getHits()) {
            ArchitectureDto art = JSONObject.parseObject(hit.getSourceAsString(), ArchitectureDto.class);
            System.out.println(JSON.toJSONString(art));
        }
    }

    /**
     * 迷糊查询
     * @throws IOException
     */
    @Test
    public void searchByLike() throws IOException {
        //定义请求对象
        SearchRequest request = new SearchRequest("architecture_index");
        //制定检索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //定制查询条件
        TermQueryBuilder builder = QueryBuilders.termQuery("name.keyword","北京大").fuzziness(Fuzziness.ONE));
        searchSourceBuilder.query(builder);
        request.source(searchSourceBuilder);

        SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        for (SearchHit hit : search.getHits().getHits()) {
            ArchitectureDto art = JSONObject.parseObject(hit.getSourceAsString(), ArchitectureDto.class);
            System.out.println(JSON.toJSONString(art));
        }
    }
}
posted @ 2022-10-09 14:41  空还是空  阅读(105)  评论(0编辑  收藏  举报