从零搭建 ES 搜索服务(二)基础搜索

时间:2022-04-24 07:45:31

一、前言

上篇介绍了 ES 的基本概念及环境搭建,本篇将结合实际需求介绍整个实现过程及核心代码。


二、安装 ES ik 分析器插件

2.1 ik 分析器简介

GitHub 地址:https://github.com/medcl/elasticsearch-analysis-ik

提供两种分词模式:「 ik_max_word 」及「 ik_smart 」

分词模式 描述
ik_max_word 会将文本做最细粒度的拆分,比如会将“*国歌”拆分为“*,中华人民,中华,华人,人民*,人民,人,民,*,共和,和,国国,国歌”,会穷尽各种可能的组合
ik_smart 会做最粗粒度的拆分,比如会将“*国歌”拆分为“*,国歌”

2.2 安装步骤

① 进入 ES 的 bin 目录

$ cd /usr/local/elasticsearch/bin/

② 通过 elasticsearch-plugin 命令安装 ik 插件

$ ./elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.3/elasticsearch-analysis-ik-5.5.3.zip

③ 安装成功后会在 plugins 目录出现 analysis-ik 文件夹


三、数据同步

3.1 方案设计

通过 Logstash 实现 MySQL 数据库中的数据同步到 ES 中,第一次全量同步,后续每分钟增量同步一次

3.2 实现步骤

3.2.1 安装 logstash-input-jdbc 插件

① 进入 Logstash 的 bin 目录

$ cd /usr/local/logstash/bin/

② 使用 logstash-plugin 命令安装 logstash-input-jdbc 插件

$ ./logstash-plugin install logstash-input-jdbc
3.2.2 MySQL 同步数据配置

① 首先在 Logstash 安装目录中新建「MySQL 输入数据源」相关目录

/usr/local/logstash/mysql > MySQL 输入数据源目录

/usr/local/logstash/mysql/config > 配置文件目录

/usr/local/logstash/mysql/metadata > 追踪字段记录文件目录

/usr/local/logstash/mysql/statement > SQL 脚本目录

② 其次上传「MySQL JDBC 驱动」至 /usr/local/logstash/mysql 目录中

mysql-connector-java-5.1.40.jar

③ 然后新建「 SQL 脚本文件」,即 /usr/local/logstash/mysql/statement 目录中新建 yb_knowledge.sql 文件,内容如下:

SELECT
id,
create_time AS createTime,
modify_time AS modifyTime,
is_deleted AS isDeleted,
knowledge_title AS knowledgeTitle,
author_name AS authorName,
knowledge_content AS knowledgeContent,
reference_count AS referenceCount
FROM
yb_knowledge
WHERE
modify_time >= :sql_last_value

④ 之后再新建「配置文件」,即 /usr/local/logstash/mysql/config 目录中新建 yb_knowledge.conf 文件,内容如下:

input {

    jdbc {
jdbc_connection_string => "jdbc:mysql://192.168.1.192/test"
jdbc_user => "root"
jdbc_password => "123456"
jdbc_driver_library => "/usr/local/logstash/mysql/mysql-connector-java-5.1.40.jar"
jdbc_driver_class => "com.mysql.jdbc.Driver"
jdbc_paging_enabled => "true"
jdbc_page_size => "50000"
jdbc_default_timezone => "UTC"
lowercase_column_names => false
# 使用其它字段追踪,而不是用时间
use_column_value => true
# 追踪的字段
tracking_column => "modifyTime"
record_last_run => true
# 上一个sql_last_value值的存放文件路径, 必须要在文件中指定字段的初始值
last_run_metadata_path => "/usr/local/logstash/mysql/metadata/yb_knowledge.txt"
# 执行的sql文件路径+名称
statement_filepath => "/usr/local/logstash/mysql/statement/yb_knowledge.sql"
# 设置监听间隔 各字段含义(由左至右)分、时、天、月、年,全部为*默认含义为每分钟都更新
schedule => "* * * * *"
# 索引类型
type => "knowledge"
}
} filter {
json {
source => "message"
remove_field => ["message"]
}
} output {
if [type] == "knowledge" {
elasticsearch {
hosts => ["localhost:9200"]
index => "yb_knowledge"
document_id => "%{id}"
}
} stdout {
# JSON格式输出
codec => json_lines
}
}

⑤ 进入 Logstash 的 bin 目录启动,启动时既可指定单个要加载的 conf 文件,也可以指定整个 config 目录

$ ./logstash -f ../mysql/config/yb_knowledge.conf

注:启动 Logstash 时,不管有多少个配置文件最后都会编译成一个文件,也就是说无论有多少个 input 或 output ,最终只有一个 pipeline

注:每分钟的 0 秒 Logstash 会自动去同步数据

elasticsearch-head 中可看到最终结果如下:

从零搭建 ES 搜索服务(二)基础搜索

从零搭建 ES 搜索服务(二)基础搜索

3.2.3 自定义模板配置

此时建立的索引中字符串字段是用的默认分析器 「standard」,会把中文拆分成一个个汉字,这显然不满足我们的需求,所以需要自定义配置以使用 ik 分析器

① 首先在 Logstash 安装目录中新建「自定义模板文件」目录

/usr/local/logstash/template > 自定义模板文件目录

② 其次在该目录中新建 yb_knowledge.json 模板文件,内容如下:

{
"template": "yb_knowledge",
"settings": {
"index.refresh_interval": "5s",
"number_of_shards": "1",
"number_of_replicas": "1"
},
"mappings": {
"knowledge": {
"_all": {
"enabled": false,
"norms": false
},
"properties": {
"@timestamp": {
"type": "date",
"include_in_all": false
},
"@version": {
"type": "keyword",
"include_in_all": false
},
"knowledgeTitle": {
"type": "text",
"analyzer": "ik_max_word"
},
"knowledgeContent": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
},
"aliases": {
"knowledge": {}
}
}

③ 然后修改 yb_knowledge.conf 文件中的 output 插件,指定要使用的模板文件路径

if [type] == "knowledge" {
elasticsearch {
hosts => ["localhost:9200"]
index => "yb_knowledge"
document_id => "%{id}"
template_overwrite => true
template => "/usr/local/logstash/template/yb_knowledge.json"
template_name => "yb_knowledge"
}
}

④ 之后停止 Logstash 并删除 metadata 目录下 sql_last_value 的存放文件

$ rm -rf /usr/local/logstash/mysql/metadata/yb_knowledge.txt

⑤ 最后删除先前创建的 yb_knowledge 索引并重启 Logstash

注:重建索引后可以通过「_analyze」测试分词结果

从零搭建 ES 搜索服务(二)基础搜索

3.2.4 自动重载配置文件

为了可以自动检测配置文件的变动和自动重新加载配置文件,需要在启动的时候使用以下命令

$ ./logstash -f ../mysql/config/ --config.reload.automatic

默认检测配置文件的间隔时间是 3 秒,可以通过以下命令改变

--config.reload.interval <second>

配置文件自动重载工作原理:

  • 检测到配置文件变化
  • 通过停止所有输入停止当前 pipline (即管道)
  • 用新的配置创建一个新的 pipeline
  • 检查配置文件语法是否正确
  • 检查所有的输入和输出是否可以初始化
  • 检查成功使用新的 pipeline 替换当前的 pipeline
  • 检查失败,使用旧的继续工作
  • 在重载过程中, Logstash 进程没有重启

注:自动重载配置文件不支持 stdin 这种输入类型


四、代码实现

以下代码实现基于 Spring Boot 2.0.4,通过 Spring Data Elasticsearch 提供的 API 操作 ES

4.1 搭建 Spring Boot 项目

Spring Boot 项目实战(一)多模块项目搭建

4.2 引入核心依赖包

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

4.3 添加 Spring Boot ES 相关配置项

在 application.properties 文件中添加 ES 相关配置项

spring.data.elasticsearch.cluster-name = compass
spring.data.elasticsearch.cluster-nodes = xxx.xxx.xxx.xxx:9300
spring.data.elasticsearch.repositories.enabled = true

4.4 核心代码

Spring Data Elasticsearch 提供了类似数据库操作的 repository 接口,可以使我们像操作数据库一样操作 ES

① 定义实体

@Data
@Document(indexName = "knowledge", type = "knowledge")
public class KnowledgeDO { @Id
private Integer id; private Integer isDeleted; private java.time.LocalDateTime createTime; private java.time.LocalDateTime modifyTime; private String knowledgeTitle; private String authorName; private String knowledgeContent; private Integer referenceCount;
}

② 定义 repository 接口

public interface KnowledgeRepository extends ElasticsearchRepository<KnowledgeDO, Integer> {
}

③ 构建查询对象

private SearchQuery getKnowledgeSearchQuery(KnowledgeSearchParam param) {
Pageable pageable = PageRequest.of(param.getStart() / param.getSize(), param.getSize());
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 使用 filter 比使用 must query 性能要好
boolQuery.filter(QueryBuilders.termQuery("isDeleted", IsDeletedEnum.NO.getKey()));
// 多字段查询
MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery(param.getKeyword(), "knowledgeTitle", "knowledgeContent");
boolQuery.must(multiMatchQuery);
return new NativeSearchQueryBuilder()
.withPageable(pageable)
.withQuery(boolQuery)
.build();
}

注:上述查询类似于 MySQL 中的 select 语句「select * from yb_knowledge where is_deleted = 0 and (knowledge_title like '%keyword%' or knowledge_content like '%keyword%')」

④ 获取返回结果

SearchQuery searchQuery = getKnowledgeSearchQuery(param);
Page<KnowledgeDO> page = knowledgeRepository.search(searchQuery);

注:最终结果默认会按照相关性得分倒序排序,即每个文档跟查询关键词的匹配程度


五、结语

至此一个简易的搜索服务已经实现完毕,后续将继续介绍一些附加功能,如同义词搜索、拼音搜索以及搜索结果高亮等


六、其它

6.1 注意事项

① 在 Logstash 的 config 目录执行启动命令时会触发以下错误,所以请移步 bin 目录执行启动命令

ERROR Unable to locate appender "${sys:ls.log.format}_console" for logger config "root"

② Logstash 中 last_run_metadata_path 文件中保存的 sql_last_value 值是最新一条记录的 tracking_column 值,而不是所有记录中最大的 tracking_column 值

③ 当 MySQL 中字段类型为 tinyint(1) 时,同步到 ES 后该字段会转化成布尔类型,改为 tinyint(4) 可避免该问题

6.2 如何使 ES 中的字段名与 Java 实体字段名保持一致?

Java 实体字段通常是小驼峰形式命名,而我们数据库表字段都是下划线形式的,所以需要将两者建立映射关系,方法如下:

① 修改 statement_filepath 的 SQL 脚本,表字段用 AS 设置成小驼峰式的别名,与 Java 实体字段名保持一致

② Logstash 配置文件中的 jdbc 配置还需要加一个配置项 lowercase_column_names => false ,否则在 ES中字段名默认都是以小写形式存储,不支持驼峰形式

6.3 Logstash 自定义模板详解

① 第一次启动 Logstash 时默认会生成一个名叫 「logstash」 的模板到 ES 里,可以通过以下命令查看

curl -XGET 'http://localhost:9200/_template/logstash'

注:默认模板内容如下:

{
"logstash": {
"order": 0,
"version": 50001,
"template": "logstash-*",
"settings": {
"index": {
"refresh_interval": "5s"
}
},
"mappings": {
"_default_": {
"dynamic_templates": [
{
"message_field": {
"path_match": "message",
"mapping": {
"norms": false,
"type": "text"
},
"match_mapping_type": "string"
}
},
{
"string_fields": {
"mapping": {
"norms": false,
"type": "text",
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
}
},
"match_mapping_type": "string",
"match": "*"
}
}
],
"_all": {
"norms": false,
"enabled": true
},
"properties": {
"@timestamp": {
"include_in_all": false,
"type": "date"
},
"geoip": {
"dynamic": true,
"properties": {
"ip": {
"type": "ip"
},
"latitude": {
"type": "half_float"
},
"location": {
"type": "geo_point"
},
"longitude": {
"type": "half_float"
}
}
},
"@version": {
"include_in_all": false,
"type": "keyword"
}
}
}
},
"aliases": {}
}
}

② 使用默认模板,适合刚入门时快速验证使用,但不满足实际需求场景,此时可以在 Logstash 「配置文件」中「 output 」插件中指定自定义模板 覆盖默认模板

output {
if [type] == "knowledge" {
elasticsearch {
hosts => ["localhost:9200"]
index => "yb_knowledge"
document_id => "%{id}"
template_overwrite => true
template => "/usr/local/logstash/template/yb_knowledge.json"
template_name => "yb_knowledge"
}
} stdout {
# JSON格式输出
codec => json_lines
}
}
配置项 说明
template_overwrite 是否覆盖默认模板
template 自定义模板文件路径
template_name 自定义模板名

注意事项:

  • 如果不指定「 template_name 」则会永久覆盖默认的「 logstash 」模板,后续即使删除了自定义模板文件,在使用默认模板的情况下创建的索引还是使用先前自定义模板的配置。所以使用自定义模板时建议指定「 template_name 」防止出现一些难以察觉的问题。

  • 如果不小心覆盖了默认模板,需要重置默认模板则执行以下命令后重启 Logstash。

    curl -XDELETE 'http://localhost:9200/_template/logstash'
  • ES 会按照一定的规则来尝试自动 merge 多个都匹配上了的模板规则,最终运用到索引上。所以如果某些自定义模板不再使用记得使用上述命令及时删除,避免新旧版本的模板规则同时作用在索引上引发问题。

    例:「 t1 」为旧模板,「 t2 」为新模板,它们的匹配规则一致,唯一的区别是「 t2 」删除了其中一个字段的规则,此时如果「 t1 」模板不删除则新建的索引还是会应用已删除的那条规则。

  • 模板是可以设置 order 参数的,默认的 order 值就是 0。order 值越大,在 merge 模板规则的时候优先级越高。这也是解决新旧版本同一条模板规则冲突的一个解决办法。

③ 自定义模板中设置索引别名,增加「 aliases 」配置项,如 yb_knowledge => knowledge

"template": "yb_knowledge",
...省略中间部分...
"aliases": {
"knowledge": {}
}

6.4 Logstash 多个配置文件里的 input 、filter 、 output 是否相互独立?

不独立;Logstash 读取多个配置文件只是简单的将所有配置文件整合到了一起。如果要彼此独立,可以通过 typetags 区分,然后在 output 配置中用 if 语句判断一下

6.5 如何不停机重建索引?

① 首先新建新索引 v2

② 其次将源索引 v1 的数据导入新索引 v2

③ 然后设置索引别名(删除源索引 v1 别名,添加新索引 v2 别名)

④ 之后修改 Logstash 配置文件中 output 的 index 值为 v2

注:前提是 Logstash 启动时指定 config.reload.automatic 设置项开启配置文件自动重载

⑤ 再次执行步骤二增量同步源索引 v1 中已修改但没同步到新索引 v2 中的数据

⑥ 最后删除源索引 v1

从零搭建 ES 搜索服务(二)基础搜索的更多相关文章

  1. 从零搭建ES搜索服务(一)基本概念及环境搭建

    一.前言 本系列文章最终目标是为了快速搭建一个简易可用的搜索服务.方案并不一定是最优,但实现难度较低. 二.背景 近期公司在重构老系统,需求是要求知识库支持全文检索. 我们知道普通的数据库 like ...

  2. 从零搭建 ES 搜索服务(三)同义词搜索

    一.前言 上篇介绍了 ES 的基础搜索,能满足我们基本的需求,然而在实际使用中还可能希望搜索「番茄」能将包含「西红柿」的结果也罗列出来,本篇将介绍如何实现同义词之间的搜索. 二.安装 ES 同义词插件 ...

  3. 从零搭建 ES 搜索服务(六)相关性排序优化

    一.前言 上篇介绍了搜索结果高亮的实现方法,本篇主要介绍搜索结果相关性排序优化. 二.相关概念 2.1 排序 默认情况下,返回结果是按照「相关性」进行排序的--最相关的文档排在最前. 2.1.1 相关 ...

  4. Sharepoint2013搜索学习笔记之创建搜索服务&lpar;二&rpar;

    第一步,进入管理中心,点击管理服务器上的服务 第二步,在服务器上选择需要承载搜索服务的服务器,并启动服务列表上的sharepoint server search 第三步,从管理中心进入管理服务应用程序 ...

  5. 从零搭建 ES 搜索服务(四)拼音搜索

    一.前言 上篇介绍了 ES 的同义词搜索,使我们的搜索更强大了,然而这还远远不够,在实际使用中还可能希望搜索「fanqie」能将包含「番茄」的结果也罗列出来,这就涉及到拼音搜索了,本篇将介绍如何具体实 ...

  6. 从零搭建 ES 搜索服务(五)搜索结果高亮

    一.前言 在实际使用中搜索结果中的关键词前端通常会以特殊形式展示,比如标记为红色使人一目了然.我们可以通过 ES 提供的高亮功能实现此效果. 二.代码实现 前文查询是通过一个继承 Elasticsea ...

  7. 从零搭建一个Redis服务

    前言 自己在搭建redis服务的时候碰到一些问题,好多人只告诉你怎么成功搭建,但是并没有整理过程中遇到的问题,所有楼主就花了点时间来整理下. linux环境安装redis 安装中的碰到的问题和解决办法 ...

  8. 从零搭建SSM框架(二)运行工程

    启动cnki-manager工程 1.需要在cnki-manager 的pom工程中,配置tomcat插件.启动的端口号,和工程名称. 在cnki-manager的pom文件中添加如下配置: < ...

  9. SharePoint配置搜索服务和指定搜索范围

    转载:http://constforce.blog.163.com/blog/static/163881235201201211843334/ 一.配置SharePoint Foundation搜索 ...

随机推荐

  1. 360:且用且珍惜!解决虚拟机linux启动缓慢以及ssh端卡顿的问题!

    优化软件以及杀毒软件想必大家都是用过的,小编自用的第一台电脑自带安装的是金山毒霸,随着时间的偏移渐渐用过小红伞,卡巴斯基,优化大师,鲁大师到后来的360优化杀毒套装,优化软件给大家带来了方便,尤其是上 ...

  2. mongo数据库基础操作

    概念 一个mongod服务可以有建立多个数据库,每个数据库可以有多张表,这里的表名叫collection,每个collection可以存放多个文档(document),每个文档都以BSON(binar ...

  3. CNN&lpar;卷积神经网络&rpar;、RNN&lpar;循环神经网络&rpar;、DNN&lpar;深度神经网络&rpar;的内部网络结构有什么区别?

    https://www.zhihu.com/question/34681168 CNN(卷积神经网络).RNN(循环神经网络).DNN(深度神经网络)的内部网络结构有什么区别?修改 CNN(卷积神经网 ...

  4. FPGA STA&lpar;静态时序分析&rpar;

    1 FPGA设计过程中所遇到的路径有输入到触发器,触发器到触发器,触发器到输出,例如以下图所看到的: 这些路径与输入延时输出延时,建立和保持时序有关. 2. 应用背景 静态时序分析简称STA,它是一种 ...

  5. Android开发之SoundPool使用详解

    使用SoundPool播放音效 如果应用程序经常播放密集.急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了.因为MediaPlayer存在如下缺点: 1) 延时时间较长 ...

  6. form表单中enctype属性作用

    上传文件时,提交的表单属性里需要加enctype="multipart/form-data",才能提交文件信息,不然会报错.那么enctype属性的作用是什么?就是设置表单传输的编 ...

  7. python的线程和进程

    1.线程的基本概念 概念 线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程 ...

  8. Hibernate&lpar;十三&rpar;迫切内连接fetch

    迫切内连接fetch 内连接和迫切内连接的区别: 其主要区别就在于封装数据,因为他们查询的结果集都是一样的,生成底层的SQL语句也是一样的. 1.内连接:发送就是内连接的语句,封装的时候将属于各自对象 ...

  9. np&period;mat&lpar;&rpar;和np&period;transpose

    例子: import numpy as np dataSet = [] with open('/home/lai/下载/20081023025304.plt') as fr: for line in ...

  10. c&num; 敏捷2 ForEach ToDictionary ToLookup Except比较

    using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; ...