草庐IT

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

其乐无涯 2023-10-02 原文

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

基于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 : port
        hosts => ["127.0.0.1:9200"]
        # 索引
        index => "blog"
        # _id
        document_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)- 博客网站全文检索实现思路的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  3. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

    我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

  4. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  5. ruby - Ping ruby 网站? - 2

    在Ruby中可以使用哪些替代方法来ping一个ip地址?标准库“ping”库的功能似乎非常有限。我对在这里滚动我自己的代码不感兴趣。有没有好的gem?我应该接受它并忍受它吗?(我在Linux上使用Ruby1.8.6编写代码) 最佳答案 net-ping值得一看。它允许TCPping(如标准ruby​​ping),但也允许UDP、HTTP和ICMPping。ICMPping需要root权限,但其他则不需要。 关于ruby-Pingruby网站?,我们在StackOverflow上找到一个类

  6. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  7. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  8. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  9. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  10. ruby - 在 Ruby 中构建长字符串的简洁方法 - 2

    在编写Ruby(客户端脚本)时,我看到了三种构建更长字符串的方法,包括行尾,所有这些对我来说“闻起来”有点难看。有没有更干净、更好的方法?变量递增。ifrender_quote?quote="NowthatthereistheTec-9,acrappyspraygunfromSouthMiami."quote+="ThisgunisadvertisedasthemostpopularguninAmericancrime.Doyoubelievethatshit?"quote+="Itactuallysaysthatinthelittlebookthatcomeswithit:themo

随机推荐