【Elasticsearch】实现分布式系统日志高效追踪

🧑 博主简介:CSDN博客专家历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程高并发设计Springboot和微服务,熟悉LinuxESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
技术合作请加本人wx(注明来自csdn):foreast_sea

在这里插入图片描述


在这里插入图片描述

【Elasticsearch】实现分布式系统日志高效追踪

一、引言

在当今的技术领域,大型分布式系统,尤其是微服务架构,已经成为构建复杂应用的主流方式。这些分布式系统由众多的微服务组成,它们相互协作以提供完整的业务功能。例如,在一个电商平台中,下单服务支付服务库存服务等多个微服务共同运作,才能完成一个订单从创建到完成支付的全过程。

然而,随着系统规模的扩大和微服务数量的增加,日志管理和问题排查变得极为复杂。当出现故障或性能问题时,开发人员往往需要在海量的日志信息中寻找线索,这些日志分散在不同的服务实例和服务器上,难以关联和整合。传统的日志分析方法在面对这种分布式环境时显得力不从心。

Elasticsearch 的出现为解决分布式系统日志追踪问题提供了强大的解决方案。它是一个分布式高可用可扩展的搜索引擎和数据分析引擎。通过合理地利用 Elasticsearch数据类型索引结构,我们能够有效地存储和检索分布式系统中的日志数据,进而实现跨服务的请求日志追踪,将分散的日志信息整合为完整的用户请求链路。这不仅有助于快速定位问题,还能为系统性能优化和业务流程分析提供有力支持。

在本文中,我们将深入探讨如何使用 Elasticsearch 来实现分布式系统日志追踪,详细介绍相关的技术细节和代码实现。

二、技术概述

(一)Elasticsearch 简介

Elasticsearch 是一个基于Lucene 库构建的开源搜索引擎。它具有分布式实时性高可用性等特点,能够快速地存储、搜索和分析大量的数据。在日志分析领域,Elasticsearch 可以高效地处理和索引日志数据,使得我们能够快速地查询和检索特定的日志信息。

(二)关键数据类型

  1. Keyword:用于精确匹配的字符串数据类型。在日志追踪中,例如服务名称、日志级别等字段可以使用 Keyword 类型,这样可以确保精确的查询和过滤。例如,当我们查询特定服务的日志时,使用 Keyword 类型的服务名称字段可以准确地定位到相关日志。
  2. Text:用于存储较长的文本数据,如日志消息内容。它会对文本进行分词处理,以便进行全文搜索。例如,当我们想要搜索日志消息中包含特定关键词的日志时,Text 类型的字段就可以发挥作用。
  3. Date:用于存储日期和时间信息。在日志数据中,日志的产生时间通常是一个重要的字段,使用 Date 类型可以方便地进行基于时间范围的查询,比如查询特定时间段内的日志。

(三)索引结构

我们可以设计一个专门用于存储日志数据的索引。索引的结构可以包含以下字段:

  • traceId:用于唯一标识一个用户请求链路的 ID。通过这个 ID,我们可以将不同服务中的相关日志关联起来。
  • serviceName:产生日志的服务名称,使用 Keyword 类型。
  • logLevel:日志的级别,如 INFO、WARN、ERROR 等,使用 Keyword 类型。
  • logMessage:日志的详细消息内容,使用 Text 类型。
  • timestamp:日志产生的时间,使用 Date 类型。

三、Maven 依赖

在使用 Elasticsearch 进行日志追踪的 Java 项目中,我们需要添加以下 Maven 依赖:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.17.0</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.17.0</version>
</dependency>

这些依赖将使我们能够在 Java 代码中方便地与 Elasticsearch 进行交互,使用其提供的高级客户端 API 来进行索引创建、数据插入、查询等操作。

四、案例实现步骤

(一)连接到 Elasticsearch

首先,我们需要创建一个连接到 Elasticsearch 的客户端。代码示例如下:

import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.RestClient;
import java.io.IOException;

public class ElasticsearchClientUtil {
    private static final String HOST = "localhost"; // Elasticsearch 主机地址
    private static final int PORT = 9200; // Elasticsearch 端口

    public static RestHighLevelClient getClient() {
        RestClient.Builder builder = RestClient.builder(
                new HttpHost(HOST, PORT, "http"));
        return new RestHighLevelClient(builder);
    }

    public static void closeClient(RestHighLevelClient client) throws IOException {
        client.close();
    }
}

在上述代码中,我们定义了一个工具类 ElasticsearchClientUtil,其中 getClient 方法用于创建一个连接到本地 Elasticsearch 实例(地址为 localhost,端口为 9200)的 RestHighLevelClient 对象,closeClient 方法用于关闭客户端连接。

(二)创建索引

接下来,我们创建用于存储日志数据的索引。代码如下:

import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;

public class LogIndexCreator {
    private static final String INDEX_NAME = "log_tracking_index";

    public static void createIndex(RestHighLevelClient client) throws IOException {
        // 创建索引请求
        CreateIndexRequest request = new CreateIndexRequest(INDEX_NAME);
        // 设置索引的设置
        request.settings(Settings.builder()
              .put("index.number_of_shards", 3)
              .put("index.number_of_replicas", 1));
        // 设置索引的映射(即字段类型等)
        String mapping = "{\n" +
                "  \"properties\": {\n" +
                "    \"traceId\": {\n" +
                "      \"type\": \"keyword\"\n" +
                "    },\n" +
                "    \"serviceName\": {\n" +
                "      \"type\": \"keyword\"\n" +
                "    },\n" +
                "    \"logLevel\": {\n" +
                "      \"type\": \"keyword\"\n" +
                "    },\n" +
                "    \"logMessage\": {\n" +
                "      \"type\": \"text\"\n" +
                "    },\n" +
                "    \"timestamp\": {\n" +
                "      \"type\": \"date\"\n" +
                "    }\n" +
                "  }\n" +
                "}";
        request.mapping(mapping, XContentType.JSON);

        // 执行创建索引操作
        CreateIndexResponse response = client.indices().create(request);
        if (response.isAcknowledged()) {
            System.out.println("索引创建成功");
        } else {
            System.out.println("索引创建失败");
        }
    }
}

在这段代码中,我们首先定义了索引名称 log_tracking_index,然后创建了 CreateIndexRequest 对象,设置了索引的分片数量和副本数量,并定义了索引的映射,即各个字段的类型。最后通过客户端执行创建索引操作,并根据响应判断索引是否创建成功。

(三)插入日志数据

假设我们有一个日志数据对象 LogMessage,包含 traceIdserviceNamelogLevellogMessagetimestamp 等属性。我们可以编写以下代码将日志数据插入到 Elasticsearch 索引中:

import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RestHighLevelClient;
import java.io.IOException;
import java.util.Date;

public class LogDataInserter {
    public static void insertLog(RestHighLevelClient client, LogMessage logMessage) throws IOException {
        // 创建索引请求
        IndexRequest request = new IndexRequest("log_tracking_index")
              .id(logMessage.getTraceId())
              .source("traceId", logMessage.getTraceId(),
                        "serviceName", logMessage.getServiceName(),
                        "logLevel", logMessage.getLogLevel(),
                        "logMessage", logMessage.getLogMessage(),
                        "timestamp", new Date());

        // 执行插入操作
        IndexResponse response = client.index(request);
        if (response.getResult() == DocWriteResponse.Result.CREATED) {
            System.out.println("日志插入成功");
        } else {
            System.out.println("日志插入失败");
        }
    }
}

这里,我们创建了 IndexRequest 对象,指定了索引名称和文档 ID(这里使用 traceId 作为文档 ID,以确保同一请求链路的日志可以通过相同的 ID 进行关联),并设置了文档的源数据,即日志的各个字段值。然后通过客户端执行插入操作,并根据响应判断插入是否成功。

(四)查询日志数据

为了实现日志追踪,我们需要根据 traceId 或其他条件查询相关的日志数据。以下是一个根据 traceId 查询日志的示例代码:

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;

public class LogDataSearcher {
    public static void searchLogsByTraceId(RestHighLevelClient client, String traceId) throws IOException {
        // 创建搜索请求
        SearchRequest request = new SearchRequest("log_tracking_index");
        // 创建搜索源构建器
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // 设置查询条件,根据 traceId 进行精确匹配
        sourceBuilder.query(QueryBuilders.termQuery("traceId", traceId));
        request.source(sourceBuilder);

        // 执行搜索操作
        SearchResponse response = client.search(request);
        // 处理搜索结果
        for (SearchHit hit : response.getHits().getHits()) {
            System.out.println("traceId: " + hit.getSourceAsMap().get("traceId"));
            System.out.println("serviceName: " + hit.getSourceAsMap().get("serviceName"));
            System.out.println("logLevel: " + hit.getSourceAsMap().get("logLevel"));
            System.out.println("logMessage: " + hit.getSourceAsMap().get("logMessage"));
            System.out.println("timestamp: " + hit.getSourceAsMap().get("timestamp"));
        }
    }
}

在上述代码中,我们创建了 SearchRequest 对象,指定了要搜索的索引名称。然后使用 SearchSourceBuilder 构建查询条件,这里使用 termQuerytraceId 进行精确匹配。最后执行搜索操作,并遍历搜索结果,打印出每个匹配日志的相关信息。

五、单元测试

我们可以编写以下单元测试来验证上述代码的正确性:

import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Date;

import static org.junit.jupiter.api.Assertions.assertTrue;

public class LogTrackingTest {
    private RestHighLevelClient client;

    @BeforeEach
    public void setUp() {
        client = ElasticsearchClientUtil.getClient();
    }

    @Test
    public void testLogTracking() throws IOException {
        // 创建索引
        LogIndexCreator.createIndex(client);

        // 插入日志数据
        LogMessage logMessage = new LogMessage("trace1", "order-service", "INFO", "订单创建成功", new Date());
        LogDataInserter.insertLog(client, logMessage);

        // 查询日志数据
        LogDataSearcher.searchLogsByTraceId(client, "trace1");

        // 这里可以根据实际情况添加更多的断言,例如验证查询结果的数量等
        assertTrue(true);
    }

    @AfterEach
    public void tearDown() throws IOException {
        ElasticsearchClientUtil.closeClient(client);
    }
}

在这个单元测试中,我们首先在 setUp 方法中获取 Elasticsearch 客户端连接。然后在 testLogTracking 方法中,依次进行索引创建、日志插入和日志查询操作。最后在 tearDown 方法中关闭客户端连接。这里我们简单地使用 assertTrue(true) 作为一个占位符,可以根据实际需求添加更详细的断言,比如验证查询到的日志数据是否与插入的数据一致,或者验证查询结果的数量是否符合预期等。

六、参考资料文献

  • Elasticsearch 官方文档https://www.elasticsearch.org/guide/index.html
  • Elasticsearch 实战》,作者:[美] 拉法尔·库奇Rafał Kuć)等,机械工业出版社。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/928681.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

K8s 十年回顾(Ten Year Review of K8s)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。Kubernetes 十年回顾 起源与…

大数据新视界 -- Hive 元数据管理:核心元数据的深度解析(上)(27 / 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

Lambda表达式提取字段名

文章目录 前言例子原理writeReplace反序列化对象缓存元数据 写一个工具 前言 实体类:方法这种方式获取字段名&#xff0c;摒弃了字符串拼接方式&#xff0c;避免拼接出现的问题&#xff0c;提高框架维护性和可修改性。 例子 引入Mybatis-Plus <dependency><groupId…

Dataset用load_dataset读图片和对应的caption的一个坑

代码&#xff1a; data_files {} if args.train_data_dir is not None:data_files["train"] os.path.join(args.train_data_dir, "**")dataset load_dataset("imagefolder",data_filesdata_files,cache_dirargs.cache_dir,) 数据&#xff1…

git查看本地库对应的远端库的地址

git查看本地库对应的远端库的地址 git remote -v 如果想要查看特定的远端库的url地址&#xff0c;可以使用如下命令&#xff0c;其中origin是默认的远端库的名称&#xff0c;可以使用其他远端库的名称 get remote get-url origin

传统PID和模糊控制在matlab仿真效果的对比

通过学习汇总和复现&#xff0c;利用matlab和simulink进行对传统PID和添加了模糊控制器的仿真效果进行对比&#xff1a; 上图中红色信号为传统PID仿真信号&#xff0c;比直接作用到对象的信号拟合度好很多PID的积分和比例的作用&#xff0c;直接作用到对象相当于只通过了二阶函…

网络编程(JavaEE)

前言&#xff1a; 熟悉了网络的基本概念之后&#xff0c;接下来就需要针对网络进行一系列的编程&#xff0c;其中可能涉及到新的一些编程操作&#xff0c;需要我们进一步探索&#xff01; 网络编程套接字&#xff1a; 套接字其实是socket的翻译。 操作系统给应用程序(传输层给…

算法第一弹-----双指针

目录 1.移动零 2.复写零 3.快乐数 4.盛水最多的容器 5.有效三角形的个数 6.查找总价值为目标值的两个商品 7.三数之和 8.四数之和 双指针通常是指在解决问题时&#xff0c;同时使用两个指针&#xff08;变量&#xff0c;常用来指向数组、链表等数据结构中的元素位置&am…

Linux-虚拟环境

文章目录 一. 虚拟机二. 虚拟化软件三. VMware WorkStation四. 安装CentOS操作系统五. 在VMware中导入CentOS虚拟机六. 远程连接Linux系统1. Finalshell安装2. 虚拟机网络配置3. 连接到Linux系统 七. 虚拟机快照 一. 虚拟机 借助虚拟化技术&#xff0c;我们可以在系统中&#…

分而治之—利用决策树和规则进行分类

当在几个具有不同薪资和福利水平的工作机会之间做出选择时&#xff0c;很多人会从列出利弊开始&#xff0c;并基于简单的规则来排除选项。比如&#xff0c;“如果我上下班的时间超过1小时&#xff0c;那么我会不高兴”。通过这种方式&#xff0c;通过这种方式&#xff0c;预测一…

【spring mvc】全局处理请求体和响应体

目录 说明实现效果逻辑图 实现步骤创建公共处理的请求和响应的类api接口测试前端请求响应结果 扩展Response响应格式实体ResponseCode 响应状态码RSA工具类 RequestBodyAdvice 介绍使用场景 ResponseBodyAdvice 介绍使用场景 说明 由于项目中需要进行加密传输数据提高项目安全…

Python酷库之旅-第三方库Pandas(255)

目录 一、用法精讲 1206、pandas.tseries.offsets.SemiMonthEnd.is_on_offset方法 1206-1、语法 1206-2、参数 1206-3、功能 1206-4、返回值 1206-5、说明 1206-6、用法 1206-6-1、数据准备 1206-6-2、代码示例 1206-6-3、结果输出 1207、pandas.tseries.offsets.S…

matlab conv函数和vivado fir ip对应输出什么时候相等

1&#xff09;下变频中&#xff0c;“matlab conv函数抽取”“vivado fir ip”。 2&#xff09;matlab conv函数的输入数据和输出数据的对应关系。 3&#xff09;vivado fir ip的输入数据和输出数据的对应关系。 与matlab conv函数一致&#xff0c;如上图。 不同的是&#xff…

大数据新视界 -- Hive 数据湖集成与数据治理(下)(26 / 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

Linux获取文件属性

目录 stat函数 获取文件属性 获取文件权限 实现“head -n 文件名”命令的功能 编程实现“ls -l 文件名”功能 stat/fstat/lstat的区别&#xff1f; stat函数 int stat(const char *path, struct stat *buf); 功能&#xff1a;获取文件属性 参数&#xff1a; path&…

容器运行应用及Docker命令

文章目录 一、使用容器运行Nginx应用1_使用docker run命令运行Nginx应用1 观察下载容器镜像过程2 观察容器运行情况 2_访问容器中运行的Nginx服务1 确认容器IP地址2 容器网络说明3 使用curl命令访问 二、Docker命令1_Docker命令获取帮助方法2_Docker官网提供的命令说明3_docker…

网络(TCP)

目录 TCP socket API 详解 套接字有哪些类型&#xff1f;socket有哪些类型&#xff1f; 图解TCP四次握手断开连接 图解TCP数据报结构以及三次握手&#xff08;非常详细&#xff09; socket缓冲区以及阻塞模式详解 再谈UDP和TCP bind(): 我们的程序中对myaddr参数是这样…

如何将快捷指令添加到启动台

如何将快捷指令添加到启动台/Finder/访达&#xff08;Mac&#xff09; 1. 打开快捷指令创建快捷指令 示例创建了一个文件操作测试的快捷指令。 2. 右键选择添加到程序坞 鼠标放在待添加的快捷指令上。 3. 右键添加到访达 鼠标放在待添加的快捷指令上。 之后就可以在启…

4.5 TCP 报文段的首部格式

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 前言1 TCP 报文段的基本结构2 固定部分2.1 源端口与目的端口2.2 序号2.3 确认号2.4 数据偏移2.5 保留字段2.6 控制位2.7 窗口2.8 检验和2.9 紧急指针 3 可变部分3.1 选项3.2 填…

计算机视觉——相机标定(Camera Calibration)

文章目录 1. 简介2. 原理3. 相机模型3.1 四大坐标系3.2 坐标系间的转换关系3.2.1 世界坐标系到相机坐标系3.2.2 相机坐标系到图像坐标系3.2.3 像素坐标系转换为图像坐标系3.2.4 世界坐标转换为像素坐标 3.3 畸变3.3.1 畸变类型3.3.1.1 径向畸变&#xff08;Radial Distortion&a…