聚合搜索平台
聚合搜索平台笔记
我们希望做一个通用的搜索功能,用户可以通过一个页面请求多种不同的数源然后聚合返回。如下图搜索“小黑子”可以获取相关的网页,图片,视频等等。相比于普通的搜索能提升用户搜素的效率与体验。
业务逻辑
流程图
技术栈
前端:Ant Design vue
Axios
Lodash
后端:
SpringBoot
MyBaties
ElasticSerch
数据同步
项目初始化
前端:快速上手 - Ant Design Vue (antdv.com)
后端:
拉取项目模板springboot-init,修改配置后启动即可
2024-09-11
前端开发
整合vue-router利用组件开发搜索页面:快速上手 - Ant Design Vue (antdv.com)
整合Axios: Axios中文文档 | Axios中文网 (axios-http.cn)
记录查询状态
目标:用url记录搜索状态当刷新后可以恢复到之前的搜索状态,类似b站搜索。
双向绑定 =》 单项绑定,靠url来改变状态,舍弃状态改变url。
todo
刷新后页面动态路由参数获取不到。2024-09-13
获取不同数据源
帖子 =》 系统内部用户 =》 系统内部
图片 =》 系统内部没有,可以尝试去互联网获取
抓取数据
+ 通过前端调试器获取接口信息,构建参数直接调用获取数据 + 获取浏览器渲染出来的页面,根据页面来解析数据 + 有一些动态加载的数据如,需要点击某一个按钮,或输入验证码才能显示数据的,可以使用一些无头浏览器注:爬虫不可乱用,小心包吃包住
图片我们系统内部没有所以需要到网络上获取。
流程图:

使用jsoup库通过解析页面来获取图片数据
jsoup: Java HTML parser, built for HTML editing, cleaning, scraping, and XSS safety
前后端联调
后端聚合多个接口,通过类型来区分查询。前端页面加载和点击搜索时查询所有页面的第一页数据,翻页时根据类型进行查询
门面模式
之后我们的数据源可能会越来越多,前端如果还是每多一个数据源就再前端再写一个方法查询,既影响性能又不便于代码的维护。所以需要利用门面模式来解决这个问题。门面模式就是抽象一个接收请求的模块并根据接收请求的参数进行响应的逻辑处理,使我们不需要关注具体的逻辑。例如酒店的前台,我们需要房间时不会说自己一个一个去找,而是请求酒店的前台帮我们寻找房间。
适配器模式
适配器模式的作用是通过适配调用参数并不适配的方法。例如插座,充电器,数据线的关系,插座和数据线因为不适配并不能连接,但通过充电器的适配就可以让他们连接起来。▼text复制代码目前搜索是通过switch根据类型来搜索不同的数据源,但聚合多个接口后会导致代码臃肿,不利于维护。所以我们可以利用适配器模式来抽象查询的代码。
我们需要先定义一套接口的规范,要接入系统的数据源需要支持搜索和分页。
2024-09-24
Elastic Stack (一套技术栈)
包含数据的整合 =》 提取 =》 存储 =》 使用- beats: 从各种不同类型的文件/应用中采取数据
- logstash: 从采集器或数据源抽取数据/转换数据输送 比如向esc输送
- elasticsearch: 存储/查询数据
- kibana: 可视化查询es数据
ElasticSearch入门
概念
elasticsearch是一个存储服务,特点是搜索功能十分强大。相比于Mysql可以帮我们进行分词搜索非常灵活。可以和mysql对比着理解
| mysql | es |
|---|---|
| 表 | 索引 |
倒排索引
正向索引: 可以理解为书的目录,根据目录去找文章内容
倒排索引: 理解为根据内容去找文章。
如何去找?
文章A : 你好,世界
文章B: 你好,code
切词
你好 世界
你好 code
构建倒排索引表
| 你好 | 文章A,文章B |
|---|---|
| 世界 | 文章A |
| code | 文章B |
用户搜索时先将搜索内容进行切词,再到倒排索引表里去找
Es的几种调用方式
restful请求 (http请求)
GET http://localhost:9200/端口占用
- 9200 开放给外部调用的接口
- 9300 集群间内部通信的接口(不对外开放)
kibana devtools
可以自由操作ES,本质页数resful请求。不建议在生产环境使用
客户端调用
java客户端等,各种语言的客户端基础用法
可以跟着文档过 [Quick start | Elasticsearch Guide [7.17] | Elastic](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/getting-started.html)▼json复制代码//创建索引 PUT user/_doc { "name": "无敌", "age": 18 } //插入数据 POST user/_doc { "name": "李四", "age": 18 } //查询 GET user/_search //修改 POST user/_doc/{id} { "name": "李四", "age": 19 } //删除 DELETE user
Es语法
**DSL**json格式,适配resful,简单易懂
EQL
专门查询ECS文档(标准指标文档)的数据,语法更加规范,适用于特定场景
SQL
学习成本低,sql需要解析效率较低
Painless scripting language
编程式取值,更加灵活,学习成本高
Mapping
类似于mysql中的表结构分词器
Es自带的分词器不是很适配中文,但Es的分词器可以自定义并且支持插件。下载ik分词器
尝试之后发现有时分词的效果不是我们想要的效果,可以通过配置词典来辅助分词。分词时会去看看词典里是否有这个词如果有就可以进行划分
打分机制
Es查询出的数据的顺序是按照分值的高低来进行排序的,得分越高越靠前。当内容与搜索词越相似分值越高。
例如:
我是一个小黑子
我是一个黑子
当我们搜索黑子时第二个的分值会比第一个的高,因为第二个更短
Java客户端
+ 官方提供的Java客户端API调用配置方便,更新迭代快
- SpringBoot Data ElasticSearch调用
配置更方便,可根据方法名生成实现,也支持复杂的聚合搜索。
对比版本7.17对应SpringBoot Data ElasticSearch 4.4.X
配置yml
▼json复制代码spring: elasticsearch: uris: http://localhost:9200
继承ElasticsearchRepository调用
提供了简单的crud方法,我们也可以按照他的方法名规范来写接口,框架会帮我们自动实现。十分便捷,适用于简单查询场景。▼java复制代码public interface PostEsDao extends ElasticsearchRepository<PostEsDTO, Long> { /** * 根据用户名查询 * @param userId * @return */ List<PostEsDTO> findByUserId(Long userId); /** * 根据id和用户名查询 * @param id * @param userId * @return */ List<PostEsDTO> findByIdAndUserId(Long id,Long userId); }
通过ElasticsearchRestTemplate调用
类似于Mybatis的queryWapper可以build各种条件进行查询,十分灵活,适用于复杂查询的场景▼java复制代码boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete", 0)); if (id != null) { boolQueryBuilder.filter(QueryBuilders.termQuery("id", id)); } if (notId != null) { boolQueryBuilder.mustNot(QueryBuilders.termQuery("id", notId)); } if (userId != null) { boolQueryBuilder.filter(QueryBuilders.termQuery("userId", userId)); } // 必须包含所有标签 if (CollectionUtils.isNotEmpty(tagList)) { for (String tag : tagList) { boolQueryBuilder.filter(QueryBuilders.termQuery("tags", tag)); } } // 包含任何一个标签即可 if (CollectionUtils.isNotEmpty(orTagList)) { BoolQueryBuilder orTagBoolQueryBuilder = QueryBuilders.boolQuery(); for (String tag : orTagList) { orTagBoolQueryBuilder.should(QueryBuilders.termQuery("tags", tag)); } orTagBoolQueryBuilder.minimumShouldMatch(1); boolQueryBuilder.filter(orTagBoolQueryBuilder); } // 按关键词检索 if (StringUtils.isNotBlank(searchText)) { boolQueryBuilder.should(QueryBuilders.matchQuery("title", searchText)); boolQueryBuilder.should(QueryBuilders.matchQuery("description", searchText)); boolQueryBuilder.should(QueryBuilders.matchQuery("content", searchText)); boolQueryBuilder.minimumShouldMatch(1); } // 按标题检索 if (StringUtils.isNotBlank(title)) { boolQueryBuilder.should(QueryBuilders.matchQuery("title", title)); boolQueryBuilder.minimumShouldMatch(1); } // 按内容检索 if (StringUtils.isNotBlank(content)) { boolQueryBuilder.should(QueryBuilders.matchQuery("content", content)); boolQueryBuilder.minimumShouldMatch(1); } // 排序 SortBuilder<?> sortBuilder = SortBuilders.scoreSort(); if (StringUtils.isNotBlank(sortField)) { sortBuilder = SortBuilders.fieldSort(sortField); sortBuilder.order(CommonConstant.SORT_ORDER_ASC.equals(sortOrder) ? SortOrder.ASC : SortOrder.DESC); } // 分页 PageRequest pageRequest = PageRequest.of((int) current, (int) pageSize); // 构造查询 NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder) .withPageable(pageRequest).withSorts(sortBuilder).build(); SearchHits<PostEsDTO> searchHits = elasticsearchRestTemplate.search(searchQuery, PostEsDTO.class);
数据同步
我们需要通过es来进行检索,肯定需要先有数据,现在我们的数据是存在数据库中的,所以要同步到es中。数据同步时我们要选择以哪一个数据源为准,如果当数据不一致时我们要知道以哪个为准,这里我们是从mysql存到es,所以以mysql为准。
数据同步分为
全量同步
编写一个一次性任务将mysql数据查出,增加到es中
增量同步
当数据是动态的时候,比如增删改时也需要进行同步,如果将全部数据查出后再同步会很消耗性能,所以我们需要同步最近方式过改变的数据。
- 定时任务
比如每分钟同步一次,每次同步查询前五分钟修改过的数据。为了防止同步失败所以设置为5分钟,同时可以增加监控告警和补偿机制
- 数据双写
当我们增删mysql的数据时,同时向es中同步本次操作,需要用到事务
- logstash数据管道
logstash他可以从数据源或应用中获取数据处理后再输出到数据库和应用中。

▼bash复制代码.\bin\logstash.bat -e "input { stdin { } } output { stdout {} }"
可以通过配置来实现
▼plain复制代码input { jdbc { jdbc_driver_library => ""D:\apache-maven-3.8.1\mav_resp\mysql\mysql-connector-java\8.0.29\mysql-connector-java-8.0.29.jar"" jdbc_driver_class => "com.mysql.cj.jdbc.Driver" jdbc_connection_string => "jdbc:mysql://localhost:3306/common_search" jdbc_user => "root" jdbc_password => "root" parameters => { "favorite_artist" => "Beethoven" } schedule => "*/5 * * * * *" statement => "SELECT * from post where 1 = 1" } } output { stdout(codec => rubydebug) }
- canal订阅同步
canal是alibaba开源的一款订阅数据库binlog的组件。其原理是mysql开启binlog后操作数据库会向binlog中写入操作内容,然后我们监听这个日志文件的变化就可以拿到改变的内容。canal是伪装成了mysql服务的从节点,利用主节点同步从节点的binlog文件,解析出我们可以看懂的数据。
快速开始:https://github.com/alibaba/canal/wiki/QuickStart++
优点:
实时更新,方便
缺点:
需要维护额外的组件,额外的学习成本
压力测试
jmeter:[https://jmeter.apache.org/](https://jmeter.apache.org/)扩展
1. 搜索词高亮显示项目总结
MI-Search是一个通用的搜索组件,是为了优化用户体验可以让用户在一个页面查询到多种类型的数据,并且可以让开发人员不用重复开发搜索功能,提高开发效率。后端可以轻松的接入多个不同的数据源,复用搜索功能。并且通过elasticSearch实现了分词搜索,高亮显示的示例,还配置了案例将mysql帖子表数据同步到Es上进行搜索。收获:
- 理解门面模式,适配器模式,注册模式
- 学习ElasticSearch概念,基本语法和SpringBoot Data ElastticSearch Java客户端,熟悉用法,有遗忘应该也可以凭借印象看文档使用。
- 全量同步,增量同步的数据同步的四种方式 (定时,双写,logstash管道,canal订阅binlog)
- 压力测试概念,工具 jmeter(主要就是查询多线程情况下接口的稳定,响应时长,qps)
