数据建模(Modeling Your Data)数据库
ES是一头不一样寻常的野兽,尤为是当你来自SQL的世界时。它拥有不少优点:性能,可扩展性,准实时的搜索,以及对大数据的分析能力。而且,它很容易上手!只须要下载就可以开始使用它了。缓存
可是它也不是魔法。为了更好的利用ES,你须要了解它从而让它可以知足你的需求。服务器
在ES中,处理实体之间的关系并不像关系型存储那样明显。在关系数据库中的黄金准则 - 数据规范化,在ES中并不适用。在处理关联关系,嵌套对象和父子关联关系中,咱们会讨论几种可行方案的优势和缺点。ide
紧接着在为可扩展性而设计中,咱们会讨论ES提供的一些用来快速灵活实现扩展的特性。对于扩展,并无一个能够适用于全部场景的解决方案。你须要考虑数据是如何在你的系统中流转的,从而恰当地对你的数据进行建模。针对基于时间的数据好比日志事件或者社交数据流的方案比相对静态的文档集合的方案是十分不一样的。post
最后,咱们会讨论同样在ES中不会扩展的东西。性能
处理关联关系(Handling Relationships)大数据
在真实的世界中,关联关系很重要:博客文章有评论,银行帐户有交易,客户有银行帐户,订单有行项目,目录也拥有文件和子目录。编码
在关系数据库中,处理关联关系的方式让你不会感到意外:.net
每一个实体(或者行,在关系世界中)能够经过一个主键惟一标识。
实体是规范化了的。对于一个惟一的实体,它的数据仅被存储一次,而与之关联的实体则仅仅保存它的主键。改变一个实体的数据只能发生在一个地方。
在查询期间,实体能够被联接(Join),它让跨实体查询成为可能。
对于单个实体的修改是原子性,一致性,隔离性和持久性的。(参考ACID事务获取更多相关信息。)
绝大多数的关系型数据库都支持针对多个实体的ACID事务。
可是关系型数据库也有它们的局限,除了在全文搜索领域它们拙劣的表现外。在查询期间联接实体是昂贵的 - 联接的实体越多,那么查询的代价就越大。对不一样硬件上的实体执行联接操做的代价太大以致于它甚至是不切实际的。这就为在单个服务器上可以存储的数据量设下了一个限制。设计
ES,像多数NoSQL数据库那样,将世界看做是平的。一个索引就是一系列独立文档的扁平集合。一个单一的文档应该包括用来判断它是否符合一个搜索请求的全部信息。
虽然在ES中改变一份文档的数据是符合ACIDic的,涉及到多份文档的事务就否则了。在ES中,当事务失败后是没有办法将索引回滚到它以前的状态的。
这个扁平化的世界有它的优点:
索引是迅速且不须要上锁的。
搜索是迅速且不须要上锁的。
大规模的数据能够被分布到多个节点上,由于每份文档之间是独立的。
可是关联关系很重要。咱们须要以某种方式将扁平化的世界和真实的世界链接起来。在ES中,有4中经常使用的技术来管理关联数据:
应用端联接(Application-side joins)
数据非规范化(Data denormalization)
嵌套对象(Nested objects)
父子关联关系(Parent/child relationships)
一般最终的解决方案会结合这些方案的几种。
应用端联接(Application-side Joins)
咱们能够经过在应用中实现联接来(部分)模拟一个关系型数据库。好比,当咱们想要索引用户和他们的博客文章时。在关系型的世界中,咱们能够这样作:
PUT /my_index/user/1 (1) { "name": "John Smith", "email": "john@smith.com", "dob": "1970/10/24" }
PUT /my_index/blogpost/2 (2) { "title": "Relationships", "body": "It's complicated...", "user": 1 (3) }
(1)(2) 索引,类型以及每份文档的ID一块儿构成了主键。
(3) 博文经过保存了用户的ID来联接到用户。因为索引和类型是被硬编码到了应用中的,因此这里并不须要。
经过用户ID等于1来找到对应的博文很容易:
GET /my_index/blogpost/_search { "query": { "filtered": { "filter": { "term": { "user": 1 } } } } }
为了找到用户John的博文,咱们能够执行两条查询:第一条查询用来获得全部名为John的用户的IDs,第二条查询经过这些IDs来获得对应文章:
GET /my_index/user/_search { "query": { "match": { "name": "John" } } }
GET /my_index/blogpost/_search { "query": { "filtered": { "filter": { "terms": { "user": [1] } (1) } } } }
(1) 传入到terms过滤器的值是第一条查询的结果。
应用端联接最大的优点在于数据是规范化了的。改变用户的名字只须要在一个地方操做:用户对应的文档。劣势在于你须要在搜索期间运行额外的查询来联接文档。
在这个例子中,只有一位用户匹配了第一条查询,可是在实际应用中可能轻易就获得了数以百万计的名为John的用户。将全部的IDs传入到第二个查询中会让该查询很是巨大,它须要执行百万计的term查询。
这种方法在第一个实体的文档数量较小而且它们不多改变时合适(这个例子中实体指的是用户)。这就使得经过缓存结果来避免频繁查询成为可能。
反规范化你的数据(Denormalizing Your Data)
让ES达到最好的搜索性能的方法是采用更直接的办法,经过在索引期间反规范化你的数据。经过在每份文档中包含冗余数据来避免联接。
若是咱们须要经过做者的名字来搜索博文,能够在博文对应的文档中直接包含该做者的名字:
PUT /my_index/user/1 { "name": "John Smith", "email": "john@smith.com", "dob": "1970/10/24" }
PUT /my_index/blogpost/2 { "title": "Relationships", "body": "It's complicated...", "user": { "id": 1, "name": "John Smith" } }
如今,咱们能够经过一条查询来获得用户名为John的博文了:
GET /my_index/blogpost/_search { "query": { "bool": { "must": [ { "match": { "title": "relationships" }}, { "match": { "user.name": "John" }} ] } } }
对数据的反规范化的优点在于速度。由于每份文档包含了用于判断是否匹配查询的全部数据,不须要执行代价高昂的联接操做。