Solr vs ElasticSearch

Solr和ElasticSearch到底有一些什么不同?我在网上搜索了一些文章,这些文章要么是列出一个表,详细地介绍两者什么功能有,什么功能没有(比较好的一篇博客),要么是从大类出发(其中比较好的一篇文章),比较两者的关注度,社区等等。但看完这些文章,还是没法解决我心中的疑惑。最近由于项目的原因,Solr和ElasticSearch都有所使用。最近又把solr和ElasticSearch的官方文档都过了一遍。对两者有了一些浅显的见解。所以在这里想跟大家分享下我的一些看法。在这篇文章中,我不会列出一个列表来说明两者的异同,而是抽出我觉得比较重要的几点来讲一讲。本文的对比基于Solr7.3和ElasticSearch7.4。两者都在快速迭代,可能有一些最新的进展我没有考虑到,同时我接触搜索引擎的时间不长,难免有一些错误或者我没关注到的点,欢迎大家指正。


相同点

两者都基于Lucene实现

** 这看起来像一句废话,因为知道这两个产品的人,一定知道Solr和ElasticSearch都是基于Luence实现的。但其实这句话蕴含了两层意思。**

** 第一层是****Luence支持的功能他们都支持,只是概念和用法上稍有不同。**Luence这个引擎已经非常强大,我们在使用搜索引擎时看到的绝大部分功能,Luence都可以实现。比如索引时的分词,查询时的切面(Facet),高亮,拼写检查,搜索建议,相似页面等等,其实这些都是Luence中实现的功能,Solr和ElasticSearch都只不过做了包装而已。Luence中不仅有倒排索引,还有TermVector这种每个文档的正排索引,还有DocValue这种列存结构,不同的数据类型支持了不同的搜索要求。举例来说,对于按某一个列排序的需求,如果只有倒排索引,拿到每个doc的ID之后再去分别做seek取出列值来做排序,DocValue这种按列存的结构可以很快地取出对应document的列值,而减少磁盘seek操作。同样,在TermVector中记录了一个doc中每个term的位置,这是实现关键词高亮的基础。

** 这句话第二层意思是****Luence做不到的事情,Solr和ElasticSearch必须自己想办法实现**。比如Luence是不支持Document的部分更新的。做为一个数据库+搜索引擎的混合体,Solr和ElasticSearch如果也不支持Document的部分更新肯定说不过去,所以他们两者都实现了先把原来的文档读出来再写的逻辑。当然,要读出原来的文档就要求文档的原始数据都在,因此Solr中的部分更新要求schema中所有字段要么是stored,要么有docvalue。而ElasticSearch中则比较激进,每个Document默认都会带_source字段,原始文档都会存在里面,不用任何配置就可以支持部分更新。另外一个Luence做不了的事情就是文档的立即可见性。Luence的Document一定要commit刷成Segment后,才能被搜索到。对于一个搜索引擎来讲,这无可厚非,写入的数据过段时间才能被查到非常正常。但是很多人是把Solr和ElasticSearch当数据库用的,如果写入的数据不能立刻可见,这对一个数据库来说是不可以接受的。所以Solr和ElasticSearch都实现了一个“Realtime Get”功能,即写入的数据,如果是用id去查,是立马可以查出来的。实现原理也很简单,Solr/ES里在index前都会先写log,面对Get请求时,Luence中不可见的数据,可以查log。另外一个有意思的例子是翻页。在Luence3.5之前是没有SearchAfter这个接口的,要实现翻页,Solr的早期版本只能全查出来,然后跳过offset条数据。可想而知,如果用户深翻,性能有多差。而当Luence开始支持SearchAfter,支持从某一个文档开始搜索,Solr也开始支持Cursor功能,给上次翻页的最后一个文档做个标记,下一页从这个文档开始查,大大优化了翻页功能。

不止于搜索

除了单纯基于Luence提供的搜索功能,Solr和ElasticSearch还提供了丰富的能力。比如说ElasticSearch的Aggregation能力。ElasticSearch一定是对他的Aggregation功能非常的自信,因为在官方文档中,Aggregation是介绍完基本概念,安装部署后的第一章。用户完全可以把ElasticSearch当成一个分析引擎来用,各种avg,sum,count以及各种聚合方式,都可以实现。Solr也同样提供了Analytics Component。两者的分析功能我都没有使用过,因此我没有发言权说谁的功能更加强大。ElasticSearch还提供了一个强大的scripting,可以用ES支持的几种脚本语言来写一些复杂的求值函数。Solr中也有streamExpression,自定义了丰富的语法和函数让用户直接写表达式来查询。当然,人见人爱的SQL功能也必不可少,Solr和ElasticSearch均支持SQL和JDBC访问。两者支持的SQL功能可能存在一些差异,在这里我没有细究。

分布式架构和高可用

Solr(Cloud)和ElasticSearch都是分布式架构,支持对索引进行分片,将索引分布到不同服务器上来实现scale out。同时,他们都支持给每个Shard来设置多个replica来实现高可用。Shard的多个replica都是主从架构,相当于一写多读。主从之间都是同步复制。因此在写入时,一定需要找到主replica,而读的时候,随机选择replica都能读到最新数据。同时,Solr和ElasticSearch还支持集群间复制,但两者的实现稍微有些不同,Solr的集群间复制采用推的方式,而ElasticSearch采用的是拉。Solr支持双向复制,从而能够实现双活,而ElasticSearch只能实现主向从的复制来做主备。此外,一些数据库常用的运维功能,如snapshot,backup&restore,两者都支持。


不同点

分布式的设计理念不同

** Solr在最开始的时候是单机版的,并不是分布式架构。当SolrCloud出现时,才有了分布式架构。Solr选用了Zookeeper来做分布式架构下的协调者。Solr中每一个节点都是对等的,Zookeeper的使用主要是用来存分片的路由信息,以及各个replica之间,overseer节点的抢主。Solr中唯一一个不同的角色就是overseer,相当于Solr集群中的master角色,所有的Collection creation等admin操作,都需要进过overseer节点。同时,overseer节点可以根据**AutoScalling框架的配置,在节点丢失和新节点加入时,做一些预设的操作。比如节点丢失时,为该节点上的replica自动再add一个新的replica,保持可用replica数为固定值。AutoScalling框架是Solr 7.0才加入的,从这里开始,Solr才有真正意义上的自动运维,在这之前,Solr的自动运维能力比较弱,就连balance集群,都需要手工去操作。

** ElasticSearch从一出生就是分布式架构设计。与Solr不同的是,ElasticSearch并没有依赖其他产品来做分布式,而是自研了一套Zen Discovery协议来做分布式协调。因此,ElasticSearch不像Solr(Cloud)那样依赖一套Zookeeper集群。Zen Discovery把节点发现,选主,广播等事情全都干完了。相对于Solr,ElasticSearch的角色更加丰富,除了有与Solr overseer节点类似的master节点,还可以配置只负责index写入过程中处理复杂ingest pipeline(比如分词,变换等等)的ingest node,以及不存数据,只负责Coordination(接受用户请求,发送到对应replica,然后聚合返回客户端)的node。当然,Solr也可以通过AutoScalling配置某个节点不放任何replica来达到Coordination node的效果,不过这个配置就复杂的多了。ElasticSearch自动运维能力比较强,通过简单配置,就可以实现集群的自动balance等运维操作。当节点宕机时,master node也会提升从replica为主,并增加一个replica来保持replica可用数。这些都是ElasticSearch的默认行为,而在Solr中,则需要自己去定义AutoScalling的框架,来配置这些行为。**

Solr能够深度定制而ElasticSearch更重于开箱即用

最近我solr和ElasticSearch的两个客户端都使用过,给我的感觉是ElasticSearch更加简单易用。有很多的功能ElasticSearch都已经内置,不要通过配置去定义。举个例子来说,在Solr中我要想定义一个名字为name,类型为string的field,我需要在managed_schema(xml)中配置两个东西:

1
2
<fieldType name="string" class="solr.StrField" sortMissingLast="true" docValues="true" />
<field name="name" type="string" indexed="true" stored="true" required="true" multiValued="false" />

也就是说,Solr预设的字段类型仍然需要自己去定义使用Solr中的那个类,比如上面的第一行定义了string这个类型使用了solr.StrField这个类。然后我才能指定id field的type为string。如果我想恶作剧把string定义为solr.IntPointField(int类型)来迷惑大家可以吗?当然可以。而在ElasticSearch中,各种类型都已经预定义好,我们只需要一个json的mapping来指定field的类型就好(keyword即不分词的string)。


1
2
3
4
5
6
7
8
9
10
PUT my_index
{
"mappings": {
  "properties": {
    "name": {
      "type": "keyword"
    }
  }
}
}

Solr的配置感觉上更加Geek,因为他一般都是直接配置java类。而ElasticSearch包装更好,各种类型都已经预制好。Solr的读写链路都可以深度定制,你可以在读写链路上增加各种Processor和Component来添加各种不同的功能。你甚至可以定义处理你请求的handler类。


1
2
3
4
5
6
7
8
9
10
11
12
<searchComponent name="terms" class="solr.TermsComponent"/>

 
<requestHandler name="/terms" class="solr.SearchHandler" startup="lazy">
  <lst name="defaults">
    <bool name="terms">true</bool>
    <bool name="distrib">false</bool>
  </lst>
  <arr name="components">
    <str>terms</str>
  </arr>
</requestHandler>

比如上面定义"/terms"路径将由solr.SearchHandler这个类来处理,同时在Search的component中加入了一个叫做solr.TermsComponent的组件。如果你愿意的话,你可以为每个Collection在访问“/terms”这个路径时,提供完全不同的行为。

再比如下面的配置可以定义一个文档在写入时需要经过的Processor(如果熟悉HBase的话,你可以认为就是HBase的Coprocessor)


1
2
3
4
5
6
<updateRequestProcessorChain name="add-unknown-fields-to-the-schema" default="${update.autoCreateFields:true}"
          processor="uuid,remove-blank,field-name-mutating,parse-boolean,parse-long,parse-double,parse-date,add-schema-fields">
  <processor class="solr.LogUpdateProcessorFactory"/>
  <processor class="solr.DistributedUpdateProcessorFactory"/>
  <processor class="solr.RunUpdateProcessorFactory"/>
</updateRequestProcessorChain>

** Solr的整条读写链路都是通过这种配置文件定义的,定制化非常自由,以至于如果配置不好,会把正常的读写链路给配置没掉。所以Solr在文档中警告,如果你在使用的updateRequestProcessorChain中没有配置RunUpdateProcessorFactory的话,写入请求是不会真正被执行的……**

** 而ElasticSearch很多功能都是开箱即用,并不需要用户去配置。Solr的配置太过于灵活,给了用户很多犯错误的可能,而ElasticSearch的设计哲学是尽量减少用户犯错的可能,ES对运行环境还做了诸多限制,以避免运行过程中出现一些莫名其妙的错误,因为很多用户并不是这些领域的专家,他们没法从这些错误中找到原因。比如说ElasticSearch在启动时会做memory check,系统是否限制了file descriptor数等等,甚至如果运行的是某个有已知bug的JVM版本,ElasticSearch也会拒绝启动。ElasticSearch在启动时还会用JarHell去检查加载的类里是否有同名类,我曾经在自己的测试工程中集成了ElasticSearch想来启动一个本地的ES集群,着实被JarHell恶心了一把,在一个大的Java工程里,各种不同的依赖,不出现同名类确实太难。花了半天按照JarHell的报错提示去exclude依赖之后终于放弃,JarHell检查还不能被关闭,我只能fake了一个空的JarHell类绕过检查。总而言之,ES降低了使用门槛,同时还极力避免用户犯错,对新手友好。**

Solr支持HDFS存储而ElasticSearch不能

** Solr能够支持HDFS做为存储是Solr的一大特点,on HDFS带来了存储计算分离的优势。比如说,在普通存储上,move一个replica从一个节点到另外一个节点意味着大量的数据拷贝。而如果on HDFS的话,move一个replica并不需要移动任何数据,每个节点都可以读到HDFS上的内容,另外一个节点只需要打开HDFS上的数据即可。当Solr on HDFS后,配上AutoScalling框架,其实只需要一个主replica即可(如果不是想分散读压力的话)。因为一台node挂掉之后,Shard在另外一个节点快速上线,不需要拷贝数据。当我要balance整个集群时,整个过程也非常快,因为只有逻辑上的shard在节点中流动,而数据在HDFS是不需要移动的。在这个点上,ElasticSearch没有on HDFS的能力,因此这些他都做不到。**

Solr支持Shard split而ElasticSearch不能

虽然说Solr/ES的shard是hash分片(根据doc id或者用户自定义的field),天生就可以分散热点。但是仍然可能存在某一些doc比较热,需要分散热点。或者说如果集群中加入一批新的机器,需要分更多的shard才能保证Collection能够利用到集群中的每一台。而Solr是支持Shard做split操作,能够将一个Shard分成多个。而ElasticSearch却不可以做shard的split,如果一个Index想要更多的shard,只能新建一个拥有更多shard的index,然后将数据迁移过去。为什么ElasticSearch不能做这样的操作?这是他们的路由策略决定的。Solr的分片定义了hash的范围,split时可以将范围分半,切分出两个子shard来分别负责这两段范围。而ElasticSearch的路由是hash后再对shard取mod,来决定落在哪个shard上,这导致如果新加了shard,取mod就会乱掉。ElasticSearch的shard不能分裂以为这在做规划时需要非常小心地预估自己的Index将来有多少数据,需要多少个shard。一旦估算错误,后期迁移需要大量拷贝数据。

ES支持功能和生态更为丰富

ElasticSearch的功能丰富程度确实令人咋舌,毕竟后面有一家非常强大的商业公司。为了吸引客户,什么事情都干得出来(褒义)。ELK套间在Log处理这块已经是业界通用的解决方案。同时,Elastic公司还通过X-Pack给不同层级的用户提供了尤为丰富的功能。而Solr背后虽然有一些商业化公司,比如LucidWorks,但总的来说还是没有Elastic知名,提供的解决方案也比较有限。我在这里列举一些ElasticSearch中比较优秀的功能:

  1. 对JSON友好:支持nested field,天生和JSON契合,而Solr中只支持nested docment。
  2. 支持Index Sorting:我觉得这个是在排序场景下的杀手级功能。如果用户的请求都会带有某个field的排序条件,ElasticSearch可以在Segment中不是按doc ID排序,而是按照这个field排序。从而在查询过程中,能够扫描前n条就可以获得快速获得结果集,从而提前完成查询。
  3. 支持Index的life cycle管理:例如超过多少天,自动删除Index,如果Index很hot,则增加replica。或者定时对index做一个snapshot等等。
  4. 支持时序数据降精度:ElasticSearch针对时序数据领域的重磅功能。能够支持配置rollup background job,对一些field数据做聚合,比如把小时数据聚合成天的存储在另外field或者Index中。
  5. 支持触发器:当特定的条件满足时,可以做出一系列事件,比如curl一个网页,从而实现类似数据库触发器的功能。

总结

总的来说,我感觉,ElasticSerach更像一个商业产品,而Solr更像一个软件。Solr的定制能力更强,几乎什么都可以配置。对于开发者来说,要实现一个新的功能,可以不用动Solr核心代码,而给Solr增加一些Processer和Component,然后通过xml配置服务器的行为。同时,Solr还提供了BlobStore,可以上传Jar代码来在Solr集群中部署这些新的插件(像不像HBase的Coprocessor?)。并且Solr独家的On HDFS能力为Solr提供了存储计算分离的便捷性,可以做到shard的自由移动而不用搬迁数据。Solr还支持分裂shard,这些能力都对运维友好,能够在扩容、宕机恢复方面会有更大的灵活性。而ElasticSearch竭尽全力地降低用户的使用门槛,用户可以非常快的上手。同时坚持不引入像Zookeeper这样的额外组件,也是为了降低部署难度。毕竟,ElasticSearch后面有一家强大的商业公司,从客户需求出发,在ElasticSearch上做了非常多丰富的功能,建立起了非常完整的生态,并拥有众多客户案例。对于一般的客户来说,一般会选择一个大而全的产品去满足他们的需求,因为在技术栈中引入一个新产品,本身学习成本和运维成本都会比较大。而ElasticSearch更加地契合了这些用户的需求,ElasticSearch入门简单,不仅可以搜索,还可以分析,同时可以处理时序数据,还在X-Pack中支持机器学习等等功能。我想,这也是目前ElasticSearch更火的原因吧。