Elasticsearch:数据

数据

无论程序怎么写,意图都是一样的:组织数据为我们的目标所服务。但数据并不只是由随机的比特和字节组成,我们在数据节点间建立关联来表示现实世界中的实体或某些东西。属于同一个人的名字和Email会有更多的意义。

现实世界中,并不是所有相同类型的实体看起来都是一样的,一个人可能有一个家庭电话,另一个人可能只有一个手机号码,有些人可能都有,甚至有多个。

面向对象编程流行的原因之一,是我们可以用对象来表示和处理现实生活中那些有着潜在关系和复杂结构的实体。

但是当我们想要存储这些实体时,传统的数据库只允许我们以行、列的形式将数据存储在关系型数据库中,这种固定的存储方式导致对象的灵活性不复存在了。因此我们需要能够以对象的形式存储对象,可以让我们专注于使用数据,将对象本来的灵活性找回来。

对象是一种语言相关,记录在内存中的数据结构,为了在网络中传输即需要一些标准的格式来表示。而JSON是一种可读的以文本表示对象的方式。当对象被序列化为JSON,就称为JSON document了。

分布式文档存储引擎

Elasticsearch是一个分布式的文档存储引擎,它可以实时存储并检索复杂数据结构——序列化的JSON文档,即文档一旦被存储在ES上,就可以在集群的任一节点上被搜索。

当存储了数据后,我们还需要快速的批量查询,虽然很多NoSQL的解决方案允许我们以文档的形式存储数据,但它们依然需要考虑如何查询这些数据,以及哪些字段需要被索引以便检索时更加快速。

在Elasticsearch中,每一个字段的数据都是默认被索引的。也就是说,每个字段专门有一个反向索引用于快速检索。而且,与其它数据库不同,它可以在同一个查询中利用所有的这些反向索引,以惊人的速度返回结果。

文档

程序中大多的实体或对象能够被序列化为包含键值对的JSON对象。通常我们可以认为对象与文档时等价相通的。

  • 对象时应该JSON结构体,类似于HashMap等,内部还可能包含其他对象
  • 文档在Elasticsearch当中特指最顶层结构或者跟对象序列化成的JSON数据,以唯一ID标识并存储在ES中

文档元数据

文档不仅仅只有数据,还包含了元数据–关于文档的信息,其中三个必须的元数据节点时:

  • _index:文档存储的地方。
    • 在ES当中index类似于数据库,是存储和索引关联数据的地方。
    • 实际上数据被存储和索引在分片中,索引只是一个把一个或多个分片分组在一起的逻辑空间。
  • _type:文档代表的对象的类。
    • 应用中,使用对象表示一些事物,每个对象都属于一个类,类定义了属性或与对象关联的数据。在关联数据库中,相同类的对象被存在一个表中。
    • ES当中,使用相同type的文档表示相同的事物,因为它们的数据结构也是相同的。每个type都有自己的mapping或结构定义,类型的mapping会告诉ES不同的文档如何被索引。
  • _id:文档的唯一标识。
    • 与index和type组合时,就可以唯一标识一个文档。

字段类型

  • text。文本类型,十分常用的类型。
    • 通常作用于需要被全文检索的字段上。这样的字段内容会被分词器拆成词项后生成倒排索引。
    • 它不用于排序,也很少用于聚合。
  • keyword。关键字类型,通常用于索引结构化的字段(通常意义明确,用于过滤)。
    • 这样的字段只能被精确搜索。
  • number。数字类型,这是一个概括。其中包含了 byteshortintegerlongfloatdoublehalf_floatscaled_float
    • 除了和Java类似的数字类型以外,还有相对于float精度折半的half_float,以及将浮点数进行缩放后存储的scaled_float字段长度越短,空间分配越合理,搜索效率越高。注意在浮点数中+0.0-0.0不同的存在。
    • 设置某字段为scaling_float,缩放因子100,适合存储精确至小数点后两位的数字,底层对数字扩大100做整形存储,而对API为float型。
  • date。日期类型,ES支持日期格式化后的字符串、从epoch开始的毫秒数(长整型)、从epoch开始的秒数(整形)
    • 在ES内部,日期都会转化为UTC时间并存储为从epoch开始的毫秒数。在开启动态映射的时候如果有新的字段被添加,默认会自动进行日期检测以判断是否该字段为日期类型(可以被关闭,将某类型的 date_detection选项设置为false)。同时日期格式也支持自定义(通过制定字段的format选项,默认为strict_date_optional_time || epoch_millis),除了yyyy-MM-dd HH:mm:ss这样的个性格式,
  • boolean。布尔类型,只接受truefalse
  • binary。二进制类型,该类型字段仅接受Base64编码后的字符串。
    • 字段默认不存储(store=false)也不搜索。
  • array。数组类型,其本身是其他类型。
    • 数组中的所有值必须为统一类型(可以包含null),而空数组由于无法确定类型会被作为missing field对待。在动态映射时,第一个加入数组的元素会决定整个数组的数据类型。
  • object。对象类型。在JSON中,对象是可以包含层级关系的,但是在ES中复合的对象会被扁平化处理,成为简单的k-v键值对。
    • 如果需要在建立索引时进行静态映射,mappings支持object的显式映射。
  • nested。嵌套对象,这是object类型的特例,支持object对象数据的独立索引和查询(ES在使用对象类型的数组时由于扁平化处理会导致一些索引问题)。
    • 当指定了nested类型进行索引某个字段时,该字段会内容会作为独立的隐藏文档存在。这样支持了嵌套对象的索引,但是由于类似结构化数据的关联查询一般,nested字段越多,搜索越复杂,所以每个索引可以使用嵌套对象被限制在50。
  • geo_point。地理坐标,用来精确存储地理经纬信息的类型,支持4中写入方式:
    • 经纬坐标字符串,如:"40.3,116.17"
    • 经纬坐标键值对,如:{"lat": 40.3, "lon": 116.17}
    • 地理位置哈希值,如:"drm3btev3e86"
    • 经纬坐标数组,如:[40.3, 116.17]
  • geo_shape。地理区块,使用GeoJSON对区块进行编码,描述一块地理区域,支持点线面等多种特征。
  • ip。ip类型,可以保存ip地址,支持IPv4及IPv6,以及无类型域间选路格式
  • range。范围类型,支持integer_rangelong_rangefloat_rangedouble_rangedate_rage
    • 其中日期区间以毫秒计时。在某字段使用range类型之后,插入数据需要指定对应的范围,可以使用 gtlte 等关键字描述。
  • token_count。词项统计类型,其本身是一个整形。
    • 一般用来给某个属性增加附加字段并指定 token_count 来统计词项长度。词项长度取决于具体内容和指定的分词器。

索引

  • created指同索引、同类型下是否已经存在同ID的文档。
  • version代表版本号,如果原来已经有一个版本,则会将之前的版本覆盖:
    • 在内部ES已经标记旧文档,其不会立即删除,但也不能访问,ES会在你继续索引更多数据时清理被删除的文档。

查找

mget允许一次检索多个文档。

更新

文档在ES中是不可变的,即我们不能修改它们,如果需要更新已存在的文档,我们可以使用indexAPI重建索引或替换它。

1
2
3
PUT /website/blog/123{
“title”: “doit”,
}

得到响应为:

1
2
3
4
5
6
7
{		
"_index":"website",
"_type" :"blog",
"_id":"123",
"_version": 2,
"created":false
}

局部更新

文档是不可变的,updateAPI局部更新也只是进行了相同的检索-修改-重建索引流程。但是我们减少了其他进程可能导致冲突的修改。

为博客增加一个tags字段和views字段。

1
2
3
4
5
6
POST /website/blog/1/_update{
"doc":{
"tags" : ["test"],
"views": 0
}
}

请求成功后:

1
2
3
4
5
6
{
"_index": "website",
"_id": "1",
"_type": "blog",
"_version": 3
}

当API不能满足要求时,使用脚本可以实现我们自己的逻辑。

批量操作

bulkAPI允许我们使用单一请求来实现多个文档的createindexupdatedelete,这对索引类似于日志活动这样的数据流非常有效,可以以成百上千的数据为一个批次按序进行索引。

bulk的请求体:

1
2
3
4
{ action: {metadata}}\n
{ request body}\n
{ action: {metadata}}\n
{ request body}\n
  • 每行必须以”\n”符号结尾,包括最后一行。这些都是作为每行有效的分离而做的标记。
  • 每一行的数据不能包含未被转义的换行符,它们会干扰分析——这意味着JSON不能被美 化打印。
  • request body由文档的_source组成——文档所包含的一些字段以及其值。

action类型:

  • create。
  • index。
  • update。
  • delete。

以delete为例:

1
{"delete":{"_index":"website","_type":"blog","_id":"123"}}

update:

1
2
{"update":{"_index":"website","_type":"blog","_id":"123",	"_retry_on_conflict":"11"}}
{"doc":{"title":"My updated blog post"} }

限制

整个批量请求需要被加载到接受我们请求节点的内存里,所以请求越大,给其它请求可用的内存就越小。有一个最佳的bulk请求大小。超过这个大小,性能不再提升而且可能降低。

最佳大小,当然并不是一个固定的数字。它完全取决于你的硬件、你文档的大小和复杂度以及索引和搜索的负载。幸运的是,这个最佳点(sweetspot)还是容易找到的:

  • 试着批量索引标准的文档,随着大小的增长,当性能开始降低,说明你每个批次的大小太大了。开始的数量可以在1000~5000个文档之间,如果你的文档非常大,可以使用较小的批次。
  • 通常着眼于你请求批次的物理大小是非常有用的。一千个1kB的文档和一千个1MB的文档大不相同。一个好的批次最好保持在5-15MB大小间。

并发控制

ES使用乐观并发控制,假设冲突不经常发生,也不区块化访问。如果在读写过程中数据发生了变化,更新操作将失败。

这时候由程序决定在失败后如何解决冲突,实际情况中可以重新尝试更新、重新读取或直接反馈给用户。

乐观并发控制

ES是分布式的,当文档被创建、更新或删除,文档的新版本会被复制到集群的其他节点。

  • ES既是同步的也是异步的。复制请求都是平行发送的,并无序地到达目的地。即需要一种方法确保老版本的文档永远不会覆盖新版本。
    • _version保证了索引修改都被正确的进行排序,如果旧版本出现在新版本后则会被简单地忽略。因此保证数据不会因为修改冲突而丢失。
    • 指定文档的版本号来做想要的更改,如果不匹配则请求失败。

使用外部版本控制系统

一种常见的结构是使用一些其他的数据库做为主数据库,然后使用Elasticsearch搜索数据,这意味着所有主数据库发生变化,就要将其拷贝到Elasticsearch中。如果有多个进程负责这些数据的同步,就会遇到上面提到的并发问题。

如果主数据库有版本字段,或时间戳,则可以在ES的查询字符串中添加version_type=external来使用这些版本号,此时对于外部版本号它不会再检查_version是否与请求中指定的一致,而是检查是否小于指定版本,如果请求成功则外部版本号将存储到_version中。

搜索

多索引和多类型

Java API

QueryBuilder是es中提供的一个查询接口, 可以对其进行参数设置来进行操作。

https://www.cnblogs.com/xxx0624/p/4306226.html

参考