![kafka教程](https://i-blog.csdnimg.cn/blog_migrate/b102ec79f85c6200a5b609af31f7fb48.png)
kafka教程
这是文章系列的第二部分:使用Storm,Kafka和ElasticSearch处理实时数据。
1.简介
在第一部分中,我们描述了问题以及我们将如何解决它。 为了刷新您的记忆,计划是创建一个历史飞行数据的数据缩减系统(您可以从此处免费下载)。 我们将使用以下技术堆栈构建DRS:Apache Storm→Apache Kafka→ElasticSearch→(可视化:Kibana,OpenLayers,OpenMap等)。
我们已经在上一篇文章中解释了ElasticSearch的基础知识及其工作原理。 在本文中,我们将学习如何在ElasticSearch中执行搜索。
2. CRUD命令
您可能熟悉数据库中的首字母缩写CRU D :
- C –创建
- R –检索或读取
- U –更新
- D –删除
下表将每个CRUD命令与其各自的ElasticSearch HTTP / REST命令相对应。 这些命令适用于索引以及文档。
CRUD命令 | HTTP / REST命令 |
创造 | PUT 或POST |
读 | GET |
更新资料 | PUT 或POST |
删除 | DELETE |
因此,让我们从上一篇文章中学到Kibana,并导航到Kibana的控制台。
2.1创建索引
要创建索引flight
期,请发出以下命令:
PUT /flight
GET /_cluster/health
请注意,现在群集的运行状况已从绿色更改为黄色。 发生这种情况是因为我们仅运行一个Elasticsearch实例。 单节点群集具有完整的功能,但是无法将数据复制到其他节点以提供弹性。 副本分片必须可用于其他节点,群集状态才能变为绿色。 如果群集状态为红色,则某些数据不可用。
为了解决这个问题,你需要创建ElasticSearch的另一个安装(相同的,但最好到另一台机器),并改变node.name
内elasticsearch.yml
; 两个实例中的cluster.name
必须保持相同(默认值为elasticsearch
)。 另一种方法是在命令行上将配置参数传递给弹性搜索,例如
bin/elasticsearch -Enode.name=node-2 -Epath.data=./node-2/data -Epath.logs=./node-2/logs
GET /_cat/indices?v
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open flight w696u4y3SYWuGW--8VzW6Q 1 1 0 0 208b 208b
我们的主分片包含一个副本分片,但未分配给任何节点。
GET /_cat/shards?v
index shard prirep state docs store ip node
flight 0 p STARTED 0 208b 127.0.0.1 MacBook-Pro.local
flight 0 r UNASSIGNED
您会注意到副本分片未分配( prirep = r
表示副本)。
2.2创建文件
让我们向索引添加一些测试数据:
PUT /flight/_doc/1
{
"Icao":"A0835D",
"Alt":2400,
"Lat":39.984322,
"Long":-82.925616
}
或作为curl
命令:
curl -X PUT "localhost:9200/flight/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
"Icao":"A0835D",
"Alt":2400,
"Lat":39.984322,
"Long":-82.925616
}'
Content-Type
对于查询成功至关重要。 我们创建了ID = 1
的新航班。 我们也可以使用POST
代替PUT
,但是,在这种情况下,我们不能传递ID。 在这种情况下,ElasticSearch将为我们的文档生成一个自动ID。 这是ElasticSearch返回的结果:
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "flight",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"Icao" : "A0835D",
"Alt" : 2400,
"Lat" : 39.984322,
"Long" : -82.925616
}
}
]
}
}
结果文档位于_source
键中。
2.3删除文件
如果您知道文档的索引:
DELETE /flight/_doc/1
2.4删除索引
要删除索引,只需发出:
DELETE /flight
2.5导入批量数据
我们的方案是处理航班数据。 理想情况下,这些数据将通过多个传感器(雷达)实时获得,但是由于这很难实现,因此我们将使用可从此处下载的批量飞行历史数据。 有关各个领域的说明,请参见此处。
该文件必须以空行结尾。 如果不是,则添加一个。 在下载批处理文件的目录中,发出以下命令(每个.json
文件):
curl -H "Content-Type: application/x-ndjson" -XPOST http://localhost:9200/flights/_bulk --data-binary "@2016-07-01-1300Z.json"
请注意,内容类型是"application/x-ndjson"
而不是"application/x-json"
。 另外,请注意,我们将数据表示为二进制以便保留换行符。 文件名为2016-07-01-1300Z.json
。 具有相同ID的所有现有文档都将被.json
文件中的文档替换。
如果运行此命令,它将失败。 如果可行,那太容易了,不是吗? ElasticSearch预计其.json
文件有特定的格式:
{"index":{"_id":4800770}}
{"Rcvr":1,"HasSig":false,"Icao":"494102", "Bad":false,"Reg":"CS-PHB", ...}
...
这意味着您必须将每个下载的.json
文件转换为上述格式,包括以下步骤:
- 在每个实际数据文档上方添加以
"index"
开头的行 - 将
"Id":<value
>移至{"_id":<value>}
如果您不想花时间手动修改.json
文档,那么在下一篇文章中,我们将开发一个Java程序来解析它们,并使用ElasticSearch的REST API将文件插入ElasticSearch中。 到那时,您可以从此处下载只有几个航班的示例.json
文件。
2.6搜索查询
ElasticSearch全部与搜索有关。 它允许您使用符合某些条件的搜索查询。
GET /flight/_search?pretty
{ "query": {
"match_all" : {
}
}
}
上面的搜索查询匹配index flight
所有文档。 也可以这样简化:
GET /flight/_search
以下查询搜索与给定的Icao匹配的文档。
GET /flight/_search?pretty
{ "query": {
"match" : {
"Icao" : "A0835D"
}
}
}
也可以执行嵌入在请求URL中的搜索:
GET /flight/_search?q=Icao:A0835D
也可以写成:
GET /flight/_search?pretty
{ "query": {
"query_string": {
"query": "Icao:A0835D"
}
}
}
除了"match"
和"query_string"
还可以使用"term"
。 使用"term"
进行完全匹配(例如布尔值,数字,日期,枚举值,关键字等)。
GET /flight/_search?pretty
{ "query": {
"term": {
"Mil": true
}
}
}
您也可以使用"terms"
来搜索值数组。
您可以按"wildcard"
进行搜索,并使用通配符*
和/或?
或加prefix
。
GET /flight/_search?pretty
{ "query": {
"wildcard": {
"Call": "NJ*"
}
}
}
但是请注意,通配符查询可能很慢,特别是如果通配符位于搜索字符串的开头。
您也可以使用“ regexp ”并为搜索字符串提供正则表达式特殊字符。
或者,您可以使用"ids"
搜索文档ID的数组,或者使用"range"
提供值的范围(分别对>, ≥, <, ≤
使用gt
, gte
, lt
, lte
)。
GET /flight/_search?pretty
{ "query": {
"range": {
"Year": {
"gte": "2010",
"lt": "2016"
}
}
}
}
您还可以使用锚定日期,例如"gte": "2010/01/01||-1y"
,这表示在锚定日期1 2010年1月1日之前的所有日期。可以在此处找到更多信息。
要搜索字段中具有非空值的文档:
GET /flight/_search?pretty
{
"query": {
"exists": {
"field": "Cou"
}
}
}
您也可以使用"match_phrase"
来匹配短语,或者使用"multi_match"
来匹配许多字段:
GET /flight/_search?pretty
{
"query": {
"multi_match": {
"query": false,
"fields": ["Mil", "Gnd"]
}
}
}
也可以构建复合查询:
GET /flight/_search?pretty
{
"query": {
"bool": {
"must": [
{
"match": {
"Icao": "A0835D"
}
},
{
"range": {
"Alt": {
"lte": 10000
}
}
}
]
}
}
}
我们可以像这样改善性能:
GET /flight/_search?pretty
{
"query": {
"bool": {
"must": [
{
"match": {
"Icao": "A0835D"
}
}
],
"filter": [
{
"range": {
"Alt": {
"lte": 10000
}
}
}
]
}
}
}
must
替代方法是: must_not
, should
。
但是,为什么第二个版本可以提高性能呢? 对象的filter
用于匹配或不匹配的查询。 没有关于文件匹配程度的概念。 因此,在这种情况下,对海拔高度≤1000的文档匹配程度进行评分不会增加任何值(当您使用范围时,结果将对查询匹配程度的得分有所帮助)。 此外, filter
缓存结果,可以提高后续查询的性能。
2.7更新文件
要更新您知道ID的文档,请使用_update
API:
POST /flight/_update/4800770
{
"doc": {
"Mil": true
}
}
使用上述命令,我们也可以向文档中添加新字段。
附带说明一下,ElasticSearch文档是不可变的! 因此,当我们请求更新文档时,ElasticSearch会在后台进行操作,它检索文档,更改其字段并为具有相同ID的文档重新索引,从而有效地替换了它。
可以使用脚本发送更复杂的查询,例如:
POST /flight/_update/4800770
{
"script": {
"source": "ctx._source.FlightsCount++"
}
}
ctx
表示上下文。 还有许多其他更新文档的方法,例如upsert (即根据文档是否已存在有条件地更新或插入文档)。
POST /flight/_update/4800771
{
"script": {
"source": "ctx._source.FlightsCount++"
},
"upsert": {
"Rcvr":1,
"HasSig":false,
"Icao":"AE4839",
...
},
}
由于ID为4800771
文档不存在,因此正在使用"upsert"
。 您还可以使用_update_by_query
API:
POST /flight/_update_by_query
{
"script": {
"source": "ctx._source.FlightsCount++"
},
"query": {
"match_all": {}
}
}
请记住,如果上面的查询失败,查询将中止,而不是回滚! 该查询不在事务内运行。 这意味着某些文档可能会更新,而有些则不会。 这就是为什么在运行更新查询之前进行备份很重要的原因。
要替换文档,只需使用与现有文档相同的ID:
PUT /flight/_doc/4800770
{
"Rcvr":1,
"HasSig":false,
"Icao":"494102",
"Bad":false,
...
}
2.8删除文件
还有一个_delete_by_query
API:
POST /flight/_delete_by_query
{
"query": {
"match_all": {}
}
}
2.9批量查询
批量API可帮助我们通过一个查询对许多文档执行这些操作。 该API包含4个动作: index,create,update,delete 。
POST /_bulk
{ "index": { "_index" : "flight", "_id": 10519389 } }
{ "Rcvr":1,"HasSig":true,"Sig":0,"Icao":"A0835D","Bad":false, ... }
{ "create": { "_index" : "flight", "_id": 4800770 } }
{"Rcvr":1,"HasSig":false,"Icao":"494102","Bad":false, ... }
{ "update": { "_index" : "flight", "_id": 4800770 } }
{ "doc": {"Mil": true } }
{ "delete": { "_index" : "flight", "_id": 4800770 } }
索引和创建操作之间的区别如下:如果文档已经存在,则创建将引发错误,而索引将替换文档。
如果批量查询要针对相同的索引运行,那么我们可以像这样简化查询:
POST /flight/_bulk
{ "index": { "_id": 10519389 } }
{ "Rcvr":1,"HasSig":true,"Sig":0,"Icao":"A0835D","Bad":false, ... }
{ "create": { "_id": 4800770 } }
{"Rcvr":1,"HasSig":false,"Icao":"494102","Bad":false, ... }
{ "update": { "_id": 4800770 } }
{ "doc": {"Mil": true } }
{ "delete": { "_id": 4800770 } }
失败的操作不会影响其他操作。
3.映射
但是,ElasticSearch如何知道如何映射数据? 动态映射意味着没有明确定义映射,或者至少没有为某些字段定义。 这是通过检查文档字段的值类型来完成的。 如果您对动态映射不满意,则可以进行显式映射。
要查看数据映射,请在Kibana中键入以下内容:
GET /flight/_mapping
ElasticSearch执行日期检测,并检查字段的内容是否与已定义的任何动态数据格式匹配。 默认情况下,这将是年,月和日,中间用斜杠和可选的时间戳分隔。 如果存在匹配项,则匹配日期格式将添加到该字段的映射中。 一个字段可以具有多个映射。 例如, String
类型的字段具有两个映射:
- 作为
"type" : "text"
, -
"fields"
属性,其中包含一个名为"keyword"
且类型为"keyword"
的字段。
例如
"Call" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
两者之间的区别在于, "text"
类型用于全文搜索,而"keyword"
类型用于精确匹配(过滤器),排序,聚合等。
存储在Elasticsearch集群中的每个文档都有一些与之关联的元数据,除了我们在索引文档时指定的数据字段(称为meta-fields)之外:
-
_index
:包含文档所属索引的名称。 -
_id
:存储文档的ID -
_source
:包含在索引文档时传递给Elasticsearch的原始JSON对象。 该字段未编制索引,因此您无法搜索它,但可以检索它。 -
_field_names
:包含每个包含非空值的字段的名称。 -
_routing
:基于指定值将文档路由到分片的定制路由:文档版本控制:可以存储定制数据
ElasticSearch数据类型可以分为四类:
- 核心:例如,
text
,数字,例如byte
(-128至127),short
,integer
,long
,float
,half_float
,scaled_float
(存储为long
的float
),double
,date
,boolean
,binary
(使用Base64编码),range
(例如{"gte": 1, "lte":10}
即1-10
) - complex :
object
(JSON),array
(展平嵌套的对象),nested
(每个对象都被索引为隐藏文档,确保每个对象都是独立的) - 地理:
geo_point
,geo_shape
(point
,polygon
,linestring
,multipoint
,multilinestring
,multipolygon
,geometrycollection
,envelope
,circle
表示为)GeoJSON
对象 - 专门:
ip
,completion
,attachment
(需要Ingest附件处理器插件)
您可以通过三种方式提供date
:
- 作为
string
(可以配置日期格式) - 以
integer
表示从epoch
以来的秒数 - 以
long
表示,代表自epoch
以来的毫秒数(这是内部存储日期的方式)。
您可以通过以下四种方式之一提供geo_point
:
- 作为带有
"lat"
和"lon"
键的对象 - 以纬度和经度以逗号分隔的
string
,并按此顺序 - 作为
geohash
- 作为具有经度和纬度值的
array
要添加映射:
PUT /flight/_mapping
{
"properties": {
"location": {
"type": "geo_point"
}
}
}
请注意,一旦创建了字段映射,就不能对其进行修改。 唯一的方法是删除并重新创建索引。
在下面的示例中,我们手动创建了各种禁用动态映射的映射。
PUT /flight/_mapping
{
"dynamic": false,
"properties": {
"Rcvr": {
"type": "integer"
},
"Icao": {
"type": "text"
},
...
"location": {
"type": "geo_point"
}
}
}
ElasticSearch的映射参数可以帮助创建新的映射。 例如,创建一个自定义location
字段,我们可以像这样使用copy_to
参数:
PUT /flight/_mapping
{
"properties": {
"Lat": {
"type": "float",
"copy_to": "location"
},
"Long": {
"type": "float",
"copy_to": "location"
},
"Location": {
"type": "geo_point"
}
}
}
这将是最有可能对你有用的另一种映射参数是format
后面的乔达时间格式(默认"strict_date_optional_time||epoch_millis"
)。
PUT /flight/_mapping
{
properties: {
"Year": {
"type": "date",
"format": "year"
}
}
}
如果您有unix时间戳,则可以将其乘以1000。
内置的日期格式可以在这里找到。
您可能会想像这样添加映射:
"FSeen": {
"type": "date",
"format": "\/Date(epoch_millis)\/"
}
映射"Fseen":"\/Date(1467378028852)\/"
但是不幸的是,这不会起作用。 我们将在下一篇文章中看到如何处理这种格式。
如果您更新了映射,请在禁用动态映射的情况下发出以下查询以更新ElasticSearch:
POST /flight/_update_by_query?conflicts_proceed
4.总结
在本文中,我们重点介绍了如何使用ElasticSearch作为主要用途,即搜索文档。 在接下来的文章中,我们将学习如何批量导入.json
文件ElasticSearch的改造后, .json
通过使用JSON库来解析文件到ElasticSearch的大宗原料药想要的格式,而且还.json
文件,插入文件ElasticSearch使用其REST API。 然后,您可以重新访问本文并重新运行各种搜索查询以评估结果。
5.参考
- Andhavarapu A.(2017年),《学习ElasticSearch》, Packt。
- Dixit B.(2016年), ElasticSearch Essentials, Packt。
- ElasticSearch教程
- Gormley C.&Tong Z.(2015),《 ElasticSearch权威指南》, O'Reilly。
- JavaCodeGeeks, ElasticSearch教程,迷你书。
- Pranav S.和Sharath KMN(2017),《学习Elastic Stack 6.0》, Packt。
- Redko A.(2017年), ElasticSearch教程, JavaCodeGeeks。
- 塔瓦A.&Azarmi B.(2019),学习Kibana 7,第2版。 Packt。
- Wong WT(2019),高级ElasticSearch 7.0 ,Packt。
5.下载命令
那是文章系列的第二部分:使用Storm,Kafka和ElasticSearch处理实时数据。
kafka教程