ELK Stack - Elasticsearch · 搜索引擎 · 部署应用 · 内部结构 · 倒排索引 · 服务接入

本章基于:RHELinux v8+,ELK Stack v8.x,Docker v23.x,VS2022,.NET6,Postman v10.x,Microsoft Edge v124.x

希望我能讲明白,有不妥,请告诉我,让我纠正更多。

一、ELK Stack 介绍

Elasticsearch、Logstash 和 Kibana。也可称为 Elastic Stack。

Elasticsearch 是一个分布式、RESTful 风格的、并且拥有极佳的 查询能力、数据分析、统计能力,每个数据都被编入索引,包括全文检索/地理位置/列存储等。能够水平扩展集群,每秒处理海量事件,自动管理索引和分布式集群中的查询,以惊叹的速度实现极其流畅的操作。

Kibana 则可以进行全面透彻的分析后,从一个 UI 中进行监督和管理,并从多个用例和团队中透视出大量信息,发现洞察/调查威胁/监测系统/评估搜索性能等。也就是将数据转变为结果,使你快速做出响应以解决。所有这些都基于Elasticsearch上完成。

Logstash 是服务器端数据处理管道,在数据传输过程中,能够解析其中的各个事件,识别已命名的字段或以重新构建结构,比如把相似的数据从新划分或组装,也可同时从多个来源采集数据,并将它们转换成通用格式,发送到(ES)存储库中,以便进行更强大的分析和实现更大价值。

ℹ️也就是说:Logstash 采集数据 >> Elasticsearch 数据分析 >> Kibana 呈现结果,不如看下官网对于ELK的架构图:

ELK

上面介绍的好像有点。。。好理解么???

ℹ️特点与应用

:这主要是针对 Elasticsearch 来说,可以做数据的搜索引擎,这比一般的关系型DB可快多了。但搜索的结果可能会有些许的偏差,因为反向索引,就像百度一样,结果中也有不一定是自己想要的信息,但它足够快。

分析:由于快,可以时时分析出一些状态数值等的变化,比如某应用的运行状况/某业务的处理能力波动/或者任何运行指标等,参考这些结果,就可以去采集/发现/预知/可能/洞察/扩展/优化等。这是 ELK Stack 套件的价值所在。

进入本章的重点,Elasticsearch,那它能做什么?

存储数据,快速搜索,一个特别的 DataBase,除 CURD 外,就是快。

我们暂且以这样的认识,往下看。

把 Elasticsearch 部署起来,完成它的 CRUD,这是本篇文章的主要任务。

作者:[Sol·wang] - 博客园,原文出处:https://www.cnblogs.com/Sol-wang/p/17490582.html

二、Elasticsearch 部署

搜索引擎 Elasticsearch,它是用JAVA写的,所以默认自带JDK,不用再部署JAVA环境。或者通过 elasticsearch-env 文件修改为指定的JAVA环境。

2.1 系统准备

1、应ES要求,在 Linux 创建用来运行ES的系统自定义用户:useradd elk

2、应ES要求,提升单进程可允许的最大内存:文件/etc/sysctl.conf追加vm.max_map_count=262144再执行命令sysctl -p后生效

3、应ES要求,为用户调整系统并发限制:vim /etc/security/limits.conf(关于Limits可参考

elk              soft    nofile          65536
elk              hard    nofile          65536

4、开放ES用到的9200/9300端口(可参考);测试环境可直接关闭防火墙:systemctl stop firewalld

2.2 部署启动

ℹ️去官网找源码下载地址:https://www.elastic.co/cn/downloads/elasticsearch

# 官网源码下载
curl -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.7.1-linux-x86_64.tar.gz
# 源码包解压缩
tar -zxvf elasticsearch-8.7.1-linux-x86_64.tar.gz
# 为主目录赋予运行用户(els)的权限(可读可写可执行)
# - 默认情况下,Elasticsearch的data数据文件/logs日志文件 都在主目录下;
# - 倘若计划将数据文件/日志文件配置到其它目录,也要为运行用户赋予响应权限。
chown -R els {path} && chmod -R 754 {path}
# 切换到解压包下的主目录
cd elasticsearch-8.7.1

ℹ️编辑配置文件:vim config/elasticsearch.yml

# 单机基础配置项:
cluster.name: my-elasticsearch            # 自定义集群名称
node.name: node-a                         # 自定义节点名称
network.host: 0.0.0.0                     # 外部可访问的IP
http.port: 9200                           # 对外开放的端口
cluster.initial_master_nodes: ["node-a"]  # 集群初始化节点

ℹ️Linux命令行指定用户启动

# 切换至创建的系统用户
su elk
# 主目录下启动 [p:存储进程号] [d:后台启动]
bin/elasticsearch [-p /tmp/es.pid] [-d]

首次启动 Elasticsearch 时,默认情况下会启用和配置以下安全功能:

- 启用身份验证和授权,为 Elasticsearch 内置超级账号 elastic 并生成密码。
- 为传输层和HTTP层生成TLS的证书和密钥,并使用这些密钥和证书启用和配置TLS。
- 为 Kibana 生成一个注册令牌,是为需要接入 Kibana 的凭证,有效期为30分钟。

(记住以上[密码/密钥/令牌]等信息,后续会用到;当是后台方式 -d 启动时,信息存于日志文件)

ℹ️部署效果预览

浏览器SSL访问:https://{IP}:9200(8.x默认开启SSL)

输入默认账号 elastic 和生成的密码(建议及时变更密码)

重新初始化 Elasticsearch

删除 data 文件夹,删除 logs 内所有文件,再启动。

ℹ️停止运行ES

 - 摧毁进程方式

  先查找ES的进程号PIDps aux | grep elastic 再依PID毁掉进程kill -15 {PID}
  如果启动时-p指定了PID的存储,更便捷/更动态的停止方式:pkill -F /tmp/es.pid

2.3 重设密码

重新生成随机密码:bin/elasticsearch-reset-password -u {uname}

或者自定义新密码:bin/elasticsearch-reset-password -u {uname} -i

2.4 容器方式

当然,任何东西,Docker 容器方式就很便捷了;测试用的「单节点模式」如下:

# 启动 ES 容器
docker run -dit --name es -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:8.10.3
# 查看启动日志〔超级用户密码/HTTP证书密钥/Kibana令牌〕等
docker logs es
# 在容器内,为 ES 的超级用户 elastic 自动重设密码,取出已生成的密码〔后续连接登录用〕
docker exec -it es /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic

2.5 登录到ES

上小节操作ES启动完成后,就可以通过多种连接方式进行登录,比如浏览器,比如Postman工具,比如某开发语言,都能连到已有节点实例上。

ℹ️浏览器访问ES首页:https://{address}:9200/〔它会弹框让你输入默认超级账号elastic和启动时生成的密码〕

看呢,登录成功了,它的首页给出了ES实例的基本信息。

ℹ️浏览器查看ES所有节点的健康检测信息:https://{address}:9200/_cat/health?v

看呢,它出现了,部署的单节点,当前的健康检测信息。

关于健康检测,首要关注的,当然是每个节点实例的运行状况 status 了,这里的状态分哪几种呢?

  • green:正常,所有分片均已分配。
  • yellow:可用,主分片已分配,但副本存在隐患,当发生隐患时,可能会丢失数据。
  • red:不可用,未分配一个或多个主分片,因此某些数据不可用。

ℹ️浏览器查看所有节点信息:https://{address}:9200/_cat/nodes?v

看呢,它出现了,各节点的信息,之前单节点启动,所以只有一条节点信息了。

ℹ️用Postman工具创建一个自定义索引,https://{address}:9200/{索引名称}

看呢,它创建成功了,当输入账号密码,以 PUT 方式,创建了一个名为「myfirstindex」的索引。

ℹ️浏览器已创建的索引:

看呢,它出现了,刚创建的索引名称「myfirstindex」。

Elasticsearch 默认是开启 SSL 的,所以应该用 HTTPS 方式访问它。

2.6 管理ES的工具与插件

当我们做一些基本的操作时,尤其是在开发测试环境,比如数据的维护,比如节点运行状态,比如后续的集群管理,等等,通过命令行操作,很不方便。

通过工具就很直观了,鼠标点点,就很快捷方便,比较受欢迎的浏览器插件:

  • 老牌浏览器插件:Multi Elasticsearch Head
  • 不轻易告诉别人的后来者优秀插件:Elasticsearch Tools
  • 应该是国人开发的浏览器插件(包括不限于插件):es-client

去搜吧,Chrome 应用市场😆

三、Elasticsearch 应用

在早前,我记得有过这样的方式辅助数据搜索:
在关系型 DataBase 中,为每笔数据贴上标签,你认为这条数据可以想象到的标签,都可以贴上,标签数量自己控制;或者在该表中追加几列,每列存一个标签,或者关联标签表等等。搜索时先用字符==匹配标签,再关联出对应数据集。个人认为 Elasticsearch 也是类似这种思路的系统化的具体实现。

3.1 内部特征

  • 索引 Index:相当于关系型DB的库或表都可以,是一组数据的集合。
  • 文档 Document:于Index上的一条数据,表示一个数据对象,相当于关系型DB中的行。
  • 栏位 Field:于Document上的一个数据属性。相当于关系型DB中的列。
  • 分词器 analyzer:将文本内容中的 词语/短语/语义 等提炼出为关键字后存储,用于全文检索很合适。
  • 这里提一下正排索引:这个应用的非常常见;就比如关系型DB,有索引列,通过索引值找对应数据的原理。
  • 倒排索引:事先提炼出的多个关键字,关键字再与此数据的Id标识对应起来,甚至对应多个数据的Id标识。

搜索的执行过程

按输入的内容,匹配到提炼出的关键字,会找到对应的一些数据索引,再通过Id值找到对应的数据。
也就是说:比关系型DB多出了 提炼关键字/关键字与索引标识列 Mapping。这也就是倒排索引的体现。

3.2 REST APIs

常见的基础操作,更多操作方式于后续的 Kibana 中详解。

ACTION Method URL Body
ES首页 GET :9200/  
健康检测 GET :9200/_cat/health?v  
节点列表 GET :9200/_cat/nodes?v  
查看所有索引 GET :9200/_cat/indices?v  
创建一个索引 PUT :9200/<index-name>  
查看单个索引 GET  
移除一个索引 DELETE  
修改数据 POST :9200/<index-name>/_update/<ID> {"doc":{<列>:<值>}}
追加数据 POST :9200/<index-name>/_doc/<ID> <JSON-Entity>
取单数据 GET  
覆盖数据 PUT  
移除数据 DELETE  
模糊搜索数据 GET :9200/<index-name>/_search?q=<列>:<值>  
全量查询数据 GET/POST :9200/<index-name>/_search  
模糊查询数据 GET/POST {"query":{"match":{<列>:<值>}}}
全字等于查询 GET/POST {"query":{"match_phrase":{<列>:<值>}}}
分页排序查询 GET/POST { "query":{"match":{}},
  "from":0, "size":10,
  "_source":["<指明结果字段>"],
  "sort":{"<列>":{"order":"asc"}}
}

关于排序

默认请求下,Elasticsearch 仅支持 数据 / 布尔 / 日期 等的排序,不支持 text 类型的字段进行排序。

如果需要 text 类型字段的排序,需要在 sort 中的 排序列名加上后缀.keyword
比如 "sort":{ "title.keyword" : {"order":"asc"}}
一般不会这样做,个人认为,是否需要回头看看或调整方案,生产环境不推荐吧。

另一种方式,改变字段属性 fielddata=true,使其不使用倒排索引,以将数据加载到内存中检索的方式。
官网不推荐这种方式,随着数据量的积累,它会占用大量的内存空间。

3.3 .NET 接入

为某种语言,用来连接到Elasticsearch服务及管理数据的客户端程序包,比较常用的,官网提供的NEST,在NuGet中搜索NEST,NEST仅支持7.x及以下版本的ES。。。然而现在,NEST已经有了替代品,官网提供的Elastic.Clients.Elasticsearch,仅针对新版ES8+的客户端,在NuGet中搜索即可;可能也是未来的客户端。

Elastic.Clients.Elasticsearch 与 NEST 的使用基本相似,NuGet 安装 Elastic.Clients.Elasticsearch。实现如下样例:

ℹ️Connecting

# 单节点连接
var es_c_settings = new ElasticsearchClientSettings(new Uri("https://localhost:9200"))
    # 证书密钥(HTTP CA certificate SHA-256 fingerprint)
    .CertificateFingerprint("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
    # 账号密钥(Password for the elastic user)
    .Authentication(new BasicAuthentication("elastic", "*******************"));
# 创建客户端
var client = new ElasticsearchClient(es_c_settings);

ℹ️集群连接

# 多节点连接 集群
# 默认情况下,通过循环以轮循方式请求节点。自动排除不正常的节点,直到恢复正常。
var es_node_pool = new StaticNodePool(new Uri[]{
	new Uri("https://myserver1:9200"),
	new Uri("https://myserver2:9200"),
	new Uri("https://myserver3:9200")
});
var es_c_settings = new ElasticsearchClientSettings(es_node_pool)
    # 证书密钥(HTTP CA certificate SHA-256 fingerprint)
    .CertificateFingerprint("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
    # 账号密钥(Password for the elastic user)
    .Authentication(new BasicAuthentication("elastic", "*******************"));
# # 创建客户端
var client = new ElasticsearchClient(es_c_settings);

ℹ️Create index

# 创建指定名称的索引(必须小写)
CreateIndexResponse index_resp = client.Indices.Create("beautifulcity");
if (index_resp.IsValidResponse) {
    Console.WriteLine($"Index name {index_resp.Index.ToString()} succeeded.");
}

ℹ️Insert data

# Entity
var m_city = new BeautifulCity() {
    name = "杭州",
    postcode = 310000,
    attractions = new string[] { "江南", "宋韵" },
    describe = "杭州市地处中国华东地区、钱塘江下游、杭州湾西端、京杭大运河南端,属亚热带季风气候,四季分明,雨量充沛。市境西部属浙西丘陵区,东部属浙北平原,水网密布,物产丰富。",
};
# Insert to index
IndexResponse index_resp = client.Index(m_city, "beautifulcity");
if (index_resp.IsValidResponse) {
    Console.WriteLine($"Index document with ID {index_resp.Id} succeeded.");
}

ℹ️Query single

# From index by ID
var index_resp = client.Get<BeautifulCity>("1001", idx => idx.Index("beautifulcity"));
if (index_resp.IsValidResponse) {
    Console.WriteLine(JsonSerializer.Serialize(index_resp.Source));
}

ℹ️Change entity

# Entity
var m_city = new BeautifulCity() {
    attractions = new string[] { "江南", "宋韵", "临安" } 
};
# From index by ID
var response = client.Update<BeautifulCity, BeautifulCity>("beautifulcity", "1001", u => u.Doc(m_city));
if (response.IsValidResponse) {
    Console.WriteLine("Update document succeeded.");
}

ℹ️Remove entity

# From index by ID
var response = client.Delete("beautifulcity", "1001");
if (response.IsValidResponse) {
    Console.WriteLine("Delete document succeeded.");
}

ℹ️分页匹配搜索

# from index by name
var response = client.Search<BeautifulCity>(s => s.Index("beautifulcity")
    .From(0).Size(10)
    .Query(q => q.Match(t => t.Field(f => f.name).Query("杭州")))
);
if (response.IsValidResponse) {
    Console.WriteLine(JsonSerializer.Serialize(response.Documents));
}

ℹ️分页包含搜索

var response = client.Search<BeautifulCity>(s => s.Index("beautifulcity")
    .From(0).Size(10).Query(q => q.Term(t => t.name, "杭"))
);
if (response.IsValidResponse) {
    Console.WriteLine(JsonSerializer.Serialize(response.Documents));
}

ℹ️聚合查询

var response = client.Search<BeautifulCity>(s => s.Index("beautifulcity")
    .Aggregations(aggs => aggs
        .Max("my-max", max => max.Field(f => f.name))
        .Avg("my-avg", avg => avg.Field(f => f.postcode))
        .Sum("my-sum", sum => sum.Field(f => f.postcode))
    )
);
if (response.IsValidResponse) {
    Console.WriteLine(JsonSerializer.Serialize(response.Documents));
}

是不是有点类似,实现了一个 DBHelper 或者 ORM。

posted @ 2024-07-15 09:22  Sol·wang  阅读(365)  评论(3编辑  收藏  举报