使用Elasticsearch快速实现社区/博客文章全文搜索

这是一篇结合实战教同学快速入门使用Elasticsearch 解决实际业务场景的问题.

如果你还在用 sql like %xxx%的方式进行内容的全文搜索,很可能DBA或者老大就要找你去聊天了, 以mysql innodb引擎为例, 这种写法将会进行十分低效全文检索,而且不会使用索引.

接下来全文将 elasticsearch 简称为ES.

那么,本文将通过将之前在Flask框架上使用的社区搜索接口从like %xxx%的写法改成ES做全文索引,观察是否会存在显著的性能提升(如果不能提升,是否还有其他改进的方式),先看下从0-1怎么快速实现.

看了其他的讲实现的文章中都是把相关组件或者ES的文档代码直接贴过来,个人认为这些都是会存在升级以及文档变更的可能,为了使用最新的api以及特性还是建议读者花几分钟读完文章中的外链文档地址.

  • ES是什么? 怎么玩?
  • 地域问题,中文文章的分词怎么整?
  • 业务的实现
  • 线上环境的部署与监控(服务的稳定性)

ES是什么? 怎么玩?

ES简单的中文入门,在上线之前需要现在本地以及测试环境跑的溜,笔者本机是MacOS系统,所以采用比较快捷的Homebrew的安装方式
brew install elasticsearch
再根据文档安装了浏览器级的control工具sense,按着教程稍微跑一下教程之后就可以进入了正题. 有些朋友想吐槽ES快速入门太简单了,毕竟是实战导向的快速入门,把重点放在ES与业务结合的实现.

地域问题,中文文章的分词怎么整

如果笔者读了上段写的中文入门教程,会发现ES已经提供英文搜索模糊匹配的功能,至于ES是怎么实现的,感兴趣的读者可以再深入查阅资料.这时候为了支持中文搜索我们需要安装插件到ES目录中的plugins文件夹中$ES_HOME/plugins/ . ik插件的github地址,按着文档安装之后可以跑一下里面的测试请求,验证中国的分词查询是有效的.ik插件的在analyzer和search_analyzer上有两种配置:

  • ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;
  • ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。

核心的分词问题就这样解决了,在大批量的文章中是否会存在性能问题需要在实战中验证,接下来我们要做的事情就是将之前有的文章导入到ES中以及为新的文章建立文档,这些要做的事情都将通过调用ES提供的api完成.

业务的实现

Elasticsearch 集群可以包含多个索引(Index),每个索引可以包含多个类型(Type),每个类型可以包含多个文档(Document),每个文档可以包含多个字段(Field)。以下是 MySQL 和 Elasticsearch 的术语类比图,帮助理解:

MySQL Elasticsearch
Database Index
Table Type
Row Document
Column Field
Schema Mappping
Index Everything Indexed by default
SQL Query DSL

就像使用 MySQL 必须指定 Database 一样,要使用 Elasticsearch 首先需要创建 Index,这边我们为社区文章创建index: forum-index,以及type:post

ES提供了一套增删改查的api,我们可以在SENSE模板中使用简化API来测试验证有效性,但是在后端开发中根据不同语言以及框架一般有已经封装好的库可以直接配置使用,笔者这边使用的是python的库elasticsearch-dsl-py,如果您使用的是其他语言或者框架,上github上搜索一下有没有封装过的框架(记得要看是否版本匹配).在进行下一步之前,我们捋一下我们要做的事情,将原有的社区文章导入type,根据提供给客户端以及后台的查询功能加入对应匹配的字段,新增文章时需要加入ES中,编辑文章后需要修改ES中对应文档的内容,以及文章下架后要将文章从ES中移除.增删改这些额外的ES操作会有额外的时间消耗以及失败的可能,为了不影响接口可以将操作封装放入队列异步执行,保证最终一致执行便可.ES查询性能与稳定性的优化等先调通基础功能与接口后再考虑.

这是导入原有帖子数据后在sense console上执行以下模糊查询语句与对应的返回:

GET /forum-index/post/_search
{
   "query": {
    "bool": {
      "should": [
        { "match": { "content":  "客户端" }},
         { "match": { "title":  "客户端" }},
        { "match": { "summary": "客户端"   }}
      ]
    }
  }
}
{
  "took": 4,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 19,
    "max_score": 25.630516,
    "hits": [
      {
        "_index": "forum-index",
        "_type": "post",
        "_id": "60000000012672",
        "_score": 25.630516,
        "_source": {
          "user_id": 10144,
          "title": "客户端发帖测试",
          "summary": "客户端发帖测试内容",
          "content": """
客户端发帖测试内容
<img data-src="3f2b7dd51ddcb7d66b22f0f06645f16e.png" src="http://img-cn-hangzhou.aliyuncs.com/nb-imgs/3f2b7dd51ddcb7d66b22f0f06645f16e.png">
 <br/>
</img>
""",
          "last_modify": 1480665329,
          "id": 60000000012672
        }
      },
...

返回json格式的数据中,took,_score,...等等很多字段这个是什么意思呢?
可以参考官文说明.这边也简单说明下:

  • took – time in milliseconds for Elasticsearch to execute the search
  • timed_out – tells us if the search timed out or not
  • _shards – tells us how many shards were searched, as well as a count of the successful/failed searched shards
  • hits – search results
  • hits.total – total number of documents matching our search criteria
  • hits.hits – actual array of search results (defaults to first 10 documents)
  • hits.sort - sort key for results (missing if sorting by score)
  • hits._score and max_score - ignore these fields for now

这边可以看到,在本地环境5000+的帖子文档分词查询,消耗时间为4ms,算是一个非常不错的结果了.将复杂的查询功能使用ES来实现,可以有效减少数据库的请求压力,接下来我们可以致力于优化ES的使用以及查询.按目前评估ES可以胜任生产级别的全文检索服务.

线上环境的部署与监控(服务的稳定性)

由于工作调用的原因笔者很遗憾的没有去实现完成最后这一步,但是可以大致说一下思路,线上部署配置后,可以使用supervisor等服务守护程序去保证服务的稳定性,并做好性能压测,为保证服务的持续可用建议部署上ES的集群.单个节点异常的时候也能保证线上服务的持续.

总结

通过这次的学习发现ES是个强大好用的工具,目前比较流行的有用ELK进行日志分析(ElasticSearch + Logstash + Kibana),这边仅仅是为读者们打开了冰山的一角,继续学习,与君共勉!

推荐阅读更多精彩内容