白话 IT 之 聊聊搜索
Square 是一家很神奇的技术驱动的公司。这个公司的文化很独特,就工程师文化来说,早期 Square 在技术上还是比较大胆和激进的。
为什么这么说呢?举几个例子。虽然 Square 的核心产品是信用卡读卡器,但 Square 尝试开发过的产品真的很多,虽然不是每一个产品都长久地活了下来。比如电商平台、电子钱包、Payroll 系统,等等等等。另外 Square 早期有一批很 Nerdy 的技术人,包括 Rails 的 Contributor、Java Guice 的发明者等等。并且 Square 从不打怵使用新技术,比如 ElasticSearch、Kafka 等,在版本还没有稳定的时候,Square 已经在产品上使用了。而且因为我们是自己的 Data Center,没有使用 Amazon。所有的配套服务,包括 Deploy 以及 Production 的工具和环境,一套套系统全部都是 Build in House。以上种种,可能和我们创始人 Jack 本身是技术出身有一定的关系。
虽然说这些对于一个公司的发展不一定完全都是好事,但是对于工程师来说,却是一个绝佳的成长环境。一来有机会接触到新技术、或者技术的最前沿。二来因为不停的做新产品,所以早期的时候几乎每个项目都是两三个人从头到尾完全自己搭建。
我在 Square 做过两个大项目,一个就是和前 Google 员工 Ken 两个人一起搭建了 Square 的搜索后端。另一个就是和 Eric 一起做了 Square Store 这个电商平台,主要处理所有 Square 软硬件销售的支付流程。
虽然后来在支付这条路上走了下去,当时一年左右做搜索的经历,还是蛮有意思的。所以想整理一下,分享给大家。当然,一年的经验离资深还差得很远,所以可能说的东西就入不了专家的法眼,因此本文称为 “白话”。
基本概念
搜索说白了就是从已有的数据和信息里找到满足用户条件的一些匹配。
拿最简单的数据库来说,完成用户对数据库的搜索,不外乎这样几个概念:数据存储格式,也就是 Table Schema;新数据的写入;对 Table 的查询;其中又包括 Indexing 来对部分查询的 Pattern 进行性能上的优化;另外可以根据某些 Column 的值对查询结果进行排序。
搜索引擎从基本概念上来说,也是极类似的。例如常见的 Apache Solr 和 Elasticsearch(以下简称 ES),这两者都是建立于 Lucene 之上的,且最核心的功能很类似。(Lucene 其实就是一个搜索引擎 Library,有一堆 Jar 文件,并提供一个 Lucene API 接口。)因为直接用 Lucene API 相对来说更灵活,但是需要更多的 Engineering Effort 才能使用,所以很多地方都是使用 Solr 或者 ES,两者都是基于 Lucene 之上添加了很多可用 Feature 的一个封装。
Indexing
Solr 和 ES 的 Schema 也可以看成定义数据的存储格式和 Structure。这样,当你有新的数据需要存到你的可搜索数据集的时候,就需要把原始数据转化为 Solr 和 ES 文档定义的数据格式。这个过程通常称为 Indexing,或者 ETL。ETL 是 Extract - Transform - Load 的简称。实际应用中,很多时候,搜索的数据源来自于多个系统或 Service,格式和数据结构也就都不相同。Extract 就是从这多种多样的数据源中解析出相关的 Field 和 Value,并可能对数据进行一定的校验。Transfer 就是把 Extract 出来的数据信息转化成搜索引擎的文档存储格式。可能会对一些数据进行组合或聚合,也可能进行一定的转换或计算。最后处理好的文档就可以按照 API 可以接受的格式 Load 到搜索引擎的文档中。
ETL 的过程虽然说起来简单,也就是搭建数据 Pipeline,但是实际中会有很多细节需要考虑和实现。就拿 Extract 来说,怎么从数据源拿数据呢?作为数据源的 Service 可能会实时地 Publish 一些数据更新,这个时候,需要一些类似于 Trigger 或者 Feed Publisher 的机制来保证这样的数据更新可以被搜索引擎的捕捉到。最常见的有三种方式。一是通过加 Trigger,数据变动时直接触发 API Call;一是 Feed 模式;一是使用 Kafka 这样的 Log 机制。而后两种都可以使用 Pull 或者 Push 的方式来触发 Indexing。
这说的是实时 Indexing。而搜索中,因为各种各样的原因(例如早期 ES 的 Re-sharding),往往需要从 0 开始重新 Index 所有的数据。这样,所有的数据源就需要一定的 Persist 机制,把所有数据变动的历史存储下来。Full Reindex 的时候,就可以从这些 Persisted 数据中,重新通过 ETL 产生搜索引擎的文档。
Transform 主要是数据转换,通常比较简单。Load 也就是 API Call,通常是固定的处理方式。但是很多系统需要考虑负载、一致性、和扩展性,因此从 Infra 的角度来说,也需要一定的 Tuning 来保证实时无错的 Load 数据到搜索后端。
Querying
Query 其实最重要的两个概念,我觉得就是 Match 和 Score。Match 就是找到所有满足 Query 条件的数据。Score 就是根据应用的需求,制定一个对结果排序的规则。
Matching 的过程有的时候也称为 Analyze 的过程。通常的做法是 Tokenizing 加 Filtering。Tokenizing 就是把数据划分成一个个不可分割的小单元,然后通过对这些小单元的组合和 Filtering,找到适合需求的所有数据。
Score 常见的也有两种,一种是搜索前静态 Score,也就是和 Query 无关,数据的不同 Field 预先制定一个权重。一种是搜索后 Score,通常和 Query 的具体内容相关。各种不同的 Score 机制可以相互组合使用。而如何调整 Score 函数,让期望的搜索结果出现在比较靠前的地方,则是很多产品的搜索开发中需要致力的地方。很多时候会使用机器学习等方法帮助调整 Score 函数的参数。
Performance and Scalability
除了上面说的两个最基本的功能:Indexing 和 Querying,搜索在实际应用中随着数据的不断增加和 Query 的逐渐复杂化,对于 Performance 和 Scalability 的要求往往也是不断提升。
不同的搜索引擎如 Solr 和 ES 会提供不同的机制用于方便定制不同的 Query。两者对于 Scalability 如分布式搜索的处理也不尽相同。还有一些对灵活性或性能要求更高的地方,往往又会基于底层 Lucene,撇开 Solr 或者 ES,定制自己的搜索系统。狼蚁网站SEO优化几个部分将进一步细化对其的讨论。
Solr 和 ElasticSearch 的对比
历史背景
Solr 问世于 2004 年,但是一开始只是某公司的内部项目。2006 年公开发布其源代码,2007 开始应用于一些高流量的网站。2008 年 1.3 版本的问世增加了一定的分布式搜索功能,例如可以做 Sharding。但是有很多功能和特征上的局限性。2010 年,ES 问世。ES 早期在各种搜索功能、社区、品牌、成熟度上都远远不及 Solr。但是因为它是针对分布式搜索而设计的,并且很快吸引了很多用户且不断迭代,因此很快在该领域成为 Solr 的有力的对手。虽然 2012 年 Solr 发布了 SolrCloud 对分布式搜索有了更好的支持,但是一来这时候 ES 已经在市场上站住脚,二来 ES 后起却在设计上致力于解决分布式搜索,因此很多需要分布式搜索的地方 ES 依然成为首选。
主要区别
有很多人对两者做过对比。例如:http://solr-vs-elasticsearch.com,从 API 、Infrastructure、Indexing、Searching、Customizability 等多个方面对两者的优缺点进行了详尽的阐述。总体说来,目前两者的版本对于大部分人需要的性能都有比较好的支持。并没有说哪个比另一个好。只有一些小的特性上有一些区别,例如 ES 的 percolation,Solr 的 Pivot Facets 等。这里说几个曾经比较大的区别(这些区别似乎在后来的版本中逐渐不那么明显)。
一是对于 Cluster 上各结点的管理。之前我在《白话 IT 之 从 ElasticSearch 到 ZooKeeper》一文中提过一些。ES 内部的节点管理系统存在 Split Brain 的问题。而 SolrCloud 使用 ZooKeeper 管理节点,解决了这个问题,但是意味着 SolrCloud 需要 ZooKeeper 的部署。但是因为 ES 其实也可以使用外部 ZooKeeper 来取代其内置的节点管理,因此,这一区别也就不那么明显。
二是 Re-sharding。我用 Solr 和 ES 是三年前,那时候两者都不能动态改变 Shard 的个数。每次改 Shard 个数,都要把所有 Index Data 抹掉重新做 Indexing(从头 ETL 所有数据)。所以我们一开始设计系统的时候,对于 Shard 的配置就特别谨慎。但是 2013 年之后,Solr 就可以支持 Shard Splitting 了。所以从这一点上来说,ES 有一定的局限性。但是 ES 同时又支持 Shard 的 Rebalancing,这一点 Solr 又没有。总之,如果一开始设计系统的时候知道两者的局限性,或者 Re-index 整个搜索系统数据的代价不是太大的情况下,Re-sharding 和 Shard Rebalancing 也不是什么大问题。
三是社区支持,也就是遇到疑难杂症的时候是不是能够很快找到解决方案。三年前的时候,Solr 比 ES 要成熟很多,那时候很多 ES 遇到的问题都很难找到详尽的解释,不过现在似乎两者的用户群都很成熟,并没有很明显的区别。此外因为 Solr 是完全开源的,ES 是允许任意用户 Contribute 但是必须 ES 的人 Review 和 Approve 才能 Merge。所以熟悉 Solr 的开发者可能更为众多。
此外还有一些比较细致的比较,我觉得这篇文章写得很好,推荐阅读:
Solr or Elasticsearch — That is the Question: https://www.datanami.com/2015/01/22/solr-elasticsearch-question/
个人感受
当年在 Square 的时候,其实 Solr 和 ES 都用过。最开始 Sqaure 的搜索后端是用 Solr 写的,和 Ruby 结合。当时 Solr 的一些定制,如 Schema,是一个 Repo,其 ETL 是在另一个 Repo。后来一次 Hackathon,我和几个同事因为好玩儿,用一周的时间用当时刚刚开始火的 ES 重写了一个搜索的原型(当然,只有一些最基本的功能,数据 Pipeline 都没有搭好,比较 Hack 地去写入一些数据。)通过这个简单的原型我们展示了 ES 的一些优势,所以后来就把整个搜索后端移到了 ES 上,做成一个独立的 Java Service。
那么当时我们从 Solr 转到 ES,是因为哪些优势呢?
一就是 Nested Typing。什么意思呢?打个比方,你需要 Index 一些类似于淘宝上店家的信息,同时又需要 Index 一些类似于商品的信息。而这两个数据之间其实是有一种类似于父子关系的联系的。ES 对此有着很好的支持。这样,你在 Indexing 的 ETL 中,可以自然地描述两个数据的关系(商品属于某店家),在查询时,也可以比较方便的写出需要对父类或子类进行 Filter 的 Query。而当时我们的搜索后端正需要这样的一个特性。虽然说用 Solr 也有办法 Work Around,但是那段代码会相对来说比较不易懂,也不好维护。
二是语法。Solr 和 Ruby 结合,很多程序可以写的特别小巧且灵活。而 ES 的纯 JSON 表达则略显臃肿。但是就好像 Ruby 和 Java 的区别,这种 JSON 表达更具有一致性,所有的代码会有很类似的结构,更好懂。另外 Solr 从最开始的设计就比较针对文本搜索。而 ES 对于非文本的数据似乎有着更好的支持。加上其比较一致性的语法规范,对于复杂的 Query 的组合,ES 语句的读写都要更简单明了。
还有一些和公司当时 Infra 相关联的其他因素。
一是前面说的,当时我们的 Solr 和 ETL 在两个 Repo 里。这样以来,每次的 Deploy 就需要两个 Repo Deploy 的相互协调。而且所有的代码改动的兼容性管理就略复杂。而 ES 我们是和我们的 ETL 一起做到了一个独立的 Java Service 里。代码管理和部署都相对简单的多。
二是数据 Pipeline 的建立。当时公司里 Kafka 的 Producer 和 Consumer 对 Java 有了比较好的支持,所以新的基于 ES 的搜索系统可以比较方便的从 Kafka 里拿数据。而 Solr + Ruby 的老系统只能使用比较老的 Trigger 和 Feed 来拿数据。这样一来,很多 Upstream 的数据管道的建立而言,ES 要干净漂亮的多。
三是性能。当时使用了 ES 的分布式支持,而老的 Solr 系统其实是单节点多 Replica 的架构。很自然的,查询性能上 ES 确实要好很多。
搜索和推荐
很多时候,搜索系统和推荐系统是可以共用部分后端 Service 的。因为他们可以使用相同的 Index 数据,也就是只需要建立一套数据 Pipelien 和 ETL系统。在此之上,Query 可以使用不同的方式来实现,从而达到搜索或者推荐的功能。ES 和 Solr 都提供了类似的不同的 API。当时我们做出来的搜索后端,后来就有另一个项目组使用我们的数据,基于其上加了一些复杂的机器学习模型,做出了一个推荐系统。而我们的搜索部分,只使用了一些最基本的 Filter 和 Scoring 机制。
搜索系统的定制
虽然说使用原始的 Lucene 挑战性更大,很多公司一开始便选择搭建自己的搜索后端(如 Airbnb)。也有一些公司使用一段时间 Solr 或者 ES 后转而开发自己的搜索引擎。而定制的原因主要就是灵活性。
不论是 ES 还是 Solr,都是 Lucene 上的一层封装。这层封装按照他们的设计,提供给用户一层新的 API,就好像很多按钮和部件,方便你的很多需求和操作。而这层封装也必然隐藏了一些底部的功能。如果公司的应用和他们的设计的(假想的)Use Case 差别比较大,使用这样的封装就会反而显得很笨拙。因此,特别的应用,很多时候 Solr 还是 ES 提供的那些基本搜索功能和 API,并不好用。相关联的另一个因素就是性能。即使使用 ES 和 Solr,很别扭地写出需要的搜索功能,但是因此整个流程变得不那么直接,而是拐了个弯去调用 Lucene 的基本功能,性能也就必然会大打折扣。
然而,搜索的定制需要更多的人力和时间,以及精通搜索的技术人员。因此直接使用 Solr 或者 ES 的还是很多。即使在这样一些有定制的搜索系统的公司里,有些新产品上线初期也可能选用 Solr 或 ES。
这里写的仅是个人一点浅见。如果对这个话题感兴趣的人比较多,后面可以找更资深的搜索方面的朋友再写写。