学习springBoot(13)集成 Elasticsearch实现高亮显示以及使用ELK服务

2,357 阅读6分钟

1、Elasticsearch简介

ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。 我们建立一个网站或应用程序,并要添加搜索功能,但是想要完成搜索工作的创建是非常困难的。我们希望搜索解决方案要运行速度快,我们希望能有一个零配置和一个完全免费的搜索模式,我们希望能够简单地使用JSON通过HTTP来索引数据,我们希望我们的搜索服务器始终可用,我们希望能够从一台开始并扩展到数百台,我们要实时搜索,我们要简单的多租户,我们希望建立一个云的解决方案。因此我们利用Elasticsearch来解决所有这些问题及可能出现的更多其它问题。

2、ELK简介

ELK是Elasticsearch、Logstash、Kibana三大开源框架首字母大写简称。市面上也被成为Elastic Stack。

Logstash: 主要是用来日志的搜集、分析、过滤日志的工具,支持大量的数据获取方式。一般工作方式为c/s架构,client端安装在需要收集日志的主机上,server端负责将收到的各节点日志进行过滤、修改等操作在一并发往elasticsearch上去。

Kibana: 也是一个开源和免费的工具,Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。

3、环境安装

本次用到的工具全部使用docker进行安装,docker和docker-compose的安装这里就不介绍了,之前有过相关教程。 下面贴一下我用到的docker-compose文件和elk相关配置文件。

需要注意的是,elk的版本要保持一致

新建docker-compose.yml文件


version: '3'

services:
  elasticsearch:
    image: elasticsearch:7.4.1
    restart: always
    container_name: "elasticsearch"
    ports:
      - "9200:9200"
      - "9300:9300"
    volumes:
      - /root/es-docker-config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
      - /root/es-docker-config/jvm.options:/usr/share/elasticsearch/config/jvm.options

  kibana:
    image: docker.elastic.co/kibana/kibana:7.4.1
    restart: always
    container_name: "kibana"
    ports:
      - "5601:5601"
    volumes:
      - /root/es-docker-config/kibana.yml:/usr/share/kibana/config/kibana.yml

  logstash:
    image: docker.elastic.co/logstash/logstash:7.4.1
    restart: always
    container_name: "logstash"
    ports:
      - "5044:5044"
    volumes:
      - /root/es-docker-config/logstash.conf:/usr/share/logstash/pipeline/logstash.conf
      - /root/es-docker-config/logstash.yml:/usr/share/logstash/config/logstash.yml
      - /root/es-docker-config/mysql-connector-java-8.0.15.jar:/usr/share/logstash/logstash-core/lib/jars/mysql-connector-java-8.0.15.jar

elasticsearch.yml

cluster.name: my-application
node.name: node-1
node.attr.rack: r1
cluster.initial_master_nodes: ["node-1"]
network.host: 0.0.0.0
http.port: 9200
http.cors.enabled: true
http.cors.allow-origin: "*"

kibana.yml

server.name: kibana
server.host: "0"
elasticsearch.hosts: [ "http://192.0.0.171:9200" ]
xpack.monitoring.ui.container.elasticsearch.enabled: true

logstash.yml

http.host: "0.0.0.0"
xpack.monitoring.elasticsearch.hosts: [ "http://192.0.0.171:9200" ]

logstash.conf

input {
    beats {
        port => 5044
    }

    jdbc {
        # jdbc驱动包位置
        jdbc_driver_library => "mysql-connector-java-8.0.15.jar"
        # 驱动包
        jdbc_driver_class => "com.mysql.jdbc.Driver"
        # MySQL连接信息
        jdbc_connection_string => "jdbc:mysql://192.0.0.101:3306/springboot?useUnicode=true&serverTimezone=UTC&characterEncoding=utf-8&allowMultiQueries=true"
        # 设置时区
        jdbc_default_timezone => "Asia/Shanghai"
        # 用户名
        jdbc_user => "root"
        # 密码
        jdbc_password => "123456"
        # 处理中文乱码
        codec => plain{charset => "UTF-8"}
        # 小写列名
        lowercase_column_names => false
        # 定时任务,默认一分钟
        schedule => "* * * * *"
        # 执行语句
        statement => "select id, book_name AS bookName, book_content AS bookContent, author, create_time AS createTime, update_time AS updateTime from book where update_time > :sql_last_value and update_time < NOW() order by update_time desc"
    }
}

output {
    elasticsearch {
        hosts => ["192.0.0.171:9200"]
        index => "book"
        document_id => "%{id}"
    }
}

准备就绪后,使用docker-compose启动

docker-compose up -d

这里启动完kibana有问题的就等elasticsearch起来了在重启一下。

都起来之后,可以简单验证一下

curl http://192.0.0.171:9200/

出现这个结果说明elasticsearch已经启动成功了。

4、elasticsearch 安装ik中文分词器

ik github官网:

https://github.com/medcl/elasticsearch-analysis-ik/releases

在这里选择自己要下载的版本,右键复制链接地址

进入docker容器安装插件

docker exec -it elasticsearch bash

进入plugins目录

cd plugins/

安装ik分词器

elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.1/elasticsearch-analysis-ik-7.4.1.zip

安装完成之后重启elasticsearch。这里可以使用kibana简单验证一下ik的分词效果,es的默认分词器对中文不是很友好。

这里“王者荣耀”给分成了两个词是因为ik的词库里面没有“王者荣耀”这个词,ik的词库可以自己扩展,这里简单演示一下,进入/plugins/ik/config目录,

新建user_1.dic,加入“王者荣耀”

vi user_1.dic

修改IKAnalyzer.cfg.xml,重启se。

再试一下之前的分词,可以正常分词了。

5、springboot集成elasticsearch

本次是简单演示一下,MySQL数据同步到es后,然后实现一个简单的关键词查询功能。

需要用到的依赖:

pom.xml

    <!--springboot版本-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
    
    <!--elasticsearch-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>

    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

配置文件application.yml

spring:
  data:
    elasticsearch:
      cluster-name: my-application
      cluster-nodes: 192.0.0.171:9300

数据库表:

CREATE TABLE `book` (
  `id` int(11) NOT NULL,
  `book_name` varchar(100) DEFAULT NULL,
  `book_content` mediumtext,
  `author` varchar(100) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `book_index` (`id`,`book_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

es映射实体类:

@Data
@Document(indexName = "book", type = "_doc",
        useServerConfiguration = true, createIndex = false)
public class EsBook implements Serializable{

    @Id
    private Integer id;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String bookName;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String bookContent;

    @Field(type = FieldType.Text)
    private String author;

    @Field(type = FieldType.Date, format = DateFormat.custom,
            pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
    private Date createTime;

    @Field(type = FieldType.Date, format = DateFormat.custom,
            pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
    private Date updateTime;

}

查询代码:

@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
    
@PostMapping("/searchHit")
    @ResponseBody
    public Map searchHit(@RequestBody Param param){
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Map<String, Object> map = new HashMap<>();

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryBuilder.should(QueryBuilders.matchQuery("bookName", param.getKeyword()))
                    .should(QueryBuilders.matchQuery("bookContent", param.getKeyword()));

        NativeSearchQuery nativeSearchQuery=new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .withHighlightFields(new HighlightBuilder.Field("bookContent"),new HighlightBuilder.Field("bookName"))
                .withHighlightBuilder(new HighlightBuilder().preTags("<span style='color:red'>").postTags("</span>"))
                .withPageable(PageRequest.of(1, 5))
                .build();

        String s = queryBuilder.toString();
        System.out.println("查询语句:"+s);


        AggregatedPage<EsBook> esBooks = elasticsearchTemplate.queryForPage(nativeSearchQuery, EsBook.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                List<EsBook> esBookList = new LinkedList<EsBook>();
                SearchHits hits = response.getHits();
                for (SearchHit searchHit : hits) {
                    if (hits.getHits().length <= 0) {
                        return null;
                    }
                    Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
                    String bookName= (String) sourceAsMap.get("bookName");
                    String bookContent= (String) sourceAsMap.get("bookContent");
                    String author= (String) sourceAsMap.get("author");

                    System.out.println(bookName);
                    System.out.println(bookContent);

                    EsBook esBook = new EsBook();

                    HighlightField contentHighlightField = searchHit.getHighlightFields().get("bookContent");
                    if(contentHighlightField==null){
                        esBook.setBookContent(bookContent);
                    }else{
                        String highLightMessage = searchHit.getHighlightFields().get("bookContent").fragments()[0].toString();
                        esBook.setBookContent(highLightMessage);
//                        esBook.setBookContent(Html2TextUtils.stripHtml(highLightMessage).replaceAll("_",""));
                    }

                    HighlightField nameHighlightField = searchHit.getHighlightFields().get("bookName");
                    if(nameHighlightField==null){
                        esBook.setBookName(bookName);
                    }else{
                        esBook.setBookName(searchHit.getHighlightFields().get("bookName").fragments()[0].toString());
                    }

                    esBook.setAuthor(author);

                    esBookList.add(esBook);
                }
                if (esBookList.size() > 0) {
                    return new AggregatedPageImpl<T>((List<T>) esBookList);
                }
                return null;

            }

            @Override
            public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
                return null;
            }
        });

        if(esBooks != null){
            List<EsBook> bookList = esBooks.getContent();
            map.put("lits", bookList);
        }

        stopWatch.stop();
        long millis = stopWatch.getTotalTimeMillis();
        map.put("searchTime", millis);
        return map;
    }
    
    @Data
    public static class Param{
        private String type;

        private String keyword;

    }

建立es映射:

{
  "settings" : {
        "index" : {
            "analysis.analyzer.default.type": "ik_max_word"
        },
        "analysis":{   
          "analyzer":{
            "ik":{
              "tokenizer":"ik_max_word"
            }
          }
        }
    },

    "mappings": {
      "properties": {
        "id": {
          "type": "long"
        },

        "bookName": {
          "type": "text",
          "analyzer": "ik_max_word"
        },

        "bookContent": {
          "type": "text",
          "analyzer": "ik_max_word"

        }
      }
    }
  
}

这样准备工作基本完成了,数据库随便搞点数据,logstash启动成功的话,数据应该可以正常同步到es。可以在kibana查看一下,没有问题

这里也可以在kibana简单使用高亮查询试一下效果

最后看一下前端展示的效果

这样一个简单的es高亮查询就完成了。


欢迎关注个人公众号