Elasticsearch是一个分布式的免费开源搜索和分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据。
Elasticsearch在Apache Lucene的基础上开发而成。然而,Elasticsearch不仅仅是Lucene,并且也不仅仅只是一个全文搜索引擎:
所以Apache Lucene是什么?
Lucene是一套用于全文索引和搜索的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜索。但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)
Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎
在Java开发环境里Lucene是一个成熟的免费开源工具。就其本身而言,Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库。人们经常提到信息检索程序库,虽然与搜索引擎有关,但不应该将信息检索程序库与搜索引擎相混淆。
Elastic Stack是一套适用于数据采集、扩充、存储、分析和可视化的免费开源工具。人们通常将Elastic Stack称为ELK Stack(代指 Elasticsearch、Logstash 和 Kibana)
Logstash是什么?
Logstash 是 Elastic Stack 的核心产品之一,可用来对数据进行聚合和处理,并将数据发送到 Elasticsearch。Logstash 是一个开源的服务器端数据处理管道,允许您在将数据索引到 Elasticsearch 之前同时从多个来源采集数据,并对数据进行充实和转换。
Kibana是什么?
Kibana 是一款适用于Elasticsearch的数据可视化和管理工具,可以提供实时的直方图、线形图、饼状图和地图。Kibana同时还包括诸如 Canvas和Elastic Maps等高级应用程序。
Canvas允许用户基于自身数据创建定制的动态信息图表
Elastic Maps用来对地理空间数据进行可视化。
Elasticsearch的特点
Elasticsearch 很快。 由于Elasticsearch是在Lucene基础上构建而成的,所以在全文本搜索方面表现十分出色。Elasticsearch同时还是一个近实时的搜索平台,这意味着从文档索引操作到文档变为可搜索状态之间的延时很短,一般只有一秒。因此,Elasticsearch 非常适用于对时间有严苛要求的用例,例如安全分析和基础设施监测。
Elasticsearch 具有分布式的本质特征。 Elasticsearch中存储的文档分布在不同的容器中,这些容器称为分片,可以进行复制以提供数据冗余副本,以防发生硬件故障。Elasticsearch的分布式特性使得它可以扩展至数百台(甚至数千台)服务器,并处理 PB 量级的数据。
Elasticsearch 包含一系列广泛的功能。 除了速度、可扩展性和弹性等优势以外,Elasticsearch 还有大量强大的内置功能(例如数据汇总和索引生命周期管理),可以方便用户更加高效地存储和搜索数据。
Elastic Stack 简化了数据采集、可视化和报告过程。 通过与 Beats 和 Logstash 进行集成,用户能够在向 Elasticsearch 中索引数据之前轻松地处理数据。同时,Kibana 不仅可针对 Elasticsearch 数据提供实时可视化,同时还提供 UI 以便用户快速访问应用程序性能监测 (APM)、日志和基础设施指标等数据。
Elasticsearch在速度和可扩展性方面都表现出色,而且还能够索引多种类型的内容,这意味着其可用于多种用例:
应用程序搜索 网站搜索 企业搜索 日志处理和分析 基础设施指标和容器监测 应用程序性能监测 地理空间数据分析和可视化 安全分析 业务分析
Elasticsearch是一个文档型数据库,为了方便理解,下面给出其与传统的关系型数据库的对比
Relational DB | Elasticsearch |
---|---|
数据库 Database | 索引 Index |
表 Table | 类型 Type |
行 Rows | 文档 Document |
列 columns | 字段 fields |
表结构 schema | 映射 mapping |
上面之所以说一个索引是一个库是因为传统的关系型数据库里面一个库下的表直接是互相有关系的,但是es这里是相互独立的没有联系的,所以最终可以理解es这个里面有各种互相独立的表,这个表下面的mappings就是所有的表的字段结构。
注意:text类型在查找的时候会分词,keyword不会,比如hello world这个字符串,在查找的时候,他会被分为 hello、world两个term,搜索这两个任意一个都能查到这个数据,但是用这个字段的keyword属性他就是一个整体,只有搜索完整的才查的到,所以在上面,每个字段后面又多一个一个field就可以理解为一个子属性。
Elasticsearch是面向文档的,索引和搜索数据的最小单位是文档,并且使用JSON来作为文档的序列化格式。每个文档可以由一个或多个字段组成,类似于关系型数据库中的一行记录,在Elasticsearch中,文档有几个重要属性
类型是文档的逻辑容器,类似于表格是行的容器。在不同的类型中,最好放入不同结构的文档。(有点类似于关系型数据库中的表的概念)。
ElasticSearch要在7.X版本去掉type
在Elasticsearch设计初期,是直接查考了关系型数据库的设计模式,存在了类型(表)的概念。 但是,其搜索引擎是基于Lucene的,这种基因决定了类型是多余的。Lucene的全文检索功能之所以快,是因为倒序索引的存在。 而这种倒序索引的生成是基于索引的,而并非 类型。多个类型反而会减慢搜索的速度——两个不同type下的两个user_name,在ES同一个索引下其实被认为是同一个filed,你必须在两个不同的type中定义相同的filed映射。否则,不同type中的相同字段名称就会在处理中出现冲突的情况,导致Lucene处理效率下降。
在Elasticsearch中,索引有两个含义:
下载docker镜像
docker pull elasticsearch:7.10.2
映射配置文件
# 配置映射文件夹
mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
# 设置文件夹权限任何用户可读可写
chmod 777 /mydata/elasticsearch -R
# 配置 http.host
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml
Copy
启动容器
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type"="single-node" \ # 设置为单节点
-e ES_JAVA_OPTS="-Xms64m -Xmx128m" \ # 设置启动时ES的初始内存以及最大内存
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.10.2
访问ES服务,http://ip:9200/
得到相应体如下:
{
"name" : "de85ed684243",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "UeIP1PrXT2OFd7FlEEl3hQ",
"version" : {
"number" : "7.4.2",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "2f90bbf7b93631e52bafb59b3b049cb44ec25e96",
"build_date" : "2019-10-28T20:40:44.881551Z",
"build_snapshot" : false,
"lucene_version" : "8.2.0",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
可以通过/_cat来获取节点信息
# 访问http://ip:9200/_cat
# 属性列表
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes
/_cat/tasks
/_cat/indices
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/thread_pool/{thread_pools}
/_cat/plugins
/_cat/fielddata
/_cat/fielddata/{fields}
/_cat/nodeattrs
/_cat/repositories
/_cat/snapshots/{repository}
/_cat/templates
下载docker镜像
docker pull kibana:7.4.2
启动容器
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.0.128:9200 -p 5601:5601 -d kibana:7.4.2
ES的基本REST命令:
Method | Url | 描述 |
---|---|---|
PUT | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定文档id) |
GET | localhost:9200/索引名称/类型名称/文档id | 通过文档id查询文档 |
POST | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) |
POST | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 |
POST | localhost:9200/索引名称/类型名称/_search | 查询所有数据 |
DELETE | localhost:9200/索引名称/类型名称/文档id | 删除文档 |
创建索引
在创建索引时,我们可以声明字段与数据类型的映射
请求:
PUT /test0
{
"mappings":{
"properties":{
"name":{
"type":"text"
},
"author":{
"type":"text"
}
}
}
}
响应:
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "test0"
}
注意⚠️:由于索引具有不变性,我们只能进行追加而不能更改已经存在的映射字段,必须创建新的索引后进行数据迁移。
POST _reindex
{
"source": {
"index": "test0"
},
"dest": {
"index": "test1"
}
}
插入文档
PUT和POST都可以插入文档:
PUT /test1/books/1
{
"name":"three days to see",
"author" : "Daniel Defoe"
}
响应:
{
"_index": "test", //索引
"_type": "book", //类型
"_id": "1", //id
"_version": 1, //版本号
"result": "updated", //操作类型
"_shards": { //分片
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1, //并发控制字段,每次更新就会+1,用来做乐观锁
"_primary_term": 1 //同上,主分片重新分配,如重启,就会变化
}
查询文档
示例:查询test1索引下的books类型中保存标识为 1 的文档的内容。
请求:
GET /test1/books/1
响应:
{
"_index": "test1",
"_type": "books",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"name": "three days to see",
"author": "Daniel Defoe"
}
}
更新文档
使用POST命令,在ID后面加_update,并把需要修改的内容放入doc属性中
示例:更新test1 索引下的books类型中保存标识为 1 的文档的内容。
请求:
POST /test1/books/1/_update
{
"doc" : {
"name":"three days to see",
"author" : "Daniel Defoe",
"country" : "England"
}
}
响应:
{
"_index": "test1",
"_type": "books",
"_id": "1",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
删除文档和索引
删除使用DELETE命令
示例:删除文档/test1/books/1
请求:
DELETE /test1/books/1
响应:
{
"_index": "test1",
"_type": "books",
"_id": "1",
"_version": 3,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 2,
"_primary_term": 1
}
示例:删除索引/test1
DELETE /test1
响应:
{
"acknowledged": true
}
查询方式
ES支持两种查询方式,一种是直接在URL后加上参数,另一种是在URL后加上JSON格式的请求体。
示例:查找到收入最高的十条记录
URL + 参数
常用的参数如下
GET /test_data/_search?q=*&sort=balance:desc&from=0&size=10
URL + QueryDSL
GET /test_data/_search
{
"query": {
"match_all" : {}
},
"sort" : [{
"balance" : "desc"
}],
"from" : 0,
"size" : 10
}
虽然URL+参数的写法非常简洁,但是随着逻辑的复杂化,其可读性也越来越差,所以通常都会使用URL + QueryDSL的格式。
无论你在任何字段上进行的是全文搜索还是精确查询,match 查询是你可用的标准查询。
对于not_analyzed的字段,match能做到精确查询,而对于analyzed的字段,match能做到匹配查询(全文搜索)。
示例:查找所有年龄为25岁的记录(精确查询)
GET /test_data/_search
{
"query":{
"match" : {
"age": 25
}
}
}
示例:查询所有地址与976 Lawrence Street相关的记录(全文搜索)
GET /test_data/_search
{
"query":{
"match" : {
"address": "976 Lawrence Street"
}
}
}
match_all 全部匹配
match_all 用于查询所有文档。在没有指定查询方式时,它是默认。
示例:查询年龄最小的十条记录
请求:
GET /test_data/_search
{
"query": {
"match_all" : {}
},
"sort" : [{
"age" : "asc"
}],
"from" : 0,
"size" : 10
}
match_phase 短语匹配
match_phase用于进行短语的匹配,它查询时并不是像term一样不进行分词直接查询,而是借助分析器返回的查询词的相对顺序以及偏移量来做判断——满足所有查询词且顺序完全相同的记录才会被匹配上。
示例:地址包含502 Baycliff Terrace的记录
GET /test_data/_search
{
"query":{
"match_phase" : {
"address": "502 Baycliff Terrace"
}
}
}
multi_match 多字段匹配
multi_match 可以在多个字段上执行相同的 match 查询。
示例:查找city或address字段中包含Dixie或Street的记录。
请求:
GET /test_data/_search
{
"query":{
"multi_match":{
"query":"Dixie Street",
"fields":[
"city",
"address"
]
}
}
}
term即直接在倒排索引中查询,也就是精确查找,不进行分词器分析,文档中必须包含整个搜索的词汇。
term和match的区别:
GET /test_data/_search
{
"query": {
"term": {
"address": "Street"
}
}
}
// 虽然文档中存在”702 Quentin Street“,但是由于文本分析器默认会转为小写,无法搜到任何数据
借助布尔查询可以实现如SQL中(and、or、!=)等逻辑条件的判断,并且可以合并任何其他查询语句,包括复合语句。复合语句之间可以相互嵌套,可以表达复杂的逻辑。
must(and):文档必须匹配这些条件才能被包含进来。(影响相关性得分)
must_not(not):文档必须不匹配这些条件才能被包含进来。(不影响相关性得分)
should(or):如果满足这些语句中的任意语句,将增加得分 。(用于修正相关性得分)
示例:查找年龄不等于18的地址包含Street的男性,且优先展示居住在30岁以上的的记录
GET /test_data/_search
{
"query":{
"bool":{
"must":[
{
"match":{
"address":"Street"
}
},
{
"match":{
"gender":"M"
}
}
],
"must_not":[
{
"match":{
"age":"18"
}
}
],
"should":[
{
"range":{
"age":{
"gt":30
}
}
}
]
}
}
}
Filter通常搭配布尔查询一起使用,用于过滤出所有满足Filter的记录,不影响相关性得分。
示例:查找年龄在30~60之间的记录
GET /test_data/_search
{
"query":{
"bool":{
"filter":[
{
"range":{
"age":{
"gte":30,
"lte":60
}
}
}
]
}
}
}
要掌握聚合,需要明白两个主要的概念:
桶(Buckets):满足特定条件的文档的集合
指标(Metrics):对桶内的文档进行统计计算
SELECT
COUNT(1),
MAX(balance)
FROM table
GROUP BY gender;
桶在概念上类似于 SQL 的分组(GROUP BY,如上面的GROUP BY gender),而指标则类似于 COUNT() 、 SUM() 、 MAX() 等统计方法,如MAX(balance)。
聚合的语法如下:
"aggregations" : {
"<聚合名称 1>" : {
"<聚合类型>" : {
<聚合体内容>
}
[,"元数据" : { [<meta_data_body>] }]?
[,"aggregations" : { [<sub_aggregation>]+ }]?
}
["聚合名称 2>" : { ... }]*
}
示例:按照性别进行分组,计算平均年龄和最高收入
GET /test_data/_search
{
"query":{
"match_all": {}
},
"aggs":{
"group_gender":{
"terms":{
"field":"gender"
},
"aggs":{
"avg_age":{
"avg":{
"field":"age"
}
},
"max_balance":{
"max":{
"field":"balance"
}
}
}
}
},
"size":0
}