由于需要提升项目的搜索质量,最近研究了一下Elasticsearch,一款非常优秀的分布式搜索程序。最开始的一些笔记放到github,这里只是归纳总结一下。
首先,为什么要使用Elasticsearch?最开始的时候,我们的项目仅仅使用MySQL进行简单的搜索,然后一个不能索引的like语句,直接拉低MySQL的性能。后来,我们曾考虑过sphinx,并且sphinx也在之前的项目中成功实施过,但想想现在的数据量级,多台MySQL,以及搜索服务本身HA,还有后续扩容的问题,我们觉得sphinx并不是一个最优的选择。于是自然将目光放到了Elasticsearch上面。
根据官网自己的介绍,Elasticsearch是一个分布式搜索服务,提供Restful API,底层基于Lucene,采用多shard的方式保证数据安全,并且提供自动resharding的功能,加之github等大型的站点也采用Elasticsearch作为其搜索服务,我们决定在项目中使用Elasticsearch。
对于Elasticsearch,如果要在项目中使用,需要解决如下问题:
对于1和2,因为我们的数据都是从MySQL生成,index的field是固定的,主要做的工作就是根据业务场景设计好对应的mapping以及search语句就可以了,当然实际不可能这么简单,需要我们不断的调优。
而对于3,则是需要一个工具将MySQL的数据导入Elasticsearch,因为我们对搜索实时性要求很高,所以需要将MySQL的增量数据实时导入,笔者唯一能想到的就是通过row based binlog来完成。而近段时间的工作,也就是实现一个MySQL增量同步到Elasticsearch的服务。
Elasticsearch底层是基于Lucene的,Lucene是一款优秀的搜索lib,当然,笔者以前仍然没有接触使用过。:-)
Lucene关键概念:
Lucene使用Inverted index来存储term在document中位置的映射关系。
譬如如下文档:
使用inverted index存储,一个简单地映射关系:
Term | Count | Docuemnt |
---|---|---|
1.0 | 1 | <1> |
4 | 1 | <3> |
Apache | 1 | <3> |
Cookbook | 1 | <3> |
Elasticsearch | 2 | <1>.<2> |
Mastering | 1 | <2> |
Server | 1 | <1> |
Solr | 1 | <3> |
对于上面例子,我们首先通过分词算法将一个文档切分成一个一个的token,再得到该token与document的映射关系,并记录token出现的总次数。这样就得到了一个简单的inverted index。
要使用Elasticsearch,笔者认为,只需要理解几个基本概念就可以了。
在数据层面,主要有:
对于熟悉MySQL的童鞋,我们只需要大概认为Index就是一个db,document就是一行数据,field就是table的column,mapping就是table的定义,而document type就是一个table就可以了。
Document type这个概念其实最开始也把笔者给弄糊涂了,其实它就是为了更好的查询,举个简单的例子,一个index,可能一部分数据我们想使用一种查询方式,而另一部分数据我们想使用另一种查询方式,于是就有了两种type了。不过这种情况应该在我们的项目中不会出现,所以通常一个index下面仅会有一个type。
在服务层面,主要有:
Elasticsearch之所以能动态resharding,主要在于它最开始就预先分配了多个shards(貌似是1024),然后以shard为单位进行数据迁移。这个做法其实在分布式领域非常的普遍,codis就是使用了1024个slot来进行数据迁移。
因为任意一个index都可配置多个replica,通过冗余备份的方式保证了数据的安全性,同时replica也能分担读压力,类似于MySQL中的slave。
Elasticsearch提供了Restful API,使用json格式,这使得它非常利于与外部交互,虽然Elasticsearch的客户端很多,但笔者仍然很容易的就写出了一个简易客户端用于项目中,再次印证了Elasticsearch的使用真心很容易。
Restful的接口很简单,一个url表示一个特定的资源,譬如/blog/article/1
,就表示一个index为blog,type为aritcle,id为1的document。
而我们使用http标准method来操作这些资源,POST新增,PUT更新,GET获取,DELETE删除,HEAD判断是否存在。
这里,友情推荐httpie,一个非常强大的http工具,个人感觉比curl还用,几乎是命令行调试Elasticsearch的绝配。
一些使用httpie的例子:
# create http POST :9200/blog/article/1 title="hello elasticsearch" tags:='["elasticsearch"]' # get http GET :9200/blog/article/1 # update http PUT :9200/blog/article/1 title="hello elasticsearch" tags:='["elasticsearch", "hello"]' # delete http DELETE :9200/blog/article/1 # exists http HEAD :9200/blog/article/1
虽然Elasticsearch能自动判断field类型并建立合适的索引,但笔者仍然推荐自己设置相关索引规则,这样才能更好为后续的搜索服务。
我们通过定制mapping的方式来设置不同field的索引规则。
而对于搜索,Elasticsearch提供了太多的搜索选项,就不一一概述了。
索引和搜索是Elasticsearch非常重要的两个方面,直接关系到产品的搜索体验,但笔者现阶段也仅仅是大概了解了一点,后续在详细介绍。
Elasticsearch是很强大,但要建立在有足量数据情况下面。我们的数据都在MySQL上面,所以如何将MySQL的数据导入Elasticsearch就是笔者最近研究的东西了。
虽然现在有一些实现,譬如elasticsearch-river-jdbc,或者elasticsearch-river-mysql,但笔者并不打算使用。
elasticsearch-river-jdbc的功能是很强大,但并没有很好的支持增量数据更新的问题,它需要对应的表只增不减,而这个几乎在项目中是不可能办到的。
elasticsearch-river-mysql倒是做的很不错,采用了python-mysql-replication来通过binlog获取变更的数据,进行增量更新,但它貌似处理MySQL dump数据导入的问题,不过这个笔者真的好好确认一下?话说,python-mysql-replication笔者还提交过pull解决了minimal row image的问题,所以对elasticsearch-river-mysql这个项目很有好感。只是笔者决定自己写一个出来。
为什么笔者决定自己写一个,不是因为笔者喜欢造轮子,主要原因在于对于这种MySQL syncer服务(增量获取MySQL数据更新到相关系统),我们不光可以用到Elasticsearch上面,而且还能用到其他服务,譬如cache上面。所以笔者其实想实现的是一个通用MySQL syncer组件,只是现在主要关注Elasticsearch罢了。
项目代码在这里go-mysql-elasticsearch,现已完成第一阶段开发,内部对接测试中。
go-mysql-elasticsearch的原理很简单,首先使用mysqldump获取当前MySQL的数据,然后在通过此时binlog的name和position获取增量数据。
一些限制:
更详细的说明,等到笔者完成了go-mysql-elasticsearch的开发,并通过生产环境中测试了,再进行补充。
最近一周,笔者花了不少时间在Elasticsearch上面,现在算是基本入门了。其实笔者觉得,对于一门不懂的技术,找一份靠谱的资料(官方文档或者入门书籍),蛋疼的对着资料敲一遍代码,不懂的再问google,最后在将其用到实际项目,这门技术就算是初步掌握了,当然精通还得在下点功夫。
现在笔者只是觉得Elasticsearch很美好,上线之后铁定会有坑的,那时候只能慢慢填了。话说,笔者是不是要学习下java了,省的到时候看不懂代码就惨了。:-)