Spring Cloud Alibaba 实战:从 0 到 1 构建可监控的微服务体系
引言:微服务架构的现状与挑战
在当今互联网技术快速迭代的背景下,单体应用架构已难以满足业务快速发展的需求。微服务架构通过将应用拆分为一系列小型、自治的服务,实现了技术栈多样化、团队独立开发和部署等优势,但同时也带来了服务治理、分布式协调、链路追踪等新的挑战。
Spring Cloud Alibaba 作为国内最流行的微服务解决方案之一,整合了阿里巴巴中间件生态与 Spring Cloud 标准,为开发者提供了一套完整的微服务治理方案。本文将带您从搭建基础框架开始,逐步实现服务注册发现、配置中心、熔断限流、网关路由等核心功能,并最终构建一套完善的监控告警体系,全方位掌握微服务从构建到运维的全链路实践。
一、环境准备与技术选型
1.1 开发环境
- JDK: 17.0.10
- Maven: 3.9.6
- MySQL: 8.0.36
- Docker: 26.1.4 (用于部署中间件)
- IDE: IntelliJ IDEA 2024.1
1.2 技术栈选型
组件 | 功能 | 版本 |
---|---|---|
Spring Boot | 应用开发基础框架 | 3.2.6 |
Spring Cloud | 微服务标准规范 | 2023.0.2 |
Spring Cloud Alibaba | 微服务组件套件 | 2023.0.1.0 |
Nacos | 服务注册与配置中心 | 2.3.2 |
Sentinel | 熔断与限流 | 1.8.7 |
Spring Cloud Gateway | 网关服务 | 4.1.2 |
OpenFeign | 服务调用 | 4.1.2 |
MyBatis-Plus | ORM 框架 | 3.5.6 |
Seata | 分布式事务 | 2.0.0 |
SkyWalking | 分布式链路追踪 | 9.7.0 |
Prometheus | metrics 收集 | 2.45.0 |
Grafana | 监控可视化 | 10.4.2 |
Lombok | 简化 Java 代码 | 1.18.30 |
Fastjson2 | JSON 处理 | 2.0.47 |
Knife4j | Swagger3 增强 | 4.4.0 |
1.3 整体架构设计
我们将构建一个包含以下服务的微服务系统:
二、基础环境搭建
2.1 安装与配置 Nacos
Nacos 是 Spring Cloud Alibaba 生态中的核心组件,提供服务注册发现和配置管理功能。我们使用 Docker 快速部署:
# 拉取镜像
docker pull nacos/nacos-server:v2.3.2# 启动容器
docker run -d \--name nacos \-p 8848:8848 \-p 9848:9848 \-p 9849:9849 \-e MODE=standalone \-e JVM_XMS=512m \-e JVM_XMX=512m \nacos/nacos-server:v2.3.2
访问 http://localhost:8848/nacos ,默认用户名密码均为 nacos,登录成功则表示 Nacos 安装成功。
2.2 安装与配置 Sentinel Dashboard
Sentinel 用于实现服务熔断与限流功能:
# 拉取镜像
docker pull bladex/sentinel-dashboard:1.8.7# 启动容器
docker run -d \--name sentinel \-p 8080:8080 \bladex/sentinel-dashboard:1.8.7
访问 http://localhost:8080 ,默认用户名密码均为 sentinel。
2.3 安装 SkyWalking
SkyWalking 用于分布式链路追踪和性能分析:
# 拉取OAP服务镜像
docker pull apache/skywalking-oap-server:9.7.0# 拉取UI镜像
docker pull apache/skywalking-ui:9.7.0# 启动OAP服务
docker run -d \--name skywalking-oap \-p 11800:11800 \-p 12800:12800 \-e SW_STORAGE=h2 \apache/skywalking-oap-server:9.7.0# 启动UI
docker run -d \--name skywalking-ui \-p 8081:8080 \-e SW_OAP_ADDRESS=http://skywalking-oap:12800 \--link skywalking-oap \apache/skywalking-ui:9.7.0
访问 http://localhost:8081 即可打开 SkyWalking 控制台。
2.4 安装 Prometheus 和 Grafana
# 拉取Prometheus镜像
docker pull prom/prometheus:v2.45.0# 创建配置文件目录
mkdir -p /tmp/prometheus# 创建配置文件
cat > /tmp/prometheus/prometheus.yml << EOF
global:scrape_interval: 15sscrape_configs:- job_name: 'prometheus'static_configs:- targets: ['localhost:9090']- job_name: 'skywalking'static_configs:- targets: ['skywalking-oap:1234']
EOF# 启动Prometheus
docker run -d \--name prometheus \-p 9090:9090 \-v /tmp/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \--link skywalking-oap \prom/prometheus:v2.45.0# 拉取Grafana镜像
docker pull grafana/grafana:10.4.2# 启动Grafana
docker run -d \--name grafana \-p 3000:3000 \--link prometheus \grafana/grafana:10.4.2
访问 http://localhost:3000 ,默认用户名密码为 admin/admin。
三、构建基础微服务框架
3.1 创建父工程
首先创建一个 Maven 父工程,统一管理依赖版本:
<?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.ken.microservice</groupId><artifactId>spring-cloud-alibaba-demo</artifactId><version>1.0.0</version><packaging>pom</packaging><name>Spring Cloud Alibaba 实战示例</name><description>Spring Cloud Alibaba 微服务从搭建到监控全链路示例</description><!-- 子模块 --><modules><module>common</module><module>user-service</module><module>order-service</module><module>product-service</module><module>gateway-service</module></modules><!-- 版本管理 --><properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>${java.version}</maven.compiler.source><maven.compiler.target>${java.version}</maven.compiler.target><!-- Spring 版本 --><spring.boot.version>3.2.6</spring.boot.version><spring.cloud.version>2023.0.2</spring.cloud.version><spring.cloud.alibaba.version>2023.0.1.0</spring.cloud.alibaba.version><!-- 其他组件版本 --><mybatis-plus.version>3.5.6</mybatis-plus.version><seata.version>2.0.0</seata.version><lombok.version>1.18.30</lombok.version><fastjson2.version>2.0.47</fastjson2.version><knife4j.version>4.4.0</knife4j.version><mysql.version>8.0.36</mysql.version></properties><!-- 依赖管理 --><dependencyManagement><dependencies><!-- Spring Boot --><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 --><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 --><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><!-- Seata --><dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId><version>${seata.version}</version></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><scope>provided</scope></dependency><!-- Fastjson2 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>${fastjson2.version}</version></dependency><!-- Knife4j (Swagger3) --><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>${knife4j.version}</version></dependency><!-- MySQL 驱动 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>${mysql.version}</version></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>
3.2 创建公共模块
创建 common 模块,存放公共实体类、工具类等:
<?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><groupId>com.ken.microservice</groupId><artifactId>spring-cloud-alibaba-demo</artifactId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>common</artifactId><name>公共模块</name><description>微服务公共组件</description><dependencies><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency><!-- Fastjson2 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId></dependency><!-- Spring 工具类 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><scope>provided</scope></dependency><!-- Google Guava --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>33.1.0-jre</version></dependency><!-- Swagger 注解 --><dependency><groupId>io.swagger.v3.oas</groupId><artifactId>swagger-models</artifactId><scope>provided</scope></dependency></dependencies>
</project>
创建公共响应类:
package com.ken.microservice.common.result;import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.annotation.JSONField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.util.ObjectUtils;/*** 统一响应结果** @param <T> 响应数据类型*/
@Data
@Schema(description = "统一响应结果")
public class Result<T> {@Schema(description = "状态码", example = "200")private int code;@Schema(description = "消息", example = "操作成功")private String msg;@Schema(description = "响应数据")private T data;@JSONField(serialize = false)private boolean success;/*** 私有构造方法,防止直接实例化*/private Result() {}/*** 成功响应** @param <T> 数据类型* @return 成功响应结果*/public static <T> Result<T> success() {return success(null);}/*** 成功响应** @param data 响应数据* @param <T> 数据类型* @return 成功响应结果*/public static <T> Result<T> success(T data) {Result<T> result = new Result<>();result.setCode(ResultCode.SUCCESS.getCode());result.setMsg(ResultCode.SUCCESS.getMsg());result.setData(data);result.setSuccess(true);return result;}/*** 失败响应** @param code 错误码* @param msg 错误消息* @param <T> 数据类型* @return 失败响应结果*/public static <T> Result<T> fail(int code, String msg) {Result<T> result = new Result<>();result.setCode(code);result.setMsg(msg);result.setData(null);result.setSuccess(false);return result;}/*** 失败响应** @param resultCode 错误码枚举* @param <T> 数据类型* @return 失败响应结果*/public static <T> Result<T> fail(ResultCode resultCode) {return fail(resultCode.getCode(), resultCode.getMsg());}/*** 失败响应** @param resultCode 错误码枚举* @param msg 错误消息* @param <T> 数据类型* @return 失败响应结果*/public static <T> Result<T> fail(ResultCode resultCode, String msg) {return fail(resultCode.getCode(), msg);}/*** 转为JSON字符串** @return JSON字符串*/@Overridepublic String toString() {return JSONObject.toJSONString(this);}
}
创建响应状态码枚举:
package com.ken.microservice.common.result;import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;/*** 响应状态码枚举*/
@Getter
@AllArgsConstructor
@Schema(description = "响应状态码枚举")
public enum ResultCode {/*** 成功*/SUCCESS(200, "操作成功"),/*** 服务器内部错误*/INTERNAL_SERVER_ERROR(500, "服务器内部错误"),/*** 请求参数错误*/PARAM_ERROR(400, "请求参数错误"),/*** 未授权*/UNAUTHORIZED(401, "未授权"),/*** 资源不存在*/RESOURCE_NOT_FOUND(404, "资源不存在"),/*** 服务调用失败*/SERVICE_CALL_FAILED(503, "服务调用失败");/*** 状态码*/private final int code;/*** 状态描述*/private final String msg;
}
四、用户服务实现
4.1 创建用户服务模块
<?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><groupId>com.ken.microservice</groupId><artifactId>spring-cloud-alibaba-demo</artifactId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>user-service</artifactId><name>用户服务</name><description>用户管理微服务</description><dependencies><!-- 公共模块 --><dependency><groupId>com.ken.microservice</groupId><artifactId>common</artifactId><version>${project.version}</version></dependency><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Cloud Alibaba Nacos Discovery --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- Spring Cloud Alibaba Nacos Config --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!-- Spring Cloud Alibaba Sentinel --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency><!-- OpenFeign --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!-- MyBatis-Plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency><!-- MySQL 驱动 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency><!-- Knife4j (Swagger3) --><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId></dependency><!-- Seata --><dependency><groupId>io.seata</groupId><artifactId>seata-spring-boot-starter</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
4.2 配置文件
创建 bootstrap.yml 配置文件:
spring:application:name: user-serviceprofiles:active: devcloud:nacos:discovery:server-addr: localhost:8848namespace: publicconfig:server-addr: localhost:8848file-extension: yamlnamespace: publicgroup: DEFAULT_GROUP
创建 application-dev.yml 配置文件:
server:port: 8082spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: rootmybatis-plus:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.ken.microservice.user.entityconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImplfeign:sentinel:enabled: truesentinel:transport:dashboard: localhost:8080port: 8719seata:enabled: trueapplication-id: ${spring.application.name}tx-service-group: my_test_tx_groupregistry:type: nacosnacos:server-addr: localhost:8848namespace:group: SEATA_GROUPapplication: seata-serverconfig:type: nacosnacos:server-addr: localhost:8848namespace:group: SEATA_GROUPlogging:level:com.ken.microservice.user: debug# SkyWalking 配置
skywalking:agent:service_name: ${spring.application.name}collector:backend_service: localhost:11800# Knife4j 配置
knife4j:enable: trueopenapi:title: 用户服务APIdescription: 用户服务接口文档version: 1.0.0group:default:api-rule: packageapi-rule-resources:- com.ken.microservice.user.controller
4.3 数据库设计
创建用户数据库和表:
-- 创建数据库
CREATE DATABASE IF NOT EXISTS user_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;-- 使用数据库
USE user_db;-- 创建用户表
CREATE TABLE IF NOT EXISTS `user` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',`username` varchar(50) NOT NULL COMMENT '用户名',`password` varchar(100) NOT NULL COMMENT '密码',`nickname` varchar(50) DEFAULT NULL COMMENT '昵称',`phone` varchar(20) DEFAULT NULL COMMENT '手机号',`email` varchar(100) DEFAULT NULL COMMENT '邮箱',`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:0-禁用,1-正常',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';-- 插入测试数据
INSERT INTO `user` (`username`, `password`, `nickname`, `phone`, `email`, `status`)
VALUES
('zhangsan', '123456', '张三', '13800138000', 'zhangsan@example.com', 1),
('lisi', '123456', '李四', '13900139000', 'lisi@example.com', 1);
4.4 实体类
package com.ken.microservice.user.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;import java.time.LocalDateTime;/*** 用户实体类*/
@Data
@TableName("user")
@Schema(description = "用户实体")
public class User {@TableId(type = IdType.AUTO)@Schema(description = "用户ID", example = "1")private Long id;@Schema(description = "用户名", example = "zhangsan")private String username;@Schema(description = "密码", example = "123456")private String password;@Schema(description = "昵称", example = "张三")private String nickname;@Schema(description = "手机号", example = "13800138000")private String phone;@Schema(description = "邮箱", example = "zhangsan@example.com")private String email;@Schema(description = "状态:0-禁用,1-正常", example = "1")private Integer status;@Schema(description = "创建时间")private LocalDateTime createTime;@Schema(description = "更新时间")private LocalDateTime updateTime;
}
4.5 Mapper 接口
package com.ken.microservice.user.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ken.microservice.user.entity.User;
import org.apache.ibatis.annotations.Mapper;/*** 用户Mapper接口*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
4.6 Service 层
package com.ken.microservice.user.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.ken.microservice.common.result.Result;
import com.ken.microservice.user.entity.User;/*** 用户服务接口*/
public interface UserService extends IService<User> {/*** 根据ID查询用户** @param id 用户ID* @return 用户信息*/Result<User> getUserById(Long id);/*** 根据用户名查询用户** @param username 用户名* @return 用户信息*/Result<User> getUserByUsername(String username);/*** 创建用户** @param user 用户信息* @return 创建结果*/Result<Long> createUser(User user);/*** 更新用户** @param user 用户信息* @return 更新结果*/Result<Boolean> updateUser(User user);/*** 删除用户** @param id 用户ID* @return 删除结果*/Result<Boolean> deleteUser(Long id);
}
package com.ken.microservice.user.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ken.microservice.common.result.Result;
import com.ken.microservice.common.result.ResultCode;
import com.ken.microservice.user.entity.User;
import com.ken.microservice.user.mapper.UserMapper;
import com.ken.microservice.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;/*** 用户服务实现类*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Overridepublic Result<User> getUserById(Long id) {log.info("查询用户信息,用户ID:{}", id);if (id == null || id <= 0) {log.warn("查询用户信息失败,用户ID不合法:{}", id);return Result.fail(ResultCode.PARAM_ERROR, "用户ID不合法");}User user = baseMapper.selectById(id);if (user == null) {log.warn("查询用户信息失败,用户不存在,用户ID:{}", id);return Result.fail(ResultCode.RESOURCE_NOT_FOUND, "用户不存在");}log.info("查询用户信息成功,用户ID:{}", id);return Result.success(user);}@Overridepublic Result<User> getUserByUsername(String username) {log.info("查询用户信息,用户名:{}", username);if (!StringUtils.hasText(username)) {log.warn("查询用户信息失败,用户名为空");return Result.fail(ResultCode.PARAM_ERROR, "用户名不能为空");}LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUsername, username);User user = baseMapper.selectOne(queryWrapper);if (user == null) {log.warn("查询用户信息失败,用户不存在,用户名:{}", username);return Result.fail(ResultCode.RESOURCE_NOT_FOUND, "用户不存在");}log.info("查询用户信息成功,用户名:{}", username);return Result.success(user);}@Overridepublic Result<Long> createUser(User user) {log.info("创建用户,用户信息:{}", user);if (user == null) {log.warn("创建用户失败,用户信息为空");return Result.fail(ResultCode.PARAM_ERROR, "用户信息不能为空");}if (!StringUtils.hasText(user.getUsername())) {log.warn("创建用户失败,用户名为空");return Result.fail(ResultCode.PARAM_ERROR, "用户名不能为空");}if (!StringUtils.hasText(user.getPassword())) {log.warn("创建用户失败,密码为空");return Result.fail(ResultCode.PARAM_ERROR, "密码不能为空");}// 检查用户名是否已存在LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUsername, user.getUsername());long count = baseMapper.selectCount(queryWrapper);if (count > 0) {log.warn("创建用户失败,用户名已存在:{}", user.getUsername());return Result.fail(ResultCode.PARAM_ERROR, "用户名已存在");}int rows = baseMapper.insert(user);if (rows <= 0) {log.error("创建用户失败,数据库操作失败");return Result.fail(ResultCode.INTERNAL_SERVER_ERROR, "创建用户失败");}log.info("创建用户成功,用户ID:{}", user.getId());return Result.success(user.getId());}@Overridepublic Result<Boolean> updateUser(User user) {log.info("更新用户,用户信息:{}", user);if (user == null || user.getId() == null) {log.warn("更新用户失败,用户ID为空");return Result.fail(ResultCode.PARAM_ERROR, "用户ID不能为空");}// 检查用户是否存在User existingUser = baseMapper.selectById(user.getId());if (existingUser == null) {log.warn("更新用户失败,用户不存在,用户ID:{}", user.getId());return Result.fail(ResultCode.RESOURCE_NOT_FOUND, "用户不存在");}int rows = baseMapper.updateById(user);if (rows <= 0) {log.error("更新用户失败,数据库操作失败,用户ID:{}", user.getId());return Result.fail(ResultCode.INTERNAL_SERVER_ERROR, "更新用户失败");}log.info("更新用户成功,用户ID:{}", user.getId());return Result.success(true);}@Overridepublic Result<Boolean> deleteUser(Long id) {log.info("删除用户,用户ID:{}", id);if (id == null || id <= 0) {log.warn("删除用户失败,用户ID不合法:{}", id);return Result.fail(ResultCode.PARAM_ERROR, "用户ID不合法");}// 检查用户是否存在User user = baseMapper.selectById(id);if (user == null) {log.warn("删除用户失败,用户不存在,用户ID:{}", id);return Result.fail(ResultCode.RESOURCE_NOT_FOUND, "用户不存在");}int rows = baseMapper.deleteById(id);if (rows <= 0) {log.error("删除用户失败,数据库操作失败,用户ID:{}", id);return Result.fail(ResultCode.INTERNAL_SERVER_ERROR, "删除用户失败");}log.info("删除用户成功,用户ID:{}", id);return Result.success(true);}
}
4.7 Controller 层
package com.ken.microservice.user.controller;import com.ken.microservice.common.result.Result;
import com.ken.microservice.user.entity.User;
import com.ken.microservice.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;/*** 用户控制器*/
@Slf4j
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
@Tag(name = "用户管理", description = "用户CRUD接口")
public class UserController {private final UserService userService;@Operation(summary = "根据ID查询用户", description = "通过用户ID获取用户详细信息")@GetMapping("/{id}")public Result<User> getUserById(@Parameter(description = "用户ID", required = true, example = "1")@PathVariable Long id) {return userService.getUserById(id);}@Operation(summary = "根据用户名查询用户", description = "通过用户名获取用户详细信息")@GetMapping("/username/{username}")public Result<User> getUserByUsername(@Parameter(description = "用户名", required = true, example = "zhangsan")@PathVariable String username) {return userService.getUserByUsername(username);}@Operation(summary = "创建用户", description = "新增用户信息")@PostMappingpublic Result<Long> createUser(@Parameter(description = "用户信息", required = true)@RequestBody User user) {return userService.createUser(user);}@Operation(summary = "更新用户", description = "修改用户信息")@PutMappingpublic Result<Boolean> updateUser(@Parameter(description = "用户信息", required = true)@RequestBody User user) {return userService.updateUser(user);}@Operation(summary = "删除用户", description = "根据ID删除用户")@DeleteMapping("/{id}")public Result<Boolean> deleteUser(@Parameter(description = "用户ID", required = true, example = "1")@PathVariable Long id) {return userService.deleteUser(id);}
}
4.8 启动类
package com.ken.microservice.user;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;/*** 用户服务启动类*/
@SpringBootApplication(scanBasePackages = "com.ken.microservice")
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.ken.microservice")
@MapperScan("com.ken.microservice.user.mapper")
public class UserServiceApplication {public static void main(String[] args) {SpringApplication.run(UserServiceApplication.class, args);}
}
五、商品服务实现
商品服务的实现与用户服务类似,这里重点展示核心代码:
5.1 数据库设计
-- 创建数据库
CREATE DATABASE IF NOT EXISTS product_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;-- 使用数据库
USE product_db;-- 创建商品表
CREATE TABLE IF NOT EXISTS `product` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品ID',`name` varchar(100) NOT NULL COMMENT '商品名称',`price` decimal(10,2) NOT NULL COMMENT '商品价格',`stock` int NOT NULL DEFAULT 0 COMMENT '商品库存',`description` varchar(500) DEFAULT NULL COMMENT '商品描述',`status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:0-下架,1-上架',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品表';-- 插入测试数据
INSERT INTO `product` (`name`, `price`, `stock`, `description`, `status`)
VALUES
('iPhone 15', 7999.00, 100, '苹果手机iPhone 15', 1),
('华为Mate 60', 6999.00, 50, '华为手机Mate 60', 1),
('小米14', 4999.00, 200, '小米手机14', 1);
5.2 核心业务代码
实体类:
package com.ken.microservice.product.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;import java.math.BigDecimal;
import java.time.LocalDateTime;/*** 商品实体类*/
@Data
@TableName("product")
@Schema(description = "商品实体")
public class Product {@TableId(type = IdType.AUTO)@Schema(description = "商品ID", example = "1")private Long id;@Schema(description = "商品名称", example = "iPhone 15")private String name;@Schema(description = "商品价格", example = "7999.00")private BigDecimal price;@Schema(description = "商品库存", example = "100")private Integer stock;@Schema(description = "商品描述", example = "苹果手机iPhone 15")private String description;@Schema(description = "状态:0-下架,1-上架", example = "1")private Integer status;@Schema(description = "创建时间")private LocalDateTime createTime;@Schema(description = "更新时间")private LocalDateTime updateTime;
}
服务实现类中扣减库存的方法:
/*** 扣减商品库存** @param productId 商品ID* @param quantity 扣减数量* @return 扣减结果*/
@Override
public Result<Boolean> decreaseStock(Long productId, Integer quantity) {log.info("扣减商品库存,商品ID:{},扣减数量:{}", productId, quantity);if (productId == null || productId <= 0) {log.warn("扣减商品库存失败,商品ID不合法:{}", productId);return Result.fail(ResultCode.PARAM_ERROR, "商品ID不合法");}if (quantity == null || quantity <= 0) {log.warn("扣减商品库存失败,扣减数量不合法:{}", quantity);return Result.fail(ResultCode.PARAM_ERROR, "扣减数量不合法");}// 查询商品信息Product product = baseMapper.selectById(productId);if (product == null) {log.warn("扣减商品库存失败,商品不存在,商品ID:{}", productId);return Result.fail(ResultCode.RESOURCE_NOT_FOUND, "商品不存在");}// 检查库存是否充足if (product.getStock() < quantity) {log.warn("扣减商品库存失败,库存不足,商品ID:{},当前库存:{},需要数量:{}", productId, product.getStock(), quantity);return Result.fail(ResultCode.PARAM_ERROR, "商品库存不足");}// 扣减库存Product updateProduct = new Product();updateProduct.setId(productId);updateProduct.setStock(product.getStock() - quantity);int rows = baseMapper.updateById(updateProduct);if (rows <= 0) {log.error("扣减商品库存失败,数据库操作失败,商品ID:{}", productId);return Result.fail(ResultCode.INTERNAL_SERVER_ERROR, "扣减库存失败");}log.info("扣减商品库存成功,商品ID:{},扣减数量:{},剩余库存:{}", productId, quantity, updateProduct.getStock());return Result.success(true);
}
六、订单服务实现
订单服务需要调用用户服务和商品服务,这里重点展示服务调用和分布式事务相关代码:
6.1 数据库设计
-- 创建数据库
CREATE DATABASE IF NOT EXISTS order_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;-- 使用数据库
USE order_db;-- 创建订单表
CREATE TABLE IF NOT EXISTS `order` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单ID',`order_no` varchar(50) NOT NULL COMMENT '订单编号',`user_id` bigint NOT NULL COMMENT '用户ID',`total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',`status` tinyint NOT NULL DEFAULT 0 COMMENT '订单状态:0-待支付,1-已支付,2-已取消,3-已完成',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_order_no` (`order_no`),KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单表';-- 创建订单项表
CREATE TABLE IF NOT EXISTS `order_item` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单项ID',`order_id` bigint NOT NULL COMMENT '订单ID',`product_id` bigint NOT NULL COMMENT '商品ID',`product_name` varchar(100) NOT NULL COMMENT '商品名称',`product_price` decimal(10,2) NOT NULL COMMENT '商品单价',`quantity` int NOT NULL COMMENT '购买数量',`total_price` decimal(10,2) NOT NULL COMMENT '商品总价',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`),KEY `idx_order_id` (`order_id`),KEY `idx_product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单项表';-- 创建undo_log表(Seata分布式事务需要)
CREATE TABLE IF NOT EXISTS `undo_log` (`branch_id` bigint NOT NULL COMMENT 'branch transaction id',`xid` varchar(100) NOT NULL COMMENT 'global transaction id',`context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',`rollback_info` longblob NOT NULL COMMENT 'rollback info',`log_status` int NOT NULL COMMENT '0:normal status,1:defense status',`log_created` datetime(6) NOT NULL COMMENT 'create datetime',`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AT transaction mode undo table';
6.2 Feign 客户端
创建用户服务 Feign 客户端:
package com.ken.microservice.order.feign;import com.ken.microservice.common.result.Result;
import com.ken.microservice.user.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;/*** 用户服务Feign客户端*/
@FeignClient(name = "user-service", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {/*** 根据ID查询用户** @param id 用户ID* @return 用户信息*/@GetMapping("/api/v1/users/{id}")Result<User> getUserById(@PathVariable("id") Long id);
}
创建商品服务 Feign 客户端:
package com.ken.microservice.order.feign;import com.ken.microservice.common.result.Result;
import com.ken.microservice.product.entity.Product;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;/*** 商品服务Feign客户端*/
@FeignClient(name = "product-service", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {/*** 根据ID查询商品** @param id 商品ID* @return 商品信息*/@GetMapping("/api/v1/products/{id}")Result<Product> getProductById(@PathVariable("id") Long id);/*** 扣减商品库存** @param productId 商品ID* @param quantity 扣减数量* @return 扣减结果*/@PostMapping("/api/v1/products/decrease-stock")Result<Boolean> decreaseStock(@RequestParam("productId") Long productId,@RequestParam("quantity") Integer quantity);
}
创建 Feign 客户端降级类:
package com.ken.microservice.order.feign;import com.ken.microservice.common.result.Result;
import com.ken.microservice.common.result.ResultCode;
import com.ken.microservice.user.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;/*** 用户服务Feign客户端降级类*/
@Slf4j
@Component
public class UserFeignClientFallback implements UserFeignClient {@Overridepublic Result<User> getUserById(Long id) {log.error("调用用户服务失败,用户ID:{},执行降级处理", id);return Result.fail(ResultCode.SERVICE_CALL_FAILED, "调用用户服务失败,请稍后重试");}
}
6.3 订单服务核心代码
订单实体类:
package com.ken.microservice.order.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;import java.math.BigDecimal;
import java.time.LocalDateTime;/*** 订单实体类*/
@Data
@TableName("order")
@Schema(description = "订单实体")
public class Order {@TableId(type = IdType.AUTO)@Schema(description = "订单ID", example = "1")private Long id;@Schema(description = "订单编号", example = "202306010001")private String orderNo;@Schema(description = "用户ID", example = "1")private Long userId;@Schema(description = "订单总金额", example = "7999.00")private BigDecimal totalAmount;@Schema(description = "订单状态:0-待支付,1-已支付,2-已取消,3-已完成", example = "0")private Integer status;@Schema(description = "创建时间")private LocalDateTime createTime;@Schema(description = "更新时间")private LocalDateTime updateTime;
}
订单项实体
package com.ken.microservice.order.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;import java.math.BigDecimal;
import java.time.LocalDateTime;/*** 订单项实体类*/
@Data
@TableName("order_item")
@Schema(description = "订单项实体")
public class OrderItem {@TableId(type = IdType.AUTO)@Schema(description = "订单项ID", example = "1")private Long id;@Schema(description = "订单ID", example = "1")private Long orderId;@Schema(description = "商品ID", example = "1")private Long productId;@Schema(description = "商品名称", example = "iPhone 15")private String productName;@Schema(description = "商品单价", example = "7999.00")private BigDecimal productPrice;@Schema(description = "购买数量", example = "1")private Integer quantity;@Schema(description = "商品总价", example = "7999.00")private BigDecimal totalPrice;@Schema(description = "创建时间")private LocalDateTime createTime;@Schema(description = "更新时间")private LocalDateTime updateTime;
}
创建订单请求 DTO:
package com.ken.microservice.order.dto;import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import java.util.List;/*** 创建订单请求DTO*/
@Data
@Schema(description = "创建订单请求DTO")
public class CreateOrderDTO {@Schema(description = "用户ID", required = true, example = "1")private Long userId;@Schema(description = "订单项列表", required = true)private List<OrderItemDTO> orderItems;/*** 校验请求参数** @return 校验结果*/public String validate() {if (userId == null || userId <= 0) {return "用户ID不合法";}if (CollectionUtils.isEmpty(orderItems)) {return "订单项不能为空";}for (OrderItemDTO item : orderItems) {if (item.getProductId() == null || item.getProductId() <= 0) {return "商品ID不合法";}if (item.getQuantity() == null || item.getQuantity() <= 0) {return "购买数量不合法";}}return null;}
}/*** 订单项DTO*/
@Data
@Schema(description = "订单项DTO")
class OrderItemDTO {@Schema(description = "商品ID", required = true, example = "1")private Long productId;@Schema(description = "购买数量", required = true, example = "1")private Integer quantity;
}
订单服务实现类(包含分布式事务):
package com.ken.microservice.order.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ken.microservice.common.result.Result;
import com.ken.microservice.common.result.ResultCode;
import com.ken.microservice.order.dto.CreateOrderDTO;
import com.ken.microservice.order.entity.Order;
import com.ken.microservice.order.entity.OrderItem;
import com.ken.microservice.order.feign.ProductFeignClient;
import com.ken.microservice.order.feign.UserFeignClient;
import com.ken.microservice.order.mapper.OrderItemMapper;
import com.ken.microservice.order.mapper.OrderMapper;
import com.ken.microservice.order.service.OrderService;
import com.ken.microservice.product.entity.Product;
import com.ken.microservice.user.entity.User;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;/*** 订单服务实现类*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {private final OrderMapper orderMapper;private final OrderItemMapper orderItemMapper;private final UserFeignClient userFeignClient;private final ProductFeignClient productFeignClient;/*** 创建订单* 使用Seata的全局事务注解保证分布式事务一致性*/@Override@GlobalTransactional(rollbackFor = Exception.class) // Seata全局事务注解public Result<String> createOrder(CreateOrderDTO createOrderDTO) {log.info("开始创建订单,请求参数:{}", createOrderDTO);// 校验请求参数String validateResult = createOrderDTO.validate();if (validateResult != null) {log.warn("创建订单失败,参数校验失败:{}", validateResult);return Result.fail(ResultCode.PARAM_ERROR, validateResult);}Long userId = createOrderDTO.getUserId();// 1. 查询用户信息Result<User> userResult = userFeignClient.getUserById(userId);if (!userResult.isSuccess()) {log.error("创建订单失败,查询用户信息失败:{}", userResult.getMsg());return Result.fail(ResultCode.SERVICE_CALL_FAILED, "查询用户信息失败:" + userResult.getMsg());}// 2. 查询商品信息并计算总金额List<OrderItem> orderItems = new ArrayList<>();BigDecimal totalAmount = BigDecimal.ZERO;for (CreateOrderDTO.OrderItemDTO itemDTO : createOrderDTO.getOrderItems()) {Long productId = itemDTO.getProductId();Integer quantity = itemDTO.getQuantity();// 查询商品信息Result<Product> productResult = productFeignClient.getProductById(productId);if (!productResult.isSuccess()) {log.error("创建订单失败,查询商品信息失败,商品ID:{},错误信息:{}", productId, productResult.getMsg());throw new RuntimeException("查询商品信息失败:" + productResult.getMsg());}Product product = productResult.getData();// 检查商品状态if (product.getStatus() != 1) {log.error("创建订单失败,商品未上架,商品ID:{},商品名称:{}", productId, product.getName());throw new RuntimeException("商品[" + product.getName() + "]未上架");}// 检查库存if (product.getStock() < quantity) {log.error("创建订单失败,商品库存不足,商品ID:{},商品名称:{},当前库存:{},需要数量:{}", productId, product.getName(), product.getStock(), quantity);throw new RuntimeException("商品[" + product.getName() + "]库存不足");}// 扣减库存Result<Boolean> decreaseResult = productFeignClient.decreaseStock(productId, quantity);if (!decreaseResult.isSuccess()) {log.error("创建订单失败,扣减商品库存失败,商品ID:{},错误信息:{}", productId, decreaseResult.getMsg());throw new RuntimeException("扣减商品[" + product.getName() + "]库存失败:" + decreaseResult.getMsg());}// 计算商品总价BigDecimal itemTotalPrice = product.getPrice().multiply(new BigDecimal(quantity));totalAmount = totalAmount.add(itemTotalPrice);// 创建订单项OrderItem orderItem = new OrderItem();orderItem.setProductId(productId);orderItem.setProductName(product.getName());orderItem.setProductPrice(product.getPrice());orderItem.setQuantity(quantity);orderItem.setTotalPrice(itemTotalPrice);orderItem.setCreateTime(LocalDateTime.now());orderItem.setUpdateTime(LocalDateTime.now());orderItems.add(orderItem);}// 3. 创建订单Order order = new Order();// 生成订单编号:年月日时分秒 + 3位随机数String orderNo = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + (100 + new Random().nextInt(900));order.setOrderNo(orderNo);order.setUserId(userId);order.setTotalAmount(totalAmount);order.setStatus(0); // 待支付order.setCreateTime(LocalDateTime.now());order.setUpdateTime(LocalDateTime.now());int orderRows = orderMapper.insert(order);if (orderRows <= 0) {log.error("创建订单失败,保存订单信息到数据库失败");throw new RuntimeException("创建订单失败");}// 4. 保存订单项for (OrderItem item : orderItems) {item.setOrderId(order.getId());orderItemMapper.insert(item);}log.info("创建订单成功,订单编号:{},订单ID:{}", orderNo, order.getId());return Result.success(orderNo);}// 其他方法省略...
}
七、网关服务实现
Spring Cloud Gateway 作为微服务网关,负责路由转发、负载均衡、认证授权等功能。
7.1 网关模块配置
<?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><groupId>com.ken.microservice</groupId><artifactId>spring-cloud-alibaba-demo</artifactId><version>1.0.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>gateway-service</artifactId><name>网关服务</name><description>微服务网关</description><dependencies><!-- 公共模块 --><dependency><groupId>com.ken.microservice</groupId><artifactId>common</artifactId><version>${project.version}</version></dependency><!-- Spring Cloud Gateway --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!-- Spring Cloud Alibaba Nacos Discovery --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- Spring Cloud Alibaba Nacos Config --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!-- Spring Cloud Alibaba Sentinel --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency><!-- Spring Cloud Gateway Sentinel --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId></dependency><!-- Knife4j Gateway --><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-gateway-spring-boot-starter</artifactId><version>${knife4j.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
7.2 网关配置文件
spring:application:name: gateway-serviceprofiles:active: devcloud:nacos:discovery:server-addr: localhost:8848namespace: publicconfig:server-addr: localhost:8848file-extension: yamlnamespace: publicgroup: DEFAULT_GROUPgateway:discovery:locator:enabled: truelower-case-service-id: trueroutes:# 用户服务路由- id: user-serviceuri: lb://user-servicepredicates:- Path=/api/v1/users/**filters:- name: Sentinel# 商品服务路由- id: product-serviceuri: lb://product-servicepredicates:- Path=/api/v1/products/**filters:- name: Sentinel# 订单服务路由- id: order-serviceuri: lb://order-servicepredicates:- Path=/api/v1/orders/**filters:- name: Sentinel# Swagger路由- id: swagger-routeuri: lb://user-servicepredicates:- Path=/swagger-ui.html,/swagger-ui/**,/v3/api-docs/**,/doc.html/**filters:- name: RewritePathargs:regexp: /doc.html/(?<segment>.*)replacement: /$\{segment}server:port: 8080sentinel:transport:dashboard: localhost:8080port: 8720scg:fallback:mode: responseresponse-status: 429response-body: '{"code":429,"msg":"请求过于频繁,请稍后再试","data":null,"success":false}'# Knife4j 配置
knife4j:gateway:enabled: trueroutes:- name: 用户服务url: /user-service/v3/api-docsservice-name: user-service- name: 商品服务url: /product-service/v3/api-docsservice-name: product-service- name: 订单服务url: /order-service/v3/api-docsservice-name: order-servicelogging:level:org.springframework.cloud.gateway: debugcom.alibaba.cloud.sentinel: debug
7.3 网关启动类
package com.ken.microservice.gateway;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;/*** 网关服务启动类*/
@SpringBootApplication(scanBasePackages = "com.ken.microservice")
@EnableDiscoveryClient
public class GatewayServiceApplication {public static void main(String[] args) {SpringApplication.run(GatewayServiceApplication.class, args);}
}
八、服务监控与可观测性
8.1 集成 SkyWalking 实现链路追踪
在各服务的启动参数中添加 SkyWalking agent 配置:
-javaagent:/path/to/skywalking-agent.jar
-Dskywalking.agent.service_name=user-service
-Dskywalking.collector.backend_service=localhost:11800
通过 SkyWalking 控制台,我们可以查看完整的服务调用链路:
8.2 集成 Prometheus 和 Grafana 实现指标监控
在各服务中添加 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>
添加配置:
management:endpoints:web:exposure:include: health,info,prometheusmetrics:export:prometheus:enabled: trueendpoint:health:show-details: alwaysprometheus:enabled: true
在 Prometheus 配置文件中添加各服务的监控端点:
scrape_configs:- job_name: 'user-service'metrics_path: '/actuator/prometheus'static_configs:- targets: ['user-service:8082']- job_name: 'product-service'metrics_path: '/actuator/prometheus'static_configs:- targets: ['product-service:8083']- job_name: 'order-service'metrics_path: '/actuator/prometheus'static_configs:- targets: ['order-service:8084']- job_name: 'gateway-service'metrics_path: '/actuator/prometheus'static_configs:- targets: ['gateway-service:8080']
在 Grafana 中导入 Spring Boot 应用的监控面板(ID:12856),可以直观地查看各服务的 JVM、请求量、响应时间等指标。
8.3 集成 Sentinel 实现熔断与限流
在 Sentinel 控制台中,可以为各服务设置限流规则:
-
为用户服务的查询接口设置 QPS 限流为 10:
- 资源名:GET:/api/v1/users/{id}
- 限流类型:QPS
- 阈值:10
-
为订单服务的创建订单接口设置线程数限流为 5:
- 资源名:POST:/api/v1/orders
- 限流类型:线程数
- 阈值:5
-
为商品服务的扣减库存接口设置熔断规则:
- 资源名:POST:/api/v1/products/decrease-stock
- 熔断策略:异常比例
- 阈值:0.5
- 熔断时长:10 秒
- 最小请求数:5
九、微服务高级特性
9.1 配置中心动态配置
在 Nacos 控制台中创建配置:
- 数据 ID:user-service-dev.yaml
- 配置内容:
user:config:version: 1.0.0enableCache: truecacheExpireSeconds: 300
在用户服务中添加配置类:
package com.ken.microservice.user.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;/*** 用户服务配置*/
@Data
@Component
@ConfigurationProperties(prefix = "user.config")
@RefreshScope // 支持配置动态刷新
public class UserConfig {private String version;private boolean enableCache;private Integer cacheExpireSeconds;
}
9.2 服务优雅降级与容错
使用 Resilience4j 实现服务容错:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
在服务调用处添加熔断注解:
@CircuitBreaker(name = "productService", fallbackMethod = "getProductFallback")
public Result<Product> getProduct(Long productId) {return productFeignClient.getProductById(productId);
}public Result<Product> getProductFallback(Long productId, Exception e) {log.error("调用商品服务失败,执行降级处理,商品ID:{}", productId, e);return Result.fail(ResultCode.SERVICE_CALL_FAILED, "获取商品信息失败,请稍后重试");
}
十、总结与展望
本文详细介绍了基于 Spring Cloud Alibaba 的微服务从搭建到监控的全链路实践,涵盖了服务注册发现、配置中心、服务调用、熔断限流、网关路由、分布式事务、链路追踪、指标监控等核心功能。通过实际代码示例,展示了如何构建一个完整的微服务体系。
附录:常见问题与解决方案
-
Nacos 服务注册失败
- 检查 Nacos 服务器是否正常运行
- 检查服务配置的 Nacos 地址是否正确
- 检查网络是否通畅,防火墙是否开放对应端口
-
Feign 调用失败
- 检查服务是否已注册到注册中心
- 检查 Feign 接口定义是否与服务端接口一致
- 检查熔断降级配置是否正确
-
分布式事务不生效
- 检查 Seata 服务器是否正常运行
- 检查各服务是否正确配置了 Seata
- 检查数据库是否创建了 undo_log 表
- 确保 @GlobalTransactional 注解正确添加到事务发起方
-
SkyWalking 链路追踪不显示
- 检查 SkyWalking agent 是否正确配置
- 检查服务启动参数是否正确
- 检查 SkyWalking OAP 服务是否正常运行
-
Sentinel 规则不生效
- 检查 Sentinel Dashboard 是否正常运行
- 检查服务是否正确配置了 Sentinel
- 确保服务已被调用过,Sentinel 已收集到服务信息