前言
Solr 和 Elasticsearch 到底有一些什么不同? 我在网上搜索了一些文章, 这些文章要么是列出一个表, 详细地介绍两者什么功能有, 什么功能没有 (比较好的一篇博客 https://solr-vs-elasticsearch.com ), 要么是从大类出发 (其中比较好的一篇文章 https://logz.io/blog/solr-vs-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) 中配置两个东西:
- <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).
- PUT my_index
- {
- "mappings": {
- "properties": {
- "name": {
- "type": "keyword"
- }
- }
- }
- }
Solr 的配置感觉上更加 Geek, 因为他一般都是直接配置 java 类. 而 Elasticsearch 包装更好, 各种类型都已经预制好. Solr 的读写链路都可以深度定制, 你可以在读写链路上增加各种 Processor 和 Component 来添加各种不同的功能. 你甚至可以定义处理你请求的 handler 类.
- <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)
- <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>
S olr 的整条读写链路都是通过这种配置文件定义的, 定制化非常自由, 以至于如果配置不好, 会把正常的读写链路给配置没掉. 所以 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 中比较优秀的功能:
对 JSON 友好: 支持 nested field, 天生和 JSON 契合, 而 Solr 中只支持 nested docment.
支持 Index Sorting: 我觉得这个是在排序场景下的杀手级功能. 如果用户的请求都会带有某个 field 的排序条件, Elasticsearch 可以在 Segment 中不是按 doc ID 排序, 而是按照这个 field 排序. 从而在查询过程中, 能够扫描前 n 条就可以获得快速获得结果集, 从而提前完成查询.
支持 Index 的 life cycle 管理: 例如超过多少天, 自动删除 Index, 如果 Index 很 hot, 则增加 replica. 或者定时对 index 做一个 snapshot 等等.
支持时序数据降精度: Elasticsearch 针对时序数据领域的重磅功能. 能够支持配置 rollup background job, 对一些 field 数据做聚合, 比如把小时数据聚合成天的存储在另外 field 或者 Index 中.
支持触发器: 当特定的条件满足时, 可以做出一系列事件, 比如 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 更火的原因吧.
来源: http://www.tuicool.com/articles/rQfA3aN