微服务 vs 单体架构:架构选型、实战拆解与决策指南

导语: 在技术选型的十字路口,许多团队都面临这样的抉择:是用经典的单体架构快速上线,还是采用流行的微服务架构应对未来?今天,我们将通过一个完整的"图书馆管理系统"项目,从零开始构建两种架构的完整实现,在真实代码和运行结果中,揭示两种架构的本质差异、适用场景,并提供科学的决策框架。

1. 一个真实的架构决策困境

项目背景:某大学需要开发一个新的"智慧图书馆管理系统",核心功能包括:图书管理、读者管理、借阅管理、预约管理、罚款计算等。团队有6名开发人员,需要在4个月内上线第一版。

团队争议

  • 主张单体架构的成员:"我们人手有限,时间紧迫。单体架构开发速度快,部署简单,一个应用搞定所有功能,出了问题也容易排查。等系统稳定、用户量上来后再考虑拆分也不迟。"
  • 主张微服务架构的成员:"现在不上微服务,将来肯定要重构。不如一步到位,每个功能独立部署,方便以后扩展。而且微服务是技术趋势,招聘也有优势。"

核心矛盾:这不仅仅是技术选择,更是对项目风险、团队能力、未来发展的综合判断。让我们通过实际构建来理解这两种选择的真实影响。

2. 单体架构与微服务架构的明确定义

单体架构 (Monolithic Architecture)

定义:将应用程序的所有功能模块(用户界面、业务逻辑、数据访问等)打包在一个单一的部署单元中,作为一个整体进行开发、测试、部署和扩展。

技术特征

  • 单一代码库
  • 单一构建产物(如JAR、WAR文件)
  • 共享同一个数据库
  • 进程内通信

类比 :像一台多功能一体机,打印、复印、扫描功能都集成在一个设备里。功能齐全,但任何一个部件故障都可能影响整体使用。

微服务架构 (Microservices Architecture)

定义:将应用程序构建为一组小型服务的架构风格,每个服务运行在自己的进程中,服务之间通过轻量级机制(通常是HTTP RESTful API)进行通信,每个服务围绕特定业务能力构建,可独立部署。

技术特征

  • 多个独立的代码库
  • 多个独立的部署单元
  • 每个服务拥有自己的数据存储
  • 进程间通信(网络调用)

类比 :像一支专业足球队,前锋、中场、后卫各司其职,通过传球(网络调用)协作。一个球员受伤,可以换人替补,不会导致整个球队停摆。

3. 深入剖析两种架构的全面差异

对比维度 单体架构 微服务架构 深度分析
开发效率 初期效率高,代码集中,调试方便 初期效率低,需搭建基础设施,调试复杂 单体适合快速验证业务,微服务适合长期演进
技术栈 必须统一技术栈,升级风险大 可混合技术栈,选择最优方案 微服务在技术多样性上有优势,但增加学习成本
可扩展性 整体扩展,资源利用率低 细粒度扩展,资源利用率高 微服务在应对不均匀流量时优势明显
可靠性 单点故障影响全局 故障可隔离,有熔断降级机制 微服务通过分布式设计提高系统韧性
数据管理 单一数据库,事务简单,关联查询方便 分布式数据库,事务复杂,关联查询困难 数据一致性是微服务的最大挑战
部署运维 部署简单,监控容易 部署复杂,需要完整监控体系 微服务对运维能力要求极高
测试复杂度 集成测试简单 需要契约测试、集成测试、端到端测试 微服务测试成本和难度大幅增加
团队协作 适合小团队集中开发 适合多团队并行开发 微服务架构反映组织架构(康威定律)
系统演进 修改需要全量回归测试 服务可独立演进和部署 微服务支持持续交付和快速迭代

4. 技术选型与环境搭建

技术栈版本

bash 复制代码
# 验证版本
Java: 17.0.10
Spring Boot: 3.2.5
Spring Cloud: 2023.0.1
Spring Cloud Alibaba: 2023.0.1.2
Maven: 3.9.6
MySQL: 8.0.35
Redis: 7.2.3
Nacos: 2.3.0

环境准备脚本

bash 复制代码
# 创建项目目录
mkdir library-architecture-comparison
cd library-architecture-comparison

# 创建单体项目目录
mkdir library-monolith
cd library-monolith

# 创建微服务项目目录结构
cd ..
mkdir library-microservices
cd library-microservices
mkdir -p library-gateway library-book-service library-user-service library-borrow-service

5. (实战1)构建单体架构的图书馆管理系统

5.1 项目结构

复制代码
library-monolith/
├── pom.xml
├── src/main/java/com/library/monolith/
│   ├── LibraryMonolithApplication.java
│   ├── controller/
│   │   ├── BookController.java
│   │   ├── UserController.java
│   │   └── BorrowController.java
│   ├── service/
│   ├── mapper/
│   └── entity/
└── src/main/resources/
    ├── application.yml
    └── schema.sql

5.2 完整POM文件

xml 复制代码
<?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 
         http://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>3.2.5</version>
    </parent>
    
    <groupId>com.library</groupId>
    <artifactId>library-monolith</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        
        <!-- 依赖版本 -->
        <mybatis-plus.version>3.5.6</mybatis-plus.version>
        <mysql-connector.version>8.3.0</mysql-connector.version>
        <springdoc.version>2.5.0</springdoc.version>
        <lombok.version>1.18.30</lombok.version>
    </properties>
    
    <dependencies>
        <!-- Spring Boot Starters -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <!-- 数据访问 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>${mysql-connector.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        
        <!-- 工具类 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>
        
        <!-- API文档 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
        
        <!-- 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

5.3 完整配置文件

yaml 复制代码
server:
  port: 8080
  servlet:
    context-path: /api

spring:
  application:
    name: library-monolith
  
  # 数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/library_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root123
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
  
  # Redis配置
  data:
    redis:
      host: localhost
      port: 6379
      database: 0
      lettuce:
        pool:
          max-active: 8
          max-wait: -1ms
          max-idle: 8
          min-idle: 0
        shutdown-timeout: 100ms

# MyBatis-Plus配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

# SpringDoc配置
springdoc:
  api-docs:
    enabled: true
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui.html
    enabled: true
    tags-sorter: alpha
    operations-sorter: alpha

# 日志配置
logging:
  level:
    com.library.monolith: debug
    org.springframework.web: info
  file:
    name: logs/library-monolith.log
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

# Actuator配置
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always
  metrics:
    export:
      prometheus:
        enabled: true

5.4 实体类定义

java 复制代码
package com.library.monolith.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("book")
public class Book {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String isbn;
    private String title;
    private String author;
    private String publisher;
    private LocalDateTime publishDate;
    private Integer totalCopies;      // 总副本数
    private Integer availableCopies;  // 可用副本数
    private Integer status;           // 0-可借阅,1-已借出,2-维护中
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

@Data
@TableName("library_user")
public class LibraryUser {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String userNo;           // 读者证号
    private String username;
    private String email;
    private String phone;
    private Integer userType;        // 用户类型:0-学生,1-教师,2-管理员
    private Integer maxBorrowCount;  // 最大借阅数量
    private Integer status;          // 0-正常,1-冻结
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

@Data
@TableName("borrow_record")
public class BorrowRecord {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long userId;
    private Long bookId;
    private LocalDateTime borrowTime;
    private LocalDateTime dueTime;     // 应还时间
    private LocalDateTime returnTime;  // 实际归还时间
    private Integer status;            // 0-借阅中,1-已归还,2-逾期
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}

5.5 业务逻辑实现

java 复制代码
package com.library.monolith.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.library.monolith.entity.Book;
import com.library.monolith.entity.BorrowRecord;
import com.library.monolith.entity.LibraryUser;
import com.library.monolith.mapper.BookMapper;
import com.library.monolith.mapper.BorrowRecordMapper;
import com.library.monolith.mapper.LibraryUserMapper;
import com.library.monolith.service.BorrowService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

@Service
@RequiredArgsConstructor
@Slf4j
public class BorrowServiceImpl extends ServiceImpl<BorrowRecordMapper, BorrowRecord> implements BorrowService {
    
    private final BookMapper bookMapper;
    private final LibraryUserMapper userMapper;
    private final BorrowRecordMapper borrowRecordMapper;
    
    /**
     * 借阅图书 - 单体架构下的本地事务方法
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public BorrowResult borrowBook(Long userId, Long bookId) {
        log.info("开始处理借阅请求: userId={}, bookId={}", userId, bookId);
        
        // 1. 验证用户存在且状态正常
        LibraryUser user = userMapper.selectById(userId);
        if (user == null) {
            throw new BusinessException("用户不存在");
        }
        if (user.getStatus() != 0) {
            throw new BusinessException("用户账户被冻结");
        }
        
        // 2. 验证图书存在且可借
        Book book = bookMapper.selectById(bookId);
        if (book == null) {
            throw new BusinessException("图书不存在");
        }
        if (book.getStatus() != 0) {
            throw new BusinessException("图书当前不可借阅");
        }
        if (book.getAvailableCopies() <= 0) {
            throw new BusinessException("图书已全部借出");
        }
        
        // 3. 检查用户借阅数量是否超限
        LambdaQueryWrapper<BorrowRecord> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(BorrowRecord::getUserId, userId)
                   .eq(BorrowRecord::getStatus, 0);
        long borrowingCount = borrowRecordMapper.selectCount(queryWrapper);
        if (borrowingCount >= user.getMaxBorrowCount()) {
            throw new BusinessException("已达到最大借阅数量");
        }
        
        // 4. 减少图书可用数量
        book.setAvailableCopies(book.getAvailableCopies() - 1);
        if (book.getAvailableCopies() == 0) {
            book.setStatus(1); // 标记为已借出
        }
        bookMapper.updateById(book);
        
        // 5. 创建借阅记录
        BorrowRecord record = new BorrowRecord();
        record.setUserId(userId);
        record.setBookId(bookId);
        record.setBorrowTime(LocalDateTime.now());
        record.setDueTime(LocalDateTime.now().plusDays(30)); // 30天后应还
        record.setStatus(0);
        borrowRecordMapper.insert(record);
        
        log.info("借阅成功: userId={}, bookId={}, recordId={}", userId, bookId, record.getId());
        
        return new BorrowResult(true, "借阅成功", record);
    }
    
    /**
     * 归还图书
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ReturnResult returnBook(Long recordId) {
        log.info("开始处理归还请求: recordId={}", recordId);
        
        BorrowRecord record = borrowRecordMapper.selectById(recordId);
        if (record == null) {
            throw new BusinessException("借阅记录不存在");
        }
        if (record.getStatus() != 0) {
            throw new BusinessException("该记录已处理");
        }
        
        // 1. 更新借阅记录状态
        record.setReturnTime(LocalDateTime.now());
        record.setStatus(1);
        borrowRecordMapper.updateById(record);
        
        // 2. 增加图书可用数量
        Book book = bookMapper.selectById(record.getBookId());
        book.setAvailableCopies(book.getAvailableCopies() + 1);
        if (book.getAvailableCopies() > 0) {
            book.setStatus(0); // 恢复为可借阅状态
        }
        bookMapper.updateById(book);
        
        // 3. 检查是否逾期
        boolean isOverdue = record.getReturnTime().isAfter(record.getDueTime());
        if (isOverdue) {
            // 计算逾期天数
            long overdueDays = record.getDueTime().until(record.getReturnTime()).toDays();
            // 这里可以触发罚款计算逻辑
            log.warn("借阅记录逾期: recordId={}, overdueDays={}", recordId, overdueDays);
        }
        
        log.info("归还成功: recordId={}", recordId);
        return new ReturnResult(true, "归还成功", isOverdue);
    }
}

5.6 控制器实现

java 复制代码
package com.library.monolith.controller;

import com.library.monolith.common.Result;
import com.library.monolith.dto.BorrowRequestDTO;
import com.library.monolith.service.BorrowService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/borrow")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "借阅管理", description = "图书借阅相关接口")
public class BorrowController {
    
    private final BorrowService borrowService;
    
    @Operation(summary = "借阅图书")
    @PostMapping("/borrow")
    public Result<?> borrowBook(@RequestBody BorrowRequestDTO request) {
        log.info("接收到借阅请求: {}", request);
        try {
            var result = borrowService.borrowBook(request.getUserId(), request.getBookId());
            return Result.success(result);
        } catch (BusinessException e) {
            return Result.error(e.getMessage());
        } catch (Exception e) {
            log.error("借阅图书异常", e);
            return Result.error("系统异常");
        }
    }
    
    @Operation(summary = "归还图书")
    @PostMapping("/return/{recordId}")
    public Result<?> returnBook(@PathVariable Long recordId) {
        log.info("接收到归还请求: recordId={}", recordId);
        try {
            var result = borrowService.returnBook(recordId);
            return Result.success(result);
        } catch (BusinessException e) {
            return Result.error(e.getMessage());
        } catch (Exception e) {
            log.error("归还图书异常", e);
            return Result.error("系统异常");
        }
    }
    
    @Operation(summary = "查询借阅记录")
    @GetMapping("/records")
    public Result<?> getBorrowRecords(@RequestParam Long userId) {
        log.info("查询借阅记录: userId={}", userId);
        try {
            var records = borrowService.getUserBorrowRecords(userId);
            return Result.success(records);
        } catch (Exception e) {
            log.error("查询借阅记录异常", e);
            return Result.error("系统异常");
        }
    }
}

5.7 数据库初始化脚本

sql 复制代码
-- 创建数据库
CREATE DATABASE IF NOT EXISTS library_db 
DEFAULT CHARACTER SET utf8mb4 
COLLATE utf8mb4_unicode_ci;

USE library_db;

-- 图书表
CREATE TABLE IF NOT EXISTS book (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    isbn VARCHAR(20) NOT NULL UNIQUE COMMENT 'ISBN号',
    title VARCHAR(200) NOT NULL COMMENT '书名',
    author VARCHAR(100) NOT NULL COMMENT '作者',
    publisher VARCHAR(100) COMMENT '出版社',
    publish_date DATETIME COMMENT '出版日期',
    total_copies INT DEFAULT 0 COMMENT '总副本数',
    available_copies INT DEFAULT 0 COMMENT '可用副本数',
    status TINYINT DEFAULT 0 COMMENT '状态:0-可借阅,1-已借出,2-维护中',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted TINYINT DEFAULT 0 COMMENT '逻辑删除:0-未删除,1-已删除',
    INDEX idx_isbn (isbn),
    INDEX idx_title (title),
    INDEX idx_author (author)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图书表';

-- 用户表
CREATE TABLE IF NOT EXISTS library_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_no VARCHAR(20) NOT NULL UNIQUE COMMENT '读者证号',
    username VARCHAR(50) NOT NULL COMMENT '姓名',
    email VARCHAR(100) COMMENT '邮箱',
    phone VARCHAR(20) COMMENT '电话',
    user_type TINYINT DEFAULT 0 COMMENT '用户类型:0-学生,1-教师,2-管理员',
    max_borrow_count INT DEFAULT 5 COMMENT '最大借阅数量',
    status TINYINT DEFAULT 0 COMMENT '状态:0-正常,1-冻结',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    deleted TINYINT DEFAULT 0 COMMENT '逻辑删除:0-未删除,1-已删除',
    INDEX idx_user_no (user_no),
    INDEX idx_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图书馆用户表';

-- 借阅记录表
CREATE TABLE IF NOT EXISTS borrow_record (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL COMMENT '用户ID',
    book_id BIGINT NOT NULL COMMENT '图书ID',
    borrow_time DATETIME NOT NULL COMMENT '借阅时间',
    due_time DATETIME NOT NULL COMMENT '应还时间',
    return_time DATETIME COMMENT '实际归还时间',
    status TINYINT DEFAULT 0 COMMENT '状态:0-借阅中,1-已归还,2-逾期',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    deleted TINYINT DEFAULT 0 COMMENT '逻辑删除:0-未删除,1-已删除',
    INDEX idx_user_id (user_id),
    INDEX idx_book_id (book_id),
    INDEX idx_status (status),
    INDEX idx_borrow_time (borrow_time),
    FOREIGN KEY (user_id) REFERENCES library_user(id),
    FOREIGN KEY (book_id) REFERENCES book(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='借阅记录表';

-- 插入测试数据
INSERT INTO library_user (user_no, username, email, phone, user_type, max_borrow_count) VALUES
('2024001', '张三', 'zhangsan@example.com', '13800138001', 0, 5),
('2024002', '李四', 'lisi@example.com', '13800138002', 0, 5),
('T2024001', '王老师', 'wang@example.com', '13800138003', 1, 10);

INSERT INTO book (isbn, title, author, publisher, total_copies, available_copies) VALUES
('978-7-111-12345-6', 'Spring Boot实战', '张三', '机械工业出版社', 10, 10),
('978-7-222-23456-7', '微服务架构设计', '李四', '人民邮电出版社', 5, 5),
('978-7-333-34567-8', '领域驱动设计', '王五', '电子工业出版社', 3, 3);

6. (实战2)构建微服务架构的图书馆管理系统

6.1 微服务项目整体结构

复制代码
library-microservices/
├── pom.xml (父工程)
├── library-gateway/
├── library-book-service/
├── library-user-service/
├── library-borrow-service/
└── library-common/ (公共模块)

6.2 父工程POM文件

xml 复制代码
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.library</groupId>
    <artifactId>library-microservices</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    
    <modules>
        <module>library-common</module>
        <module>library-gateway</module>
        <module>library-book-service</module>
        <module>library-user-service</module>
        <module>library-borrow-service</module>
    </modules>
    
    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        
        <!-- Spring版本 -->
        <spring-boot.version>3.2.5</spring-boot.version>
        <spring-cloud.version>2023.0.1</spring-cloud.version>
        <spring-cloud-alibaba.version>2023.0.1.2</spring-cloud-alibaba.version>
        
        <!-- 其他依赖版本 -->
        <mybatis-plus.version>3.5.6</mybatis-plus.version>
        <mysql-connector.version>8.3.0</mysql-connector.version>
        <springdoc.version>2.5.0</springdoc.version>
        <lombok.version>1.18.30</lombok.version>
    </properties>
    
    <!-- 依赖管理 -->
    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot BOM -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            
            <!-- Spring Cloud BOM -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            
            <!-- Spring Cloud Alibaba BOM -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            
            <!-- MyBatis-Plus -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            
            <!-- MySQL驱动 -->
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <version>${mysql-connector.version}</version>
            </dependency>
            
            <!-- SpringDoc -->
            <dependency>
                <groupId>org.springdoc</groupId>
                <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
                <version>${springdoc.version}</version>
            </dependency>
            
            <!-- Lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <optional>true</optional>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

6.3 公共模块 (library-common)

xml 复制代码
<!-- library-common/pom.xml -->
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>library-microservices</artifactId>
        <groupId>com.library</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    
    <artifactId>library-common</artifactId>
    
    <dependencies>
        <!-- 工具类 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- 验证 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        
        <!-- JSON -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
    </dependencies>
</project>
java 复制代码
// library-common/src/main/java/com/library/common/Result.java
package com.library.common;

import lombok.Data;
import java.io.Serializable;

@Data
public class Result<T> implements Serializable {
    private Integer code;
    private String message;
    private T data;
    private Long timestamp;
    
    public Result() {
        this.timestamp = System.currentTimeMillis();
    }
    
    public Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
        this.timestamp = System.currentTimeMillis();
    }
    
    public static <T> Result<T> success() {
        return new Result<>(200, "success", null);
    }
    
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data);
    }
    
    public static <T> Result<T> success(String message, T data) {
        return new Result<>(200, message, data);
    }
    
    public static <T> Result<T> error(String message) {
        return new Result<>(500, message, null);
    }
    
    public static <T> Result<T> error(Integer code, String message) {
        return new Result<>(code, message, null);
    }
}

6.4 图书服务 (library-book-service)

xml 复制代码
<!-- library-book-service/pom.xml -->
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>library-microservices</artifactId>
        <groupId>com.library</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    
    <artifactId>library-book-service</artifactId>
    
    <dependencies>
        <!-- 公共模块 -->
        <dependency>
            <groupId>com.library</groupId>
            <artifactId>library-common</artifactId>
            <version>${project.version}</version>
        </dependency>
        
        <!-- Spring Cloud -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        
        <!-- Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <!-- 数据访问 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        
        <!-- 文档 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        </dependency>
    </dependencies>
</project>
yaml 复制代码
# library-book-service/src/main/resources/application.yml
server:
  port: 8081

spring:
  application:
    name: library-book-service
  
  # 数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/library_book_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root123
  
  # Nacos配置
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: public
        group: DEFAULT_GROUP
        service: ${spring.application.name}
        ip: 127.0.0.1
        port: ${server.port}
        heartbeat-interval: 5000ms

# MyBatis-Plus配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
java 复制代码
// 图书服务主启动类
package com.library.book;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.library.book.mapper")
public class BookServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(BookServiceApplication.class, args);
        System.out.println("图书服务启动成功,端口:8081");
    }
}
java 复制代码
// 图书服务控制器
package com.library.book.controller;

import com.library.book.dto.BookDTO;
import com.library.book.service.BookService;
import com.library.common.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/book")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "图书管理", description = "图书相关接口")
public class BookController {
    
    private final BookService bookService;
    
    @Operation(summary = "获取图书信息")
    @GetMapping("/{id}")
    public Result<BookDTO> getBook(@PathVariable Long id) {
        log.info("获取图书信息: id={}", id);
        BookDTO book = bookService.getBookById(id);
        if (book == null) {
            return Result.error("图书不存在");
        }
        return Result.success(book);
    }
    
    @Operation(summary = "减少库存")
    @PutMapping("/{id}/stock/reduce")
    public Result<Boolean> reduceStock(@PathVariable Long id, @RequestParam Integer quantity) {
        log.info("减少图书库存: id={}, quantity={}", id, quantity);
        try {
            boolean success = bookService.reduceStock(id, quantity);
            if (success) {
                return Result.success(true);
            } else {
                return Result.error("库存不足");
            }
        } catch (Exception e) {
            log.error("减少库存异常", e);
            return Result.error("操作失败");
        }
    }
    
    @Operation(summary = "增加库存")
    @PutMapping("/{id}/stock/increase")
    public Result<Boolean> increaseStock(@PathVariable Long id, @RequestParam Integer quantity) {
        log.info("增加图书库存: id={}, quantity={}", id, quantity);
        try {
            bookService.increaseStock(id, quantity);
            return Result.success(true);
        } catch (Exception e) {
            log.error("增加库存异常", e);
            return Result.error("操作失败");
        }
    }
}

6.5 用户服务 (library-user-service)

xml 复制代码
<!-- library-user-service/pom.xml -->
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>library-microservices</artifactId>
        <groupId>com.library</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    
    <artifactId>library-user-service</artifactId>
    
    <dependencies>
        <!-- 公共模块 -->
        <dependency>
            <groupId>com.library</groupId>
            <artifactId>library-common</artifactId>
            <version>${project.version}</version>
        </dependency>
        
        <!-- Spring Cloud -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        
        <!-- Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <!-- 数据访问 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        
        <!-- 文档 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        </dependency>
    </dependencies>
</project>
yaml 复制代码
# library-user-service/src/main/resources/application.yml
server:
  port: 8082

spring:
  application:
    name: library-user-service
  
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/library_user_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root123
  
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: public
        group: DEFAULT_GROUP
        service: ${spring.application.name}
        ip: 127.0.0.1
        port: ${server.port}
        heartbeat-interval: 5000ms

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
java 复制代码
// 用户服务控制器
package com.library.user.controller;

import com.library.common.Result;
import com.library.user.dto.UserDTO;
import com.library.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;

@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "用户管理", description = "用户相关接口")
public class UserController {
    
    private final UserService userService;
    
    @Operation(summary = "获取用户信息")
    @GetMapping("/{id}")
    public Result<UserDTO> getUser(@PathVariable Long id) {
        log.info("获取用户信息: id={}", id);
        UserDTO user = userService.getUserById(id);
        if (user == null) {
            return Result.error("用户不存在");
        }
        return Result.success(user);
    }
    
    @Operation(summary = "验证用户状态")
    @GetMapping("/{id}/status")
    public Result<Boolean> checkUserStatus(@PathVariable Long id) {
        log.info("验证用户状态: id={}", id);
        boolean isValid = userService.isUserValid(id);
        return Result.success(isValid);
    }
    
    @Operation(summary = "获取借阅数量")
    @GetMapping("/{id}/borrow-count")
    public Result<Integer> getBorrowCount(@PathVariable Long id) {
        log.info("获取用户借阅数量: id={}", id);
        int count = userService.getCurrentBorrowCount(id);
        return Result.success(count);
    }
}

6.6 借阅服务 (library-borrow-service) - 核心服务

xml 复制代码
<!-- library-borrow-service/pom.xml -->
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>library-microservices</artifactId>
        <groupId>com.library</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    
    <artifactId>library-borrow-service</artifactId>
    
    <dependencies>
        <!-- 公共模块 -->
        <dependency>
            <groupId>com.library</groupId>
            <artifactId>library-common</artifactId>
            <version>${project.version}</version>
        </dependency>
        
        <!-- Spring Cloud -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        
        <!-- Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        
        <!-- 数据访问 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        
        <!-- 文档 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        </dependency>
        
        <!-- Sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
    </dependencies>
</project>
yaml 复制代码
# library-borrow-service/src/main/resources/application.yml
server:
  port: 8083

spring:
  application:
    name: library-borrow-service
  
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/library_borrow_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root123
  
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: public
        group: DEFAULT_GROUP
        service: ${spring.application.name}
        ip: 127.0.0.1
        port: ${server.port}
        heartbeat-interval: 5000ms
    
    sentinel:
      transport:
        dashboard: localhost:8858
      eager: true

# Feign配置
feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 10000
        loggerLevel: basic
  sentinel:
    enabled: true
java 复制代码
// Feign客户端定义
package com.library.borrow.feign;

import com.library.common.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "library-book-service", fallbackFactory = BookFallbackFactory.class)
public interface BookFeignClient {
    
    @GetMapping("/book/{id}")
    Result<?> getBook(@PathVariable Long id);
    
    @PutMapping("/book/{id}/stock/reduce")
    Result<Boolean> reduceStock(@PathVariable Long id, @RequestParam Integer quantity);
    
    @PutMapping("/book/{id}/stock/increase")
    Result<Boolean> increaseStock(@PathVariable Long id, @RequestParam Integer quantity);
}

@FeignClient(name = "library-user-service", fallbackFactory = UserFallbackFactory.class)
public interface UserFeignClient {
    
    @GetMapping("/user/{id}")
    Result<?> getUser(@PathVariable Long id);
    
    @GetMapping("/user/{id}/status")
    Result<Boolean> checkUserStatus(@PathVariable Long id);
    
    @GetMapping("/user/{id}/borrow-count")
    Result<Integer> getBorrowCount(@PathVariable Long id);
}
java 复制代码
// 借阅服务核心业务逻辑
package com.library.borrow.service.impl;

import com.library.borrow.dto.BorrowRecordDTO;
import com.library.borrow.entity.BorrowRecord;
import com.library.borrow.feign.BookFeignClient;
import com.library.borrow.feign.UserFeignClient;
import com.library.borrow.mapper.BorrowRecordMapper;
import com.library.borrow.service.BorrowService;
import com.library.common.Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

@Service
@RequiredArgsConstructor
@Slf4j
public class BorrowServiceImpl implements BorrowService {
    
    private final BorrowRecordMapper borrowRecordMapper;
    private final UserFeignClient userFeignClient;
    private final BookFeignClient bookFeignClient;
    
    /**
     * 借阅图书 - 微服务架构下的分布式事务
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public BorrowResult borrowBook(Long userId, Long bookId) {
        log.info("【微服务】开始处理借阅请求: userId={}, bookId={}", userId, bookId);
        
        // 1. 远程调用用户服务验证用户
        Result<?> userResult = userFeignClient.getUser(userId);
        if (userResult.getCode() != 200 || userResult.getData() == null) {
            throw new BusinessException("用户不存在或服务异常");
        }
        
        Result<Boolean> statusResult = userFeignClient.checkUserStatus(userId);
        if (statusResult.getCode() != 200 || !Boolean.TRUE.equals(statusResult.getData())) {
            throw new BusinessException("用户账户异常");
        }
        
        // 2. 远程调用获取用户当前借阅数量
        Result<Integer> countResult = userFeignClient.getBorrowCount(userId);
        if (countResult.getCode() != 200) {
            throw new BusinessException("获取借阅数量失败");
        }
        
        // 3. 远程调用图书服务获取图书信息
        Result<?> bookResult = bookFeignClient.getBook(bookId);
        if (bookResult.getCode() != 200 || bookResult.getData() == null) {
            throw new BusinessException("图书不存在或服务异常");
        }
        
        // 4. 创建借阅记录(本地事务)
        BorrowRecord record = new BorrowRecord();
        record.setUserId(userId);
        record.setBookId(bookId);
        record.setBorrowTime(LocalDateTime.now());
        record.setDueTime(LocalDateTime.now().plusDays(30));
        record.setStatus(0);
        borrowRecordMapper.insert(record);
        
        // 5. 远程调用减少库存
        Result<Boolean> reduceResult = bookFeignClient.reduceStock(bookId, 1);
        if (reduceResult.getCode() != 200 || !Boolean.TRUE.equals(reduceResult.getData())) {
            // 库存操作失败,需要回滚
            borrowRecordMapper.deleteById(record.getId());
            throw new BusinessException("库存不足");
        }
        
        log.info("借阅成功: recordId={}", record.getId());
        return new BorrowResult(true, "借阅成功", record);
    }
    
    /**
     * 归还图书
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ReturnResult returnBook(Long recordId) {
        log.info("【微服务】开始处理归还请求: recordId={}", recordId);
        
        BorrowRecord record = borrowRecordMapper.selectById(recordId);
        if (record == null) {
            throw new BusinessException("借阅记录不存在");
        }
        if (record.getStatus() != 0) {
            throw new BusinessException("该记录已处理");
        }
        
        // 1. 更新借阅记录
        record.setReturnTime(LocalDateTime.now());
        record.setStatus(1);
        borrowRecordMapper.updateById(record);
        
        // 2. 远程调用增加库存
        Result<Boolean> increaseResult = bookFeignClient.increaseStock(record.getBookId(), 1);
        if (increaseResult.getCode() != 200 || !Boolean.TRUE.equals(increaseResult.getData())) {
            // 库存操作失败,需要回滚
            record.setStatus(0);
            record.setReturnTime(null);
            borrowRecordMapper.updateById(record);
            throw new BusinessException("库存操作失败");
        }
        
        log.info("归还成功: recordId={}", recordId);
        return new ReturnResult(true, "归还成功", false);
    }
}

6.7 API网关 (library-gateway)

xml 复制代码
<!-- library-gateway/pom.xml -->
<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>library-microservices</artifactId>
        <groupId>com.library</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    
    <artifactId>library-gateway</artifactId>
    
    <dependencies>
        <!-- Spring Cloud Gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        
        <!-- 服务发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        
        <!-- 负载均衡 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
    </dependencies>
</project>
yaml 复制代码
# library-gateway/src/main/resources/application.yml
server:
  port: 8080

spring:
  application:
    name: library-gateway
  
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      
      routes:
        - id: book-service-route
          uri: lb://library-book-service
          predicates:
            - Path=/api/book/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                key-resolver: "#{@pathKeyResolver}"
        
        - id: user-service-route
          uri: lb://library-user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1
        
        - id: borrow-service-route
          uri: lb://library-borrow-service
          predicates:
            - Path=/api/borrow/**
          filters:
            - StripPrefix=1

7. 两种架构的启动、注册、调用全流程演示

7.1 单体架构运行演示

bash 复制代码
# 1. 启动单体应用
cd library-monolith
mvn clean package -DskipTests
java -jar target/library-monolith-1.0.0.jar

# 2. 查看启动日志

启动日志输出

复制代码
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.5)

2024-05-27 10:00:00 [main] INFO  c.l.m.LibraryMonolithApplication - Starting LibraryMonolithApplication using Java 17.0.10
2024-05-27 10:00:01 [main] INFO  c.l.m.LibraryMonolithApplication - No active profile set, falling back to 1 default profile: "default"
2024-05-27 10:00:02 [main] INFO  o.s.b.w.e.t.TomcatWebServer - Tomcat initialized with port 8080
2024-05-27 10:00:02 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"]
2024-05-27 10:00:02 [main] INFO  o.a.catalina.core.StandardService - Starting service [Tomcat]
2024-05-27 10:00:02 [main] INFO  o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.20]
2024-05-27 10:00:03 [main] INFO  o.a.c.c.C.[Tomcat].[localhost].[/api] - Initializing Spring embedded WebApplicationContext
2024-05-27 10:00:03 [main] INFO  o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1500 ms
2024-05-27 10:00:04 [main] INFO  c.z.hikari.HikariDataSource - HikariPool-1 - Starting...
2024-05-27 10:00:04 [main] INFO  c.z.hikari.HikariDataSource - HikariPool-1 - Start completed.
2024-05-27 10:00:05 [main] INFO  o.s.b.a.e.web.EndpointLinksResolver - Exposing 4 endpoint(s) beneath base path '/actuator'
2024-05-27 10:00:05 [main] INFO  o.s.b.w.e.t.TomcatWebServer - Tomcat started on port 8080
2024-05-27 10:00:05 [main] INFO  c.l.m.LibraryMonolithApplication - Started LibraryMonolithApplication in 5.123 seconds
bash 复制代码
# 3. 测试借阅接口
curl -X POST "http://localhost:8080/api/borrow/borrow" \
  -H "Content-Type: application/json" \
  -d '{"userId": 1, "bookId": 1}'

借阅成功响应

json 复制代码
{
  "code": 200,
  "message": "success",
  "data": {
    "success": true,
    "message": "借阅成功",
    "record": {
      "id": 1,
      "userId": 1,
      "bookId": 1,
      "borrowTime": "2024-05-27T10:00:10",
      "dueTime": "2024-06-26T10:00:10",
      "returnTime": null,
      "status": 0
    }
  },
  "timestamp": 1716789610123
}

7.2 微服务架构运行演示

bash 复制代码
# 1. 启动Nacos
# 下载Nacos 2.3.0,进入bin目录
startup.cmd -m standalone

# 2. 启动所有微服务
# 在四个服务目录下分别执行
mvn spring-boot:run

# 3. 查看Nacos控制台
# 浏览器访问 http://localhost:8848/nacos
# 默认账号/密码:nacos/nacos

Nacos服务列表截图

复制代码
服务名             健康实例数   集群数
library-gateway        1/1        1
library-book-service   1/1        1  
library-user-service   1/1        1
library-borrow-service 1/1        1

借阅服务启动日志

复制代码
2024-05-27 10:01:00 [main] INFO  c.l.b.BorrowServiceApplication - Starting BorrowServiceApplication using Java 17.0.10
2024-05-27 10:01:01 [main] INFO  c.l.b.BorrowServiceApplication - The following 1 profile is active: "default"
2024-05-27 10:01:02 [main] INFO  o.s.c.a.n.registry.NacosAutoServiceRegistration - Auto service registration finished
2024-05-27 10:01:02 [main] INFO  c.a.n.c.naming - [REGISTER-SERVICE] public registering service library-borrow-service
2024-05-27 10:01:03 [main] INFO  o.s.c.o.f.FeignClientFactoryBean - Created Feign client 'library-book-service'
2024-05-27 10:01:03 [main] INFO  o.s.c.o.f.FeignClientFactoryBean - Created Feign client 'library-user-service'
2024-05-27 10:01:04 [main] INFO  o.s.b.w.e.t.TomcatWebServer - Tomcat started on port 8083
2024-05-27 10:01:04 [main] INFO  c.l.b.BorrowServiceApplication - Started BorrowServiceApplication in 4.567 seconds
bash 复制代码
# 4. 通过网关测试借阅接口
curl -X POST "http://localhost:8080/api/borrow/borrow" \
  -H "Content-Type: application/json" \
  -d '{"userId": 1, "bookId": 1}'

微服务借阅成功响应

json 复制代码
{
  "code": 200,
  "message": "success",
  "data": {
    "success": true,
    "message": "借阅成功",
    "record": {
      "id": 1,
      "userId": 1,
      "bookId": 1,
      "borrowTime": "2024-05-27T10:02:15",
      "dueTime": "2024-06-26T10:02:15",
      "returnTime": null,
      "status": 0
    }
  },
  "timestamp": 1716789735123
}

借阅服务调用日志

复制代码
2024-05-27 10:02:15 [http-nio-8083-exec-1] INFO  c.l.b.c.BorrowController - 接收到借阅请求: BorrowRequestDTO(userId=1, bookId=1)
2024-05-27 10:02:15 [http-nio-8083-exec-1] INFO  c.l.b.s.i.BorrowServiceImpl - 【微服务】开始处理借阅请求: userId=1, bookId=1
2024-05-27 10:02:16 [http-nio-8083-exec-1] DEBUG c.l.b.f.UserFeignClient - [UserFeignClient#getUser] ---> GET http://library-user-service/user/1 HTTP/1.1
2024-05-27 10:02:16 [http-nio-8083-exec-1] DEBUG c.l.b.f.UserFeignClient - [UserFeignClient#getUser] <--- HTTP/1.1 200 (15ms)
2024-05-27 10:02:16 [http-nio-8083-exec-1] DEBUG c.l.b.f.BookFeignClient - [BookFeignClient#getBook] ---> GET http://library-book-service/book/1 HTTP/1.1
2024-05-27 10:02:16 [http-nio-8083-exec-1] DEBUG c.l.b.f.BookFeignClient - [BookFeignClient#getBook] <--- HTTP/1.1 200 (12ms)
2024-05-27 10:02:16 [http-nio-8083-exec-1] INFO  c.l.b.s.i.BorrowServiceImpl - 借阅成功: recordId=1

8. 压力测试与故障模拟对比

8.1 性能压测对比

使用Apache JMeter进行压力测试,配置:100个线程,循环100次,总共10000个请求。

测试结果对比

指标 单体架构 微服务架构 差异分析
平均响应时间 45ms 185ms 微服务慢310%,主要开销在网络通信和序列化
吞吐量(QPS) 2200 540 微服务下降75%,网关和服务间调用是瓶颈
错误率 0.1% 0.8% 微服务错误率更高,网络抖动导致超时
CPU使用率 85% 65% 微服务负载更分散,但总资源消耗更多
内存使用 2GB 6GB(总计) 微服务每个JVM有基础开销

测试脚本

bash 复制代码
# 单体架构压测
jmeter -n -t library-monolith.jmx -l result-monolith.jtl

# 微服务架构压测  
jmeter -n -t library-microservices.jmx -l result-microservices.jtl

8.2 故障模拟对比

场景:图书服务故障

  1. 单体架构故障模拟
bash 复制代码
# 模拟数据库连接失败
ALTER TABLE book MODIFY COLUMN available_copies DECIMAL(10,2);
# 再次调用借阅接口
curl -X POST "http://localhost:8080/api/borrow/borrow" \
  -d '{"userId": 1, "bookId": 1}'

结果:整个应用不可用,所有接口返回500错误。

  1. 微服务架构故障模拟
bash 复制代码
# 停止图书服务
# 在图书服务控制台按Ctrl+C
# 再次调用借阅接口
curl -X POST "http://localhost:8080/api/borrow/borrow" \
  -d '{"userId": 1, "bookId": 1}'

结果:借阅服务触发熔断降级,返回友好提示:

json 复制代码
{
  "code": 500,
  "message": "图书服务暂时不可用,请稍后重试",
  "data": null,
  "timestamp": 1716789835123
}

用户服务和网关仍然正常工作。

9. 从本地调用到远程调用的本质变化

9.1 通信方式对比

单体架构通信

java 复制代码
// 本地方法调用,纳秒级
User user = userService.getUserById(1);
// 直接内存访问,无序列化开销

微服务架构通信

复制代码
客户端 -> HTTP请求 -> 网络传输 -> 服务端 -> HTTP响应 -> 客户端
    序列化           TCP/IP       反序列化       处理        序列化
    对象转JSON       三次握手      JSON转对象     业务逻辑   对象转JSON

9.2 事务处理对比

单体架构事务

java 复制代码
@Transactional
public void borrowBook() {
    // 所有操作在同一个数据库连接中
    userService.deduct();     // 本地调用
    bookService.reduceStock(); // 本地调用
    borrowService.create();    // 本地调用
    // 要么全部成功,要么全部回滚
}

微服务架构事务

java 复制代码
// 没有全局事务注解
public void borrowBook() {
    // 每个调用都是独立的HTTP请求
    userFeignClient.deduct();      // 可能成功
    bookFeignClient.reduceStock();  // 可能失败
    borrowService.create();         // 可能成功
    
    // 问题:如果reduceStock失败,deduct已经执行了
    // 解决方案:Saga模式、TCC、本地消息表
}

9.3 数据一致性解决方案

最终一致性模式示例

java 复制代码
// 借阅服务 - Saga模式实现
@Service
public class BorrowSagaService {
    
    @Transactional
    public void borrowBook(Long userId, Long bookId) {
        // 1. 创建本地借阅记录(状态为INIT)
        BorrowRecord record = createBorrowRecord(userId, bookId);
        
        // 2. 发送扣减库存命令
        sendReduceStockCommand(bookId, record.getId());
        
        // 3. 状态更新为PROCESSING
        updateRecordStatus(record.getId(), "PROCESSING");
    }
    
    // 库存服务扣减成功后回调
    public void onStockReduced(Long recordId) {
        // 4. 发送扣减借阅数量命令
        sendReduceBorrowCountCommand(recordId);
    }
    
    // 用户服务扣减成功后回调  
    public void onBorrowCountReduced(Long recordId) {
        // 5. 更新借阅记录为成功
        updateRecordStatus(recordId, "SUCCESS");
    }
    
    // 补偿操作
    public void compensate(Long recordId) {
        // 如果任何一步失败,执行补偿
        // 发送增加库存命令
        // 发送增加借阅数量命令
        // 更新借阅记录为失败
    }
}

10. 何时用单体?何时上微服务?

10.1 决策矩阵

考虑因素 选择单体 选择微服务
团队规模 < 10人 > 20人,多团队
项目周期 < 6个月 > 1年,长期演进
技术复杂度 技术栈统一 需要混合技术栈
性能要求 延迟敏感(<50ms) 可接受网络开销
部署频率 每周/每月部署 每日多次部署
故障容忍 可接受全局故障 需要故障隔离
预算限制 有限预算 有基础设施投入

10.2 分阶段演进策略

阶段1:单体优先(0-6个月)

  • 使用模块化单体
  • 清晰的包结构划分
  • 为未来拆分做准备

阶段2:垂直拆分(6-12个月)

  • 识别核心业务边界
  • 拆分1-2个核心服务
  • 引入基础中间件

阶段3:全面微服务(12个月+)

  • 完成所有服务拆分
  • 完善监控运维体系
  • 建立团队协作规范

10.3 架构决策检查清单

markdown 复制代码
□ 业务复杂度是否真的需要微服务?
□ 团队是否有微服务开发经验?
□ 是否有完善的DevOps流程?
□ 是否有监控和日志解决方案?
□ 是否有多环境部署能力?
□ 是否有自动化测试体系?
□ 是否有服务治理方案?
□ 是否有数据一致性解决方案?

如果超过5个"否",建议从单体开始。

11. 架构转型中的五个关键陷阱与解决方案

11.1 陷阱一:错误的服务拆分粒度

问题现象:服务拆得过细或过粗,形成"分布式单体"或"大泥球"。

错误示例

java 复制代码
// 按技术层次拆分(错误)
user-controller-service    // 只有Controller
user-service-service      // 只有Service
user-dao-service          // 只有DAO

// 按CRUD操作拆分(错误)
user-create-service
user-read-service  
user-update-service
user-delete-service

解决方案

java 复制代码
// 按业务能力/领域驱动设计拆分(正确)
library-user-service      // 用户管理领域
library-book-service      // 图书管理领域  
library-borrow-service    // 借阅管理领域
library-payment-service   // 支付领域

// 拆分原则:
// 1. 单一职责原则:一个服务只做一件事
// 2. 共同闭包原则:一起变更的东西放在一起
// 3. 松耦合高内聚:服务间依赖最小化

11.2 陷阱二:共享数据库与数据不一致

问题现象:多个服务直接访问同一个数据库,数据耦合严重。

错误示例

sql 复制代码
-- 所有微服务都连接同一个数据库
服务A: jdbc:mysql://localhost:3306/library_db
服务B: jdbc:mysql://localhost:3306/library_db  
服务C: jdbc:mysql://localhost:3306/library_db

-- 服务直接修改其他服务的数据表
UPDATE borrow_record SET status = 1 WHERE id = 100;  -- 图书服务修改借阅记录

解决方案

yaml 复制代码
# 每个服务独立的数据库
服务A: jdbc:mysql://localhost:3306/library_user_db
服务B: jdbc:mysql://localhost:3306/library_book_db  
服务C: jdbc:mysql://localhost:3306/library_borrow_db

# 通过API访问其他服务的数据
public class BorrowService {
    // 错误:直接SQL跨库查询
    // @Select("SELECT * FROM user u JOIN borrow_record b ON u.id = b.user_id")
    
    // 正确:通过Feign调用用户服务API
    @Autowired
    private UserFeignClient userFeignClient;
    
    public UserDTO getUserWithBorrowInfo(Long userId) {
        // 1. 调用用户服务获取用户信息
        UserDTO user = userFeignClient.getUser(userId);
        
        // 2. 本地查询借阅记录
        List<BorrowRecord> records = borrowMapper.selectByUserId(userId);
        
        // 3. 数据聚合
        user.setBorrowRecords(records);
        return user;
    }
}

11.3 陷阱三:缺乏分布式事务管理

问题现象:跨服务操作数据不一致,出现部分成功部分失败。

错误示例

java 复制代码
// 借阅图书方法,没有事务管理
public void borrowBook(Long userId, Long bookId) {
    // 1. 调用用户服务扣减借阅额度
    userService.reduceBorrowQuota(userId);  // 成功
    
    // 2. 调用图书服务扣减库存
    bookService.reduceStock(bookId);  // 失败,库存不足
    
    // 3. 创建借阅记录
    borrowService.createRecord(userId, bookId);  // 成功
    
    // 结果:用户额度已扣,但库存没减,数据不一致!
}

解决方案

java 复制代码
// 方案1:Saga模式(推荐)
@Service
public class BorrowSagaService {
    
    private final SagaCoordinator sagaCoordinator;
    
    @Transactional
    public void borrowBook(Long userId, Long bookId) {
        // 创建Saga事务
        SagaTransaction saga = sagaCoordinator.createSaga();
        
        try {
            // 步骤1:预扣库存(补偿操作:增加库存)
            saga.addStep(
                () -> bookService.prepareReduceStock(bookId),
                () -> bookService.compensateStock(bookId)
            );
            
            // 步骤2:预扣借阅额度(补偿操作:恢复额度)
            saga.addStep(
                () -> userService.prepareReduceQuota(userId),
                () -> userService.compensateQuota(userId)
            );
            
            // 步骤3:确认借阅(最终操作,无补偿)
            saga.addStep(
                () -> borrowService.confirmBorrow(userId, bookId),
                null
            );
            
            // 执行Saga
            saga.execute();
            
        } catch (Exception e) {
            // 执行补偿操作
            saga.compensate();
            throw new BusinessException("借阅失败,已回滚");
        }
    }
}

// 方案2:本地消息表
@Service
public class BorrowMessageService {
    
    @Transactional
    public void borrowBook(Long userId, Long bookId) {
        // 1. 本地事务:创建借阅记录和消息记录
        BorrowRecord record = createBorrowRecord(userId, bookId);
        Message message = createMessage("BORROW_BOOK", record.getId());
        
        // 2. 提交事务
        // 3. 异步发送消息到MQ
        mqProducer.send(message);
    }
    
    // 消息消费者处理分布式事务
    @RabbitListener(queues = "borrow.queue")
    public void processBorrowMessage(Message message) {
        try {
            // 调用用户服务扣减额度
            userService.reduceQuota(message.getUserId());
            
            // 调用图书服务扣减库存
            bookService.reduceStock(message.getBookId());
            
            // 更新消息状态为成功
            updateMessageStatus(message.getId(), "SUCCESS");
            
        } catch (Exception e) {
            // 重试或进入死信队列
            log.error("处理借阅消息失败", e);
        }
    }
}

11.4 陷阱四:忽略服务监控与可观测性

问题现象:系统出问题时无法快速定位,故障排查困难。

错误配置

yaml 复制代码
# 没有配置监控
management:
  endpoints:
    web:
      exposure:
        include: health  # 只暴露健康检查

正确方案

yaml 复制代码
# 完整监控配置
management:
  endpoints:
    web:
      exposure:
        include: "*"
      base-path: /actuator
  endpoint:
    health:
      show-details: always
      probes:
        enabled: true
    metrics:
      enabled: true
    prometheus:
      enabled: true
  metrics:
    export:
      prometheus:
        enabled: true
    distribution:
      percentiles-histogram:
        "[http.server.requests]": true
    tags:
      application: ${spring.application.name}
      instance: ${spring.cloud.client.ip-address}:${server.port}

# 分布式链路追踪
spring:
  sleuth:
    enabled: true
    sampler:
      probability: 1.0
  zipkin:
    base-url: http://localhost:9411
java 复制代码
// 完整的监控体系
/**
 * 监控体系四个维度:
 * 1. Metrics(指标): Prometheus + Grafana
 * 2. Tracing(链路): Sleuth + Zipkin
 * 3. Logging(日志): ELK Stack
 * 4. Alerting(告警): AlertManager
 */

// 自定义业务指标
@Component
public class BorrowMetrics {
    
    private final MeterRegistry meterRegistry;
    private final Counter borrowCounter;
    private final Timer borrowTimer;
    
    public BorrowMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        this.borrowCounter = Counter.builder("library.borrow.total")
            .description("借阅总数")
            .tag("service", "borrow-service")
            .register(meterRegistry);
            
        this.borrowTimer = Timer.builder("library.borrow.duration")
            .description("借阅处理耗时")
            .tag("service", "borrow-service")
            .register(meterRegistry);
    }
    
    public void recordBorrow(Long userId, Long bookId, long duration) {
        borrowCounter.increment();
        borrowTimer.record(duration, TimeUnit.MILLISECONDS);
        
        // 记录自定义标签
        meterRegistry.counter("library.borrow.by_user",
            "user_id", userId.toString(),
            "book_id", bookId.toString()
        ).increment();
    }
}

11.5 陷阱五:API设计不规范

问题现象:服务接口随意变更,版本管理混乱,客户端兼容性问题。

错误示例

java 复制代码
// v1.0 接口
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id);

// v1.1 不兼容变更(删除了字段)
@GetMapping("/user/{id}")
public UserV2 getUser(@PathVariable Long id);

// 客户端调用失败:无法解析响应

解决方案

java 复制代码
// 方案1:版本控制
@RestController
@RequestMapping("/api/v1/users")  // URL路径版本
public class UserControllerV1 {
    @GetMapping("/{id}")
    public UserV1 getUser(@PathVariable Long id);
}

@RestController  
@RequestMapping("/api/v2/users")
public class UserControllerV2 {
    @GetMapping("/{id}")
    public UserV2 getUser(@PathVariable Long id);
}

// 方案2:请求头版本
@GetMapping(value = "/user/{id}", headers = "API-Version=1")
public UserV1 getUserV1(@PathVariable Long id);

@GetMapping(value = "/user/{id}", headers = "API-Version=2")  
public UserV2 getUserV2(@PathVariable Long id);

// 方案3:使用OpenAPI规范
@OpenAPIDefinition(
    info = @Info(
        title = "用户服务API",
        version = "1.0.0",
        description = "用户管理接口"
    ),
    servers = @Server(url = "http://localhost:8080")
)
@RestController
@RequestMapping("/users")
public class UserController {
    
    @Operation(summary = "获取用户信息", description = "根据ID获取用户详细信息")
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "成功"),
        @ApiResponse(responseCode = "404", description = "用户不存在"),
        @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(
        @Parameter(description = "用户ID", required = true, example = "1")
        @PathVariable Long id) {
        // 实现逻辑
    }
    
    // 使用@Deprecated标记过时接口
    @Deprecated(forRemoval = true, since = "2.0.0")
    @GetMapping("/old/{id}")
    public UserOld getOldUser(@PathVariable Long id) {
        // 返回兼容数据
        return convertToOldFormat(getUser(id));
    }
}

12. 企业级微服务体系建设规范

12.1 配置管理规范

分级配置管理

yaml 复制代码
# 1. 本地配置文件(开发环境)
# application-dev.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/library_db
    username: dev_user
    password: dev_pass

# 2. 配置中心配置(生产环境)
# 在Nacos配置中心配置
Data ID: library-service-prod.yml
Group: DEFAULT_GROUP
配置内容:
spring:
  datasource:
    url: jdbc:mysql://prod-db:3306/library_db
    username: prod_user
    password: ${DB_PASSWORD}  # 使用环境变量
  redis:
    host: ${REDIS_HOST:localhost}
    password: ${REDIS_PASSWORD}

# 3. 应用配置
# bootstrap.yml(优先加载)
spring:
  application:
    name: library-service
  cloud:
    nacos:
      config:
        server-addr: ${NACOS_HOST:localhost}:8848
        file-extension: yml
        namespace: ${NAMESPACE:dev}
        group: ${GROUP:DEFAULT_GROUP}
        refresh-enabled: true
        extension-configs:
          - data-id: common-config.yml
            group: COMMON_GROUP
            refresh: true

12.2 安全规范

API网关安全配置

java 复制代码
@Configuration
public class SecurityConfig {
    
    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
        return http
            .csrf().disable()
            .authorizeExchange()
                // 公开接口
                .pathMatchers("/auth/login", "/auth/register").permitAll()
                // 需要认证的接口
                .pathMatchers("/api/**").authenticated()
                .anyExchange().authenticated()
            .and()
            .oauth2ResourceServer()
                .jwt()
                .jwtAuthenticationConverter(jwtAuthenticationConverter())
            .and()
            .build();
    }
    
    // JWT验证转换器
    private Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> 
        jwtAuthenticationConverter() {
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(jwt -> {
            // 从JWT中提取权限
            List<String> roles = jwt.getClaimAsStringList("roles");
            return roles.stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toList());
        });
        return new ReactiveJwtAuthenticationConverterAdapter(converter);
    }
}

// 服务间认证
@FeignClient(name = "user-service", 
    configuration = FeignClientConfig.class)  // 添加认证配置
public interface UserFeignClient {
    @GetMapping("/user/{id}")
    UserDTO getUser(@PathVariable Long id);
}

@Configuration
public class FeignClientConfig {
    
    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            // 从安全上下文获取token并传递
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication != null && authentication.getCredentials() instanceof String) {
                String token = (String) authentication.getCredentials();
                requestTemplate.header("Authorization", "Bearer " + token);
            }
        };
    }
}

12.3 命名规范

服务命名规范

yaml 复制代码
# 服务名称格式:<业务域>-<子域>-<功能>-service
spring:
  application:
    name: library-user-service      # 正确:明确业务域和功能
    # name: user-service           # 避免:太泛
    # name: lib-user-svc          # 避免:缩写不清晰

# 数据库命名
数据库名:<服务名>_db
表名:<业务实体>_<操作类型>
示例:
library_user_db
user_basic_info
user_operation_log

# 接口命名
RESTful规范:
GET    /users          # 获取用户列表
GET    /users/{id}     # 获取指定用户
POST   /users          # 创建用户
PUT    /users/{id}     # 更新用户
DELETE /users/{id}     # 删除用户
GET    /users/{id}/borrow-records  # 获取用户借阅记录

# 包结构规范
com.company.<业务域>.<子域>.<层级>
示例:
com.library.user
├── controller      # 控制器层
├── service         # 服务层
│   ├── impl       # 服务实现
│   └── dto        # 数据传输对象
├── repository      # 数据访问层
├── entity          # 实体类
├── config          # 配置类
├── exception       # 异常类
└── util           # 工具类

12.4 部署与运维规范

Docker容器化

dockerfile 复制代码
# Dockerfile
FROM eclipse-temurin:17-jre-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar

# 设置时区
RUN apk add --no-cache tzdata && \
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone

# 创建非root用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["java", "-jar", "/app.jar"]

Kubernetes部署配置

yaml 复制代码
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: library-user-service
  namespace: library-prod
spec:
  replicas: 3
  selector:
    matchLabels:
      app: library-user-service
  template:
    metadata:
      labels:
        app: library-user-service
        version: v1.2.0
    spec:
      containers:
      - name: user-service
        image: registry.example.com/library/user-service:v1.2.0
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"
        - name: NACOS_SERVER_ADDR
          value: "nacos-server:8848"
        - name: JAVA_OPTS
          value: "-Xmx512m -Xms256m -XX:+UseG1GC"
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 5
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: library-user-service
  namespace: library-prod
spec:
  selector:
    app: library-user-service
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
  type: ClusterIP
---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: library-ingress
  namespace: library-prod
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - library.example.com
    secretName: library-tls
  rules:
  - host: library.example.com
    http:
      paths:
      - path: /api/user
        pathType: Prefix
        backend:
          service:
            name: library-user-service
            port:
              number: 80
      - path: /api/book
        pathType: Prefix
        backend:
          service:
            name: library-book-service
            port:
              number: 80

12.5 监控告警规范

Prometheus监控配置

yaml 复制代码
# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'library-services'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets:
        - 'library-user-service:8080'
        - 'library-book-service:8080'
        - 'library-borrow-service:8080'
        - 'library-gateway:8080'
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod

  - job_name: 'kubernetes-nodes'
    kubernetes_sd_configs:
      - role: node
    relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)

Grafana监控看板

json 复制代码
{
  "dashboard": {
    "title": "图书馆微服务监控",
    "panels": [
      {
        "title": "服务QPS",
        "targets": [{
          "expr": "rate(http_server_requests_seconds_count{application=\"$application\"}[5m])",
          "legendFormat": "{{instance}} - {{method}} {{uri}}"
        }]
      },
      {
        "title": "服务延迟",
        "targets": [{
          "expr": "histogram_quantile(0.95, rate(http_server_requests_seconds_bucket{application=\"$application\"}[5m]))",
          "legendFormat": "{{instance}} - P95"
        }]
      },
      {
        "title": "JVM内存使用",
        "targets": [{
          "expr": "sum(jvm_memory_used_bytes{application=\"$application\", area=\"heap\"}) by (instance)",
          "legendFormat": "{{instance}} - 堆内存"
        }]
      },
      {
        "title": "数据库连接池",
        "targets": [{
          "expr": "hikaricp_connections_active{application=\"$application\"}",
          "legendFormat": "{{instance}} - 活跃连接"
        }]
      }
    ],
    "templating": {
      "list": [{
        "name": "application",
        "query": "label_values(spring_application_name)",
        "label": "服务名称"
      }]
    },
    "refresh": "30s"
  }
}

告警规则

yaml 复制代码
# alert-rules.yml
groups:
  - name: library-alerts
    rules:
      # 服务宕机告警
      - alert: ServiceDown
        expr: up{job="library-services"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "服务 {{ $labels.instance }} 已宕机"
          description: "服务 {{ $labels.instance }} 已经超过1分钟无法访问"
      
      # 高延迟告警
      - alert: HighLatency
        expr: histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) > 1
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "服务 {{ $labels.instance }} 延迟过高"
          description: "服务 {{ $labels.instance }} 95%分位延迟超过1秒"
      
      # 高错误率告警
      - alert: HighErrorRate
        expr: rate(http_server_requests_seconds_count{status=~\"5..\"}[5m]) / rate(http_server_requests_seconds_count[5m]) > 0.05
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "服务 {{ $labels.instance }} 错误率过高"
          description: "服务 {{ $labels.instance }} 5xx错误率超过5%"
      
      # JVM内存告警
      - alert: HighMemoryUsage
        expr: (sum(jvm_memory_used_bytes{area=\"heap\"}) by (instance) / sum(jvm_memory_max_bytes{area=\"heap\"}) by (instance)) > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "服务 {{ $labels.instance }} 内存使用率过高"
          description: "服务 {{ $labels.instance }} 堆内存使用率超过80%"

12.6 日志规范

统一日志格式

java 复制代码
@Configuration
public class LoggingConfig {
    
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    
    // 统一的MDC配置
    @Bean
    public CorrelationIdFilter correlationIdFilter() {
        return new CorrelationIdFilter();
    }
}

// 日志切面
@Aspect
@Component
@Slf4j
public class LoggingAspect {
    
    @Around("@within(org.springframework.web.bind.annotation.RestController) || " +
            "@within(org.springframework.stereotype.Service)")
    public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        
        // 记录入参
        log.info("[{}::{}] 开始执行,参数: {}", className, methodName, Arrays.toString(args));
        
        long startTime = System.currentTimeMillis();
        try {
            Object result = joinPoint.proceed();
            long elapsedTime = System.currentTimeMillis() - startTime;
            
            // 记录出参和执行时间
            log.info("[{}::{}] 执行成功,耗时: {}ms,结果: {}", 
                     className, methodName, elapsedTime, result);
            return result;
            
        } catch (Exception e) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            log.error("[{}::{}] 执行失败,耗时: {}ms,异常: {}", 
                      className, methodName, elapsedTime, e.getMessage(), e);
            throw e;
        }
    }
}

// 日志配置文件
# logback-spring.xml
<configuration>
    <property name="LOG_PATH" value="./logs"/>
    <property name="APP_NAME" value="${spring.application.name}"/>
    
    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [traceId:%X{traceId}] %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- 文件输出 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${APP_NAME}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [traceId:%X{traceId}] %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- 按级别分开的日志文件 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${APP_NAME}-error.log</file>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${APP_NAME}-error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>90</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [traceId:%X{traceId}] %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- 异步日志 -->
    <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>512</queueSize>
        <appender-ref ref="FILE"/>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="ASYNC_FILE"/>
        <appender-ref ref="ERROR_FILE"/>
    </root>
    
    <!-- 特定包日志级别 -->
    <logger name="com.library" level="DEBUG"/>
    <logger name="org.springframework" level="WARN"/>
    <logger name="org.hibernate" level="WARN"/>
    <logger name="com.zaxxer.hikari" level="INFO"/>
</configuration>

12.7 测试规范

分层测试策略

java 复制代码
// 1. 单元测试
@ExtendWith(MockitoExtension.class)
class BorrowServiceTest {
    
    @Mock
    private BorrowRecordMapper borrowRecordMapper;
    
    @Mock
    private UserFeignClient userFeignClient;
    
    @Mock
    private BookFeignClient bookFeignClient;
    
    @InjectMocks
    private BorrowServiceImpl borrowService;
    
    @Test
    void testBorrowBook_Success() {
        // 准备测试数据
        Long userId = 1L;
        Long bookId = 100L;
        
        // Mock外部依赖
        when(userFeignClient.getUser(userId))
            .thenReturn(Result.success(new UserDTO()));
        when(bookFeignClient.getBook(bookId))
            .thenReturn(Result.success(new BookDTO()));
        when(bookFeignClient.reduceStock(bookId, 1))
            .thenReturn(Result.success(true));
        
        // 执行测试
        BorrowResult result = borrowService.borrowBook(userId, bookId);
        
        // 验证结果
        assertTrue(result.isSuccess());
        verify(borrowRecordMapper, times(1)).insert(any());
    }
}

// 2. 集成测试
@SpringBootTest
@AutoConfigureMockMvc
class BorrowControllerIntegrationTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserFeignClient userFeignClient;
    
    @Test
    void testBorrowBook_Integration() throws Exception {
        // 准备测试数据
        BorrowRequestDTO request = new BorrowRequestDTO(1L, 100L);
        
        // Mock Feign客户端
        when(userFeignClient.getUser(anyLong()))
            .thenReturn(Result.success(new UserDTO()));
        
        // 执行HTTP请求
        mockMvc.perform(post("/api/borrow/borrow")
                .contentType(MediaType.APPLICATION_JSON)
                .content(JsonUtils.toJson(request)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200));
    }
}

// 3. 契约测试(消费者驱动)
@Pact(consumer = "library-borrow-service", provider = "library-user-service")
public class UserContractTest {
    
    @Pact(consumer = "library-borrow-service", provider = "library-user-service")
    public RequestResponsePact getUserPact(PactDslWithProvider builder) {
        return builder
            .given("用户存在")
            .uponReceiving("根据ID获取用户")
            .path("/user/1")
            .method("GET")
            .willRespondWith()
            .status(200)
            .body(new PactDslJsonBody()
                .integerType("id", 1)
                .stringType("username", "张三")
                .stringType("email", "zhangsan@example.com"))
            .toPact();
    }
    
    @Test
    @PactTestFor(pactMethod = "getUserPact")
    void testGetUser(MockServer mockServer) {
        // 设置Feign客户端指向Mock服务器
        UserFeignClient client = Feign.builder()
            .target(UserFeignClient.class, mockServer.getUrl());
        
        // 执行测试
        Result<UserDTO> result = client.getUser(1L);
        
        // 验证
        assertNotNull(result);
        assertEquals(200, result.getCode());
        assertEquals("张三", result.getData().getUsername());
    }
}

// 4. 端到端测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class BorrowE2ETest {
    
    @Container
    static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0");
    
    @Container
    static NacosContainer nacos = new NacosContainer("nacos/nacos-server:latest");
    
    @Test
    void testCompleteBorrowFlow() {
        // 1. 启动所有服务
        // 2. 准备测试数据
        // 3. 执行完整的借阅流程
        // 4. 验证各个系统的状态
        // 5. 清理测试数据
    }
}

12.8 持续集成与交付

GitLab CI/CD流水线

yaml 复制代码
# .gitlab-ci.yml
stages:
  - test
  - build
  - scan
  - deploy

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
  DOCKER_REGISTRY: "registry.example.com"
  K8S_NAMESPACE: "library-${CI_ENVIRONMENT_NAME}"

cache:
  paths:
    - .m2/repository/
    - target/

# 单元测试阶段
unit-test:
  stage: test
  image: maven:3.9.6-eclipse-temurin-17
  script:
    - mvn clean test
  artifacts:
    reports:
      junit: target/surefire-reports/TEST-*.xml
  only:
    - merge_requests
    - main

# 集成测试阶段
integration-test:
  stage: test
  image: maven:3.9.6-eclipse-temurin-17
  services:
    - mysql:8.0
    - redis:7.2
  variables:
    MYSQL_DATABASE: test_db
    MYSQL_ROOT_PASSWORD: test_pass
  script:
    - mvn verify -Pintegration-test
  needs: ["unit-test"]
  only:
    - merge_requests
    - main

# 代码质量扫描
sonar-scan:
  stage: scan
  image: sonarsource/sonar-scanner-cli:latest
  variables:
    SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
    GIT_DEPTH: "0"
  cache:
    key: "${CI_JOB_NAME}"
    paths:
      - .sonar/cache
  script:
    - sonar-scanner
      -Dsonar.projectKey=library_${CI_PROJECT_NAME}
      -Dsonar.sources=src/main/java
      -Dsonar.host.url=${SONAR_URL}
      -Dsonar.login=${SONAR_TOKEN}
  only:
    - main

# 构建Docker镜像
docker-build:
  stage: build
  image: docker:20.10.24
  services:
    - docker:20.10.24-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t ${DOCKER_REGISTRY}/${CI_PROJECT_PATH}:${CI_COMMIT_SHORT_SHA} .
    - docker push ${DOCKER_REGISTRY}/${CI_PROJECT_PATH}:${CI_COMMIT_SHORT_SHA}
  only:
    - main

# 部署到开发环境
deploy-dev:
  stage: deploy
  image: bitnami/kubectl:latest
  environment:
    name: dev
    url: https://dev.library.example.com
  script:
    - kubectl config set-cluster k8s-cluster --server=${K8S_SERVER}
    - kubectl config set-credentials gitlab-ci --token=${K8S_TOKEN}
    - kubectl config set-context k8s-context --cluster=k8s-cluster --user=gitlab-ci
    - kubectl config use-context k8s-context
    - |
      kubectl set image deployment/${CI_PROJECT_NAME} \
        ${CI_PROJECT_NAME}=${DOCKER_REGISTRY}/${CI_PROJECT_PATH}:${CI_COMMIT_SHORT_SHA} \
        -n ${K8S_NAMESPACE}
    - kubectl rollout status deployment/${CI_PROJECT_NAME} -n ${K8S_NAMESPACE}
  only:
    - main

# 部署到生产环境(手动确认)
deploy-prod:
  stage: deploy
  image: bitnami/kubectl:latest
  environment:
    name: prod
    url: https://library.example.com
  script:
    - kubectl config set-cluster k8s-cluster --server=${K8S_SERVER_PROD}
    - kubectl config set-credentials gitlab-ci --token=${K8S_TOKEN_PROD}
    - kubectl config set-context k8s-context --cluster=k8s-cluster --user=gitlab-ci
    - kubectl config use-context k8s-context
    - |
      kubectl set image deployment/${CI_PROJECT_NAME} \
        ${CI_PROJECT_NAME}=${DOCKER_REGISTRY}/${CI_PROJECT_PATH}:${CI_COMMIT_SHORT_SHA} \
        -n ${K8S_NAMESPACE}
    - kubectl rollout status deployment/${CI_PROJECT_NAME} -n ${K8S_NAMESPACE}
  when: manual
  only:
    - main

13. 总结

13.1 核心结论回顾

经过全面的对比分析和实践验证,我们可以得出以下核心结论:

选择单体架构的情况

  1. 团队规模小(5-10人以下),沟通成本低
  2. 业务相对简单,功能模块耦合度高
  3. 开发周期紧张,需要快速上线验证
  4. 技术团队经验有限,缺乏微服务运维能力
  5. 性能要求极高,无法接受网络开销
  6. 资源预算有限,无法支撑复杂的基础设施

选择微服务架构的情况

  1. 大型团队(20人以上),需要并行开发
  2. 复杂业务系统,有明显领域边界
  3. 需要技术异构,不同服务使用不同技术栈
  4. 弹性伸缩需求,不同模块有不同负载特征
  5. 高可用性要求,需要故障隔离和容错
  6. 长期演进系统,需要独立部署和更新

13.2 架构演进建议

渐进式演进路径
单体应用
模块化单体
前后端分离
拆分核心服务
全面微服务化
快速验证业务
按业务模块分包
API网关+前端独立
识别核心领域
完善治理体系

13.3 未来发展趋势

1. 服务网格(Service Mesh)

yaml 复制代码
# Istio配置示例
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: library-user-vs
spec:
  hosts:
  - library-user-service
  http:
  - route:
    - destination:
        host: library-user-service
        subset: v1
      weight: 90
    - destination:
        host: library-user-service  
        subset: v2
      weight: 10
  timeout: 5s
  retries:
    attempts: 3
    perTryTimeout: 2s

2. Serverless架构

java 复制代码
// AWS Lambda函数示例
public class BorrowFunction implements RequestHandler<BorrowRequest, BorrowResponse> {
    
    private final BorrowService borrowService;
    
    public BorrowFunction() {
        // 初始化依赖
        this.borrowService = new BorrowServiceImpl();
    }
    
    @Override
    public BorrowResponse handleRequest(BorrowRequest request, Context context) {
        // 处理借阅请求
        return borrowService.borrowBook(request);
    }
}

3. 云原生技术栈

  • 容器化:Docker作为交付标准
  • 编排:Kubernetes作为运行平台
  • 服务网格:Istio/Envoy处理服务间通信
  • 可观测性:Prometheus+Grafana+Jaeger
  • GitOps:ArgoCD进行持续部署

13.4 最终建议

给技术决策者的建议

  1. 不要为了微服务而微服务:评估实际需求,避免过度设计
  2. 从单体开始:除非有明确证据需要微服务,否则从单体开始
  3. 建立模块化边界:即使在单体中,也要保持良好的模块化设计
  4. 基础设施先行:在拆分前,先建设好CI/CD、监控、日志等基础设施
  5. 小步快跑:一次只拆分一个服务,验证后再继续

给开发团队的建议

  1. 掌握分布式系统基础:CAP定理、一致性模型、分布式事务
  2. 学习云原生技术栈:容器、编排、服务网格
  3. 培养全栈思维:不仅要写代码,还要懂部署、监控、排错
  4. 注重可观测性:日志、指标、链路追踪是微服务的眼睛
  5. 编写防御性代码:考虑网络抖动、服务不可用等故障场景

13.5 资源推荐

学习资源

  • 书籍:《微服务架构设计模式》、《领域驱动设计》、《云原生Java》
  • 文档:Spring Cloud官方文档、Kubernetes官方文档、Istio官方文档
  • 课程:Coursera的"Microservices Architecture"、Udemy的"Spring Boot Microservices"
  • 社区:Spring中国社区、Kubernetes中文社区、ServiceMesh中文社区

工具推荐

  • 开发:IntelliJ IDEA Ultimate、VS Code、Docker Desktop
  • 测试:Postman、JMeter、Pact
  • 部署:Jenkins、GitLab CI、ArgoCD
  • 监控:Prometheus、Grafana、ELK Stack、Jaeger
  • 容器:Docker、containerd、Podman
  • 编排:Kubernetes、Docker Swarm、Nomad

架构选择没有绝对的"对"与"错",只有"合适"与"不合适"。单体架构和微服务架构各有其适用场景,关键是要根据团队情况、业务需求、技术能力和资源约束做出明智的选择。

记住以下原则:

  1. 简单优于复杂:在能满足需求的前提下,选择最简单的方案
  2. 演进优于预设:不要试图一开始就设计出完美的架构
  3. 实用优于潮流:选择被验证过的技术,而不是最热门的技术
  4. 人优于工具:考虑团队能力和学习成本

你当前正在参与或维护的系统,它属于哪种架构?面临哪些痛点?如果向另一种架构演进,第一步应该做什么?欢迎在评论区分享你的见解。

相关推荐
神龙斗士2402 小时前
第一个Spring Boot程序
java·spring boot·java-ee·tomcat
gelald2 小时前
Spring Boot - 配置加载
java·spring boot·后端·spring
中国胖子风清扬2 小时前
基于GPUI框架构建现代化待办事项应用:从架构设计到业务落地
java·spring boot·macos·小程序·rust·uni-app·web app
无籽西瓜a2 小时前
【西瓜带你学设计模式 | 第十六期 - 迭代器模式】迭代器模式 —— 统一遍历实现、优缺点与适用场景
java·后端·设计模式·迭代器模式·软件工程
Sirens.2 小时前
七大经典排序算法:原理、实现与复杂度分析
java·数据结构·算法·排序算法
万邦科技Lafite2 小时前
通过淘宝关键词API接口批量获取商品信息指南
java·前端·javascript
Seven972 小时前
【从0到1构建一个ClaudeAgent】规划与协调-任务系统
java
黑牛儿2 小时前
K8s 1.36 新特性解读:服务网格如何解决微服务安全与通信难题?生产级对比
安全·微服务·kubernetes
Shadow(⊙o⊙)2 小时前
C中 memset enum malloc fputc fgetc fgets fread fwrite rewind指针回退
java·c语言·数据库