Elasticsearch的诞生

1. ES诞生之前

  • 以下内容以故事的叙事方式阐述了ES的诞生的过程。但ES的处理逻辑是真实的

经过3年的历练,张大胖已经成为了一个利用Lucene这个著名的开源软件做搜索的高手,各种细节知识和最佳实践尽在掌握

随着互联网应用的爆炸式增长,搜索变成了网站的一个常见需求,各个网站都想搜索产品,搜索帖子,搜索服务……张大胖的“业务”变得十分繁忙,经常在业余时间给人做Lucene的咨询,赚了不少外快。

但是张大胖也敏锐地觉察到了两个问题:

  1. Lucene做搜索很强大,但是API用起来太“低级”,很多人抱怨:我就想搜索一下我的产品描述,现在还得理解什么“Directory”,”Analyzer”,”IndexWriter”,实在是太复杂了!
  2. 互联网的数据是海量的,仅仅是单机存储索引是远远不够的

俗话说:“软件开发中遇到的所有问题,都可以通过增加一层抽象而得以解决”。 张大胖觉得,是时候对Lucene做一个抽象了:

Java API -> Web API

2. ES的初始设计

保险起见,张大胖拉了大神Bill来做顾问,帮自己设计。

这个新的抽象层应该对外提供一个什么样的API呢?

很多时候,Web开发面对的都是领域模型,比如User,Product, Blog,Account之类。用户想做的无非就是搜索产品的描述, 搜索Blog的标题,内容等等。

张大胖说:“如果能围绕着领域模型的概念进行搜索的各种操作就好了,这样简单而直接,如同CRUD。”

Bill 提示到:“Web时代了,程序员都喜欢采用 RESTful 的方式来描述一个Web资源, 对于搜索而言,完全可以借鉴一下嘛!”

张大胖眼前一亮: “要不这样?”

/coolspace/blog/1001 : 表示一个编号为1001的博客

/coolspace/user/u3876:表示一个ID为u3876的用户

/coolspace表示一个“索引库”

/blog ,/user 表示“索引的类型”(可以理解为编程中的领域模型)。 1001, u3876表示数据的ID

格式是///

如果和关系数据库做个类比的话:

索引库<—>数据库

索引的类型 <—>数据库的表

Bill说:“这样挺好的,用户看到的就是领域模型, 当用户想去操作时候,用HTTP的GET, PUT等操作就好了,交互的数据可以使用JSON这个简单的格式。”

3. ES的数据操作设计

经过上面的讨论之后,张大胖开始定义基本的操作

(1) 把文档加入索引库

例如:把一个blog的“文档”加入索引库,这个文档的数据是JSON格式,其中的每个字段将来都可以被搜索:

PUT /coolspace/blog/ 1001

{

“title”: “xxxxxxx”,

“content”: “xxxxxxxxxxxx”,

“author”: “xxxxx”,

“create_date”: “xxxxxx”

…

}

备注:当然,在发起HTTP请求的时候,需要加上服务器的地址和端口,下同

(2)把一个blog文档删除,从此就再也搜索不到了

DELETE /coolspace/blog/1001

(3) 用户搜索

用户想搜索的时候也很简单,发起一个这样的请求就行:

GET /coolspace/blog/_search

但是如何表达查询的具体需求呢,这时候必须得定义一个规范了,例如:想查询一个内容字段(content)包含“java”的 blog。

GET/coolspace/blog/_search

{
	“query”: {

		“match”: {

			“content”: “java”

		}

	}
   
}

这个query下面可以增加更加复杂的条件,表示用户的查询需求,反正是JSON格式,可以非常灵活。

返回值也是JSON, 这里就不再展示了。

这个抽象层是以HTTP+JSON来表示的, 和具体的编程语言无关,不管是Java, 还是Python,Ruby,只要能发起HTTP调用,就可以使用。抽象的模型如下图:

通过上面这样一个抽象层, Lucene那些复杂的API全部被隐藏到了海平面以下。

对于程序员来说,使用HTTP+JSON是非常自然的事情,好用就是最大的生产力。

4. ES存储海量数据的设计(分布式设计)

分布式

到目前为止,进展还算顺利,接下来要考虑的就是如何存储海量的索引数据。

对于海量的数据,单一服务器肯定不能满足存储需求,那么只能考虑分散的存储了

张大胖说: “这个简单,如果索引太大,我们把它切割一下,分成一片一片的,存储到各个机器上不就得了?”,像下面这样

Bill问道: “想得美! 你分片以后,用户去保存索引的时候,还有搜索索引数据的时候,到哪个机器上去取?”

张大胖说:“这个简单,首先我们保存每个分片和机器之间的对应关系, 嗯,我觉得叫node显得更专业。”

分片1 :node1

分片2 :node2

分片3 :node3

“分片在英文中叫做shard。 ” Bill 友情提示

“好的, 然后可以用余数算法来确定一个‘文档’到底保存在哪个shard中。” 虽然张大胖觉得这个词看起来不爽,还是开始使用了

shard 编号 = hash(文档的ID) % shard 总数

“这样对于任意一个文档,对它的ID做hash计算,然后对总分片数量求余, 就可以得到shard的编号,然后就可以找到对应的机器了。 ” 张大胖觉得自己的这个算法又简单,效率又高,洋洋得意。

Bill觉得这两年张大胖进步不小,开始使用算法来解决问题了, 他问道:“如果用户想增加shard数该怎么处理呢? 这个余数算法就会出问题啊 !”

比如原来shard 总数是3, hash值是100, shard编号 = 100 % 3 = 1

假设用户又增加了两台机器,shard总数变成了5, 此时 shard 编号 = 100 % 5 = 0 , 如果去0号机器上去找索引,肯定是找不到的。

张大胖挠挠头:“要不采用分布式一致性算法, 嗯,它会减少出错的情况,还是无法避免,这该怎办?”

Bill建议:“要不这样,我们可以立下一个规矩:用户在创建索引库的时候,必须要指定shard数量,并且一旦指定,就不能更改了!”

PUT /coolspace

{

	“settings”: {

		“number_of_shards”: 3

	}

}

虽然对用户来说有点不爽, 但余数算法的高效和简单确实太吸引人了,张大胖表示同意

“索引数据分布了,如果某个节点坏掉了,数据就会丢失,我们得做个备份才行。” 张大胖的思考很深入。

“对, 我们可以用新的node 来做replica,也可以为了节省空间, 复用现有的node来做replica。为了做区分,可以把之前的分片叫做主分片,primary shard。” Bill英文就是好。

(此处的设置为:每个主分片有两个副本)

PUT /coolspace/_settings

{

“number_of_replicas”: 2

}

虽然主分片的数目在创建“索引库”的时候已经确定,但是副本的数目是可以任意增减的,这依赖于硬件的情况:性能和数量。

“现在每个主分片都有两个副本, 如果某个节点挂了也不怕,比如节点1挂了,我们可以位于节点3上的副本0提升为 主分片0, 只不过每个主分片的副本数不够两个了。” 张大胖说道。

Bill 满不在乎地说: “没事,等到节点1启动后,还可以恢复副本。”

5. ES集群设计

集群

Bill和张大胖立刻意识到,他们建立了一个集群, 这个集群中可以包含若干node , 有数据的备份,能实现高可用性。

但是另外一个问题马上就出现了:对于客户端来说,通过哪个node来读写‘文档’呢?

比如说用户要把一个’文档’加入索引库: PUT /coolspace/blog/1001, 该如何处理?

Bill说:“这样吧,我们可以让请求发送到集群的任意一个节点,每个节点都具备处理任何请求的能力。”

张大胖说:“具体怎么做呢? ”

Bill 写下了处理过程:

(1)假设用户把请求发给了节点1

(2)系统通过余数算法得知这个’文档’应该属于主分片2,于是请求被转发到保存该主分片的节点3

(3)系统把文档保存在节点3的主分片2中,然后将请求转发至其他两个保存副本的节点。副本保存成功以后,节点3会得到通知,然后通知节点1, 节点1再通知用户。

交互过程如下图:

“如果是做查询呢? 比如说用户查询一个文档: GET /coolspace/blog/1001, 该如何处理?” 张大胖问道。

“同样,查询的请求也可以分发到任意一个节点,然后该节点可以找到主分片或者任意一个副本,返回即可。 ”

(1) 请求被发给了节点1

(2)节点1计算出该数据属于主分片2,这时候,有三个选择,分别是位于节点1的副本2, 节点2的副本2,节点3的主分片2, 假设节点1为了负载均衡,采用轮询的方式,选中了节点2,把请求转发。

(3) 节点2把数据返回给节点1, 节点1 最后返回给客户端。

流程如下图:

“这个方式比较灵活,但是要求各个节点之间得能互通有无才行!” 张大胖说道。

“不仅如此,对于一个集群来说,还得有一个主节点(master node),这个主节点在处理数据请求上和其他节点是平等的,但是它还有更重要的工作,需要维护整个集群的状态,增加移除节点,创建/删除索引库,维护主分片和集群的关系等等。”

“那如果这个主节点挂了呢? ” 张大胖追问。

“那只好从剩下的节点中重新选举喽!”

“哎呀,这就涉及到分布式系统的各种问题了,什么一致性,脑裂,太难了!” 张大胖开始打退堂鼓。

“我们只是选取一个Master, 要简单得多,你可以看看一个叫做Bully的算法, 改进一下应该就可以用了。 ”

开发分布式系统的难度要远远大于一个单机系统,半年以后,这个被Bill命名为Elasticsearch的系统才发布了第一个版本。

由于它屏蔽了很多Lucene的细节,又支持海量索引的存储,很快就大受欢迎。

6. ES真正的设计者

当然, Elasticsearch不是Bill和张大胖创造的,这里才是其传奇的历史:(来源:《Elasticsearch权威指南》)

许多年前,一个刚结婚的名叫 Shay Banon 的失业开发者,跟着他的妻子去了伦敦,他的妻子在那里学习厨师。 在寻找一个赚钱的工作的时候,为了给他的妻子做一个食谱搜索引擎,他开始使用 Lucene 的一个早期版本。

直接使用 Lucene 是很难的,因此 Shay 开始做一个抽象层,Java 开发者使用它可以很简单的给他们的程序添加搜索功能。 他发布了他的第一个开源项目 Compass。

后来 Shay 获得了一份工作,主要是高性能,分布式环境下的内存数据网格。这个对于高性能,实时,分布式搜索引擎的需求尤为突出, 他决定重写 Compass,把它变为一个独立的服务并取名 Elasticsearch。

第一个公开版本在2010年2月发布,从此以后,Elasticsearch 已经成为了 Github 上最活跃的项目之一,他拥有超过300名 contributors(目前736名 contributors )。 一家公司已经开始围绕 Elasticsearch 提供商业服务,并开发新的特性,但是,Elasticsearch 将永远开源并对所有人可用。

据说,Shay 的妻子还在等着她的食谱搜索引擎…

posted @ 2024-04-08 14:15  BigSun丶  阅读(19)  评论(0编辑  收藏  举报