对ElasticSearch的几点思考

来源:互联网 发布:淘宝客推广海报 编辑:程序博客网 时间:2024/06/06 16:33

最近在项目中碰到了使用ElasticSearch的部分,有了初步了解后,也产生了几个疑问,这篇文章主要就是我对这几个疑问的思考,引出疑问之前,首先简单介绍一下ElasticSearch

Es是一个实时的分布式搜索分析引擎,是基于Apache Lucene的一个开源项目,那么,对它的基本印象就在于:分布式、搜索引擎,分析功能

基于这个简单定义,再提一下Es在我遇到的项目中所扮演的角色,项目中,有着mysql数据库的存在,而很大一部分的数据查询功能由Es提供,当然,这会涉及到mysql和Es数据的同步问题,这里暂且不提,那么我产生的第一个疑问是,为什么查询不直接走数据库,而要走搜索引擎,第一想法就是走Es比走数据库快,那么快的原因是什么:

我是从两个角度想的:

1、从搜索引擎和数据库的结构来比较的话,搜索引擎的底层是通过倒排索引,而mysql数据库是通过B+树进行索引,到底哪个效率高没做实验得不出结论,但是有一点,数据库给字段加了后查询效率比不加快很多,但是mysql加索引的代价比较高,每给一个字段加上索引,就意味着数据库额外维持了一个B树索引,每次加索引都得小心翼翼,加多字段联合索引就更是了,所以mysql数据库给所有字段或是字段组合是很不现实的

搜索引擎则不一样,它基于倒排索引,这种结构中,每个字段都要维系一个包含它的文档列表,那就不存在像数据库这样需要额外添加索引的问题,那么我得出一个初步结论,至少在复杂或者说灵活的查询需求下,mysql显得有些笨重,要提供高效的查询的代价比较高昂,而Es相比之下就灵活的多(就点像Google的时候和写sql的时候的区别)

2、Es本身就是一个分布式的框架,而且它有一个很显著的有优点就是横向扩展比较方便,那么我认为在查询数量较大的时候,Es可以通过多节点并行查询的方式来大大提高查询效率,Es里有这样的机制,当要查询的数据分布在不同节点上时,各个节点分别进行查询并将结果发给协调节点进行汇总,当然,mysql同样能通过分库达到类似的效果,但我觉得这样做就太复杂了,还可能涉及到分布式事务的问题。而且我遇到的项目中并没有分库,只做了分表。

那么再引出我的第二个疑问,因为我印象中的搜索引擎是通过权重的排名来决定检索结果的,这跟数据库那种精确检索似乎不太一样,有点模糊检索的意思,而项目中我接触到的部分没有看到有模糊匹配的需求,那么Es是怎么做到的,我去es文档里找了找,发现es确实是提供这种模糊检索的功能,也叫全文检索,就是说它会把存在es里的标记为全文检索的数据进行分词等操作,然后对分出来的词建立倒排索引,然后就是搜索引擎那一套计算权重的东西,大概就是这么个原理,但是es并不是全是全文检索,也有精确值检索,都是可以设置的,所以这个疑问其实只是概念上没转过弯来,不过这也突出了Es相对数据库的强大之处,就是Es能够轻易的提供相关度检索,模糊检索等功能,而数据库要做到这点则是非常困难,要是有这样的需求,写sql就很难应付过来。

那么引出我的第三个疑问,Es能不能对嵌套对象里的属性也进行检索? 这个问题也是项目中遇到的,因为mysql的数据表中,有个metadata字段,存的是一个json字符串,由于这个数据库中数据来源比较多,所以metadata里面存放的是其实是不同来源的特有的数据,那就意味着matadata里的属性的名称和数目都是不定的,对mysql数据库而言,将这些数据塞进metadata而不是单独开设字段,是为了防止数据库的表结构变得庞大,同时也是避免数据库结构和业务的耦合过大,但这么做的同时,也基本上放弃了对metadata里面数据进行筛选或是索引的可行性,那么我就想,mysql做不到,能不能通过es做到?(因为我觉得metadata里的数据还挺重要的,不能做检索的话很可惜)

es文档里有这么一段话:

…也许有一天你想把这些对象存储在数据库中。使用关系型数据库的行和列存储,这相当于是把一个表现力丰富的对象挤压到一个非常大的电子表格中:你必须将这个对象扁平化来适应表结构–通常一个字段>对应一列–而且又不得不在每次查询时重新构造对象。

Elasticsearch 是 面向文档 的,意味着它存储整个对象或 文档。Elasticsearch 不仅存储文档,而且 索引 每个文档的内容使之可以被检索。在 Elasticsearch 中,你 对文档进行索引、检索、排序和过滤–而不是对行列数据。这是一种完全不同的思考数据的方式,也是 Elasticsearch 能支持复杂全文检索的原因。

mysql不能支持在一个字段里放一个复杂对象并提供检索,那es可以吗,这应该叫做内部对象,我在本地搭的Es上试了一下,存上去一个嵌套json,(metadata字段是额外加上去的, es里已经有两个正常的employee对象),看es会怎么做映射:

PUT 127.0.0.1:9200/megacorp/employee/3
para:

{    "first_name" :  "Jane",    "last_name" :   "Smith",    "age" :         32,    "about" :       "I like to collect rock albums",    "interests":  [ "music" ],    "metaData":{                    "money":1000,                    "house":"big"    }}

存是ok的,再看看Es这个时候对emploee类型是怎么做的映射:
Get 127.0.0.1:9200/megacorp/_mapping/employee
response:

{    "megacorp": {        "mappings": {            "employee": {                "properties": {                    "about": {                        "type": "text",                        "fields": {                            "keyword": {                                "type": "keyword",                                "ignore_above": 256                            }                        }                    },                    "age": {                        "type": "long"                    },        省略一部分...                    "metaData": {                        "properties": {                            "house": {                                "type": "text",                                "fields": {                                    "keyword": {                                        "type": "keyword",                                        "ignore_above": 256                                    }                                }                            },                            "money": {                                "type": "long"                            }                        }                    }                }            }        }    }}

果然,es会根据存进来的对象,推测出字段的类型,并自动改变了映射,这么看是可行的,而且,尽管此时employee类型的映射变了,但是对之前存进去的对象属性并没有影响,并不会多出些冗余的字段

Get 127.0.0.1:9200/megacorp/employee/1
response:

{    "_index": "megacorp",    "_type": "employee",    "_id": "1",    "_version": 1,    "found": true,    "_source": {        "first_name": "John",        "last_name": "Smith",        "age": 25,        "about": "I love to go rock climbing",        "interests": [            "sports",            "music"        ]    }}

那么问题就来了,对于这种嵌套的情况,es怎么进行索引的,metadata里有money和house这两个属性,直接对这两个属性建立倒排索引吗,貌似不合理,万一外层的属性里有重名的呢,去文档里找了找,原文是这样的:

Lucene 不理解内部对象。 Lucene 文档是由一组键值对列表组成的。为了能让 Elasticsearch
有效地索引内部类,它把我们的文档转化成这样:
{
“tweet”: [elasticsearch, flexible, very],
“user.id”: [@johnsmith],
“user.gender”: [male],
“user.age”: [26],
“user.name.full”: [john, smith],
“user.name.first”: [john],
“user.name.last”: [smith]
}

那么在本例中,就是

{...           "metadata.money":1000,        "metadata.house":"big"}

倒排索引还是要建立,但是属性变得扁平化,变成了对metadata.money和metadata.house建立 。所以理论上可以将mysql做不了的复杂检索给放到es上,而mysql专心做存储和处理事务问题。

我对Es的了解还很表面,Es的底层有一些很有意思的东西,比如分布式存储、分布式检索、分片管理等等,官方的文档很全,好好学习学习

这是翻译文档的地址:
https://elasticsearch.cn/book/elasticsearch_definitive_guide_2.x

原创粉丝点击