Java项目(二)--Springboot + ElasticSearch 构建博客检索系统(2)- 博客网站全文检索实现思路

本文主要是介绍Java项目(二)--Springboot + ElasticSearch 构建博客检索系统(2)- 博客网站全文检索实现思路,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

博客网站全文检索实现思路

基于MySQL实现

在MySQL创建表为t_blog,然后增加测试数据。

CREATE TABLE `t_blog` (`id` int(11) PRIMARY KEY AUTO_INCREMENT COMMENT '自增ID',`title` varchar(60) DEFAULT NULL COMMENT '博客标题',`author` varchar(60) DEFAULT NULL COMMENT '博客作者',`content` mediumtext COMMENT '博客内容',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`update_time` datetime DEFAULT NULL COMMENT '更新时间') ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4;

如果使用MySQL实现全文检查,查询语句为:

SELECT * FROM t_blog WHERE title LIKE '%spring%' OR content LIKE '%spring%';

在数据量很少的时候这种查询还能应付应用,但是数据量变大之后就会变得很慢,并且查询的时候占用资源无法释放。

优化这种查询,人们就想到了添加索引。

数值型或者字符串类型的全文比较的话,索引是有效果的。但是如果使用like去查询建了索引的字段,索引效果就会失效,不仅如此,他还会做全表的扫描。

虽然说MySQL已经支持了fulltext的索引的类型,它依然不是很适合全文检索的场景。因为当我们数据量越来越多的时候,我们不可避免的面临分布式架构,相应的我们数据也需要拆分,也就是分库分表。分库分表如果对应的是ID或者其他数值类型的字段,我们可以使用哈希或者其他数据分片的规则,从而让分布式数据库中间件,如MyCat或者sharding-jdbc,帮我们能映射到具体的数据的节点。但是搜索的业务场景是一种模糊匹配,且并不知道用户会输入什么样的字符,没办法对用户输入的数据做哈希或者其他数据分片的算法,从而也就无法实现单库节点的准确映射,从而对后端的所有节点全部做一次全表扫描,再由中间件处理返回结果,这个时候结果过滤的阶段,查询效率就会更加的低。由此MySQL不太适合全文检索的业务场景。

基于ES实现

ES的索引是使用了倒排索引,如图所示。
在这里插入图片描述
首先ES对我们新增的数据会分词,分词的规则可以是原生的内部支持的分词规则,也可以使用特别的分词来实现。分词以后ES会维护最小词源到文档的映射。得到用户输入的词后进行拆分,形成最小的词源找到对应的文档,最后把结果整合以下返回给用户。

ES相较于MySQL,拥有更好的分布式或者水平扩展的能力。一个运行中的ES实例,我们称之为节点,整个集群由一个或者多个相同cluster.name配置的节点组成的,他们共同承担着数据以及负载的压力。当有节点加入集群,或者从集群当中移除某个节点的时候,整个集群会重新平均分布所有的数据。
在这里插入图片描述
比如上图,ES的CLUSTER最初只有两个节点NODE1、NODE2,后面新加入了节点叫NODE3。当这个节点一进来,集群之前选举出来的主节点就会感知到,并且做后续的管理及负载编排的工作。首先,主节点负责管理集群范围内的所有变更,比如说增加索引、删除索引,或者增加节点、删除节点等。而主节点并不涉及到文档级别的变更和搜索等一系列操作。所以集群当只有一个主节点的情况下,即使用户流量非常大、非常高,它也不可能成为瓶颈。与此同时,任何节点都可以成为主节点。
我们的实例集群虽然只有一个节点,即这个节点即是主节点,也是从节点。用户把请求发送到集群当中任意节点,当然也包括主节点,哪个节点都知道任意文档存储的位置,并且能够将请求直接转发到存储所需文档的具体节点。所以无论将请求发送到哪个节点,他都能负责从各个包含所需文档目标节点当中收回数据,并且最终把结果展示给客户端。

MySQL、ES数据同步

数据同步中间件

全量:第一次建立完ES索引之后,把MySQL的数据一次性全部打包同步过去。
增量:全量同步之后,MySQL产生新的数据,包括新插入的数据、以前的老数据得到Update、以前的老数据得到Delete,这三种情况同样的需要同步到ES的Index里面去,然后让他对应的去做新增、更新、删除。

那怎么样把MySQL数据同步到ES呢?

全量同步的话,单独写个脚本,如select * from t_blog,得到数据之后再程序里面再循环,然后得到数据插入到ES。

但是正好执行的时候,又有新的数据进来了,怎么办?
可以记录一个maxCreateTime,每一次全表扫描的时候记录当前扫描到的数据里面的最大的创建时间,然后扫描的时候小于等于这个创建时间。等到这批同步到ES之后,后续还会有更多的定时任务去大于上次记录的最大时间,然后同样的过程反复进行下去。这样就保证第一次全量同步,后续的定时任务不断的进行增量的同步数据。

还有种思路是,针对增量同步数据,在代码里配置一个切面就可以了,对于每个方法写一个ES的CRUD的切面,就可以实现数据的同步了。切面乍一听很高大上,但是它使代码、业务、数据这三者耦合度太高了,不利于业务和数据的隔离。

其实对数据同步问题,开源社区或ES官方早就给出了一些解决方案了。

开源中间件
首先,第一个基于MySQL的binlog订阅。
binlog可以说是一种日志,它用来实时记录MySQL数据产生的一些变化,然后通过MySQL的主从复制协议,自己实现一个客户端,然后和MySQL的一个主节点进行连接。其实就是把自己伪装成一个从节点。接下来,主节点只要发生了数据变更,他就会把这个变更的事件传递给客户端。
在这里插入图片描述
binlog同步组件,国内已经有做的很好的,如阿里巴巴的canal。还有go语言实现的go-mysql-elasticshearch(还不支持ES 6.x以上版本,也不支持MySQL 8版本),目前中间件还处于开发阶段,还不够成熟。

ES官方数据收集及同步组件,它叫logstash。
在这里插入图片描述
logstash的输入源可以有很多,比如说log4j。
logstash-JDBC设计于用于对接MySQL等数据源。实现方式有点像上面提到的思路(每次记录时间增量同步),具体的要求有以下几点:
首先,id对应的Elasticsearch里面_id,id设置必须来着MySQL中的id字段,它提供了MySQL和Elasticsearche之间,就是文档数据之间映射关系。如果一条记录在MySQL更新,那么Elasticsearch所有关联文档都应该被重写(先把之前的文档删除,再新增一个新的文档,因为他没有Update操作)。
另外一个,MySQL表里必须有一个标识创建时间或更新时间的字段。Logstash可以实现每次请求只获取上次轮询后,更新或插入的数据。其实给他标识了一个maxTime最大时间,下次通过和这个最大时间的比较,能够完成增量的同步。

logstash全量、增量同步解决方案

首先,安装logstash,下载路径如下,选好版本下载解压即可。(logstash版本尽量和ES版本保持一致)
https://www.elastic.co/cn/downloads/past-releases#logstash

因为需要和MySQL进行jdbc的连接,需要下载mysql-connector-java.jar包(注意版本跟mysql版本要一致),下载地址如下,下载完把jar包放到logstash解压路径下。
https://search.maven.org/artifact/mysql/mysql-connector-java/8.0.29/jar
在这里插入图片描述
然后在config目录下新建文件为mysql.conf

input{jdbc{# jdbc驱动包位置jdbc_driver_library => "D:\\elastic\\logstash-6.8.23\\mysql-connector-java-8.0.29.jar"# 要使用的驱动包类jdbc_driver_class => "com.mysql.cj.jdbc.Driver"# mysql数据库的连接信息jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/es-db"# mysql用户jdbc_user => "root"# mysql密码jdbc_password => "root"# 定时任务,多久执行一次查询,默认一分钟,如果想要没有延迟,可以使用 schedule => "* * * * * *"schedule => "* * * * *"# 清空上传的sql_last_value记录clean_run => true# 你要执行的语句statement => "select * FROM t_blog WHERE update_time > :sql_last_value AND update_time < NOW() ORDER BY update_time desc"}
}output {elasticsearch{# es host : porthosts => ["127.0.0.1:9200"]# 索引index => "blog"# _iddocument_id => "%{id}"}
}

然后执行logstash,cmd窗口切换到D:\elastic\logstash-6.8.23\bin目录,然后执行logstash -f …/config/mysql.conf(执行之前得保证ES已经启动)
在这里插入图片描述
然后Kibana界面执行GET /blog/_stats
在这里插入图片描述
在Kibana界面执行GET /blog/_search也能查到数据
在这里插入图片描述
最后谈谈,需要执行的sql语句的演变由来,它其实经过三次迭代,最初版本为:

select * FROM t_blog WHERE update_time > :sql_last_value ORDER BY update_time desc

这个sql会产生如图所示,记录时间临界点的数据可能不会被扫描到。
在这里插入图片描述
在这里插入图片描述
为了扫描到临界点的数据,把sql进行了升级为:(把条件改为update_time >= :sql_last_value)

select * FROM t_blog WHERE update_time >= :sql_last_value ORDER BY update_time desc

这个sql执行的结果就是,会把上次最后一个临界点的数据也会重复扫描上,数据量大了之后会产生额外的性能开销。在这里插入图片描述
最后sql升级为当前版本:

select * FROM t_blog WHERE update_time > :sql_last_value AND update_time < NOW() ORDER BY update_time desc

两个条件同时满足的情况下很好的解决临界点数据的扫描问题。
在这里插入图片描述

这篇关于Java项目(二)--Springboot + ElasticSearch 构建博客检索系统(2)- 博客网站全文检索实现思路的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/480483

相关文章

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法