微服务架构

单体架构

所有功能集成在一起,打成一个jar包部署

  • 架构简单
  • 部署成本低
  • 协作能力差、
  • 发布效率低
  • 系统可用性差

面对高并发时容易卡顿

Screenshot 2025-10-15 212939.png

300个进程连续访问,tomcat资源耗尽,访问非常卡。

微服务架构

把单体架构中的功能拆分为多个独立的项目

  • 粒度小
  • 团队自制
  • 服务自制 (分别打包,分别部署) 数据隔离

单体架构的拆分

什么时候需要拆分

单体架构快速开发,规模扩大后拆分

确定的项目,资金充足,可直接选择微服务架构

怎么拆

  • 高内聚 : 职责尽量单一
  • 低耦合 : 服务独立,减少对其他微服务的依赖
  • 横向拆分 按照业务模块拆分
  • 横向拆分 抽取公共服务提高复用性

结构

  • 独立project 适合超大型项目
  • Maven聚合

两个项目的联系

通过网络请求联系 访问两个数据库,通过请求获取数据

如何用java发送网络请求

RestTemplate 工具 (存在一些问题)

注册中心

  • 注册服务信息
  • 订阅服务
  • 负载均衡
  • 发送请求,远程调用
  • 所有服务定期向服务中心发送请求,没收到请求就会剔除这个服务
  • 推送变更

Nacos(注册中心组件 ) 阿里巴巴 Nacos官网

部署

可参考我的另一篇文章

https://laning.com.cn/archives/nacosbu-shu

服务注册

  1. 引入依赖

Screenshot 2025-10-22 204339.png

<!--nacos 服务注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>  <!-- 版本号在父工程中定义-->
</dependency>
  1. 配置yaml文件

Screenshot 2025-10-22 204829.png

  • 配置自己的服务器/虚拟机ip
  • 之后重新启动会自动进行服务注册

Screenshot 2025-10-22 205256.png

多服务启动。

Screenshot 2025-10-22 205418.png

服务发现

  1. 与服务注册相同 前两部需要引入依赖和 配置yaml文件
  2. 加入bean方法
    
    @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
  3. 注入要调用的类中

Screenshot 2025-10-22 215320.png

  1. 获取服务示例,写负载均衡

Screenshot 2025-10-22 215144.png

OpenFeign

声明式的http客户端 ,帮助java发送http请求

入门

  1. 引入依赖
  <!--openFeign-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <!--负载均衡器-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>
  1. 启动类加入注解 (@EnableFeignClients)
  2. 编写FeignClient

Screenshot 2025-10-22 221320.png

  1. 在service中注入itemClient
  2. 调用方法

Screenshot 2025-10-22 221205.png

连接池

底层实现原理是用 Client 接口实现 效率低

  1. 引入依赖

Screenshot 2025-10-27 220226.png

  1. 配置开关
feign:
  okhttp:
    enabled: true # 开启OKHttp功能

OKHttp 中有连接池效率高

最佳方案

Screenshot 2025-10-27 220853.png

  1. 使用新的模块 (耦合度高)

Screenshot 2025-10-27 220934.png

创建新模块(引入依赖, 加入dto , client)

Screenshot 2025-10-27 222053.png

在cart-service中删除dto和client ,导入hm-api的依赖

<!--hm-api-->
<dependency>
    <groupId>com.heima</groupId>
    <artifactId>hm-api</artifactId>
    <version>1.0.0</version>
</dependency>

在启动类中加入,需要扫描的api包(不然扫描不到 client)

@EnableFeignClients(basePackages = "com.hmall.api.client")

日志

只有级别为debug时才会输出 , 且日志有四个级别

  1. NONE 不记录任何日志 (默认)
  2. BASIC 仅记录请求的方法,URL ,响应码和时间
  3. HEADERS 在BASIC基础上额外记录了请求和响应头的信息
  4. FULL 记录所有请求和响应的明细, 包括头信息、请求体、元数据

全局配置

package com.hmall.api.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfig {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.FULL;
    }
}

网关

  • 网络的关口, 负责请求的路由,转发和身份校验。

微服务必不可少的组件, 从注册中心拉取各种微服务地址发送给前端

网关路由

  • 创建网关微服务
  • 引入SpringCloudGateway、NacosDiscovery依赖
  • 编写启动类
  • 配置网关路由

spring cloud gateway

配置文件

<?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>hmall</artifactId>
        <groupId>com.heima</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hm-gateway</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <!--common-->
        <dependency>
            <groupId>com.heima</groupId>
            <artifactId>hm-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!--网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--nacos discovery-->
        <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>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:  # 注册中心
      server-addr: 192.168.100.132
    gateway:
      routes:
          - id: item-service  # 路由的ID,没有固定规则但要求唯一,建议配合服务名
            uri: lb://item-service  # 目标微服务的地址,lb表示负载均衡
            predicates:  # 断言,即判断该请求是否需要转发
              - Path=/items/**,/search/**  # 匹配的路径-
          - id: user-service
            uri: lb://user-service
            predicates:
              - Path=/addresses/**,/users/**

路由规则

  • id 路由的唯一标识
  • uri 目标路由地址
  • predicates 路由断言, 判断是否是当前路由
  • filters 路由过滤器,对请求或响应做特殊处理

身份验证

  • 在网关做jwt校验

自定义过滤器

  • GatewayFilter 路由过滤器选择生效
  • GlobalFilter 全局过滤器, 声明后全局生效
  • package com.hmall.gateway.filters;
    
    
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    @Component
    public class MyGlobalFilter implements GlobalFilter {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
            // TODO 模拟实现登录逻辑
            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            System.out.println("请求头:" + headers);
            // 放行
            return chain.filter(exchange);
        }
    
        public int getOrder() {
            // 过滤器优先级  , 值越小优先级越高
            return 0;
        }
    }
    

配置管理(Nacos)

配置共享

Screenshot 2025-11-07 212348.png

在nacos中编写配置文件

配置热更新

修改微服务时无需重启使配置生效

动态路由

服务保护和分布式事务

雪崩问题

  • 由于某个服务故障导致所有微服务都不可用
  • 服务提供者出现故障
  • 调用者没有做好异常处理 , 导致自身出现故障
  • 级联失败 ,集群出现故障

解决方案

  1. 请求限流

限制访问的并发量 , 避免出现故障,使用限流器

  1. 线程隔离

限定每个业务能使用的线程数量将故障业务隔离

  1. 服务熔断

断路器统计监测请求异常 ,熔断业务,拦截请求使用 fallback 的逻辑

Sentinel

安装

  1. 安装 sentinel 为一个jar包

打开cmd,执行这个命令

Screenshot 2025-11-14 111012.png

java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

访问本机8090端口

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2025-11-14 11:09:25.772 ERROR 15684 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   :

***************************
APPLICATION FAILED TO START
***************************

Description:

Web server failed to start. Port 8090 was already in use.

Action:

Identify and stop the process that's listening on port 8090 or configure this application to listen on another port.

这个错误就是8090端口被占用了

java -Dserver.port=8091 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

启动8091端口

Screenshot 2025-11-14 111403.png

账号密码都为 sentinel

引入依赖

<!--sentinel-->
<dependency>
    <groupId>com.alibaba.cloud</groupId> 
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
  cloud: 
    sentinel:
      transport:
        dashboard: localhost:8090

Screenshot 2025-11-14 113030.png

限流

限流测试

Screenshot 2025-11-14 213424.png

  • 在簇点链路设置单机阈值 , 进行测试, 我设置为6 ,一秒之内只能处理6个请求,超出的请求会报错
  • 使用JMeter进行测试,1秒发送10个请求 ,可以看到6个成功 ,4个失败,成功率大概60%
  • 失败返回状态码为 429 限流错误

Screenshot 2025-11-14 213320.png

线程隔离

Screenshot 2025-11-14 220205.png

Fallback

线程隔离 , 无法得到线程所执行的方法

  • 定义 FallbackFactory 继承 FallbackFactory< T >
  • 泛型为需要的 Client 方法

Screenshot 2025-11-15 172810.png

  • 注册Bean
  • 添加在Client注解
@FeignClient(value = "item-service" , fallbackFactory = ItemClientFallbackFactory.class , configuration = DefaultFeignConfig.class)
feign:
  sentinel:
    enabled: true # 开启Sentinel功能
  • 添加sentinel 配置 使远程Client调用也成为簇点规则, 配置远程调用的规则。

这样查询失败后不再是异常 ,返回空结果 ,不影响其他调用。

异常为 0%

Screenshot 2025-11-15 181426.png

服务熔断

拦截访问该服务的所有请求 ,直接走Fallback ,当服务恢复时放行访问。

  • 断路器(closed , Open , Half-Open)

Screenshot 2025-11-15 184110.png

RT 超过RT算慢 , 比例阈值 慢的比例超过多少进行熔断 , 熔断时长Open状态维持的时间

分布式事务(难)

一次调用需要多个服务合作完成,必须同时成功或者失败,这就是分布式事务

Seata

  • TC 单独的微服务
  • TM 引入依赖
  • RM 引入依赖
TC 服务的部署 , 在虚拟机环境部署。
  1. 在虚拟机mysql中执行sql脚本
  2. 首先检查nacos容器在哪个网络下
docker inspect nacos
# 这个就是网络名 bridge 和 heima

"Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "MacAddress": "02:42:ac:11:00:02",
                    "NetworkID": "609f9badbe96938b3d091aef8b978390509467a62d9331b2b6e35e5200d33bdd",
                    "EndpointID": "568a59f42ae260105b9f94bf101c460d72c93fb4ff8dc53c078f1f54262cdaa5",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "DriverOpts": null,
                    "DNSNames": null
                },
                "heima": {   
                    "IPAMConfig": {},
                    "Links": null,
                    "Aliases": [],
                    "MacAddress": "02:42:ac:12:00:02",
                    "NetworkID": "e3bb39e2165d24c719c49a9946c6643565b8a6bd79a48dcfc690aeb4d37a9acf",
                    "EndpointID": "e7ebc7937eb5a4fdb6ce0f8e36a268af0d67920d7034eaebb7848c57fedc10ce",
                    "Gateway": "172.18.0.1",
                    "IPAddress": "172.18.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "DriverOpts": {},
                    "DNSNames": [
                        "mysql",
                        "5d758047248d"
                    ]
                }
            }

我的mysql 和 nacos 都在 heima 这个网络下

如果不在可以用,这个指令添加到一个网络中

docker network connect [网络名] [容器名]
  1. 导入seata的tar包和配置文件
  2. 执行docker命令, $$ ip地址需要改为自己的 , 我这个版本是 1.5.2, 大家可以使用最新版的, 我因为tar包是1.5.2所以就用1.5.2 , 这个网络名也需要改为自己的哦
docker run --name seata \
-p 8099:8099 \
-p 7099:7099 \
-e SEATA_IP=192.168.100.132 \
-v ./seata:/seata-server/resources \
--privileged=true \
--network heima \
-d \
seataio/seata-server:1.5.2

Screenshot 2025-11-15 230818.png

Screenshot 2025-11-15 230857.png

seata-server 这个服务添加成功了

访问虚拟机的7099端口

Screenshot 2025-11-15 231109.png

账号密码都是admin,这样我们就成功启动这个TC服务

在微服务中集成 Seata
  • 引入依赖
<!--统一配置管理-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  <!--读取bootstrap文件-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
  </dependency>
  <!--seata-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  </dependency>
  • yaml配置(抽取到 nacos) 把需要的服务引入nacos的配置
seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 192.168.100.132:8848 # nacos地址
      namespace: "" # namespace,默认为空
      group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      application: seata-server # seata服务名称
      username: nacos
      password: nacos
  tx-service-group: hmall # 事务组名称
  service:
    vgroup-mapping: # 事务组与tc集群的映射关系
      hmall: "default"

Screenshot 2025-11-15 234743.png

Screenshot 2025-11-15 234717.png

XA模式

XA规范描述了全局TM和局部RM之间的接口

  • 实现简单,满足ACID的规则
  • 常用数据库都支持
  • 在第一阶段锁定数据库资源,结束才会释放,性能较差。

XA模式 的使用方法

  1. 配置yaml文件 , 开启XA模式
seata:
  data-source-proxy-mode: XA
  1. 在方法上使用 @GlobalTransactional 注解

AT模式

AT模式执行完立刻提交sql , 弥补XA模型资源锁定缺陷。在执行sql之前,记录快照 。

  • TC判断执行成功,删除快照
  • TC判断执行失败,恢复快照
  • AT模式,服务如果出现异常,在回滚之前会存在一段时间数据异常。(但是持续时间较短 ,发生服务异常概率也少低 , 一般不用考虑)

AT模式 的使用方法

  1. 为每个微服务提供,一个快照的表
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) 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(11)      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
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
  1. 在配置文件配置AT模式(不设置,自动也是AT)
seata:
  data-source-proxy-mode: AT # 或者直接不写

XA模式 强一致 , AT模式 最终一致 , 但 AT模式效率比XA模式高。所以需要看情况选择(一般为AT模式)如果对一致要求非常高,那就选择XA模式。

结束

经过一个月的时间, 我的微服务这篇总算是写完了。吧唧吧唧 ☆: .。. o(≧▽≦)o .。.:

下一篇为中间件 MQ的学习

数据分析还没学完 悲(;´д`)ゞ

Bay Bay!

迷茫java练习生