MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java微服务架构下的Spring Boot实践

2022-07-033.7k 阅读

Java 微服务架构基础

微服务架构概念

在传统的单体应用架构中,整个应用被打包成一个整体,所有的功能模块都运行在同一个进程中。这种架构在应用规模较小时易于开发、部署和维护,但随着业务的增长,单体应用会变得臃肿不堪,一个小的改动可能会影响到整个系统,并且难以进行独立的扩展。

微服务架构应运而生,它将一个大型应用拆分成多个小型、独立的服务,每个服务都专注于单一的业务功能,这些服务可以独立开发、部署和扩展。每个微服务都有自己独立的数据库、运行时环境等,通过轻量级的通信机制(如 RESTful API)进行交互。

微服务架构优势

  1. 独立开发与部署:不同的团队可以独立负责不同的微服务开发,开发过程互不干扰。并且可以针对每个微服务进行独立的部署,当某个微服务需要更新时,不会影响其他微服务的运行。例如,电商系统中,商品服务和订单服务可以由不同团队开发和部署,商品服务的代码更新不会影响订单服务的正常使用。
  2. 可扩展性:根据业务需求,可以对特定的微服务进行水平扩展。如果订单服务在促销期间压力增大,可以增加订单服务的实例数量,而无需对整个系统进行大规模调整。
  3. 技术多样性:不同的微服务可以根据自身业务特点选择最合适的技术栈。例如,对于实时数据处理的微服务可以选择使用 Kafka 和 Scala,而对于传统的业务逻辑处理微服务可以使用 Java 和 Spring Boot。

微服务架构挑战

  1. 分布式系统复杂性:多个微服务之间通过网络进行通信,这就引入了网络延迟、网络故障等问题。例如,当一个微服务调用另一个微服务时,可能会因为网络波动导致调用失败,需要考虑重试机制、超时处理等。
  2. 数据一致性:每个微服务都可能有自己独立的数据库,在进行跨服务的业务操作时,保证数据一致性变得更加困难。例如,在电商系统中,下单操作可能涉及到库存微服务和订单微服务,需要确保库存减少和订单创建这两个操作要么都成功,要么都失败。
  3. 运维复杂度:微服务数量增多,运维的难度也随之增加。需要管理多个微服务的运行状态、监控指标、日志等。例如,当某个微服务出现故障时,需要快速定位问题所在,这就需要完善的监控和日志系统。

Spring Boot 基础

Spring Boot 简介

Spring Boot 是由 Pivotal 团队提供的全新框架,它旨在简化 Spring 应用的初始搭建以及开发过程。Spring Boot 遵循“约定优于配置”(Convention over Configuration)的原则,通过默认配置和自动配置机制,让开发者能够快速地创建一个可以运行的 Spring 应用,而无需进行大量的繁琐配置。

Spring Boot 核心特性

  1. 自动配置:Spring Boot 能够根据项目中引入的依赖,自动配置 Spring 应用的各种组件。例如,当在项目中引入 spring-boot-starter-jdbc 依赖时,Spring Boot 会自动配置数据源、JdbcTemplate 等相关组件,开发者无需手动编写大量的配置文件。
  2. 起步依赖:Spring Boot 提供了一系列的起步依赖(Starter),这些依赖是一些预定义的 Maven 或 Gradle 依赖集合。通过引入特定的起步依赖,开发者可以快速添加所需的功能。例如,spring-boot-starter-web 依赖包含了开发 Web 应用所需的 Spring MVC、Tomcat 等组件。
  3. 内嵌容器:Spring Boot 支持内嵌的 Servlet 容器,如 Tomcat、Jetty 等。这意味着开发者可以将 Spring Boot 应用打包成一个可执行的 JAR 文件,直接运行,而无需像传统的 Web 应用那样部署到外部的 Servlet 容器中。

Spring Boot 项目结构

一个典型的 Spring Boot 项目结构如下:

src/
├── main/
│   ├── java/
│   │   └── com/
│   │       └── example/
│   │           └── myproject/
│   │               ├── MyProjectApplication.java
│   │               ├── controller/
│   │               │   └── HelloController.java
│   │               ├── service/
│   │               │   └── HelloService.java
│   │               └── repository/
│   │                   └── HelloRepository.java
│   └── resources/
│       ├── application.properties
│       └── static/
│           └── index.html
│       └── templates/
│           └── welcome.html
└── test/
    └── java/
        └── com/
            └── example/
                └── myproject/
                    └── MyProjectApplicationTests.java
  • MyProjectApplication.java:Spring Boot 应用的主类,包含 main 方法,用于启动应用。
  • controller 包:存放控制器类,处理 HTTP 请求并返回响应。
  • service 包:存放业务逻辑类,处理具体的业务操作。
  • repository 包:存放数据访问层类,用于与数据库进行交互。
  • application.properties:存放应用的配置属性。
  • static 目录:存放静态资源,如 CSS、JavaScript、图片等。
  • templates 目录:存放模板文件,如 Thymeleaf 模板,用于动态生成 HTML 页面。

Spring Boot 在微服务架构中的应用

构建微服务

  1. 创建 Spring Boot 微服务项目
    • 使用 Spring Initializr(https://start.spring.io/)可以快速创建一个 Spring Boot 项目。在 Spring Initializr 页面,选择项目的构建工具(如 Maven 或 Gradle)、Spring Boot 版本、项目元数据(如 Group、Artifact)以及需要的依赖。例如,如果要创建一个提供 RESTful API 的微服务,可以选择 spring-boot-starter-web 依赖。
    • 使用命令行创建 Maven 项目:
mvn archetype:generate -DgroupId=com.example -DartifactId=my -service -DarchetypeArtifactId=maven -archetype -quickstart -DinteractiveMode=false

然后在项目的 pom.xml 文件中添加 Spring Boot 相关依赖:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring - boot - starter - parent</artifactId>
    <version>2.6.3</version>
    <relativePath/>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring - boot - starter - web</artifactId>
    </dependency>
</dependencies>
  1. 定义微服务接口
    • 以一个简单的用户微服务为例,首先定义用户实体类:
package com.example.myservice.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;

    // getters and setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
- 然后定义用户服务接口和实现类:
package com.example.myservice.service;

import com.example.myservice.model.User;

import java.util.List;

public interface UserService {
    User saveUser(User user);
    List<User> getAllUsers();
    User getUserById(Long id);
    void deleteUserById(Long id);
}
package com.example.myservice.service.impl;

import com.example.myservice.model.User;
import com.example.myservice.service.UserService;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import java.util.List;

@Service
@Transactional
public class UserServiceImpl implements UserService {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public User saveUser(User user) {
        entityManager.persist(user);
        return user;
    }

    @Override
    public List<User> getAllUsers() {
        String jpql = "SELECT u FROM User u";
        return entityManager.createQuery(jpql, User.class).getResultList();
    }

    @Override
    public User getUserById(Long id) {
        return entityManager.find(User.class, id);
    }

    @Override
    public void deleteUserById(Long id) {
        User user = entityManager.find(User.class, id);
        if (user != null) {
            entityManager.remove(user);
        }
    }
}
- 接着定义控制器类,提供 RESTful API:
package com.example.myservice.controller;

import com.example.myservice.model.User;
import com.example.myservice.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity<User> saveUser(@RequestBody User user) {
        User savedUser = userService.saveUser(user);
        return new ResponseEntity<>(savedUser, HttpStatus.CREATED);
    }

    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        List<User> users = userService.getAllUsers();
        return new ResponseEntity<>(users, HttpStatus.OK);
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        if (user != null) {
            return new ResponseEntity<>(user, HttpStatus.OK);
        } else {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<HttpStatus> deleteUserById(@PathVariable Long id) {
        userService.deleteUserById(id);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}
  1. 配置微服务
    • application.properties 文件中可以配置各种属性,如数据库连接属性:
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.database - platform=org.hibernate.dialect.MySQL5Dialect
- 也可以配置服务器端口:
server.port=8081

服务间通信

  1. RESTful API 通信
    • 在微服务架构中,RESTful API 是最常用的服务间通信方式。例如,假设有一个订单微服务需要调用用户微服务获取用户信息。在订单微服务中,可以使用 RestTemplateWebClient(Spring 5 引入)来发起 HTTP 请求。
    • 使用 RestTemplate
package com.example.orderservice.service;

import com.example.myservice.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class OrderService {
    private final RestTemplate restTemplate;

    @Autowired
    public OrderService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public User getUserById(Long userId) {
        String url = "http://user - service/users/" + userId;
        ResponseEntity<User> responseEntity = restTemplate.getForEntity(url, User.class);
        return responseEntity.getBody();
    }
}
- 使用 `WebClient`:
package com.example.orderservice.service;

import com.example.myservice.model.User;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import org.springframework.web.reactive.function.client.WebClient;

@Service
public class OrderService {
    private final WebClient webClient;

    public OrderService(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.baseUrl("http://user - service").build();
    }

    public Mono<User> getUserById(Long userId) {
        String url = "/users/" + userId;
        return webClient.get().uri(url).retrieve().bodyToMono(User.class);
    }
}
  1. 消息队列通信
    • 消息队列可以解耦微服务之间的直接依赖,提高系统的异步处理能力和可靠性。例如,可以使用 RabbitMQ 作为消息队列。
    • 首先在项目中添加 RabbitMQ 依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring - boot - starter - amqp</artifactId>
</dependency>
- 配置 RabbitMQ 连接属性:
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
- 发送消息:
package com.example.myservice.sender;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MessageSender {
    private static final String QUEUE_NAME = "my - queue";
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendMessage(String message) {
        rabbitTemplate.convertAndSend(QUEUE_NAME, message);
    }
}
- 接收消息:
package com.example.myservice.receiver;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class MessageReceiver {
    @RabbitListener(queues = "my - queue")
    public void handleMessage(String message) {
        System.out.println("Received message: " + message);
    }
}

服务治理

  1. 服务注册与发现
    • Eureka 是 Netflix 开发的服务注册与发现组件,Spring Boot 可以很方便地集成 Eureka。
    • 首先创建 Eureka 服务器:
      • 在项目 pom.xml 文件中添加 Eureka Server 依赖:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring - cloud - starter - netflix - eureka - server</artifactId>
</dependency>
    - 在主类上添加 `@EnableEurekaServer` 注解:
package com.example.eurekaserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}
    - 在 `application.properties` 文件中配置 Eureka Server:
server.port=8761
eureka.instance.hostname=localhost
eureka.client.register - with - eureka=false
eureka.client.fetch - registry=false
- 然后将微服务注册到 Eureka Server:
    - 在微服务项目 `pom.xml` 文件中添加 Eureka Client 依赖:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring - cloud - starter - netflix - eureka - client</artifactId>
</dependency>
    - 在主类上添加 `@EnableEurekaClient` 注解:
package com.example.myservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class MyServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyServiceApplication.class, args);
    }
}
    - 在 `application.properties` 文件中配置 Eureka Client:
eureka.client.service - url.defaultZone=http://localhost:8761/eureka/
  1. 负载均衡
    • Ribbon 是 Netflix 开源的客户端负载均衡器,Spring Boot 集成 Ribbon 后,微服务之间的调用可以实现负载均衡。
    • 当使用 RestTemplate 进行服务间调用时,只需要在 RestTemplate 的配置方法上添加 @LoadBalanced 注解:
package com.example.orderservice.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
- 这样,当 `OrderService` 调用 `UserService` 时,Ribbon 会从 Eureka Server 中获取 `UserService` 的多个实例列表,并根据负载均衡算法(如轮询、随机等)选择一个实例进行调用。

3. 熔断器: - Hystrix 是 Netflix 开源的熔断器组件,用于防止微服务之间的级联故障。 - 在项目 pom.xml 文件中添加 Hystrix 依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring - cloud - starter - netflix - hystrix</artifactId>
</dependency>
- 在主类上添加 `@EnableHystrix` 注解:
package com.example.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;

@SpringBootApplication
@EnableHystrix
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}
- 在需要进行熔断处理的方法上添加 `@HystrixCommand` 注解,并指定 fallback 方法:
package com.example.orderservice.service;

import com.example.myservice.model.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class OrderService {
    private final RestTemplate restTemplate;

    @Autowired
    public OrderService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @HystrixCommand(fallbackMethod = "getUserByIdFallback")
    public User getUserById(Long userId) {
        String url = "http://user - service/users/" + userId;
        return restTemplate.getForObject(url, User.class);
    }

    public User getUserByIdFallback(Long userId) {
        // 熔断后的处理逻辑,返回默认数据或错误提示
        User user = new User();
        user.setName("Default User");
        user.setEmail("default@example.com");
        return user;
    }
}

微服务的监控与日志

监控

  1. Actuator 监控
    • Spring Boot Actuator 提供了对应用程序的监控和管理端点。在项目 pom.xml 文件中添加 Actuator 依赖:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring - boot - actuator</artifactId>
</dependency>
- 默认情况下,Actuator 会暴露一些端点,如 `/health` 用于检查应用的健康状态,`/info` 用于获取应用的基本信息等。可以在 `application.properties` 文件中配置暴露更多端点:
management.endpoints.web.exposure.include=*
- 访问 `/actuator/health` 端点,可以看到应用的健康状态信息:
{
    "status": "UP"
}
  1. Metrics 监控
    • Spring Boot 集成 Micrometer 可以方便地收集应用的各种指标数据,如 CPU 使用率、内存使用率、HTTP 请求计数等。在项目 pom.xml 文件中添加 Micrometer 相关依赖,例如添加 Prometheus 支持:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring - boot - starter - actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer - registry - prometheus</artifactId>
</dependency>
- 配置 Prometheus 端点:
management.endpoints.web.exposure.include=prometheus
- 启动应用后,访问 `/actuator/prometheus` 端点可以获取 Prometheus 格式的指标数据,然后可以将这些数据发送到 Prometheus 服务器进行存储和分析,并通过 Grafana 进行可视化展示。

日志

  1. 日志配置
    • Spring Boot 默认使用 Logback 作为日志框架。在 src/main/resources 目录下创建 logback.xml 文件,可以进行详细的日志配置。例如:
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy - MM - dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender - ref ref="STDOUT"/>
    </root>
</configuration>
- 上述配置将日志输出到控制台,日志格式包含时间、线程名、日志级别、日志记录器名称和日志消息。

2. 日志收集与分析: - 对于微服务架构,集中式的日志收集和分析非常重要。可以使用 ELK(Elasticsearch、Logstash、Kibana)或 EFK(Elasticsearch、Fluentd、Kibana)堆栈来实现。 - Fluentd 可以作为日志收集器,收集各个微服务的日志并发送到 Elasticsearch。在每个微服务中配置 Fluentd 客户端,例如在 Docker 容器中运行的微服务,可以通过在容器内安装 Fluentd 并配置相应的输出插件将日志发送到 Elasticsearch。 - Elasticsearch 用于存储日志数据,Kibana 用于可视化展示和查询日志数据。通过 Kibana 的界面,可以根据各种条件(如时间范围、微服务名称、日志级别等)进行日志查询和分析,方便快速定位问题。

通过以上对 Spring Boot 在微服务架构中的实践介绍,涵盖了微服务的构建、服务间通信、服务治理以及监控与日志等方面,希望能帮助开发者更好地理解和应用 Spring Boot 来构建高效、可靠的微服务架构应用。