Spring Boot Admin集成与自定义监控告警

news2024/9/23 3:12:21

目录

一.Spring Boot Admin集成

1.引入依赖

2.添加配置

3.监控界面

二.Spring Boot Admin告警机制

1. 基本告警机制

2. 配置告警

2.1 triggers触发器讲解

3. 自定义通知

3.1 Instance 对象

三.Spring Boot Admin支持的监控属性

1.常见的Spring Boot Admin监控属性

2.端点接口

3.访问示例


一.Spring Boot Admin集成

1.引入依赖

<dependencies>
    <!-- Spring Boot Admin Server 服务端引入-->
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-server</artifactId>
        <version>2.7.1</version>
    </dependency>

    <!-- Spring Boot Admin Client客户端引入 -->
    <dependency>
        <groupId>de.codecentric</groupId>
        <artifactId>spring-boot-admin-starter-client</artifactId>
        <version>2.7.1</version>
    </dependency>
	
	 <!-- 使用nacos时引入-->
	<dependency>
		<groupId>com.alibaba.cloud</groupId>
		<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
	</dependency>
	
    <!-- Spring Boot Starter Mail 邮件提醒时引入-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>

</dependencies>

2.添加配置

management:
  endpoints:
    web:
      exposure:
        #启用所有的 Actuator 端点
        include: "*"
  endpoint:
    health:
      show-details: always

server:
  port: 9000
spring:
  application:
    name: admin-client
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

3.监控界面

启动后访问:http://IP:9000,即可访问到监控页面

二.Spring Boot Admin告警机制

Spring Boot Admin 的告警机制可以帮助你监控 Spring Boot 应用的状态并在发生重要事件时发送通知。Spring Boot Admin 提供了多种告警方式和配置选项,以便与不同的通知系统集成。以下是 Spring Boot Admin 的告警机制的详细说明

1. 基本告警机制

Spring Boot Admin 的告警机制基于监控事件和实例状态变化。可以配置不同的触发条件和通知方式来处理这些事件。

Spring Boot Admin 监控应用的状态变化(例如:UP、DOWN、OFFLINE)。当应用的状态发生变化时,可以触发告警。告警通常包括以下几个方面:

  1. 实例状态变化: 例如,当应用从 UP 状态变为 DOWN 状态时。
  2. 健康检查: 健康检查失败时,可以触发告警。

2. 配置告警

配置邮件信息

在 Spring Boot Admin 服务器的配置文件中,可以设置告警的基本信息,包括通知发送者、SMTP 配置等。

示例 application.yml 配置:

spring:
  boot:
    admin:
      notify:
        mail:
          from: "your-email@example.com"
          to: "alert-email@example.com"
          host: "smtp.example.com"
          port: 587
          username: "your-email@example.com"
          password: "your-email-password"
          properties.mail.smtp.auth: true
          properties.mail.smtp.starttls.enable: true

或者通过bean配置方式

@Bean
public JavaMailSender javaMailSender() {
    JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
    mailSender.setHost(MailUtil.MAIL_SMTP_HOST); // 邮件服务器地址
    mailSender.setPort(Integer.parseInt(MailUtil.MAIL_SMTP_PORT)); // 使用SSL的端口号
    mailSender.setUsername(MailUtil.MAIL_SMTP_USER); // 发送邮件的用户名
    mailSender.setPassword(MailUtil.MAIL_SMTP_PASSWORD); // 发送邮件的密码
    Properties props = mailSender.getJavaMailProperties();
    props.put("mail.transport.protocol", "smtp");//常见的协议类型smtp,imap,pop3
    props.put("mail.smtp.auth", "true");
    props.put("mail.smtp.socketFactory.port", MailUtil.MAIL_SMTP_PORT); // 配置socket工厂端口
    props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); // 使用SSL
    props.put("mail.smtp.starttls.enable", "false"); // 由于使用SSL,禁用STARTTLS
   // props.put("mail.debug", "true"); // 调试模式
    return mailSender;
}

配置触发条件

Spring Boot Admin 允许基于特定条件触发告警。以下是一些常见的告警触发条件:

  1. CPU 使用率: 当 CPU 使用率超过指定阈值时触发告警。
  2. 内存使用率: 当 JVM 内存使用超过指定阈值时触发告警。
  3. 线程数: 当线程数超出设定范围时触发告警。
  4. GC 暂停: 当垃圾回收暂停时间过长时触发告警。
  5. 自定义指标: 可以基于应用程序的自定义指标来触发告警。

示例配置:

spring:
  boot:
    admin:
      monitor:
        status-lifetime: 10m
        threshold: 0.9
      notify:
        triggers:
          - name: cpu
            metric: "system.cpu.usage"
            threshold: 0.9
            above: true
            period: 5m
          - name: memory
            metric: "jvm.memory.used"
            threshold: 0.9
            above: true
            period: 5m
          - name: jvm-heap
            metric: "jvm.memory.heap.used"
            threshold: 0.9
            above: true
            period: 5m
          - name: threads
            metric: "jvm.threads.live"
            threshold: 1000
            above: true
            period: 5m
          - name: gc
            metric: "jvm.gc.pause"
            threshold: 0.1
            above: true
            period: 5m

2.1 triggers触发器讲解

spring.boot.admin.monitor.triggers 是 Spring Boot Admin 的配置项,用于定义在监控应用程序实例时的触发器。通过这些触发器,你可以设定特定的条件,当这些条件满足时,Spring Boot Admin 会执行相应的动作,比如发送通知、触发告警等。

触发器的基本概念

触发器(Triggers)是基于条件表达式的逻辑,用于自动化监控和管理任务。当应用程序的状态或性能指标满足预设的条件时,触发器会激活并执行预定义的动作。这种机制使得系统监控变得更加智能和自动化。

配置项解释

spring.boot.admin.monitor.triggers 配置项用于定义一个或多个触发器。每个触发器可以包含以下部分:

1.condition: 条件表达式,用于定义触发器何时触发。这通常是一个SpEL(Spring Expression Language)表达式。

2.action: 当条件满足时,应该执行的动作。动作可以包括发送邮件、触发Webhook、记录日志等。

触发器配置结构

触发器通常配置在 application.yml 或 application.properties 文件中,以下是配置结构的详细解释:

spring:
  boot:
    admin:
      monitor:
        triggers:
          - condition: "health.status=='DOWN' or info.memory.free<52428800 or info.cpu.usage>0.8"
            # 在这里定义动作,比如发送通知或执行其他操作
          - action: ...

1.condition

condition: 这是触发器的核心部分,定义了触发的条件。条件可以基于健康状态、度量指标(如 CPU 使用率、内存使用情况等),甚至是自定义的应用程序信息。

典型的条件表达式可能包括健康状态检查、资源使用率监控等。

条件可以包括对监控数据、应用状态或其他系统指标的检查。以下是一些常见的配置条件:

1. 应用健康状态

    health.status=='UP': 应用健康状态为 "UP"。

    health.status=='DOWN': 应用健康状态为 "DOWN"。

    health.status=='OUT_OF_SERVICE': 应用健康状态为 "OUT_OF_SERVICE"。

2. 内存使用情况

    info.memory.usage > 0.8: 内存使用率超过 80%。

    info.memory.usage < 0.5: 内存使用率低于 50%。

3. CPU 使用率

    info.cpu.usage > 0.7: CPU 使用率超过 70%。

    info.cpu.usage < 0.3: CPU 使用率低于 30%。

4. 自定义指标

    custom.metric > 100: 某个自定义指标超过 100。

    custom.metric < 10: 某个自定义指标低于 10。

5. 应用实例

    instance.name=='app-instance-1': 应用实例名称为 app-instance-1。

    instance.id=='12345': 应用实例 ID 为 12345。

6. 事件类型

    event.type=='InstanceRegisteredEvent': 事件类型为 InstanceRegisteredEvent。

    event.type=='InstanceDeregisteredEvent': 事件类型为 InstanceDeregisteredEvent。

7. 响应时间

    http.server.requests.max > 5000: 最大 HTTP 请求响应时间超过 5000 毫秒。

    http.server.requests.mean > 2000: 平均 HTTP 请求响应时间超过 2000 毫秒。

示例配置

在配置中可以结合使用多种条件。例如:

triggers:
  - condition: health.status=='DOWN' or info.cpu.usage > 0.8
  - condition: info.memory.usage > 0.7

这将触发警报当应用健康状态为 "DOWN" 或 CPU 使用率超过 80%,或者内存使用率超过 70% 时。

2.action:

action: 这个部分定义了当条件满足时应该执行的操作。可以是多种操作,比如发送邮件、触发 HTTP 请求(Webhook)、记录日志,或者调用自定义的动作。

spring.boot.admin 可以与其他系统集成,通过 Webhook 通知外部系统,或者使用电子邮件通知运维人员。

使用场景

    健康监控: 你可以设置一个触发器,当应用程序的健康状态变为 DOWN 时自动发送警报或执行恢复脚本。

    性能监控: 通过监控 CPU 使用率或内存使用情况,可以在资源使用率超过特定阈值时触发警报,从而避免潜在的性能问题。

    自动化响应: 你可以在触发条件下执行自动化的恢复操作,或者通知运维团队采取行动。

触发器的示例

spring:
  boot:
    admin:
      monitor:
        triggers:
          - condition: "health.status=='DOWN'"
            action: 
              type: "email"
              recipients:
                - "admin@example.com"
              subject: "Application Down"
              body: "The application is currently down. Please check immediately."
          - condition: "info.memory.free < 104857600" # 100MB
            action: 
              type: "webhook"
              url: "https://alert-system.example.com/notify"
              method: "POST"
              headers:
                Content-Type: "application/json"
              body: '{"message": "Memory usage is critically low"}'

在这个示例中:

    第一个触发器检测到应用程序健康状态为 DOWN 时发送电子邮件通知。

    第二个触发器检测到可用内存小于 100MB 时,触发一个 HTTP POST 请求,通知外部的告警系统。

完成示例

server.port=9000
#启用所有的 Actuator 端点
management.endpoints.web.exposure.include="*"
management.endpoint.health.show-details=always
#是否暴露 metrics 端点,提供如 JVM 内存使用、系统 CPU 使用率、线程使用情况等信息,Actuator 端点包括Health,Info,Threads, Heap Dump,Environment,Loggers,Caches,Dump,Trace?
management.endpoint.metrics.enabled=true
#是否将应用程序的度量指标导出到控制台或日志中
management.metrics.export.simple.enabled=true


#邮件服务器配置
spring.mail.host=smtp.your-email-provider.com
spring.mail.port=465
spring.mail.username=your-email@example.com
spring.mail.password=your-email-password
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

# 告警邮件配置
spring.boot.admin.notify.mail.from=xxxxx@qq.com
spring.boot.admin.notify.mail.to=877507054@qq.com
spring.boot.admin.notify.mail.subject="Service Alert:[${spring.application.name}] "
spring.boot.admin.notify.mail.enabled=true



# 检查和更新注册的客户端应用程序的状态的频率
spring.boot.admin.monitor.instances.poll-interval=10s
#应用程序的状态在这个时间段内没有更新,会将其状态标记为过时(OFFLINE)
spring.boot.admin.monitor.status-lifetime=10s
spring.boot.admin.ui.title=消费帮扶监控平台
#触发器配置,当应用程序的健康状态为 DOWN或者可用内存少于 50MB 时或者当 CPU 使用率超过 80% 时触发
spring.boot.admin.monitor.triggers.0.condition=health.status=='DOWN' or info.memory.free<52428800 or info.cpu.usage>0.1
spring.boot.admin.monitor.triggers.0.action.mail.from=xxxxx@qq.com
spring.boot.admin.monitor.triggers.0.action.mail.to=877507054@qq.com,xxxx@163.com
spring.boot.admin.monitor.triggers.0.action.subject=[${spring.application.name}] 程序告警
spring.boot.admin.monitor.triggers.0.action.mail.text=The application [${spring.application.name}] (ID: ${spring.boot.admin.client.instance-id}) has a CPU usage over 80%. Immediate attention required. Instance details: IP - ${instance.remoteAddress}

关键变量解释

    ${spring.application.name}: 代表当前应用程序的名称。这是在 Spring Boot 配置中常用的属性,能够标识应用程序。

    ${spring.boot.admin.client.instance-id}: 代表 Spring Boot Admin 为每个客户端实例分配的唯一 ID。如果你在应用程序的 application.properties 中配置了 spring.boot.admin.client.instance-id,它可以用来唯一标识每个实例。

    ${instance.remoteAddress}: 代表应用程序实例的 IP 地址,能够帮助你识别警报来自哪个服务器。

总结

spring.boot.admin.monitor.triggers 是 Spring Boot Admin 中一个强大的功能,用于自动化应用程序监控和管理。通过定义触发器条件和相应的动作,可以实现智能化的监控响应,确保系统的稳定性和可用性。使用触发器,你可以在应用程序状态出现异常时及时收到通知或采取自动化措施,从而提高系统的可靠性和响应速度。

3. 自定义通知

可以实现自定义的通知逻辑,例如发送邮件、推送通知、调用 API 等。通过实现 Notifier 接口或扩展 AbstractEventNotifier 类,可以定义自己的告警处理方式。

示例CustomEventNotifier

/**
 * 自定义通知器类
 * {
 * "names": [
 * "http.server.requests:HTTP 请求的计数和统计信息",
 * "jvm.buffer.count:JVM 中缓冲区的数量",
 * "jvm.buffer.memory.used:JVM 中缓冲区使用的内存量",
 * "jvm.buffer.total.capacity:JVM 中缓冲区的总容量",
 * "jvm.classes.loaded:当前加载的 JVM 类的数量",
 * "jvm.classes.unloaded:已卸载的 JVM 类的数量",
 * "jvm.gc.live.data.size:JVM 堆中活跃数据的大小",
 * "jvm.gc.max.data.size:最大可回收的数据大小",
 * "jvm.gc.memory.allocated:分配的 JVM 内存量",
 * "jvm.gc.memory.promoted:晋升的内存量",
 * "jvm.gc.pause:垃圾回收暂停时间",
 * "jvm.memory.committed:已提交的 JVM 内存量",
 * "jvm.memory.max:JVM 可用的最大内存量",
 * "jvm.memory.used:当前使用的 JVM 内存量",
 * "jvm.threads.daemon:当前活跃的守护线程数",
 * "jvm.threads.live:当前活跃的线程总数",
 * "jvm.threads.peak:JVM 线程的峰值数量",
 * "jvm.threads.states:各个线程状态的数量",
 * "logback.events:Logback 事件的计数",
 * "process.cpu.usage:进程的 CPU 使用率",
 * "process.files.max:进程允许的最大文件描述符数量",
 * "process.files.open:当前打开的文件描述符数量",
 * "process.start.time:进程启动时间",
 * "process.uptime:进程的运行时间",
 * "system.cpu.count:系统中的 CPU 核心数量",
 * "system.cpu.usage:系统的 CPU 使用率",
 * "system.load.average.1m:系统 1 分钟内的平均负载",
 * "tomcat.sessions.active.current:当前活跃的 Tomcat 会话数量",
 * "tomcat.sessions.active.max:Tomcat 中的最大活跃会话数量",
 * "tomcat.sessions.alive.max:Tomcat 中的最大存活会话数量",
 * "tomcat.sessions.created:创建的 Tomcat 会话总数",
 * "tomcat.sessions.expired:过期的 Tomcat 会话数量",
 * "tomcat.sessions.rejected:被拒绝的 Tomcat 会话数量"
 * ]
 * }
 */
@Component
public class CustomNotifier extends AbstractEventNotifier implements HealthIndicator {
    private static final Logger logger = LoggerFactory.getLogger(CustomNotifier.class);
    private RestTemplate restTemplate;
    //阈值
    private static Double cpuThreshold = 50.0;//CPU使用率阈值
    private static Double jvmMemoryThreshold = 50.0;//内存使用率阈值
    //使用率
    private static Double cpuUsage = 0.0;//CPU 使用率
    private static Double jvmMemoryUsage = 0.0;//jvm内存使用率
    //所属环境
    private static String environment;

    static {
        if (EnvironmentUtil.isProdProfile()) {//生产环境
            environment = "生产环境";
        } else {
            environment = "测试环境";
        }
    }

    @Resource
    private JavaMailSender mailSender;

    public CustomNotifier(InstanceRepository repository, RestTemplate restTemplate) {
        super(repository);
        this.restTemplate = restTemplate;
    }

    @Override
    protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
        return Mono.fromRunnable(() -> {
            try {
                if (event instanceof InstanceRegisteredEvent) {//新实例加入事件
                    handleEvent(instance, "1");
                    logger.info("新实例加入事件,实例名:{}", instance.getRegistration().getName());
                } else if (event instanceof InstanceDeregisteredEvent) {//实例关闭或注销事件
                    handleEvent(instance, "2");
                    logger.info("实例关闭或注销事件,实例名:{}", instance.getRegistration().getName());
                } else if (event instanceof InstanceStatusChangedEvent) {//实例健康状态改变事件
                    handleEvent(instance, "3");
                    logger.info("实例健康状态改变事件,实例名:{}", instance.getRegistration().getName());
                } else if (event instanceof InstanceInfoChangedEvent) {//实例信息更新事件
                    handleEvent(instance, "4");
                    logger.info("实例信息更新事件,实例名:{}", instance.getRegistration().getName());
                } else {
                    handleEvent(instance, "5");//其他事件
                    logger.info("其他事件,实例名:{}", instance.getRegistration().getName());
                }
            } catch (Exception e) {
                logger.info("监控平台异常:" + e.getMessage());
            }
        });
    }

    private void handleEvent(Instance instance, String type) {
        String alertMessage = "";

        /*****************************************1.收集信息************************************************************/
        String applicationName = instance.getRegistration().getName();
        //状态
        String status = instance.getStatusInfo().getStatus();
        //获取Metrics信息
        Map<String, Object> allMetrics = new HashMap<>();
        try {
            // 获取 /metrics 基本 URL
            String baseMetricUrl = instance.getRegistration().getServiceUrl() + "/actuator/metrics";
            // 获取所有 metrics 名称
            Map<String, Object> metricsResponse = restTemplate.getForObject(baseMetricUrl, Map.class);
            if (metricsResponse == null||metricsResponse.isEmpty()) {
                throw new RuntimeException("获取metrics端点信息为null");
            }
            List<String> nameList = (List) metricsResponse.get("names");
            for (String metricName : nameList) {
                String metricUrl = baseMetricUrl + "/" + metricName;
                Map<String, Object> metricDetails = restTemplate.getForObject(metricUrl, Map.class);
                allMetrics.put(metricName, metricDetails);
            }

            Double jvmMemoryUsed = 0.0;
            Double jvmMemoryMax = 0.0;
            Map<String, Object> memoryUsedMap = (Map<String, Object>) allMetrics.get("jvm.memory.used");//当前使用的 JVM 内存量
            Map<String, Object> memoryMaxMap = (Map<String, Object>) allMetrics.get("jvm.memory.max");//JVM 可用的最大内存量
            if (memoryUsedMap != null && memoryMaxMap != null) {
                // 获取内存使用和最大值的 measurements
                List<Map<String, Object>> memoryUsedMeasurements = (List<Map<String, Object>>) memoryUsedMap.get("measurements");
                List<Map<String, Object>> memoryMaxMeasurements = (List<Map<String, Object>>) memoryMaxMap.get("measurements");
                if (memoryUsedMeasurements != null && !memoryUsedMeasurements.isEmpty() && memoryMaxMeasurements != null && !memoryMaxMeasurements.isEmpty()) {
                    jvmMemoryUsed = (Double) memoryUsedMeasurements.get(0).get("value");
                    jvmMemoryMax = (Double) memoryMaxMeasurements.get(0).get("value");
                }
            }
            // 计算内存使用率
            jvmMemoryUsage = (jvmMemoryUsed / jvmMemoryMax) * 100;
            logger.info("JVM 内存使用率: {}%", jvmMemoryUsage);
            // CPU 使用率
            Map<String, Object> cpuUsageMap = (Map<String, Object>) allMetrics.get("system.cpu.usage");
            if (cpuUsageMap != null) {
                List<Map<String, Object>> cpuUsageMeasurements = (List<Map<String, Object>>) cpuUsageMap.get("measurements");
                if (cpuUsageMeasurements != null && !cpuUsageMeasurements.isEmpty()) {
                    cpuUsage = (Double) cpuUsageMeasurements.get(0).get("value") * 100;
                }
            }
            logger.info("系统 CPU 使用率: {}%", cpuUsage);


            /*****************************************2.组装输出信息************************************************************/
            alertMessage =
                    "\n应用ID:" + instance.getId() +
                            "\n应用名:" + applicationName +
                            "\n应用名:" + applicationName +
                            "\n状态:" + status +
                            "\n事件类型:" + type +
                            "\n实例注册名称:" + instance.getRegistration().getName() +
                            "\n管理URL:" + instance.getRegistration().getManagementUrl() +
                            "\n健康URL:" + instance.getRegistration().getHealthUrl() +
                            "\n当前使用的JVM内存量(MB):" +  Math.round(jvmMemoryUsed/ (1024*1024) * 100) / 100.0+
                            "\nJVM可用的最大内存量(MB):" +  Math.round(jvmMemoryMax / (1024*1024) * 100) / 100.0+
                            "\nJVM内存使用率:" + Math.round(jvmMemoryUsage * 100) / 100.0+
                            "\nCPU使用率:" + Math.round(cpuUsage * 100) / 100.0 +
                            "\nmetrics端点JSON:" + JSON.toJSONString(allMetrics) ;
            logger.info(alertMessage);
        } catch (Exception e) {
            logger.error("获取 Metrics 端点信息错误: ", e);
        }

        /*****************************************3.根据情况发送邮件************************************************************/

        if ("2".equals(type)) {
            sendEmail(environment + "监控告警]-实例关闭或注销事件:" + applicationName, alertMessage, null);
        }
        if (cpuUsage > cpuThreshold) {
            sendEmail(environment + "监控告警]-系统CPU使用率达到" + cpuUsage + "%告警:" + applicationName, alertMessage, null);
        }
        if (jvmMemoryUsage > jvmMemoryThreshold) {
            sendEmail(environment + "监控告警]-JVM内存使用率达到" + jvmMemoryUsage + "%告警:" + applicationName, alertMessage, null);
        }
    }


    private void sendEmail(String title, String body, String toMail) {
        try {
            SimpleMailMessage message = new SimpleMailMessage();
            if (StringUtils.isEmpty(toMail)) {
                toMail = MailUtil.MAIL_SMTP_TO;
            }
            message.setTo(toMail.split(",")); // 收件人
            message.setFrom(MailUtil.MAIL_SMTP_USER);
            message.setSubject(title);
            message.setText(body);
            logger.info("发送告警邮件:", message.getSubject() + ",TO:" + message.getTo());
            mailSender.send(message);
        } catch (Exception e) {
            logger.error("发送邮件失败,失败信息:" + e.getMessage());
        }

    }




    /**
     * 定义信息到/actuator/health端点,向/actuator/info端点自定义信息可以继承InfoContributor接口
     * HealthIndicator 是一个用于提供应用健康检查的接口
     * HealthIndicator 会被 Spring Boot Actuator 自动扫描,并在 /actuator/health 端点上暴露。
     * 访问 /actuator/health
     * {
     * "status": "UP",
     * "details": {
     * "maxMemory": 4294967296,
     * "totalMemory": 104857600,
     * "freeMemory": 73741824,
     * "usedMemory": 31115776
     * }
     * }
     *
     * @return
     */
    @Override
    public Health health() {
        // 获取当前JVM内存使用情况
        Runtime runtime = Runtime.getRuntime();
        /**获取当前JVM 内存使用情况**/
        MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
        MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
        MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage();
        /**获取垃圾回收器信息**/
        List<GarbageCollectorMXBean> garbageCollectorMXBeans = ManagementFactory.getGarbageCollectorMXBeans();
        StringBuilder gcInfo = new StringBuilder();
        for (GarbageCollectorMXBean gcBean : garbageCollectorMXBeans) {
            gcInfo.append(gcBean.getName())
                    .append(": ")
                    .append("Collection count: ").append(gcBean.getCollectionCount() == -1 ? "N/A" : gcBean.getCollectionCount())
                    .append(", Collection time (ms): ").append(gcBean.getCollectionTime() == -1 ? "N/A" : gcBean.getCollectionTime())
                    .append("\n");
        }
        /**获取线程信息**/
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        /**获取操作系统信息**/
        OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean();
        // 检查内存使用情况是否超出了阈值
        long usedMemory = runtime.totalMemory() - runtime.freeMemory();
        return Health.up()
                .withDetail("JVM堆最大内存", runtime.maxMemory())
                .withDetail("JVM堆总内存", runtime.totalMemory())//返回当前 JVM 堆的总内存量,代码所在jvm
                .withDetail("JVM堆空闲的内存量", runtime.freeMemory())//返回当前 JVM 堆中空闲的内存量
                .withDetail("JVM堆已用内存", usedMemory)
                .withDetail("非堆内存(Non-Heap)内存使用", nonHeapMemoryUsage.getUsed() + "/" + Math.round(nonHeapMemoryUsage.getMax()))
                .withDetail("堆(Heap)内存使用", heapMemoryUsage.getUsed() + "/" + Math.round(heapMemoryUsage.getMax()))
                .withDetail("可用处理器", runtime.availableProcessors())
                .withDetail("线程信息", threadMXBean.getThreadCount())
                .withDetail("峰值线程数", threadMXBean.getPeakThreadCount())
                .withDetail("总启动线程数", threadMXBean.getTotalStartedThreadCount())
                .withDetail("操作系统可用处理器", osMXBean.getAvailableProcessors())
                .withDetail("系统负载平均值", osMXBean.getSystemLoadAverage())
                .withDetail("GC信息", gcInfo)
                .build();
    }

}

其他配置信息

@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

在 CustomNotifier 类中,doNotify 方法的 Instance 参数代表了一个注册的应用实例的详细信息。理解 Instance 的结构和内容可以帮助你在处理通知时更好地利用这些数据。

Instance 的主要字段,你可以访问 Instance 对象来获取有关应用实例的信息,以便在通知中使用。例如,你可以:

  1. 获取实例的基本信息:如 name、id 等,来确定是哪个实例触发了事件。
  2. 检查实例的状态:通过 statusInfo 和 status 字段,你可以了解实例的健康状况,并据此做出通知决策。
  3. 使用注册信息:例如,managementUrl 和 healthUrl,可以用于构建通知内容或执行进一步的检查。
  4. 访问实例的端点信息:例如,你可以查看哪些 Actuator 端点被暴露,并利用这些端点进行更深入的监控或操作。

3.1 Instance 对象

以下是 Instance 对象的一些关键字段及其含义:

id:

唯一标识该实例的 ID。

version:

该实例的版本号。

registration:

实例注册信息,包括:

name: 实例的名称。

managementUrl: 用于管理的 URL(例如 /actuator 的 URL)。

healthUrl: 用于健康检查的 URL。

serviceUrl: 服务的基础 URL。

source: 实例的注册来源(例如 discovery)。

metadata: 与实例相关的元数据,如心跳超时、Nacos 配置等。

registered:

布尔值,指示实例是否已注册。

statusInfo:

实例的状态信息,包括:

status: 实例的健康状态(例如 UP、DOWN)。

details: 包含详细的状态信息,可能包括:sentinel, elasticsearch, diskSpace, ping, discoveryComposite, refreshScope, nacosDiscovery, db, redis 等。每个状态组件可能有 status 和 details,这些详细信息可以包括服务的健康状况、性能指标等。

statusTimestamp:

实例状态的时间戳。

info:

实例的额外信息,通常是应用程序的自定义信息。

endpoints:

实例暴露的 Actuator 端点的列表,包括每个端点的 ID 和 URL(例如 /actuator/health、/actuator/metrics)。

tags:

实例的标签,用于标识或分类实例。

三.Spring Boot Admin支持的监控属性

在Spring Boot Admin中,你可以监控应用程序的各种指标。Spring Boot Admin通过Spring Boot Actuator提供的端点来收集这些监控信息。下面是Spring Boot Admin支持的监控属性的详细列表,它们通常包括系统的健康状态、度量数据、环境信息等。

1.常见的Spring Boot Admin监控属性

1. Health

    status: 应用程序的整体健康状态 (UP, DOWN, OUT_OF_SERVICE, UNKNOWN)。

    details: 各个健康检查的详细信息,如数据库、消息中间件等。

2. Metrics

    jvm.memory.used: 已使用的JVM内存。

    jvm.memory.max: 最大JVM内存。

    jvm.memory.committed: 已提交的JVM内存。

    jvm.gc.total: JVM垃圾回收总数。

    jvm.gc.time: JVM垃圾回收时间。

    system.cpu.usage: 系统CPU使用率。

    system.cpu.load: 系统CPU负载。

    disk.space.free: 磁盘可用空间。

    disk.space.total: 磁盘总空间。

    disk.space.used: 磁盘已用空间。

3. Environment

    properties: 环境属性,如server.port, spring.datasource.url等。

    configurations: 应用程序配置文件的详细信息。

    beans: Spring上下文中定义的bean信息。

4. Info

    app.version: 应用程序的版本。

    app.description: 应用程序的描述。

    build.version: 构建版本。

    build.name: 构建名称。

    build.time: 构建时间。

    git.commit.id: Git提交ID。

    git.commit.time: Git提交时间。

    git.branch: Git分支。

5. Loggers

    loggers: 各个日志记录器的级别(例如DEBUG, INFO, WARN, ERROR)。

    logging.level: 各个包的日志级别配置。

6. Threads

    thread.count: 当前活动线程的数量。

    thread.states: 各种线程状态的计数(例如RUNNABLE, WAITING, TIMED_WAITING, BLOCKED)。

    thread.details: 线程的详细信息。

7. Caches

    caches: 相关缓存的信息,如缓存名称、大小等。

8. Heap Dump

    heap: 堆转储的详细信息,包括各个类的实例和内存占用情况。

9. Dump

    dump: 应用程序的线程转储信息。

10. Trace

    trace: 跟踪信息,通常用于调试目的。

配置Spring Boot Admin以显示这些属性

确保你的application.properties或application.yml中配置了Actuator的相关端点,以便Spring Boot Admin能够获取这些监控信息。例如:

management.endpoints.web.exposure.include=health,metrics,info,env,loggers,threaddump

management.endpoint.health.show-details=always


https://docs.spring.io/spring-boot/reference/actuator/endpoints.html

2.端点接口

1. /actuator/health

状态信息 (statusInfo):包括应用程序的健康状态(如 UP、DOWN),以及各种健康检查的详细信息(例如数据库、缓存、磁盘空间的健康状况)。这对应于 Instance 对象的 statusInfo 字段。

   {
      "status": "UP",
      "details": {
        "diskSpace": {
          "status": "UP",
          "details": {
            "total": 51510251520,
            "free": 35968552960
          }
        }
      }
    }

2. /actuator/info

    额外信息 (info):通常用于返回应用程序的元数据,例如版本信息、构建信息等。对应 Instance 对象的 info 字段。示例:

 {
      "app": "my-app",
      "version": "1.0.0"
    }

3. /actuator/env

    环境信息:提供有关应用程序的环境属性的详细信息。这不是直接包含在 Instance 对象中的字段,但可以在 info 中看到。  示例:

{
      "propertySources": [
        {
          "name": "systemProperties",
          "properties": {
            "java.version": "1.8.0_212"
          }
        }
      ]
    }

4. /actuator/metrics

    性能指标:提供关于应用程序性能的详细数据(例如内存使用情况、垃圾回收信息等)。这些信息可以用来补充 statusInfo 中的性能数据,但通常 metrics 不直接映射到 Instance 的字段中。示例:

   {
      "jvm.memory.used": 12345678,
      "jvm.memory.max": 98765432
    }

5. /actuator/mappings

    端点映射:提供所有控制器端点的映射信息。这可以与 Instance 的 endpoints 字段相关联。 示例:

 {
      "controllers": {
        "home": "/home",
        "admin": "/admin"
      }
    }

6. /actuator/beans

    Bean 信息:列出所有 Spring Bean 和其配置。这有助于调试和了解应用程序的内部结构,虽然它通常不直接映射到 Instance 对象的字段中。示例:

    {
      "beans": [
        {
          "name": "dataSource",
          "type": "javax.sql.DataSource"
        }
      ]
    }

这些端点提供了不同类型的监控和管理信息,可以帮助你从 Actuator 中提取所需的数据

3.访问示例

访问:http://localhost:9000/actuator

{
  "_links": {
    "self": {
      "href": "http://localhost:9000/actuator",
      "templated": false
    },
    "archaius": {
      "href": "http://localhost:9000/actuator/archaius",
      "templated": false
    },
    "nacosdiscovery": {
      "href": "http://localhost:9000/actuator/nacosdiscovery",
      "templated": false
    },
    "sentinel": {
      "href": "http://localhost:9000/actuator/sentinel",
      "templated": false
    },
    "beans": {
      "href": "http://localhost:9000/actuator/beans",
      "templated": false
    },
    "caches-cache": {
      "href": "http://localhost:9000/actuator/caches/{cache}",
      "templated": true
    },
    "caches": {
      "href": "http://localhost:9000/actuator/caches",
      "templated": false
    },
    "health": {
      "href": "http://localhost:9000/actuator/health",
      "templated": false
    },
    "health-path": {
      "href": "http://localhost:9000/actuator/health/{*path}",
      "templated": true
    },
    "info": {
      "href": "http://localhost:9000/actuator/info",
      "templated": false
    },
    "conditions": {
      "href": "http://localhost:9000/actuator/conditions",
      "templated": false
    },
    "configprops": {
      "href": "http://localhost:9000/actuator/configprops",
      "templated": false
    },
    "env-toMatch": {
      "href": "http://localhost:9000/actuator/env/{toMatch}",
      "templated": true
    },
    "env": {
      "href": "http://localhost:9000/actuator/env",
      "templated": false
    },
    "loggers": {
      "href": "http://localhost:9000/actuator/loggers",
      "templated": false
    },
    "loggers-name": {
      "href": "http://localhost:9000/actuator/loggers/{name}",
      "templated": true
    },
    "heapdump": {
      "href": "http://localhost:9000/actuator/heapdump",
      "templated": false
    },
    "threaddump": {
      "href": "http://localhost:9000/actuator/threaddump",
      "templated": false
    },
    "metrics-requiredMetricName": {
      "href": "http://localhost:9000/actuator/metrics/{requiredMetricName}",
      "templated": true
    },
    "metrics": {
      "href": "http://localhost:9000/actuator/metrics",
      "templated": false
    },
    "scheduledtasks": {
      "href": "http://localhost:9000/actuator/scheduledtasks",
      "templated": false
    },
    "mappings": {
      "href": "http://localhost:9000/actuator/mappings",
      "templated": false
    },
    "refresh": {
      "href": "http://localhost:9000/actuator/refresh",
      "templated": false
    },
    "features": {
      "href": "http://localhost:9000/actuator/features",
      "templated": false
    },
    "service-registry": {
      "href": "http://localhost:9000/actuator/service-registry",
      "templated": false
    }
  }
}

访问http://localhost:9000/actuator/metrics

{
  "names": [
    "http.server.requests",
    "jvm.buffer.count",
    "jvm.buffer.memory.used",
    "jvm.buffer.total.capacity",
    "jvm.classes.loaded",
    "jvm.classes.unloaded",
    "jvm.gc.live.data.size",
    "jvm.gc.max.data.size",
    "jvm.gc.memory.allocated",
    "jvm.gc.memory.promoted",
    "jvm.gc.pause",
    "jvm.memory.committed",
    "jvm.memory.max",
    "jvm.memory.used",
    "jvm.threads.daemon",
    "jvm.threads.live",
    "jvm.threads.peak",
    "jvm.threads.states",
    "logback.events",
    "process.cpu.usage",
    "process.files.max",
    "process.files.open",
    "process.start.time",
    "process.uptime",
    "system.cpu.count",
    "system.cpu.usage",
    "system.load.average.1m",
    "tomcat.sessions.active.current",
    "tomcat.sessions.active.max",
    "tomcat.sessions.alive.max",
    "tomcat.sessions.created",
    "tomcat.sessions.expired",
    "tomcat.sessions.rejected"
  ]
}

访问具体的参数可以得到具体的值,比如访问/actuator/metrics/jvm.memory.used

访问http://localhost:9000/applications

 Spring Boot Admin 的 /applications 接口 获取的。该接口返回当前在 Spring Boot Admin 中注册的所有应用程序的信息,包括每个应用的详细实例数据,例如 health、status、managementUrl、endpoints 等。

name: 应用程序的名称,status: 应用程序的整体状态,通常是 UP 或 DOWN。

instances: 应用程序的所有实例信息。

    id: 实例的唯一标识符。

    statusInfo: 包含实例的详细状态信息,例如 Sentinel、Elasticsearch、DiskSpace、Redis 等相关服务的状态。

    endpoints: 列举了实例暴露的 Actuator 端点,例如 /actuator/health, /actuator/metrics 等。

{
    "name": "YIXIEKEJI-MEMBER",
    "buildVersion": null,
    "status": "UP",
    "statusTimestamp": "2024-09-03T05:18:26.492Z",
    "instances": [
      {
        "id": "xxx",
        "version": 2,
        "registration": {
          "name": "YIXIEKEJI-MEMBER",
          "managementUrl": "http://IP:PORT/actuator",
          "healthUrl": "http://IP:PORT/actuator/health",
          "serviceUrl": "http://IP:PORT",
          "source": "discovery",
          "metadata": {
            "preserved.heart.beat.timeout": "5000",
            "nacos.instanceId": "xxxxxx",
            "nacos.weight": "1.0",
            "nacos.cluster": "DEFAULT",
            "nacos.ephemeral": "true",
            "nacos.healthy": "true",
            "preserved.register.source": "SPRING_CLOUD",
            "preserved.heart.beat.interval": "5000"
          }
        },
        "registered": true,
        "statusInfo": {
          "status": "UP",
          "details": {
            "diskSpace": {
              "status": "UP",
              "details": {
                "total": 11111,
                "free": 11111,
                "threshold": 11111,
                "exists": true
              }
            },
            "ping": {
              "status": "UP"
            }
          }
        },
        "statusTimestamp": "2024-09-03T05:18:26.492Z",
        "info": {
          
        },
        "endpoints": [
          {
            "id": "sentinel",
            "url": "http://IP:PORT/actuator/sentinel"
          },
          {
            "id": "caches",
            "url": "http://IP:PORT/actuator/caches"
          },
          {
            "id": "loggers",
            "url": "http://IP:PORT/actuator/loggers"
          },
          {
            "id": "health",
            "url": "http://IP:PORT/actuator/health"
          },
          {
            "id": "refresh",
            "url": "http://IP:PORT/actuator/refresh"
          },
          {
            "id": "env",
            "url": "http://IP:PORT/actuator/env"
          },
          {
            "id": "nacosdiscovery",
            "url": "http://IP:PORT/actuator/nacosdiscovery"
          },
          {
            "id": "heapdump",
            "url": "http://IP:PORT/actuator/heapdump"
          },
          {
            "id": "features",
            "url": "http://IP:PORT/actuator/features"
          },
          {
            "id": "scheduledtasks",
            "url": "http://IP:PORT/actuator/scheduledtasks"
          },
          {
            "id": "mappings",
            "url": "http://IP:PORT/actuator/mappings"
          },
          {
            "id": "archaius",
            "url": "http://IP:PORT/actuator/archaius"
          },
          {
            "id": "beans",
            "url": "http://IP:PORT/actuator/beans"
          },
          {
            "id": "configprops",
            "url": "http://IP:PORT/actuator/configprops"
          },
          {
            "id": "threaddump",
            "url": "http://IP:PORT/actuator/threaddump"
          },
          {
            "id": "metrics",
            "url": "http://IP:PORT/actuator/metrics"
          },
          {
            "id": "conditions",
            "url": "http://IP:PORT/actuator/conditions"
          },
          {
            "id": "service-registry",
            "url": "http://IP:PORT/actuator/service-registry"
          },
          {
            "id": "info",
            "url": "http://IP:PORT/actuator/info"
          }
        ],
        "buildVersion": null,
        "tags": {
          
        }
      }
    ]
  }

参考:

https://juejin.cn/post/6956483441227464740

开始使用Spring Boot Admin吧-使用Nacos注册SBA_spring boot admin nacos-CSDN博客

springBoot Admin整合nacos_springboot admin nacos-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进阶SpringBoot之配置 Swagger API 框架信息

Swagger&#xff1a;API 框架 RestFul API 文档在线自动生成工具&#xff0c;API 文档与 API 定义同步更新 Swagger 官网 Maven 仓库 创建 Spring Boot 项目&#xff0c;依赖勾选 Spring Web pom.xml 导入相关依赖&#xff1a; springfox-swagger2、springfox-swagger-ui …

何为数据中台

数据中台 什么是数据中台 2014年马云正式提出“DT&#xff08;Data Technology&#xff09;”的概念&#xff0c;人类从IT时代走向了DT时代&#xff0c;阿里内部的数据平台事业部大刀阔斧的建立整个集团的数据资产&#xff0c;同年&#xff0c;阿里从芬兰Supercell公司接触到…

Canny算子 一张图看懂

对于最高值和最低值的设置&#xff0c; 1&#xff0c;high t最大值一般以一阶导数幅度图的最大值的30%-40%来定 2&#xff0c;最小值一般halcon里默认为 low theigh t/3得到 3&#xff0c;canny的优势是有极大值抑制&#xff0c;所以提取的边缘是1个像素的窄边缘。 3&#xff0…

Golang path/filepath包详解:高效路径操作与实战案例

Golang path/filepath包详解&#xff1a;高效路径操作与实战案例 引言基础用法Abs 函数Base 函数Clean 函数Dir 函数Ext 函数FromSlash 和 ToSlash 函数 基础用法Abs 函数Base 函数Clean 函数Dir 函数Ext 函数FromSlash 和 ToSlash 函数 路径操作Join 函数Split 函数Rel 函数Ma…

LabVIEW编程语言出于什么原因开发的?

LabVIEW最初由美国国家仪器公司&#xff08;NI&#xff09;于1986年开发&#xff0c;目的是为工程师和科学家提供一种图形化编程环境&#xff0c;简化数据采集、仪器控制、自动化测试和测量系统开发等工作。开发LabVIEW的主要原因包括以下几点&#xff1a; 简化复杂系统开发&am…

《数字图像处理(面向新工科的电工电子信息基础课程系列教材)》Chapter 1课件2024

每一轮备课都有新的感悟。 禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》 禹晶、肖创柏、廖庆敏《数字图像处理》资源二维码

Tektronix泰克MSO5204B混合信号示波器4+16通道2G

Tektronix泰克MSO5204B混合信号示波器416通道2G 2 GHz、416 通道、10/5 GS/s&#xff08;2/4 通道&#xff09;混合信号示波器&#xff0c;50 M/25 M 记录长度泰克 MSO5204B 2 GHz、416 通道、10/5 GS/s&#xff08;2/4 通道&#xff09;混合信号示波器&#xff0c;50 M/25 M …

机器学习强化学习

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl1. 强化学习概述 1.1 定义与核心概念 强化学习是一种目标导向的机器学习方法,它使智能体能够在环境中通过试错学习最优行为策略。这种学习过程涉及到智能体与环境之间的交互,智能体根据当前状态…

AI基础 L8 Local Search I 局部搜索

Iterative Improvement Algorithms • In many optimization problems, the path to a goal is irrelevant — the goal state itself is the solution • State space a set of goal states — find one that satisfies constraints (e.g., no two classes at same time) —…

《系统安全架构设计及其应用》写作框架,软考高级系统架构设计师

论文真题 随着社会信息化进程的加快&#xff0c;计算机及网络已经被各行各业广泛应用&#xff0c;信息安全问题也变得愈来愈重要。它具有机密性、完整性、可用性、可控性和不可抵赖性等特征。信息系统的安全保障是以风险和策略为基础&#xff0c;在信息系统的整个生命周期中提…

【审批流】基于JAVA开发的工作流审批系统(直接集成或者直接可使用)

基于Javavue开发的智能审批系统&#xff0c;低代码平台 软件资料清单列表部分文档清单&#xff1a;工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查单&#xff0c;用户需…

Android APK插件化:DynamicAPK技术如何改变游戏规则

在移动应用开发领域&#xff0c;尤其是Android平台&#xff0c;应用的体积和更新速度一直是开发者和用户关注的焦点。随着应用功能的不断增加&#xff0c;APK文件的大小也在逐渐膨胀&#xff0c;这不仅增加了用户的下载成本&#xff0c;也影响了应用的更新效率。DynamicAPK技术…

数学建模笔记——层次分析法

数学建模笔记——层次分析法 数学建模笔记——层次分析法1. 层次分析法的基本原理和步骤2. 层次分析法的建模过程2.1 问题的提出2.2 模型原理2.3 为该问题建立层次结构模型2.4 构造判断矩阵1. 判断矩阵的含义2. 为该问题构造判断矩阵 2.5 一致性检验1. 一致性检验方法2. 对上述…

【Linux】HTTP协议中的cookie和session

一、B站的登录和未登录——一种登录场景的演示 我们现在上的是B站大学&#xff0c;所以对于B站&#xff0c;我们是很熟悉的。当我们打开浏览器&#xff0c;并访问B站网页时&#xff08;很熟悉&#xff09;&#xff0c;会发现我们会自动登录上B站&#xff0c;为什么呢&#xff1…

解锁 macOS 剪贴板历史记录,高效复制、粘贴技巧

在Mac上&#xff0c;我们经常需要在不同文档之间复制和粘贴内容。然而&#xff0c;macOS自带的剪贴板只能保存最后一个复制项&#xff0c;这大大限制了我们的工作效率。幸运的是&#xff0c;一些第三方应用程序可以帮助我们查看和管理剪贴板的历史记录&#xff0c;从而提升我们…

基于RP2350 MCU的树莓派Pico 2开发板及MicroPython编程使用

2021年1月21日,树莓派基金会同时发布了第1代RP2040 MCU芯片和基于RP2040 MCU的第1代树莓派Pico开发板(Raspberry Pi Pico/ Raspberry Pi Pico 1)。2024年8月8日,树莓派基金会又发布了第2代RP2350 MCU芯片并推出了基于RP2350 MCU的第2代树莓派Pico开发板(Raspberry Pi Pico 2)…

pandas:一个强大的数据处理Python库

我是东哥&#xff0c;一个热衷于探索Python世界的自媒体人。今天&#xff0c;我要为大家介绍一个在Python数据分析领域中非常强大的库——Pandas。如果你对数据分析充满好奇&#xff0c;或者正在寻找一个简单易用的库来处理和分析数据&#xff0c;那么Pandas绝对是你的不二之选…

MySQL——库操作

首先先来说一下MySQL中常见的操作&#xff1a; 1. 清屏 system clear; 2. 如果你使用的是腾讯云的Ubuntu&#xff0c;登陆的时候用户名可能是ubuntu&#xff0c;进入后可以使用 sudo -i 切换为高级用户 一、创建数据库 create database db_name; 示例&#xff1a; mysql> …

汽车测试展︱AUTO TECH 2025 广州国际汽车测试测量技术展览会

汽车测试展︱AUTO TECH 2025 广州国际汽车测试测量技术展览会 The China Guangzhou Automotive Test Expo 2025 2025年11月20-22日&#xff0c;专注于华南地区专业的汽车质量控制展览会&#xff0c;将在广州保利世贸博览馆继续举办。是关于各种汽车测试解决方案的专业展如汽车电…

[C高手编程] static与extern: 作用域、可见性与存储类全面解析

&#x1f496;&#x1f496;⚡️⚡️专栏&#xff1a;C高手编程-面试宝典/技术手册/高手进阶⚡️⚡️&#x1f496;&#x1f496; 「C高手编程」专栏融合了作者十多年的C语言开发经验&#xff0c;汇集了从基础到进阶的关键知识点&#xff0c;是不可多得的知识宝典。如果你是即将…