系列目录
ELK Stack - Elasticsearch · 搜索引擎 · 全文检索 · 部署应用 · 内部结构 · 倒排索引 · 服务接入
ELK Stack - Kibana (待续)
ELK Stack - Logstash (待续)
ELK Stack - Beats (待续)
ELK Stack - Application Performance Monitoring (待续)
本章基于: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的架构图:
上面介绍的好像有点。。。好理解么???
ℹ️特点与应用
快:这主要是针对 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 | :9200/<index-name> | |
移除一个索引 | DELETE | :9200/<index-name> | |
修改数据 | POST | :9200/<index-name>/_update/<ID> | {"doc":{<列>:<值>}} |
追加数据 | POST | :9200/<index-name>/_doc/<ID> | <JSON-Entity> |
取单数据 | GET | :9200/<index-name>/_doc/<ID> | |
覆盖数据 | PUT | :9200/<index-name>/_doc/<ID> | |
移除数据 | DELETE | :9200/<index-name>/_doc/<ID> | |
模糊搜索数据 | GET | :9200/<index-name>/_search?q=<列>:<值> | |
全量查询数据 | GET/POST | :9200/<index-name>/_search | |
模糊查询数据 | GET/POST | :9200/<index-name>/_search | {"query":{"match":{<列>:<值>}}} |
全字等于查询 | GET/POST | :9200/<index-name>/_search | {"query":{"match_phrase":{<列>:<值>}}} |
分页排序查询 | GET/POST | :9200/<index-name>/_search | { "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。