快速Spring Cloud+ELK+AOP搭建一个简单的项目

目录

[3 Spring Cloud+ELK+AOP](#3 Spring Cloud+ELK+AOP)

[3.1 整体架构和调用流程](#3.1 整体架构和调用流程)

整体架构总览

调用流程

[3.2 具体代码](#3.2 具体代码)

[3.2.1 ELK](#3.2.1 ELK)

[3.2.2 生成者项目](#3.2.2 生成者项目)

[3.2.3 消费者项目](#3.2.3 消费者项目)

[3.2.4 运行](#3.2.4 运行)

[3.3 面试题](#3.3 面试题)


3 Spring Cloud+ELK+AOP

使用Spring Cloud+ELK+AOP搭建一个简单的项目

3.1 整体架构和调用流程

整体架构总览

  • 生产者服务 cloud-provider:端口 8081,提供接口服务
  • 消费者服务 cloud-consumer:端口 8082,通过 OpenFeign 远程调用生产者
  • AOP 切面:拦截消费者所有接口,打印请求、返回日志
  • ELK 整套环境:Logback → Logstash → Elasticsearch → Kibana 存储展示日志

调用流程

复制代码
浏览器
    ↓
消费者Controller接口
    ↓
AOP切面拦截(打印请求日志)
    ↓
Feign远程调用
    ↓
生产者Controller接口(处理业务)
    ↓
生产者返回数据
    ↓
Feign链路回传给消费者
    ↓
AOP切面打印返回日志
    ↓
Logback日志框架
    ↓
Logstash
    ↓
Elasticsearch存储
    ↓
Kibana查询展示
  1. 启动环境 首先启动本地 Docker 中的 ELK 全套服务 (Elasticsearch、Logstash、Kibana),Logstash 监听本地 4560 端口等待接收日志。随后依次在 IDEA 中启动生产者微服务消费者微服务

  2. 浏览器发起请求用户浏览器访问消费者接口地址:

    http://localhost:8082/call

请求进入消费者项目的 ConsumerController 的 call() 接口方法。

  1. AOP 切面拦截请求
    消费者项目的 AOP 环绕切面 拦截该接口请求,执行前置日志打印,记录接口方法名、请求参数信息。

  2. 消费者通过 Feign 远程调用生产者
    消费者内部通过 @FeignClient 声明的远程接口, 向本地 8081 端口的生产者服务 发起 HTTP 请求,调用生产者的 /provider/hello 接口。

  3. 生产者接收并处理请求
    生产者服务接收到消费者的远程调用请求,执行自身接口业务,返回结果字符串:

    我是生产者,你调用成功啦!

  4. 结果回传给消费者
    生产者将接口返回数据,通过 Feign 调用链路原路返回给消费者。

  5. 消费者拼接结果并响应浏览器
    消费者拿到生产者返回数据,拼接返回内容,最终响应给浏览器:

    消费者调用生产者:我是生产者,你调用成功啦!

  6. AOP 后置日志打印
    接口方法执行完毕后,AOP 切面继续执行,打印接口最终返回结果,完整日志输出到 IDEA 控制台。

  7. 日志发送至 ELK
    项目通过 logback-spring.xml 日志配置,将 AOP 打印的所有日志,发送到本地 4560 端口的 Logstash

  8. 日志存储与可视化查看
    Logstash 接收日志后,将日志数据存入 Elasticsearch 搜索引擎;最后在 Kibana 可视化界面中,通过索引检索,即可查询到全部接口调用日志。

3.2 具体代码

3.2.1 ELK


logstash.conf

复制代码
# 接收Spring传来的日志,转发到ES
input {
  tcp {
    port => 4560        # Spring 连接这个端口
    codec => json_lines
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "spring-log-%{+YYYY.MM.dd}"  # 日志存储的索引名
  }
}

docker-compose.yml

复制代码
# 一键启动 ELK 脚本
version: '3'

services:
  # 搜索引擎:存储日志
  elasticsearch:
    image: elasticsearch:7.17.0
    container_name: es
    ports:
      - "9200:9200"
    environment:
      - discovery.type=single-node
      - ES_JAVA_OPTS=-Xms512m -Xmx512m  # 限制内存,不卡电脑

  # 日志收集:接收Spring传来的日志
  logstash:
    image: logstash:7.17.0
    container_name: logstash
    ports:
      - "4560:4560"
    volumes:
      - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
    depends_on:
      - elasticsearch

  # 界面:查看日志
  kibana:
    image: kibana:7.17.0
    container_name: kibana
    ports:
      - "5601:5601"
    depends_on:
      - elasticsearch

运行

成功后访问浏览器,Kibana 界面:http://localhost:5601,出现下面这个页面就是成功

3.2.2 生成者项目

创建SpringBoot项目cloud-provider。

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com</groupId>
    <artifactId>cloud-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cloud-provider</name>
    <description>cloud-provider</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- AOP依赖:实现切面统一日志功能 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- 日志发送依赖:把Spring日志发送到本地Logstash(你的ELK环境) -->
        <dependency>
            <groupId>net.logstash.logback</groupId>
            <artifactId>logstash-logback-encoder</artifactId>
            <version>7.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

复制代码
# 生产者服务端口
server:
  port: 8081

# 服务名称(微服务标识,随便写)
spring:
  application:
    name: service-provider

logback-spring.xml

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 发送日志到 ELK Logstash -->
    <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>localhost:4560</destination>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder" />
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="LOGSTASH"/>
    </root>

</configuration>

package com.cloudprovider.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

// AOP 切面:自动拦截所有接口,打印日志
@Aspect
@Component
@Slf4j
public class LogAop {

    // 拦截所有Controller接口
    @Around("execution(* com.cloudprovider.controller..*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        // 打印请求方法名
        log.info("【AOP】请求接口:{}", joinPoint.getSignature().getName());

        // 执行原方法
        Object result = joinPoint.proceed();

        // 打印返回结果
        log.info("【AOP】返回结果:{}", result);
        return result;
    }
}

package com.cloudprovider.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

// 生产者接口
@RestController
@RequestMapping("/provider")
public class ProviderController {

    // 提供给消费者调用的接口
    @GetMapping("/hello")
    public String hello() {
        return "我是生产者,你调用成功啦!";
    }
}

3.2.3 消费者项目

创建SpringBoot项目cloud-consumer

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com</groupId>
    <artifactId>cloud-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cloud-consumer</name>
    <description>cloud-consumer</description>

    <properties>
        <spring-cloud.version>2021.0.8</spring-cloud.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- AOP 日志 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- OpenFeign 调用生产者 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!-- 日志发送到 ELK -->
        <dependency>
            <groupId>net.logstash.logback</groupId>
            <artifactId>logstash-logback-encoder</artifactId>
            <version>7.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <!-- Spring Cloud 依赖管理 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

复制代码
# 消费者端口
server:
  port: 8082

# 消费者服务名(标识作用,随便写)
spring:
  application:
    name: service-consumer

logback-spring.xml

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">

    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 发送日志到 ELK Logstash -->
    <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>localhost:4560</destination>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder" />
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="LOGSTASH"/>
    </root>

</configuration>

package com.cloudconsumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients  // 开启Feign调用
@SpringBootApplication
public class CloudConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudConsumerApplication.class, args);
    }

}

package com.cloudconsumer.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

// AOP 切面:自动拦截所有接口,打印日志
@Aspect
@Component
@Slf4j

public class LogAop {

    // 拦截所有Controller接口
    @Around("execution(* com.cloudconsumer.controller..*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        // 打印请求方法名
        log.info("【AOP】请求接口:{}", joinPoint.getSignature().getName());

        // 执行原方法
        Object result = joinPoint.proceed();

        // 打印返回结果
        log.info("【AOP】返回结果:{}", result);
        return result;
    }
}

package com.cloudconsumer.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

// AOP 切面:自动拦截所有接口,打印日志
@Aspect
@Component
@Slf4j

public class LogAop {

    // 拦截所有Controller接口
    @Around("execution(* com.cloudconsumer.controller..*(..))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        // 打印请求方法名
        log.info("【AOP】请求接口:{}", joinPoint.getSignature().getName());

        // 执行原方法
        Object result = joinPoint.proceed();

        // 打印返回结果
        log.info("【AOP】返回结果:{}", result);
        return result;
    }
}

package com.cloudconsumer.controller;

import com.cloudconsumer.feign.ProviderFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConsumerController {

    // 注入Feign客户端
    @Autowired
    private ProviderFeign providerFeign;

    // 消费者调用生产者
    @GetMapping("/call")
    public String call() {
        return "消费者调用生产者:" + providerFeign.hello();
    }
}

3.2.4 运行

依次运行ELK、生成者项目、消费者项目。

浏览器访问接口

查看日志,此外项目的控制台也有日志输出

3.3 面试题

相关推荐
Volunteer Technology1 小时前
SpringAI Chat Client (四)
人工智能·spring
ShiJiuD6668889992 小时前
springboot基础篇
java·spring boot·spring
敲敲千反田2 小时前
Spring AI
java·人工智能·spring
拽着尾巴的鱼儿3 小时前
spring 动态代理
java·后端·spring
云烟成雨TD3 小时前
Spring AI Alibaba 1.x 系列【52】Interrupts 中断机制:案例演示
java·人工智能·spring
云烟成雨TD4 小时前
Spring AI Alibaba 1.x 系列【51】Graph 整体运行全流程
java·人工智能·spring
_waylau5 小时前
“Java+AI全栈工程师”问答02:Spring Boot 自动配置原理
java·开发语言·spring boot·后端·spring
Ting-yu5 小时前
SpringCloud快速入门(4)---- nacos安装与使用
java·spring·spring cloud
霸道流氓气质6 小时前
Spring AI ChatMemory 对话记忆配置JDBC方式到Mysql数据库实战示例与原理讲解
数据库·人工智能·spring
Java面试题总结6 小时前
我删掉了项目里 80% 的 try-catch,系统反而更稳了
spring