您现在的位置是:首页 > 技术文章 > 详情<<文章列表阅读 择其善而从之——我为什么开始学习ElasticSearch ElasticSearch ES Solr 搜索引擎 wjyuian 2019-10-12 464 1 > 人生天地之间,若白驹之过隙,忽然而已。 ### 感慨 刚毕业与同学合租打网游的场景仿佛还在眼前,现在参加工作都已经九年了,这时间流逝的速度一点不亚于“过隙白驹”啊! 九年时间,我工作过三家公司,包括现在这家,三家公司的工作时间分别是一年、四年、4年。像我这个跳槽频率,应该可以算低了,特别是我们这一行。而且,其中有八年时间我都专注于Java领域的搜索引擎开发;往细节说,就是专注于垂直领域的基于Solr的搜索引擎开发。 所以,在“Solr应用于垂直搜索”这个领域,我应该可以算得上专家了,至少时间上差不多够到专家门槛了。 来现在这家公司之前,面试过几个公司,对我的技术和业务能力还算认可,不过他们都要求我转ElasticSearch。关于这个ES,我也是听说过,只是没用过。所以我问他们,为什么选这个框架而不是Solr(之所以这么问,有一部原因是我一直使用的Solr居然被人看不起)?是基于数据量考量还是基于功能考量。大部分人都没有颇具说服力的理由,主要的原因有以下几条: 1. ElasticSearch 是一个分布式搜索引擎框架,分布式又是互联网热门词汇 2. ElasticSearch 实时性比较好 3. ElasticSearch 支持的数据量更大 4. ElasticSearch 查询性能更高,尤其是大数据量的时候 但是一问具体有测试报告或者自己做过测试没有,都说没有。问号脸??? **没有调查就么有发言权。** 当时针对这几个理由,我也无力反驳,谁叫我不懂ElasticSearch呢。 ----- ### 理性分析 不过话说回来,为什么我遇到的公司的技术负责人选技术这么随意呢?ElasticSearch 能搭上分布式的船就选它?分布式自带大数据属性,所以就是一个互联网公司了?而且部署了支持大数据的分布式搜索引擎的互联网公司? 暂且不纠结是不是面试官不屑于跟我说明具体原因,下面让我来分析分析上面的几条原因。 #### 分布式 这没什么好说的,ElasticSearch天生就是分布式的。 而Solr是以单机版出道的;后来出了Solr-cloud,需要手动维护节点;现在Solr也学习了ElasticSearch的优点,cloud版本就是天然分布式,部署和维护也更方便了。 #### 实时性 这是Lucene的能力,而不是ElasticSearch或者Solr的。在我的知识范畴里面,Lucene(4.7.2)本身是没有索引更新能力的,都是先删除再插入,这应该是跟倒排链表数据结构有关。 官方文档这样说的: > In either case, documents are added with addDocument and removed with deleteDocuments(Term) or deleteDocuments(Query). A document can be updated with updateDocument (which just deletes and then adds the entire document). When finished adding, deleting and updating documents, close should be called. 要说实时性,必须先简单介绍下Lucene搜索工具包支持的能力和概念。 > Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎 **工具包** ,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。 > Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene提供了一个简单却强大的应用程式接口,能够做全文 **索引** 和 **查询** 。 > 在Java开发环境里Lucene是一个成熟的免费开源工具。就其本身而言,Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库。人们经常提到信息检索程序库,虽然与搜索引擎有关,但不应该将信息检索程序库与搜索引擎相混淆。 索引和查询就是全文搜索引擎两个最重要的组成模块,在Lucene中分别提供了`IndexWriter`和`IndexReader`两个类,让我们可以轻松实现这两个模块的读写。 ##### IndexWriter 它负责一个索引的创建和维护,所以它会打开一个(这里的一个指一个完整的索引包含的全部索引文件,而不是单独一个索引文件)已有的索引文件,或者创建一个新的索引。具体有三种模式、创建(CREATE)、更新(APPEND)、创建或更新(CREATE_OR_APPEND),具体定义见枚举OpenMode(org.apache.lucene.index.IndexWriterConfig.OpenMode)。 文档中还提到,创建一个IndexWriter(打开一个索引,就是将索引加载到内存)时会给索引文件目录创建一个lock文件,也就是上锁,此时如果尝试在同一个目录创建另一个IndexWriter对象时,将会抛`LockObtainFailedException`错。所以Solr配置多个Core的时候是分文件夹的。 所以,IndexWriter负责的是将索引数据持久化到磁盘。 ##### IndexReader 这是一个虚拟类,有很多不同功能的子类,主要分为两大派系:AtomicReader和CompositeReader。 看类名,应该就知道AtomicReader是负责对索引进行原子操作的类,负责访问实际索引文件内容的,比如存储的字段定义、文档值以及term信息。 CompositeReader是一个复合类,会包含多个不同功能的Reader,进行组合使用,本身是不能直接访问索引文件内容的。 比如,CompositeReader持有多个AtomicReader实例,通过这些AtomicReader实例来进行索引文件的读取。 ##### Writer和Reader的关系 当一个索引正在被IndexReader使用时,也可以同时被IndexWriter打开。 **不过IndexReader持有的索引版本是打开时的快照版本,如果此时IndexWriter对索引进行了更新(先删除再新增,下同),IndexReader也不会看到这部分更新的数据,除非IndexReader重新打开这个新的索引。** 所以,一个索引更新(IndexWriter写入)想马上被IndexReader发现(说通俗点就是被用户感知),就必须将索引文件重新加载到内存,而加载一次索引的开销非常大(IO开销),具体耗时由索引文件的大小决定。 为了在加载索引文件过程中,系统还能提供查询服务,所以必须同时保持之前的IndexReader对象,直到新所以文件加载完成并替换掉旧的IndexReader对象。 ##### IndexReader支持实时性 简单说说第一种情况,称为过程A: 1. 初始化加载索引文件,生成一个IndexReader对象记为R_1 2. 此时发生数据更新,那么先生成一个IndexWrter对象,记为W_1,然后由W_1进行数据更新操作(如果是需要删除的话,会同步从R_1中进行删除),生成新的索引文件 3. 系统重新加载索引文件,包含刚刚W_1生成的新文件,记为R_2;在R_2完全加载成功之前,内存中必须同时存在R_1和R_2,由R_1继续提供查询服务 4. 当R_2加载成功,则会替换掉R_1,向外提供查询服务,那么此时对于用户来说就能感知到W_1更新的数据了 不过过程A有个很大的问题就是,如果R_2加载时间比较长,那么从W_1更新数据到R_2 加载完成并替换掉R_1的这段时间内,用户依旧感知不到数据更新,那么就谈不上实时性了,所以有了过程B: 1. 初始化加载索引文件,生成一个IndexReader对象记为R_1 2. 此时发生数据更新,那么先生成一个IndexWrter对象,记为W_1,然后由W_1进行数据更新操作,生成新的索引文件 3. 同时在内存中生成一个只包含更新数据的R_2,后续新增的数据都会同步在R_2中进行维护。此时由R_1和R_2共同向外提供查询服务,两者的并集就是完整的数据 4. 系统重新加载完整的索引文件,包含刚刚W_1生成的新文件,记为R_3(内存中的R_1和R_2无法合并,必须通过重新加载)。在R_3完全加载成功之前,内存中必须同时存在R_1、R_2和R_3,且由R_1和R_1共同提供查询服务 5. 当R_3加载成功,则会替换掉R_1,系统同时丢弃R_2,最终R_3向外提供查询服务,那么此时对于用户来说就能感知到W_1更新的数据了 整个过程B中,查询服务提供者变化是: R_1 ---> (R_1 + R_2) ---> R_3 其中从R_2+R_1过度到R_3的过程我们可以称之为“搜索预热”,内存占用率其实会很高,最高的情况将占用一个完整索引所占用内存的两倍大小。 再极限的情况,如果上一个搜索预热过程没有完成,又发生了下一个预热,系统将会开始下一个搜索预热过程,依次循环。所以,内存中会有多个过程B存在,内存占用率可想而知。这就是近实时搜索(Near Real Time Search)的代价。 ##### Solr中的实时性 在Solr的solrconfig.xml配置文件中,就有一项针对近实时搜索内存占用率的参数配置: ```xml 2 ``` 这个参数值指的就是内存中同时存在过程B的数量上限,一旦超过就会报错,内存中丢失一部分数据,磁盘中的索引数据不受影响。 关于Solr的实时搜索触发、配置、自动软提交、硬提交等内容,将会在另一篇文章中进行详细介绍。 所以,ElasticSearch的实时性与Solr的实时性搜索是一样的,并没有优劣之分,它们都是继承自Lucene,而且代价都很大。 #### 数据量更大 分布式的ElasticSearch支持的数据量确实更大,毫无疑问。但这是相对于单机版Solr来说的,分布式Solr-cloud数据量也是一样的支持。 我说一下个人的经验数据吧。 在使用单机版Solr时,我目前达到的最大数据量在千万级,具体是960W,而且单索引占用内存的大小为40GB。所以,在千万级数据量且索引大小比较大的情况下,单机Solr完全能支持,而且搜索性能也是比较高。这一点,我在另一篇搜索文章[《搜索引擎入门——启动第一个Solr应用》](https://oomabc.com/articledetail?atclid=f9b37293ec184ab6ad4d672327057dd7)中关于“关于版本的选择”章节有介绍。 所以,根据应用场景的当前数据量以及未来可期的数据量,可以自行评估,进行合理的选择。毕竟单机Solr的可维护性是远远高于分布式版本的Solr和ElasticSearch的。 #### 查询性能 脱离场景和数据量谈性能就是耍流氓。 ### 择其善者而从之 作为开源搜索引擎的明星框架,ElasticSearch必须是尤其优势的地方。除了更方便的集群管理,它的管道(aggregate)数据统计功能是非常非常非常强大。注意,我用了三个非常。 Solr的facet功能与之相比,简直小巫见大巫,不过场景不同,本身facet就是很消耗性能的一个功能。新版本Solr应该也有代替方案了。 之前使用过MongoDB的group和aggregate功能,虽然也很好用,但是其性能是远远不及ElasticSearch的aggregate的。 所以,我准备从零开始,学习下ElasticSearch的使用。学习方面主要有: + 基础查询语法 + 统计aggregate功能 + 集群部署、管理和维护 + 分词器接入,词库动态加载 + 同义词模块 --- 相关文章 为什么会有这个博客以及搭建的简略步骤 搜索引擎进阶——IK扩展之动态加载与同义词 SpringBoot2从零开始(一)——项目启动 SpringBoot2从零开始(二)——多数据源配置 应用算法学习(一)—— TopN算法 SpringBoot2从零开始(三)—— rabbit MQ Java网络编程之Netty框架学习(一) Java网络编程之Netty学习(二)—— 简单RPC实现 Java网络编程之Netty学习(三)—— RPC的服务注册、发现、降级 搜索引擎进阶——solr自定义function 栏目导航 关于我 不止技术 工程化应用(23) 技术学习/探索(32) 自娱自乐(2) 还有生活 随便写写(1) 娱乐/放松(1) 点击排行 SpringBoot2从零开始(二)——多数据源配置 搜索引擎进阶——IK扩展之动态加载与同义词 从零开发参数同步框架(二)—— 前期准备之工具类 Nginx的nginx.conf配置部分解释 springMVC中controller参数拦截问题处理 Maven项目一键打包、上传、重启服务器 微信小程序深入踩坑总结 微信小程序的搜索高亮、自定义导航条等踩坑记录 标签云 Java(19) 搜索引擎(13) Solr(7) 参数同步(6) SpringBoot(4) ES(3) ElasticSearch(3) JVM(3) Netty(3) Spring(3) mongoDB(3) 设计模式(3) Curator(2) Docker(2) Dubbo(2) 大家推荐 魔神重返战场!厄祭战争的巴巴托斯:第四形态 搜索引擎入门——Solr查询参数详解以及如何使用Java完成对接 来聊一聊这个被淘汰的图片验证码 搜索引擎入门——聊聊schema.xml配置 搜索引擎入门——启动第一个Solr应用 君子性非异也,善假于物也——功能强大的Postman 择其善而从之——我为什么开始学习ElasticSearch 实现一个关于队列的伪需求是一种怎样的体验