关于.Net Core使用Elasticsearch(俗称ES)、Kibana的研究说明
关于ElasticSearch
Elasticsearch
是一个分布式的开源搜索和分析引擎,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。Elasticsearch 以其简单的 REST 风格 API、分布式特性、速度和可扩展性而闻名,是 Elastic Stack 的核心组件;Elastic Stack 是适用于数据采集、充实、存储、分析和可视化的一组开源工具。人们通常将 Elastic Stack
称为 ELK Stack
(代指 Elasticsearch
、Logstash
和 Kibana
),目前 Elastic Stack 包括一系列丰富的轻量型数据采集代理,这些代理统称为 Beats,可用来向 Elasticsearch 发送数据。
ElasticSearch用途
- 应用程序搜索
- 网站搜索
- 企业搜索
- 日志处理和分析
- 基础设施指标和容器监测
- 应用程序性能监测
- 地理空间数据分析和可视化
- 安全分析
- 业务分析
工作原理
原始数据会从多个来源(包括日志、系统指标和网络应用程序)输入到 Elasticsearch 中。数据采集指在 Elasticsearch 中进行索引之前解析、标准化并充实这些原始数据的过程。这些数据在 Elasticsearch 中索引完成之后,用户便可针对他们的数据运行复杂的查询,并使用聚合来检索自身数据的复杂汇总。在 Kibana 中,用户可以基于自己的数据创建强大的可视化,分享仪表板,并对 Elastic Stack 进行管理。
索引是什么
Elasticsearch 索引指相互关联的文档集合。Elasticsearch 会以 JSON 文档的形式存储数据。每个文档都会在一组键(字段或属性的名称)和它们对应的值(字符串、数字、布尔值、日期、数值组、地理位置或其他类型的数据)之间建立联系。
Elasticsearch 使用的是一种名为倒排索引的数据结构,这一结构的设计可以允许十分快速地进行全文本搜索。倒排索引会列出在所有文档中出现的每个特有词汇,并且可以找到包含每个词汇的全部文档。
在索引过程中,Elasticsearch 会存储文档并构建倒排索引,这样用户便可以近实时地对文档数据进行搜索。索引过程是在索引 API 中启动的,通过此 API 您既可向特定索引中添加 JSON 文档,也可更改特定索引中的 JSON 文档。
为何使用Elasticsearch
Elasticsearch很快。 由于Elasticsearch 是在 Lucene 基础上构建而成的,所以在全文本搜索方面表现十分出色。Elasticsearch 同时还是一个近实时的搜索平台,这意味着从文档索引操作到文档变为可搜索状态之间的延时很短,一般只有一秒。因此,Elasticsearch 非常适用于对时间有严苛要求的用例,例如安全分析和基础设施监测。
Elasticsearch具有分布式的本质特征。 Elasticsearch 中存储的文档分布在不同的容器中,这些容器称为分片,可以进行复制以提供数据冗余副本,以防发生硬件故障。Elasticsearch 的分布式特性使得它可以扩展至数百台(甚至数千台)服务器,并处理 PB 量级的数据。
Elasticsearch 包含一系列广泛的功能。 除了速度、可扩展性和弹性等优势以外,Elasticsearch 还有大量强大的内置功能(例如数据汇总和索引生命周期管理),可以方便用户更加高效地存储和搜索数据。
Elastic Stack 简化了数据采集、可视化和报告过程。 通过与 Beats 和 Logstash 进行集成,用户能够在向 Elasticsearch 中索引数据之前轻松地处理数据。同时,Kibana 不仅可针对 Elasticsearch 数据提供实时可视化,同时还提供 UI 以便用户快速访问应用程序性能监测 (APM)、日志和基础设施指标等数据。
Elasticsearch免费吗
用户可以基于 Apache 2 许可证免费使用 Elasticsearch 的开源功能。用户还可基于 Elastic 许可证使用更多免费功能,同时通过付费订阅服务,可以获取支持并使用诸如 Alerting 和 Machine Learning 等高级功能。
Elasticsearch案例
- 2013年初,GitHub抛弃了Solr,采取ElasticSearch 来做PB级的搜索。 “GitHub使用ElasticSearch搜索20TB的数据,包括13亿文件和1300亿行代码”
- 维基百科:启动以elasticsearch为基础的核心搜索架构
- SoundCloud:“SoundCloud使用ElasticSearch为1.8亿用户提供即时而精准的音乐搜索服务”
- 百度:百度目前广泛使用ElasticSearch作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层面异常。目前覆盖百度内部20多个业务线(包括casio、云分析、网盟、预测、文库、直达号、钱包、风控等),单集群最大100台机器,200个ES节点,每天导入30TB+数据
- 新浪使用ES 分析处理32亿条实时日志
- 阿里使用ES 构建挖财自己的日志采集和分析体系
Elasticsearch正式分发包
基于WSL2的Docker Desktop配置
如果你打算将ES用于生产,你需要把Docker设置下vm.max_map_count
参数
在PS里面运行如下命令即可
wsl -d docker-desktop
sysctl -w vm.max_map_count=262144
Elasticsearch安装By Docker On WSL2
1. 基本安装
docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.9.1
2. 定制安装
以下操作均在Ubuntu的Root用户下执行,如果切换Root,请
su root
- 先以非挂载模式运行实例,后续再操作挂载,不然会有权限问题
docker run --publish 9200:9200 --publish 9300:9300 -e "discovery.type=single-node" --name elasticsearch --restart always \
elasticsearch:7.9.1
这样得到一个临时的运行实例,访问http://localhost:9200查看是否成功,如果成功,继续后续操作。
- 创建挂载持久化数据的目录
切换到你希望持久化的根目录,在这个目录底下创建关于elasticsearch
两个关键目录data
和config
mkdir elasticsearch
cd elasticsearch/
mkdir data
mkdir config
以笔者为例,创建成功之后,最终会得到以下两个目录:
- /home/username/elasticsearch/data/
- /home/username/elasticsearch/config/
- 拷贝实例的当前Data和Config到我们创建的目录。
docker cp elasticsearch:/usr/share/elasticsearch/data /home/username/elasticsearch/
docker cp elasticsearch:/usr/share/elasticsearch/config /home/username/elasticsearch/
它会自动把运行实例下data
和config
所有文件拷贝到我们创建的那两个对应文件夹中。
- 停止并删除之前的实例,重新创建带持久化挂载的实例。
先删除
docker stop elasticsearch
docker rm elasticsearch
再创建
docker run --publish 9200:9200 --publish 9300:9300 -e "discovery.type=single-node" --name elasticsearch --restart always \
--volume /home/username/elasticsearch/data/:/usr/share/elasticsearch/data \
--volume /home/username/elasticsearch/config/:/usr/share/elasticsearch/config \
elasticsearch:7.9.1
- 绑定
/usr/share/elasticsearch/data
目录可以防止重启实例时数据丢失。 - 绑定
/usr/share/elasticsearch/config/
目录可以防止重启实例时配置丢失。 - 设置
--restart always
可以确保重启Docker时自动启动es。 - 镜像直接使用
elasticsearch:7.9.1
将从Docker Hub搜索镜像并拉取。 - 请务必将其中的
username
替换成你本地的用户名。 - https://hub.docker.com/_/elasticsearch
恭喜你,这时候就完美运行起来了。
访问ES管理后台
{
"name" : "dc7593c4d74d",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "YqAcQFMnRamIdLHBIRNVjg",
"version" : {
"number" : "7.9.1",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "083627f112ba94dffc1232e8b42b73492789ef91",
"build_date" : "2020-09-01T21:22:21.964974Z",
"build_snapshot" : false,
"lucene_version" : "8.6.2",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
看到类似返回,就说明运行成功了。
Kibana安装By Docker On WSL2
docker run --publish 5601:5601 --name kibana --restart always \
--link elasticsearch:elasticsearch \
kibana:7.9.1
- https://hub.docker.com/_/kibana
- https://www.elastic.co/guide/en/kibana/current/docker.html
- kibana的端口就是5601
- 通过
--link
,跟上同一个网络的es的名字的名字或者id都行,就可以连接上,这里之前安装的es名字就是elasticsearch
--link YOUR_ELASTICSEARCH_CONTAINER_NAME_OR_ID:elasticsearch
安装运行成功之后,可打开网址:http://localhost:5601
ElasticSearch常见操作
检查集群或节点健康情况
http://localhost:9200/_cat/health?v
- green:每个索引的primary shard和replica shard都是active状态的
- yellow:每个索引的primary shard都是active状态的,但是部分replica shard不是active状态,处于不可用的状态
- red:不是所有索引的primary shard都是active状态的,部分索引有数据丢失了
查看所有的索引及统计
http://localhost:9200/_cat/indices?v
创建和删除索引
- 创建索引
PUT http://localhost:9200/yourindexname
{
"settings" : {
"number_of_shards" : 3, # 分片数
"number_of_replicas" : 1 # 副本数
}
}
- 删除索引
DELETE http://localhost:9200/yourindexname
.Net Core的ES开发包
ES主要是通过RESTFul风格的API来进行操作的,本身是没有提供UI界面,如果.Net Core程序想要操作,除了自己写相关的API调用代码,最方便的方法就是直接安装官方提供的Nuget包。
这两的差异就是,说白了NEST
就是基于Elasticsearch.Net
来实现的。
简单说呢,Elasticsearch.Net
是粗狂豪放型,可以非常自由的组织API请求内容,任君发挥了
而NEST
是结合.Net高级特性的高级语法糖封装,可以让你类LINQ的方式来编写你的代码。
总结而言,还是推荐先用高级封装的NEST
吧,这样能让问题更简单一些。
Nuget安装到现有项目中
搜索关键词安装即可
NEST
开始编写代码
- 连接ElasticSearch
var singleNode = new Uri("http://localhost:9200");
var connSettings = new ConnectionSettings(singleNode);
var esClient = new ElasticClient(connSettings);
如果是链接多Node节点。
var nodes = new Uri[]
{
new Uri("http://esNode1:9200"),
new Uri("http://esNode2:9200"),
new Uri("http://esNode3:9200")
};
var pool = new StaticConnectionPool(nodes);
var settings = new ConnectionSettings(pool);
var client = new ElasticClient(settings);
- 写入新数据并自动创建索引
/// <summary>
/// 用户信息模型
/// </summary>
public class UserInfo
{
/// <summary>
/// 名字
/// </summary>
public string Name { get; set; }
}
var userInfo = new UserInfo { Name = "demoname" };
var nodeUri = new Uri("http://localhost:9200");
var connSetting = new ConnectionSettings(nodeUri);
var targetIndexName = "demo_indexname";
var esClient = new ElasticClient(connSetting.DefaultIndex(targetIndexName));
// 初始化索引
esClient.IndexDocument(userInfo);
// 或者 推荐异步语法糖写法
var response = await esClient.IndexAsync(userInfo, idx => idx.Index(targetIndexName));
以上动作,在最后一句才会发出HTTP API请求,内容如下:
POST http://localhost:9200/demo_indexname/_doc
{"name":"demoname"}
值得注意,这里面demo_indexname
是索引的名字,这里面_doc
是索引的默认_type
名字
另外,你会发现,NEST
框架已经自动把UserInfo模型的大写转成驼峰小写了,非常方便。
- 获取指定ID的文档数据
// 获取指定文档Id的文档内容
var targetIndexName = "demo_indexname";
var response = await esClient.GetAsync<UserInfo>("rnwnnXQBwd6SZB0378vX", idx => idx.Index(targetIndexName));
var userInfo = response.Source;
以上动作,发出HTTP API请求,内容如下:
GET http://localhost:9200/demo_indexname/_doc/rnwnnXQBwd6SZB0378vX
如果找不到就会返回404,这时候拿到的对象就是NULL,如果找得到就能正确返回对象数据。
{
"_index": "demo_indexname",
"_type": "_doc",
"_id": "rnwnnXQBwd6SZB0378vX",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"name": "demoname"
}
}
- 基于LINQ模式查询目标文档
// 基于LINQ模式查询目标文档
var response = await esClient.SearchAsync<UserInfo>(s => s
.Index(targetIndexName)
.From(0)
.Size(10)
.Query(q => q.Term(t => t.Name, "demoname") || q.Match(m => m.Field(f => f.Name).Query("demoname"))
)
);
var userinfos = response.Documents;
POST http://localhost:9200/demo_indexname/_search?typed_keys=true
{
"from": 0,
"query": {
"bool": {
"should": [
{
"term": {
"name": {
"value": "demoname"
}
}
},
{
"match": {
"name": {
"query": "demoname"
}
}
}
]
}
},
"size": 10
}
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.5753642,
"hits": [
{
"_index": "demo_indexname",
"_type": "_doc",
"_id": "rnwnnXQBwd6SZB0378vX",
"_score": 0.5753642,
"_source": {
"name": "demoname"
}
}
]
}
}
- 带指定ID的创建对象
/// <summary>
/// 用户信息模型
/// </summary>
public class UserInfo
{
/// <summary>
/// 键值
/// </summary>
public string Id { get; set; }
/// <summary>
/// 名字
/// </summary>
public string Name { get; set; }
/// <summary>
/// Person
/// </summary>
public PersonInfo Person { get; set; }
}
var userInfo = new UserInfo { Id = Guid.NewGuid().ToString(), Name = "3333" ,Person = new PersonInfo { Phone= "188****4444" } };
.....
// 带指定ID的创建对象
var response = await esClient.CreateAsync(userInfo, idx => idx.Index(targetIndexName));
var isSuccess = response.Result == Result.Created;
PUT http://localhost:9200/demo_indexname/_create/1aa9de3f-f610-48d4-93fb-be41fbfd8987
{"id":"1aa9de3f-f610-48d4-93fb-be41fbfd8987","name":"3333","person":{"phone":"188****4444"}}
查询带子结构文档
// 基于LINQ模式查询带子结构文档
var response = await esClient.SearchAsync<UserInfo>(s => s
.Index(targetIndexName)
.From(0)
.Size(10)
.Query(q => q.Term(t => t.Person.Phone, "188xxxxx0637"))
);
var userinfos = response.Documents;
POST http://localhost:9200/demo_indexname/_search?typed_keys=true HTTP/1.1
{
"from": 0,
"query": {
"term": {
"person.phone": {
"value": "188xxxxx0637"
}
}
},
"size": 10
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步