分布式搜索
DSL查询语法
DSL查询分类和基本语法
[DSL Query的分类]:
Elasticsearch提供了基于JSON的DSL(Domin Specific Language)来定义查询.常见的查询类型包括:
- 查询所有:查询所有数据,一般用于测试.例如:match_all
- 全文检索查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配.例如:match_query、multi_match_query
- 精确查询:根据精确词条值查找数据,一般查找keyword、数值、日期、Boolean等类型字段.例如:ids、range、term
- 地理查询:根据经纬度查询.例如:geo_distance、geo_bouding_box
- 复合查询:复合查询可以将上述各种查询条件组合起来,合并查询条件.例如:bool、function_score
[查询的基本语法如下]:
全文检索查询
全文检索查询,会对用户输入内容分词,常用于搜索框搜索:
- match查询:全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索,语法:
- multi_match:与match查询类似,只不过允许同时查询多个字段,语法:
[注意]:下面两种情况查询结果是一样的效果
精确查询
精确查询一般是查找keyword、
数值、日期、boolean等类型字段.所以不会对搜索条件分词.常见的有:
地理查询
- geo_bouding_box:查询geo_point值落在某个矩形范围的所有文档
- geo_distance:查询到指定中心点小于某个距离值得所有文档
相关性算分
复合查询:复合查询可以将其他简单查询组合起来,实现更加复杂的搜寻逻辑,例如:
- fuction score:算分函数查询,可以控制文档相关性算分(控制查询后结果的排序;比如你根据某个字段值分词查询后,你查出的结果中对应字段的词条与查询词条重合个数较高[相关性,其中相关性有三种算法],你的分数就越高,排名越靠前),控制文档排名.例如百度竞价(你给了钱,我就能提高你的打分将你的查询排名放在前面)
FunctionScoreQuery
使用function score query,可以修改文档的相关性算分,根据新得到的算分排序.
举例说明:
BooleanQuery
[BooleanQuery查询]:
[案例]:
需求:搜索名字包含"如家",价格不高于400,在坐标31.21,121.5周围10km范围内的酒店
搜索结果处理
排序
elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序.我们可以指定字段排序,这样就会取消掉算分可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等.
[案例1]:对酒店数据按照用户评价降序排序,评价相同的按照价格升序排序(评价是score字段,价格是price字段,按照顺序添加两个排序规则即可)
[案例2]:实现对酒店数据按照到你的位置坐标的距离升序排序
分页
elasticsearch默认情况下只返回top10的数据.而且如果要查询更多的数据就需要修改分页参数了.elasticsearch中通过修改from、size参数来控制要返回的分页结果:比如下面找的就是第990个数据到第1000条数据,由于ES的数据结构决定的只能先获取前1000条数据然后再截取第990-1000条数据
上面属于的是单点查询,由于企业是ES集群查询(集群中的每个单位属于分片),所以真正的查询方式如下:
深度分页的解决方案:
高亮
RestClient查询文档
快速入门
通过查询请求获取数据
结果以json的形式返回给我们了
对查询到的数据(响应)进行解析
查询请求和解析响应整合:
match、term、range、boot查询
[全问检索擦查询]:
match与multi查询:
[精确查询]:term与range查询
[复合查询]:boolean query
排序和分页
高亮显示
旅游案例
搜索、分页
[案例]:实现黑马旅游的酒店搜索功能,完成关键字搜索和分页,我们的hotel-demo项目中,自带了前端页面,启动项目以及服务器后我们可以看到:
具体的分析:
点击"搜索"按钮后,浏览器会发送一个请求,而请求的信息(请求的url地址、请求类型、请求状态和请求的端口号可以借助可视化工具看到)
除了请求的信息,请求携带的参数 (key[搜索框查询的内容]、page[当前页码数]、size[每页数据的展示量]、sortBy[按照什么排序]) 也可以通过可视化工具看到:
发送请求后,后端会有相应的返回值由响应(Long total[数据的总条数]、List<HotelDoc> hotels
[酒店数据])携带回来
[实现步骤]:
-
步骤一:定义实体类,接收前端请求携带的json参数(key、page、size和sortBy)
-
步骤二:定义controller接口,接收页面请求,调用IHotelService的search方法
说明:
(1)、定义一个实体类PageResult接收返回值
(2)、HotelController类
-
步骤三:定义IHotelService接口中的search方法,利用match查询实现根据关键字搜索酒店信息
service层中IHotelService接口与HotelService实现类处理查询逻辑的实现方法
IHotelService接口:
HotelService实现类:由于我们要实现DSL语句在java环境的操作,所以我们要使用RestClient,为了方便以后全局的使用,我们将其注入到启动类中
结果解析:
条件过滤
[案例]: 添加品牌、城市、星级、价格等过滤功能:
说明:点击标签后,请求则会携带标签内容作为参数传递给后台,后台根据标签内容筛选查询结果返回给浏览器
[步骤]:
- 修改RequestParams类、添加brand、city、starName、minPrice、maxPrice等参数
- 修修改search方法的实现,在关键字搜索时,如果brand等参数存在,对其做过滤
我附近的酒店
[案例]:我附近的酒店
说明:除了显示出酒店数据还得将该酒店离我的距离展示出来
[步骤]:
-
修改ReuestRarams参数,接收location字段
-
修改search方法业务,如果location有值,添加根据geo_distance排序功能.
-
显示每个酒店离我们当前位置的距离
根据我们以前在elasticsearch发送查询请求的结果来看,其实结果中的sort已经包含了距离值这个内容[==由于我们按照距离来排序,所以sort里面是距离的值,要是以后我们按照多个字段来排序,那么sort里面就包含多个字段数组 ==]:
因此我们要修改结果解析:
广告置顶
[案例]:让指定的酒店在搜索结果中排名置顶
说明:
[步骤]:
- 给HotelDoc类添加isAD字段,Boolean类型
- 挑选几个你喜欢的酒店,给它的文档数据添加isAD字段,值为true
- 修改search方法,添加function score功能,给isAD值为true的酒店增加权重
数据聚合
聚合的分类
聚合(aggregations)可以实现对文档数据的统计、分析、运算.聚合常见的有三类:
桶(Bucket)聚合:用来对文档做分组
- TermAggregation:按照文档字段值分组
- Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
- Avg:求平均值
- Max:求最大值
- Min:求最小值
- State:同时求max、min、avg、sum等
管道(pipeline)聚合:其它聚合的结果为基础做聚合
DSL实现Bucket聚合
[案例]:现在,我们要统计所有数据中的酒店品牌有几种,此时可以根据品牌的名称做聚合.类型为term类型,DSL示例:
演示效果:
[说明]:默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序,我们可以修改结果排序的方式:
默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以限定要聚合的文档范围,只要添加query条件即可:
DSL实现Metrics聚合
[案例]:我们要求获取每个品牌的用户评分的min、max、avg等值,我们可以利用stats聚合:
[演示示例]:
RestClient实现聚合
聚合查询品牌:
结果解析:
具体代码展示:
多条件聚合
[案例]:在IUserService中定义方法,实现对品牌、城市、星级的聚合
需求:搜索页面的品牌、城市等信息不应该是在页面有我们写死的,毕竟品牌和城市有成千上万种,我们应该通过聚合索引库中的酒店数据得来的:
[步骤]:首先在service中的业务实现接口类IHotelService中定义聚合方法
在HotelService类中实现接口的方法
带过滤体条件的聚合
[现象]:
当我们在搜索框中点击搜索时,浏览器会发送两个携带参数(参数如下)的请求(请求信息如下):
搜索请求:
聚合请求:
[案例需求]:
我们发现聚合请求携带的参数与搜索请求的参数一摸一样,可是为什么聚合要携带与搜索一样的参数呢?
聚合携带的参数是为了给聚合提供条件,和搜索请求携带的参数一样是为了点击搜索后聚合出来的结果是与搜索内容相关的,以便达到动态聚合的效果,示例如下图
点击价格范围,并且搜索虹桥后
聚合内容范围就会缩小:
[分析]:
[实现步骤]:
- 编写controller接口,接收聚合请求
- 修改IUserService#getFilter()方法,添加RequestParam参数
- 修改getFilter方法的业务,聚合添加query条件
自动补全
安装拼音分词器
[自动补全]:当用户在搜索框输入字符时,我们应该提示出与该字符有关的搜索项,如图:
要实现根据字母做补全,就必须对文档按照拼音分词.在GitHub上恰好有elasticsearch的拼音分词插件.地址:https://github.com/medcl/elasticsearch-analysis-pinyin
[安装步骤]:
-
解压
-
上传到虚拟机中,elasticsearch的plugin目录
-
重启elasticsearch
-
测试
自定义分词器
[问题]:
从上面分词的测试中我们可以看出以下问题:
分词器实现的内部结构:
具体实现:自定义的词库只能对当前索引库适用"/test是当前索引库",要想使用必须按照图二和图三的写法啊
图二:
图三:
[注意]:
[解决方案]:
DSL实现自动补全查询
completion suggester查询:
字段约束:
查询语法:
演示示例:
修改酒店索引库数据结构
[案例]:实现hotel索引库的自动补全、拼音搜索功能
[步骤]:
-
(1)、修改hotel索引库结构,设置自定义拼音分词器,其中completion_analyzer中的tokenizer"keyword"表示不分词将其转换成英文,具体内容看分词器的的结构
-
(2)、修改索引库的name、all字段,使用自定义分词器
-
(3)、索引库添加一个新字段suggestion,类型为completion类型,使用自定义的分词器
-
(4)、给HotelDoc类添加suggestion字段,内容包含brand、business
-
(5)、重新导入数据到hotel库
RestAPI实现自动补全查询
请求参数构造的API:
结果解析:
实现搜索框自动补全
[案例]:实现酒店搜索页面输入框的自动补全
说明:查看前端页面,可以发现当我们在输入框键入时,前端会发起ajax请求:
在服务端编写接口,接收该请求,返回补全结果的集合,类型为List<String>
[步骤]:
在controller类里实现getSuggestion方法,接收请求携带的参数并且处理
在IHotelService接口中定义getSuggeions方法
在IHotelService接口的实现类HotelService实现getSuggestions方法
数据同步
同步方案分析
[数据同步问题分析]:elasticsearch中的酒店数据来自于mysql数据库,因此mysql数据发生改变时,elasticsearch也必须跟着改变,这个就是elasticsearch与mysql之间的数据同步.
[解决方案]:
- 方案一:同步调用
优点:实现简单,粗暴
缺点:业务耦合度高
- 方案二:异步通知
优点:低耦合,实现难度一般
缺点:依赖mq的可靠性
方案三:监听binlog
优点:完全解除服务间耦合
开启binlog增加数据库负担,实现复杂度高
导入酒店管理项目
案例:实现hotel-admin项目作为酒店管理的微服务.当酒店数据发生增、删、改时,要求对elasticsearch中数据也要完成相同操作.
步骤:
- 导入课前资料提供的hotel-admin项目,启动并测试酒店数据的CRUD
- 声明exchange、queue、RoutingKey
- 在hotel-admin中的增、删、改业务中完成消息发送
- 在hotel-demo中完成消息监听,并更新elasticsearch中数据
- 启动并测试数据同步功能
[hotel-admin项目导入说明]:
Controller层:
application.yml配置文件:
页面展示:
声明队列和交换机
[分析]:根据分析案例,elasticsearch对于增和改属于同一类型的请求(DSL语句可以使用一样的),所以elasticsearch只要针对(增、改)和删除两种请求做出操作即可,因此我们只要声明两个队列(分别绑定关键字[bindingKey]hotel_insert和hotel_delete即可),具体模型如下:
[步骤]:
-
步骤一:在消费者项目hotel-demo中引入amqp依赖
-
步骤二:在hotel-demo中配置rabbitmq的地址信息
-
步骤三:新建一个MqConstants类,将交换机、队列、RoutingKey的名字声明为常量:
-
步骤四:在config目录下新建一个MqConfig类,在里面定义交换机、队列和绑定关系的bean,用来声明
发送MQ消息
- 步骤一:在消息提供者hotel_admin中同样加入(复制过来即可)交换机、队列和绑定关系常量
步骤二:同样在服务提供者hotel_admin中添加依赖
步骤三:同样在application.yml配置上配置rabbitmq地址:
步骤四:在Controller层实现发送MQ消息的代码
监听MQ消息
新增一个监听类HotelListener,编写监听业务
在IHotelService接口定义相应的方法,并在实体类完成对应的业务逻辑
ES集群
集群结构介绍
单机的elasticsearch做数据存储,必然面临两个问题:海量数据存储、单点故障问题.
- 海量数据存储问题:将索引库从逻辑上拆分为N个分片(shard),存储到多个节点
- 单点故障问题:将分片数据在不同节点备份(replica)
搭建集群
【说明】:
【步骤】:
建立es集群
集群状态监控
cerebro界面:
创建索引库
- 利用kibana的DevTools创建索引库
- 利用cerebro创建索引库
查看分片效果
集群职责及脑裂
elasticsearch中集群节点有不同的职责划分:
ES集群中的节点默认有这四种属性以及职责,但是实际开发中我们要根据节点类型的不同去为节点分配主机配置
elasticsearch中每个节点角色都有自己不同的职责,因此建议集群部署时,每个节点都有独立的角色:
默认情况下,每个节点都是master eligible节点,因此一旦master节点宕机,其他候选节点会选举一个成为主节点。当主节点与其他节点网络故障时,可能发生脑裂问题
为了避免脑裂问题,需要要求选票超过(eligible节点数量+1)/2才能当选为主,因此eligible节点数量最好是奇数。对应配置项discovery.zen.minmum_master_nodes,在es7.0以后,已经成为默认配置,因此一般不会发生脑裂问题
分布式新增和查询流程
当我们向某个节点分片(通过节点的ip和端口找到该节点)新增多条文档时,我们发信啊这些文档并不是存放在当前分片上,而是通过某一规则分别存储在各个分片上,这就体现了es集群分片的好处(而执行这一规则的节点责任是coordinating)
那么当新增文档时候,应该保存到不同分片,保证数据均衡,那么coordinating node如何确定数据该存储到哪一个分片呢?elasticsearch会通过hash算法来计算文档存储到哪个分片:
【分布式新增工作流程】:
【分布式查询过程】:
故障转移
集群的master节点会监控集群中的节点状态,如果发现有节点宕机,会立即将宕机节点的分片数据迁移到其他节点,确保数据安全,这个叫做故障转移。
【测试】:
停掉主节点es01
结果:刷新后
当我们重启es01后,分片又会恢复,但es01将不再是主节点了