快速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 面试题

相关推荐
smileNicky2 小时前
Spring AI系列之基于MCP协议实现天气预报工具插件
人工智能·spring boot·spring
杰克尼2 小时前
天机学堂项目总结(day11~day12)
spring·spring cloud
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【36】FlowAgent 和 BaseAgent 抽象类
java·人工智能·spring
青槿吖2 小时前
第二篇:从复制粘贴到自定义规则!Spring Cloud Gateway 断言 + 过滤全玩法,拿捏微服务流量管控
java·spring boot·后端·spring cloud·微服务·云原生·架构
武超杰3 小时前
Spring Cloud Gateway 从入门到实战
spring cloud·gateway
人道领域3 小时前
【黑马点评日记】高并发秒杀:库存超卖与锁机制解析
java·开发语言·redis·spring·intellij-idea
Java成神之路-3 小时前
面试题:Spring事务失效场景
java·spring
云烟成雨TD4 小时前
Spring AI Alibaba 1.x 系列【37】ReactAgent 构建、执行流程分析
java·人工智能·spring