ElK技术栈
ElasticSearch
版本:7.6.1
Elastic Searcher简称es,是一个开源的高扩展的分布式全文检索引擎,es使用Java开发Lucene作为核心实现所有的索引和搜索功能
Lucene工具包;是一套信息检索工具包,jar包,不包含搜索引擎
包含:索引结构,读写索引的工具,排序,搜索规则。
ElasticSearch是基于Lucene做了一些封装和增强
Elastic Search对比Solr
1.es基本是开箱即用,简单,Solr回稍微复杂
2.Solr利用Zookeeper进行分布式管理,而es自身带有分布式协调管理功能
3.Solr支持更多格式的数据,比如JSON,XML,CSV,而ElasticSearch仅支持JSON文件格式
4.Solr官方提供的功能更多,而Elastic Search本身更注重于黑犀牛功能,高级功能更多的是由插件提供的,例如图形化界面需要Kibana
5.Solr查询块,但更新索引时慢(插入删除慢)用于电商等查询多的应用,
ES建立索引块(即查询慢),实时性查询快
Solr是传统搜索应用的有利解决方案,但ES更适用于新兴的实时性搜索应用
6.Solr比较成熟,而ES相对开发维护者较少,更新太快,学习成本高
Elastic Search安装
jdk最低1.8
目录
解压即用
bin 启动文件
config 配置文件
log4j2 日志文件
jvm.options Java虚拟机的相关配置
elasticsearch.yml elasticsearch的配置文件
lib 相关jar包
modules 模块
plugins 插件
打开bin目录双击运行elasticsearch.bat
访问端口9200
head
可视化插件 elasticsearch
head:https://github.com/mobz/elasticsearch-head/archive/master.zip
解决跨域问题(跨端口 跨IP)
打开ES配置文件elasticsearch.yml
http.cors.enabled: true
http.cors.allow-origin: “*”
跨域打开 全部可访问启动可视化 npm install npm start
索引
可以把这个索引当做是数据库 可以把这个可视化界面当做是数据展示工具,后面的查询使用kiabana
ELK了解
安装kiabana之前先了解下ELK(Elasticsearch Logstash Kibana) 也称之Elasticseacher Stack, 而Elasticsearch是一个基于Lucene 分布式 通过Restful方式进行交互的近实时搜索平台框架,类似百度 谷歌等搜索大数据搜索引擎的场景都可使用作为底层支持框架,Logstash是ELK的中央数据流引擎,用于不同目标(文件/数据存储/MQ)搜集不同格式的数据,经过过滤后支持输出到不同目的地(文件/MQ/redis/elasticsearch/kafka等) kiabana可以将ES的数据通过友好的页面展示,提供实时分析功能,
kibana
kibna是一个针对Elascticsearch的开源分析可视化平台,用来搜索 查看交互存储在Elasticsearch索引中的数据。使用kibana可以通过各种图表进行高级数据分析及展示,kibana让海量数据更容易理解,操作简单基于浏览器的用户界面快速的创建仪表盘实时显示Elasticsearch查询动态。
注意安装和Elasticsearch版本
启动
解压后打开bin目录
访问路径:127.0.0.1:5601
找到开发工具 之后在此处操作
国际化
找到kibana的配置文件 kibana.yml最后一行i18n.locale: "zh-CN"即可
Elasticsearch核心
问题
集群,节点,索引,类型,文档,分片,映射是什么?
Elasticsearch和关系型数据与对比
DB | Elasticsearch |
---|---|
数据库(Database) | 索引(indices) |
表(tables) | types(正在弃用) |
行(rows) | documents(文档) |
字段(cloumns) | fields |
elasticsearch(集群)中可以包含多个索引(数据库),每个索引中可包含多个类型(表),每个类型下又可包含多个文档(行),每个文档中又可包含多个字段(列)
物理设计
elasticsearch在后台把每个索引划分为多个分片,每个分片可以在集群中的不同服务器间迁移(elasticsearch自己也是个集群)
逻辑设计
一个索引类型中,包含多个文档,例如文档1 文档2,当搜索一篇文档时的顺序:索引==>类型==>文档id。通过这个组合我们就能索引到某个具体的文档,注意:id不必是整数,实际上它是个字符串
文档
elasticsearch是面向文档的,之所以这么说,是因为索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个重要的属性:
1,自我包含,一遍文档同时包含字段和对应的值,也就是同时包含key:value
2,可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这样的;{就是JSON,我们使用框架会自动生成索引}
3,灵活的结构,文档不依赖预先定义的模式,关系数据库中,需要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候我们可以忽略字段或者动态的添加一个新字段
尽管我们可以随意的新增或忽略某个字段,但是每个字段的类型还是很重要,比如一个年龄字段,可以是字符串也可以是整型,因为elasticsearch会保存字段和类型之间的映射及其其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也称之为映射类型。
类型
类型是文档的逻辑容器,就像关系型数据库那样,表格是行的容器,类型中对于字段的定义称为映射,比如name映射为字符串类型。文档是无模式的,不需要拥有映射中所定义的所有字段,比如新增一个字段,elasticsearch会自动将新字段加入映射,但是这个字段不确定是什么类型。所以最安全的做法就是提前定义好所需要的映射,然后在使用。
索引
索引是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合。索引存储了映射类型的字段和其他设置,然后保存到各个分片上。
物理设计:节点和分片怎么工作?
一个集群至少有一个节点,一个节点就是一个elasticsearch进程,节点可以有多个索引,创建一个索引将会有5个分片(primary shard 也称为主分片)来构成,而每个主分片又会有一个副本(replica shard 又称为复制分片)
可以看到主分片和对应的复制分片都不在一个节点内,这样即使是一个节点挂掉,数据也不会丢失。实际上一个分片就是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得elasticsearch在不扫描全部文档的情况下,就能告诉你那些文档包含特定的关键字。
倒排索引
elasticsearch使用的是一种称为倒排索引的结构,采用的是Lucene倒排索引作为底层。这种结构利于全文检索,一个索引是由文档中所有不重复的列表构成,对于每个词,都有一个包含它的文档列表、
例子:
For the sins I am about to commit #放入文档1
may James Gosling forgive me #放入文档2
创建倒排索引,我们需要将每个文档拆分成独立的词(或称为词条或者tokens),然后创建一个包含所有不重复词条的排序列表:
trem | doc1 | doc2 |
---|---|---|
For | √ | × |
may | × | × |
the | √ | √ |
James | x | √ |
sins | √ | √ |
Gosling | × | √ |
I | √ | √ |
forgive | √ | √ |
am | √ | × |
me | √ | √ |
about | √ | √ |
to | √ | x |
commit | √ | √ |
例如搜索 For the
文档1包含2个词,文档2包含一个词,显然文 档1 的权重更高。
Elasticsearch的索引和Lucene的索引对比
在Elasticsearch中,索引(库)被分为多个分片,每份分片是一个Lucene的索引,所以一个Elasticsearch多个Lucene索引组成的(多个Lucene倒排索引)
IK分词器插件
标准的中文分词器
IK提供两种算法:ik_smart和ik_max_word,其中ik_smart为最少切分,ik_max_word为最细粒度划分
安装
解压到elasticsearch的plugins下即可
然后重启
[2022-01-23T19:50:23,025][INFO ][o.e.p.PluginsService ] [LAPTOP-0054K098] loaded plugin [analysis-ik]
代表已经加载了ik分词器
也可已使用elasticsearch-plugin list 查看插件列表
然后重启kiabana
ik_smart测试
ik_max_word:最细粒度划分
由分词可看出,ik不是瞎分的,而是有类似字典
当有自己的词不连贯的时候,会一个字一个字的分出来,这是我们需要自己将我们的词加入到ik的分词器字典中
ik分词器增加自定义
1.找到ik的配置文件
2.编写自己的词典(lph.dic)
3.重启
4.测试
发现会变成一个词了
Rest风格
一种软件架构风格,不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互的软件,基于rest风格的代码可以更简洁有层次,更易于实现缓存等机制。
基本的Rest命令说明:
method | URL地址 | 描述 |
---|---|---|
PUT | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定文档id) |
DELETE | localhost:9200/索引名称/类型名称/文档id | 删除文档 |
POST | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) |
GET | localhost:9200/索引名称/类型名称/文档id | 根据文档id查询文档 |
POST | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 |
POST | localhost:9200/索引名称/类型名称/_Search | 查询所有数据 |
举个栗子
索引相关的基本操作
1.创建一个索引(增)
PUT /索引名称/类型名(在淘汰)/文档id
{ “请求体”
}
#! Deprecation: [types removal] Specifying types in document index requests is deprecated, use the typeless endpoints instead (/{index}/_doc/{id}, /{index}/_doc, or /{index}/_create/{id}). { "_index" : "test1", #库 "_type" : "type1", #类型 "_id" : "1", #id "_version" : 1, #版本 未被更新过 "result" : "created", #创建状态 "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 0, "_primary_term" : 1 }
查看数据
2.基本的数据类型
字符串类型
text,keyword
数值类型
long,integer,short,byte,double,float,half float,scaled float
日期类型
date
te布尔值类型
boolean
二进制类型
binary
…
3.定义字段类型
4.获得规则
get 后可以使库名或表名
类似于MySQL查看表结构
5.查看默认的信息
如果我们不写类型 Elasticsearch会默认配置字段类型
6.扩展
7.修改(改)
可使用PUT命令覆盖 也可使用UPDATE
PUT方式
version此时改变,result 更新状态
update方式
8.删除(删)
文档相关的基本操作
1.添加数据
# 创建index
PUT /lph/user/1
{
"name":"铁柱",
"age":30,
"info":["钱","美元","欧元"]
}
2.获取数据
# 获取数据
GET /lph/user/1
3.更新 POST _update
如果post不带_update的话 其他会空
4. 简单的搜索
# 搜索
GET /lph/user/_search?q=name:"铁柱"
5.复杂查询
GET 排序 分页 高亮 模糊 精准
# 字段过滤,排序,分页 GET lph/user/_search { "query": { "match": { "name": "李" } }, "_source": ["name","age"] # _source 可选 指定输出的字段,不指定输出全部 , "sort": [ # sort 排序 选择字段进行排序 不进行排序有_score 排序没有 { "age": { "order": "desc" } } ], "from": 0 #from size 分页参数 from 第几页 size 每页容量 ,"size": 2 } # 返回结果 #! Deprecation: [types removal] Specifying types in search requests is deprecated. { "took" : 0, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { #JSON对象 "total" : { "value" : 3, #结果总数量 "relation" : "eq" }, "max_score" : 0.6063718, #最大匹配度 _score:条件的匹配度 "hits" : [ { "_index" : "lph", "_type" : "user", "_id" : "3", "_score" : 0.6063718, "_source" : { "name" : "李四", "age" : 29, "desc" : [ "牛", "羊", "小何" ] } }, { "_index" : "lph", "_type" : "user", "_id" : "1", "_score" : 0.6063718, "_source" : { "name" : "李二", "age" : 13, "desc" : [ "钱", "美元", "欧元" ] } }, { "_index" : "lph", "_type" : "user", "_id" : "4", "_score" : 0.6063718, "_source" : { "name" : "李四", "age" : 13, "desc" : [ "钱", "美元", "欧元" ] } } ] } } #bool 多条件and查询 GET lph/user/_search { "query": { "bool": { "must": [ # must 类似MySQL的and 所有条件都要匹配 { "match": { "name": "李" #条件1 name包含李 } }, { "match": { "age": "13" #条件2 age=13 } } ] } } } #bool 多条件or查询 GET lph/user/_search { "query": { "bool": { "should": [ # should 类似MySQL的or条件 满足条件1 或 满足条件2 { "match": { "name": "李" } }, { "match": { "age": "13" } } ] } } } #bool 不等于查询 GET lph/user/_search { "query": { "bool": { "must_not": [ # must_not 类似MySQL的!=条件 { "match": { "name": "李" } } ] } } } # bool 过滤查询 GET lph/user/_search { "query": { "bool": { "must": [ { "match": { "name": "李" } } ], "filter": { #过滤 age>10的 "range": { "age": { "gt": 10 # gt,lt,gte,lte... } } } } } } # 匹配多个条件 GET lph/user/_search { "query": { "match": { "desc": "钱 美元" # desc是数组描述 匹配多个条件可空格分开 } } } # 返回数据 #! Deprecation: [types removal] Specifying types in search requests is deprecated. { "took" : 970, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 3, "relation" : "eq" }, "max_score" : 1.510969, "hits" : [ { "_index" : "lph", "_type" : "user", "_id" : "2", "_score" : 1.510969, "_source" : { "name" : "陈六", "age" : 13, "desc" : [ "钱", "美元", "欧元" ] } }, { "_index" : "lph", "_type" : "user", "_id" : "1", "_score" : 1.510969, "_source" : { "name" : "李二", "age" : 13, "desc" : [ "钱", "美元", "欧元" ] } }, { "_index" : "lph", "_type" : "user", "_id" : "4", "_score" : 0.6983144, "_source" : { "name" : "李四", "age" : 13, "desc" : [ "钱", "珐琅", "欧元" ] } } ] } } # 精确查询 (准确的匹配分词器解析的分词) term查询是直接通过倒排索引指定的词条进行查找 此处需要解释下分词: trem:直接查找精确的 match:会使用分词器解析,先分析文档 在通过分析的文档进行查找 需要提到的两个类型: text:会被分词器解析 keyword:不会被分词器解析 # 测试
#创建规则 PUT participledb { "mappings": { "properties": { "name":{ "type": "text" }, "desc":{ "type": "keyword" } } } } # 添加数据 PUT participledb/_doc/1 { "name":"SOD密", # text类型 "desc":"SOD密很好" # keyword类型 } PUT participledb/_doc/2 { "name":"SOD密", "desc":"SOD密不好" } #查询测试 GET participledb/_search { "query": { "term": { "name": "密" #“sod” or “密” 都可以直接查找出来 } } } GET participledb/_search { "query": { "term": { "desc": "SOD密不好" #查询desc 由于desc字段是keyword类型 不会被分词器解析 所以只能查询到desc=SOD密不好的数据 } } } #使用trem 进行多条件查询 GET participledb/_search { "query": { "bool": { "should": [ { "term": { "name": { "value": "sod" } } }, { "term": { "desc": { "value": "SOD密不好" } } } ] } } } # 高亮查询
集成springboot
创建空java项目,配置springboot模块后匹配版本号!
1.配置类
package org.ph.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <p>
*
* </p>
*
* @author lph
* @package org.ph.config
* @project elastic-api
* @since 2022 -02 -13 14:47
*/
@Configuration
public class ElasticSearchClientConfig {
//restHighLevelClient 使用自动注入 默认方法名
@Bean
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1", 9200, "http")
//集群new多个
)
);
return restHighLevelClient;
}
}
2.测试APi
package org.ph;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class EsApiApplicationTests {
@Autowired
private RestHighLevelClient restHighLevelClient;
@Test
void contextLoads() {
}
@Test
void createIndex() {
try {
//创建索引请求 索引小写
CreateIndexRequest createIndexRequest = new CreateIndexRequest("es_create_index");
//执行索引请求
CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
System.out.println(createIndexResponse);
} catch (Exception e) {
} finally {
try {
restHighLevelClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Test
void ExistIndex() {
try {
//索引是否存在
GetIndexRequest es_create_index = new GetIndexRequest("es_create_index");
boolean exists = restHighLevelClient.indices().exists(es_create_index, RequestOptions.DEFAULT);
System.out.println(exists);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
restHighLevelClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
void DeleteIndex() {
try {
//删除索引对象 可先检查是否存在索引
DeleteIndexRequest es_create_index = new DeleteIndexRequest("es_create_index");
// 执行删除
AcknowledgedResponse delete = restHighLevelClient.indices().delete(es_create_index, RequestOptions.DEFAULT);
if (delete.isAcknowledged())
System.out.println("删除成功");
else
System.out.println("删除失败");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
restHighLevelClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Test
void AddDocument() {
User tiezhu = new User("铁柱", 32);
User cuihua = new User("翠花", 12);
User aiguo = new User("爱国", 52);
//创建请求
IndexRequest indexRequest = new IndexRequest("es_create_index");
//创建规则 超时时间。。
indexRequest.id("1").timeout(TimeValue.timeValueSeconds(1)).timeout("1s");
//执行请求
indexRequest.source(JSON.toJSONString(tiezhu), XContentType.JSON);
try {
IndexResponse index = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
System.out.println(index.toString());
System.out.println(index.status());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
restHighLevelClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
void documentIfExists() throws IOException {
GetRequest es_create_index = new GetRequest("es_create_index", "1");
//过滤_source的上下文
es_create_index.fetchSourceContext(new FetchSourceContext(false));
boolean exists = restHighLevelClient.exists(es_create_index, RequestOptions.DEFAULT);
System.out.println("exists = " + exists);
}
@Test
void getDocument() throws IOException {
GetRequest es_create_index = new GetRequest("es_create_index", "1");
boolean exists = restHighLevelClient.exists(es_create_index, RequestOptions.DEFAULT);
if (exists) {
GetResponse documentFields = restHighLevelClient.get(es_create_index, RequestOptions.DEFAULT);
//documentFields 可以获取很多信息 类型 版本
System.out.println("user对象信息" + documentFields.getSourceAsString());
System.out.println("所有信息" + documentFields);
} else
System.out.println("文档不存在");
}
@Test
void updateDocument() throws IOException {
UpdateRequest updateRequest = new UpdateRequest("es_create_index", "1");
//更新的数据
User cuihua = new User("翠花", 12);
updateRequest.doc(JSON.toJSONString(cuihua), XContentType.JSON);
UpdateResponse update = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(update.status());
}
@Test
void deleteDocument() throws IOException {
// 删除文档
DeleteRequest updateRequest = new DeleteRequest("es_create_index", "01");
DeleteResponse delete = restHighLevelClient.delete(updateRequest, RequestOptions.DEFAULT);
System.out.println(delete.status());
}
@Test
void bulkDocument() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
List<User> users = new ArrayList<>();
User tiezhu = new User("铁柱", 32);
User cuihua = new User("翠花", 12);
User aiguo = new User("爱国", 52);
users.add(tiezhu);
users.add(cuihua);
users.add(aiguo);
for (int i = 0; i < users.size(); i++) {
bulkRequest.add(
new IndexRequest("es_create_index")
.id("" + (i + 1))
.source(JSON.toJSONString(users.get(i)), XContentType.JSON)
);
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulk.hasFailures());
}
}
@Test
void search() throws IOException {
SearchRequest searchRequest = new SearchRequest("es_create_index");
//条件构造
SearchSourceBuilder builderQuery = new SearchSourceBuilder();
// TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("userName", "翠");
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
builderQuery.query(matchAllQueryBuilder);
//分页
// builderQuery.from();
// builderQuery.size();
//超时时间
// builderQuery.timeout(new TimeValue(60, TimeUnit.SECONDS));
//将构造器放请求
searchRequest.source(builderQuery);
//执行请求
SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(search.getHits().getHits()));
}
}
简单解释一下 text 、keyword、term、match
text和keyword是数据类型,表示在创建的时候是否会分词建立索引。
term和match是指查询时是否启用分词查询。
例如:现在有一个数据 “my cat”是text类型。
那么,使用term查询“my cat”时,失败,因为“my cat”在被创建时分词器将其索引建立为“my”和“cat”。
使用term查询“my”或“cat”时,则会成功,因为索引又能够精确匹配的数据。
使用match查询“my cat”也能成功,因为match是模糊查询,查询语句“my cat”会被分词成为“my”和“cat”和“my cat”,只要有任意一个满足就会返回数据。
如果“my cat”是keyword,那么建立时,只会有一个索引“my cat”
所以此时,不管是term还是match都只有查询“my cat”时才会返回数据,其余的都查询不到。