Elasticsearch篇面试题
# Solr和ES的差距在哪?
面试话术
一句话讲,solr的实时性偏弱,ES的实时性强,在高并发地写入数据时,需要频繁的构建索引库,而solr的索引库构建过程会影响查询效率,ES的索引库重建过程不会影响查询效率,所以ES的实时性会高于solr。
# ES比MySQL的优势在哪里?
面试话术
- MySQL的查询海量数据时,查询的字段一般不会设置索引,比如一些标题名称之类的,并且模糊查询会使索引失效,导致搜索效率比较低,而Elasticsearch采用倒排索引法检测数据,效率高很多。
- MySQL的搜索功能比较弱,只有like这种模糊搜索,而Elasticsearch拥有大量复杂搜索的API,更加适合数据搜索场景,我觉得最具有特色的有这几个,首先他能够做到地理位置检索,甚至能通过sort排序计算出查询范围内的每个文档距离当前坐标的距离,以及平时的网站使用中最常见的关键字高亮展示啊、拼音搜索啊、自动补全啊、这些ES都能轻松实现。
# 请说说ES倒排索引原理?
面试话术
- 首先,Elasticsearch将文档数据进行索引构建。将文档数据需要分词的字段内容使用分词器进行分词,并记录每个词条和原文档的出现位置和出现频率等信息,构建出文档的索引库。
- 然后,用户搜索时,可以对关键词进行分词,使用分词后词条来匹配索引库,在索引库匹配到记录后,通过文档位置频率信息,反查具体的文档数据。
# 请说说什么是分词器?ES有哪些常用的分词器
面试话术
分词器是Elasticsearch用于对内容进行分词的工具(程序),Elasticsearch内置许多分词器,默认使用Standard标准分词器,而标准分词器对中文支持并不好友(因为它对中文进行单字分词)。
所以在开发中进行中文分词时我们一般使用第三方的ik分词器,ik分词器内置有ik_smart和ik_max_word算法,ik_smart是最小分词器法,ik_max_word是最细分词法,不过一般最小分词法就能应对大部分的需求了,所以最细分词法其实用的不是很多,毕竟不必要的过细分词也会影响ES的查询性能。
# 你们项目的日志怎么管理的?
面试话术
我之前待的公司上线的项目,通常都会由运维那边搭建日志分析平台,我记得上家搭建的是ELK平台(Elasticsearch+Logstash+Kibana),这个平台会自动采集项目运行的日志,并且进行分析,运维人员只要到Kibana查看日志即可,当然ELK平台也可以设计一个告警规则,当系统出现异常时,自动给运维实时通知(短信+电话)
# MySQL、Redis、MongoDB、ES各自的优势?
面试话术
- MySQL:是关系型数据库,数据存储于磁盘。有复杂表关系(一对一,一对多,多对多),并且有完善事务机制(ACID),方便我们对数据的一个分类,以及增删改操作的管理。MySQL一般是用于存放完整的数据,Redis、MongoDB、ES的数据来源一般都是MySQL。
- Redis: 非关系数据库,数据存储于内存。由于内存还是比较珍贵的资源,从开发成本上来说,为了尽可能的节约成本,Redis的话一般建议只存储热点(用户查询频率极高)的数据,且数据量相对小的数据。比如:秒杀的库存量 、商品的价格、标题、手机验证码之类的,关于库存量的话,因为并发下库存量需要频繁的更新,一般来说是不建议建立缓存的,但由于是秒杀环境下,库存量的访问频率非常高,我觉得还是有这个必要的。
- MongoDB: 非关系数据库,磁盘。MongoDB适合相对高频擦写(增删改)的海量数据。 比如评论。
- Elasticsearch: 非关系数据库,磁盘。Elasticsearch适合海量数据的复杂检索。ES的应用就比较广了,基本有这个海量数据的搜索需求,ES属于首选了。
效率: Redis > MongoDB /Elasticsearch> MySQL
# ES中match和term检索的区别?
面试话术
match是全文检索,会分词,用在text类型,流程是:
- 对搜索的内容进行分词,得到词条
- 通过词条匹配倒排索引获取文档ID
- 最后通过文档ID查询具体文档记录进行聚合(并集或交集)
term是精确匹配,不分词,用在keyword,boolean,日期,数值等类型,流程比match少掉分词的步骤:
- 使用搜索内容直接匹配倒排索引获取文档ID
- 然后通过文档ID查询具体文档记录进行聚合(交集或并集)
一般match的话会用于我们常见的搜索栏的数据,也就是所谓的关键字,而term一般用于大体的分类,比如分类、品牌、日期、价格范围之类的。
# 请问ES中如何实现多条件检索?must和filter的区别是什么?
面试话术
ES的多条件检索要依赖布尔查询,布尔查询就是用来做子查询的嘛,每一个子查询都是一个条件,然后布尔查询有四种:must、must_not、should、filter,其中must和should都是参与算分的,must_not和filter都是不参与算分的。
一般来说当我们不希望这个条件的添加影响排序的话就将这个子查询会放在filter里,比如我们通过搜索栏的关键字查询了一个结果,进行一个排序,然后选择分类的时候,我们一般是希望能在关键字检索的结果的基础上进行一个过滤,所以就会将这个分类的子查询放在filter中。
而且参与算分的字段越多,ES查询的性能也就越差,所以应该尽可能减少参与算分的字段,一般的做法是搜索栏的关键字使用全文搜索,也就是must查询,参与算分,其他过滤条件都放filter,不参与算分。
# 请问ES如何实现搜索附近的酒店?
面试话术
是这样的,酒店信息在数据库里有一个经纬度的字段,代表这个酒店的一个坐标,然后前端会传一个当前用户的坐标过来,具体这个坐标是怎么拿到的我没太去了解,好像是调用了高德的接口,总之用户的当前坐标前端会传过来,然后有了用户当前坐标和酒店的坐标就可以通过ES的sort排序来实现了,通过排序geo_distance,就是以地理位置排序,RestAPI提供了一个接口来操作这个DSL语句,我记得是叫geoDistanceSort,将当前用户坐标传入这个方法中,就可以构建这个DSL语句,然后这个地理位置排序就是通过比较每个文档的坐标与当前坐标的距离进行排序的,而且我们可以在响应结果中拿到这个距离,不过默认的话是倒序排序,就是远的会在前面,所以需要设置一下order为asc进行一个升序排序,就能达到一个搜索附近酒店的效果了。
# 请问ES如何实现竞价排名的?
面试话术
首先ES的默认排序是通过分数的倒序来进行排序的,分数的计算主要依赖于一个算法,现在ES默认是使用BM25算法来进行算分,这个算法在5.1版本之前用是的TF-TDF算法,TF-TDF算法分为两个算法,TF和TDF,TF计算的是词频,TDF计算的是一个逆文档频率,词频,字面意思,就是词条在这个文档出现的频率,频率越高TF就越高,逆文档频率的话就是说,这个词条在所有文档中出现的越少,TDF就越高,可以理解成一个稀有词条的计算,物以稀为贵嘛,词条越稀有分数越高,然后BM25就这个TF-TDF的优化版,他优化了单个词条的算分上限,减少了某个词条分数特别高的情况,让整体的一个分数曲线更平滑了。
然后基于这个算分算法,排名的提升我们可以通过 function_score 函数来实现,里面有一个filter过滤条件,就是过滤哪些文档需要提分,通过weight和boostmod进行一个文档的加权,从而达到竞价排名的效果。具体说的话,比如文档里有一个是否投放广告的字段,以及投资费,然后就可以通过是否投放广告来过滤结果,然后以投资费为加权量进行加权提升排名。
竞价排名功能建议谨慎写入简历,因为会出现需要竞价则说明有很多商家入驻网站,规模很大才有这种需求
# 如何实现拼音搜索?
面试话术
首先得在Elasticsearch安装拼音分词器的插件,因为拼音分词器只是把词的每个字进行转换拼音,无法实现对一个词的拼音转换,不太满足项目的需求,所以还得结合ik中文分词器设计自定义分词器,建索引库的时,在settings的analysis下配置自定义分词器,先进行ik分词再进行拼音分词,也就是将ik分词器分词后的结果再进行一次pinyin分词,这样不管我们检索的时候搜的拼音还是中文都能定位到文档位置。
然后当时我们这里发现了个问题,就是如果在搜索时也使用自定义分词器的话,他会对我们搜索的词也进行pinyin转化,就比如我输入搜索地名绵阳,我希望是只查出绵阳这个地方相关的文档,但除了绵阳却还搜出了动物的那个绵羊相关的文档,就是出现了同音字的问题。
最后呢,是在设置索引库字段映射时,设置构建索引库使用自定义分词器,检索时,使用ik分词器即可,就不再进行拼音分词,就是设置search_analyzer来指定搜索时的分词器。
# 如何实现搜索关键词自动补全?
面试话术
要在索引库添加一个type为completion类型的字段,就是补全类型,然后存储的数据是一个数组,也就是补全联想的词都存在这个数组中,搜索时使用suggest搜索,搜索类型选择completion,就会根据搜索内容从头开始匹配联想字段,同样也能设置分词器进行分词,查询分词的结果定位到文档中的这个字段,将联想词数组返回。
其实这个查询也是能把文档的详细信息查出来的,但我们只要这个字段的信息,用于填充前端搜索栏类似下拉框的那个位置的内容,所以只返回这个联想词列表就行了,然后前端调用的话就是每当更新搜索栏内容时都发送一个请求,来请求这个列表。
# 请说说ES与MySQL数据如何同步?
面试话术
我知道的同步方案有三种:同步调用、异步调用、binlog监听。
同步调用的话就是在service层编写业务代码,在数据库添加成功后调用RestHighLevelClient发送请求到ES。这种优点就是实时性高,但是代码耦合度也高,而且会使主线程阻塞,并发度低。
异步调用的话就是通过MQ来进行通知ES客户端发送请求到ES,比如在生产者方增删改数据后发送数据ID到rabbitMQ,然后在消费者方编写监听类,监听消息队列,进行对应的操作。
binlog监听这个我也只是了解过原理,没有实际运用过,我记得这种方法就是监听mysql的日志,而且要开启这个功能,结论上来说就是会加大mysql性能的消耗,好像不太建议这样做来着。
# 请问ES的脑裂问题如何产生?如何解决?
面试话术
脑裂就是指一个ES集群中出现一个以上的master节点,导致集群分裂,出现异常。出现脑裂的原因,我能想到的有这些,首先有可能是网络延迟嘛,master节点和slave节点间出现网络延迟或阻塞,导致slave节点没能即时得到master的响应,因此判定master节点挂了,然后就选举出新的master,就导致脑裂了,然后还有比如master节点负载过重啊、master节点设置的JVM内存太少,GC的时候被回收掉,造成ES进程失去响应,总之主要的原因都是因为master节点没能及时的响应slave嘛,出现假死之类的。
所以解决方案也就很明确了,比如调高ES节点的通信超时时间,默认是3秒,可以调大一点,然后让master职责单一,设置nodes.data=false只做路由不做存储,提高master的一个负载性能,以及扩展ES分配的内存,默认1G,也可以通过那个什么Xms参数来设置,然后还可以设置一下选举票数,ES7.0默认是过半票数,ES6和ES5是固定2票。
# 你有遇到过批量导入大量数据到ES吗?
面试话术
有过一次,当时好像是导十几万条数据来着,我们开始的时候试了一下一次性导入,果不其然的就超时了,ES服务器那边直接挂了,然后就分批导,一次1w条,搞了个循环,觉得还是慢,然后就弄了个线程池,异步的去提交批量导入请求,一直调那个参数,最后就是调到6 7 个线程的时候比较快,十来秒可以导完。