关于.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(代指 ElasticsearchLogstashKibana),目前 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

  1. 先以非挂载模式运行实例,后续再操作挂载,不然会有权限问题
docker run --publish 9200:9200 --publish 9300:9300 -e "discovery.type=single-node" --name elasticsearch --restart always \
elasticsearch:7.9.1

这样得到一个临时的运行实例,访问http://localhost:9200查看是否成功,如果成功,继续后续操作。

  1. 创建挂载持久化数据的目录

切换到你希望持久化的根目录,在这个目录底下创建关于elasticsearch两个关键目录dataconfig

mkdir elasticsearch
cd elasticsearch/
mkdir data
mkdir config

以笔者为例,创建成功之后,最终会得到以下两个目录:

  • /home/username/elasticsearch/data/
  • /home/username/elasticsearch/config/
  1. 拷贝实例的当前Data和Config到我们创建的目录。
docker cp elasticsearch:/usr/share/elasticsearch/data /home/username/elasticsearch/
docker cp elasticsearch:/usr/share/elasticsearch/config /home/username/elasticsearch/

它会自动把运行实例下dataconfig所有文件拷贝到我们创建的那两个对应文件夹中。

  1. 停止并删除之前的实例,重新创建带持久化挂载的实例。

先删除

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

安装运行成功之后,可打开网址: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

开始编写代码

  1. 连接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);
  1. 写入新数据并自动创建索引
/// <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模型的大写转成驼峰小写了,非常方便。

  1. 获取指定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"
    }
}
  1. 基于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"
                }
            }
        ]
    }
}

  1. 带指定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
}

附录

posted @ 2020-09-16 00:40  TaylorShi  阅读(2145)  评论(0编辑  收藏  举报