曲延温 發表於 2025-9-28 15:49:00

Spring篇知识点(4)

<hr>
<p>&nbsp;</p>
<h1 style="text-align: center">MVC</h1>
<h1>一、Spring MVC的工作流程</h1>
<ol>
<li>客户端向服务端发送一次请求,这个请求会先到前端控制器DispacherServlet</li>
<li>DispacherServlet接收到请求后会调用HandlerMapping处理器映射器——该请求由哪个Controller来处理</li>
<li>DispacherServlet调用HandlerAdapter处理器适配器,告诉处理器适配器应该去执行哪个Controller(这个存在的意义是有不同的Controller类型)</li>
<li>DispacherServlet将ModelAndView交给视图解析器解析,然后返回真正的视图。</li>
<li>DispacherServlet将模型数据填充到视图中。</li>
<li>DispacherServlet将结果返回给客户端。</li>
</ol>
<p><img src="https://img2024.cnblogs.com/blog/2297173/202509/2297173-20250913164734424-232709558.png"></p>
<h1>&nbsp;二、SpringMVC Restful风格的接口的流程是怎么样的?</h1>
<p><img src="https://img2024.cnblogs.com/blog/2297173/202509/2297173-20250913165352330-1242826407.png"></p>
<ul>
<li data-start="89" data-end="170">
<p data-start="92" data-end="102"><strong data-start="92" data-end="100">请求进入</strong></p>
<ul data-start="106" data-end="170">
<li data-start="106" data-end="150">
<p data-start="108" data-end="150">浏览器发请求,首先到 <strong data-start="119" data-end="140">DispatcherServlet</strong>(前端控制器)。</p>
</li>
<li data-start="154" data-end="170">
<p data-start="156" data-end="170">👉 相当于“总调度”。</p>
</li>
</ul>
</li>
<li data-start="172" data-end="290">
<p data-start="175" data-end="186"><strong data-start="175" data-end="184">找到处理器</strong></p>
<ul data-start="190" data-end="290">
<li data-start="190" data-end="246">
<p data-start="192" data-end="246">DispatcherServlet 问 <strong data-start="212" data-end="230">HandlerMapping</strong>:“这个请求该交给谁处理?”</p>
</li>
<li data-start="250" data-end="290">
<p data-start="252" data-end="290">HandlerMapping 返回对应的 <strong data-start="273" data-end="287">Controller</strong>。</p>
</li>
</ul>
</li>
<li data-start="292" data-end="436">
<p data-start="295" data-end="314"><strong data-start="295" data-end="312">执行 Controller</strong></p>
<ul data-start="318" data-end="436">
<li data-start="318" data-end="380">
<p data-start="320" data-end="380">DispatcherServlet 交给 <strong data-start="341" data-end="359">HandlerAdapter</strong> 去执行 Controller 方法。</p>
</li>
<li data-start="384" data-end="436">
<p data-start="386" data-end="436">👉 Controller 就是你写的 <code data-start="406" data-end="428">@GetMapping("/user")</code> 这种方法。</p>
</li>
</ul>
</li>
<li data-start="438" data-end="623">
<p data-start="441" data-end="452"><strong data-start="441" data-end="450">处理返回值</strong></p>
<ul data-start="456" data-end="623">
<li data-start="456" data-end="509">
<p data-start="458" data-end="509">如果 Controller 方法上有 <code data-start="477" data-end="492">@ResponseBody</code>,返回对象就要转成 JSON。</p>
</li>
<li data-start="513" data-end="623">
<p data-start="515" data-end="520">过程:</p>
<ul data-start="526" data-end="623">
<li data-start="526" data-end="588">
<p data-start="528" data-end="588">Spring 用 <strong data-start="537" data-end="561">HttpMessageConverter</strong>(默认 Jackson)把对象序列化成 JSON。</p>
</li>
<li data-start="594" data-end="623">
<p data-start="596" data-end="623">写到响应的 <strong data-start="602" data-end="618">OutputStream</strong> 里。</p>
</li>
</ul>
</li>
</ul>
</li>
<li data-start="625" data-end="741">
<p data-start="628" data-end="642"><strong data-start="628" data-end="640">响应返回给浏览器</strong></p>
<ul data-start="646" data-end="741">
<li data-start="646" data-end="682">
<p data-start="648" data-end="682">这时 JSON 已经写进了 HTTP 响应体,直接返回给客户端。</p>
</li>
<li data-start="686" data-end="741">
<p data-start="688" data-end="741">👉 如果方法返回 <code data-start="698" data-end="712">ModelAndView</code>,才会走视图解析,这里返回的是 JSON,所以不需要。</p>
</li>
</ul>
</li>
</ul>
<hr>
<p>&nbsp;</p>
<h1 style="text-align: center">Spring Boot</h1>
<h1>一、介绍一下Spring Boot,有哪些优点</h1>
<ul>
<li><strong>自动配置</strong>:内置了大量常用场景的配置,开发者只需要少量配置。比如加上<code data-start="377" data-end="402">spring-boot-starter-web</code>,就能快速构建 Web 应用。</li>
<li><strong>starter机制</strong>:提供一系列start依赖包,整合第三方框架很方便,spring-boot-starter-redis。</li>
<li><strong>内嵌服务器</strong>:传统的方式写完代码后,要把项目打包成WAR包,还要准备一个外部的应用服务器(如Tomcat),把war包放到服务器的webapps目录下,重启tomcat才能跑。但在SpringBoot中直接内嵌了服务器,只需要打包一个jar包,然后直接命令执行,会自动把内嵌的Tomcat启动起来。</li>
<li>约定优于配置:提供了一套合理的默认约定,若不写配置就用默认值,若有特殊需求,也可以再覆盖配置。</li>
<li>依赖管理:提供官方Start POM,一行依赖搞定一整套功能,避免版本冲突。</li>
</ul>
<h1>二、Spring Boot自动配置原理</h1>
<p>  传统Spring需要写一堆XML或配置,如web项目要自己配置DispatcherServlet、ViewResolver、消息转换器,数据库要自己配置dataSource等。那SpringBoot的最大特点就是这些常见配置不需要你写,自动帮你配好。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
    }
}
</pre>
</div>
<p>  1、 启动类上有一个@SpringBootApplication注解,这个注解里面包含@EnableAutoConfiguration。</p>
<p>  2、SpringFactories 机制:Spring Boot 在自己的 jar 包里,放了一个文件:<br data-start="674" data-end="677">
<code data-start="677" data-end="704">META-INF/spring.factories。如果启用了自动配置,请把这些类也加载进来。</code></p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
...
</pre>
</div>
<p>  3、条件注解:比如<code data-start="1016" data-end="1045">DataSourceAutoConfiguration</code></p>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@Configuration
@ConditionalOnClass(DataSource.class) // classpath 里有 DataSource 才生效
@ConditionalOnMissingBean(DataSource.class) // 容器里没有你自己定义的 DataSource 才生效
public class DataSourceAutoConfiguration {
   // 自动帮你注册一个 DataSource bean
}
</pre>
</div>
<ul>
<li>你引入了 <code data-start="1320" data-end="1346">spring-boot-starter-jdbc</code> → classpath 里就有 <code data-start="1363" data-end="1375">DataSource</code> → 自动配置生效 ✅</li>
<li>如果你自己写了一个 <code data-start="1401" data-end="1419">@Bean DataSource</code> → Boot 就尊重你写的,不去覆盖 ❌</li>
</ul>
<h1>三、Spring Boot启动原理</h1>
<div class="cnblogs_Highlighter">
<pre class="brush:java;gutter:true;">@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
    }
}</pre>
</div>
<ol>
<li>创建一个SpringApplication对象,判断应用类型(普通应用/servlet Web应用),加载ApplicationContext类型。</li>
<li>读取配置:去<code data-start="585" data-end="602">application.yml</code> / <code data-start="605" data-end="629">application.properties</code> 里读配置。把数据库地址、端口号、日志级别等全都存好。</li>
<li>加载Bean定义:你写的类上有 <code data-start="729" data-end="741">@Component</code>、<code data-start="742" data-end="752">@Service</code>、<code data-start="753" data-end="766">@Repository</code>、<code data-start="767" data-end="780">@Controller</code>,Spring 都会扫描到。Spring Boot 还会根据依赖(比如 <code data-start="821" data-end="846">spring-boot-starter-web</code>)自动帮你装好“常用机器”(Tomcat、Jackson、异常处理器等)。</li>
<li>创建Bean+依赖注入:Spring 会 new 出所有对象(实例化),Spring 会 new 出所有对象(实例化)。</li>
<li>启动内嵌服务器:若是Web项目,会自动帮你把Tomcat启动起来,端口默认8080.注册好DispatcherServlet,专门接收用户请求并分发给Controller。</li>
<li>应用就绪。</li>
</ol>
<h1>四、对Spring Cloud的了解</h1>
<h2>&nbsp;1、概念</h2>
<p>  微服务是一种架构风格,代表着一种通过将应用程序拆分成小型、独立的功能模块的开发方式。每个模块(服务)实现独立的业务功能不限语言,服务之间通过轻量级的通信机制,如HTTP/REST或消息队列进行交互。微服务架构的核心思想是:解耦应用程序,提升灵活性和维护性。</p>
<p>  微服务的优点:</p>
<ul>
<li>模块独立解耦</li>
<li>独立部署、快速迭代(如果改动了某个微服务,不需要重启整体的项目)</li>
<li>灵活技术栈</li>
<li>高扩展性</li>
<li>容错性好</li>
</ul>
<p>  缺点:分布式系统复杂性(服务器成本,开发人员成,运维成本增加)</p>
<h2>2、Spring Cloud介绍&amp;搭建</h2>
<ul>
<li><strong data-start="85" data-end="93">服务治理</strong>:通过注册中心(如 <strong data-start="103" data-end="112">Nacos</strong>)实现服务的注册、发现与剔除,并依靠心跳机制保证实例健康可用。</li>
<li><strong data-start="148" data-end="157">服务间调用</strong>:通过声明式调用工具(如 <strong data-start="170" data-end="179">Feign</strong>)简化服务之间的通信逻辑,实现透明化的远程调用。</li>
<li><strong data-start="208" data-end="216">统一入口</strong>:利用 <strong data-start="220" data-end="231">Gateway</strong> 作为流量入口,对外提供统一的访问网关,实现路由转发与权限控制。</li>
<li><strong data-start="268" data-end="277">容错与限流</strong>:引入 <strong data-start="281" data-end="293">Sentinel</strong> 进行熔断、限流与降级,提升系统在异常情况下的自我保护能力。</li>
<li><strong data-start="328" data-end="336">问题排查</strong>:借助 <strong data-start="340" data-end="354">SkyWalking</strong> 等链路追踪工具,监控请求在各微服务间的调用路径,快速定位故障。</li>
<li><strong data-start="391" data-end="400">分布式事务</strong>:通过 <strong data-start="404" data-end="413">Seata</strong> 协调跨服务的全局事务,确保在多服务协同操作时的数据一致性。</li>
</ul>
<p>  引入Spring Cloud pom</p>
<div class="cnblogs_code">
<pre>&lt;dependencyManagement&gt;
    &lt;dependencies&gt;
      &lt;!-- Spring Cloud 版本依赖(根据 Boot 版本来选) --&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-dependencies&lt;/artifactId&gt;
            &lt;version&gt;2021.0.8&lt;/version&gt; &lt;!-- 举例,实际要看Boot版本 --&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;scope&gt;import&lt;/scope&gt;
      &lt;/dependency&gt;

      &lt;!-- Spring Cloud Alibaba 版本依赖(Nacos, Sentinel, Seata 等) --&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;
            &lt;artifactId&gt;spring-cloud-alibaba-dependencies&lt;/artifactId&gt;
            &lt;version&gt;2021.0.5.0&lt;/version&gt; &lt;!-- 要和上面保持兼容 --&gt;
            &lt;type&gt;pom&lt;/type&gt;
            &lt;scope&gt;import&lt;/scope&gt;
      &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/dependencyManagement&gt;</pre>
</div>
<h2>3、整合Nacos注册中心进行服务发现</h2>
<p>  假设现在有个小demo,有两个功能(下单、库存),这时候下单服务是通过用HTTP的方式来调用库存服务的接口。假设这个库存服务进行了集群,就会有很多的地址和端口,如果都需要自己去管理的话会非常麻烦。这时候就可以用nacos来帮我们解决这个问题。</p>
<p>  (1)安装nacos服务:https://nacos.io/download/nacos-server/?spm=5238cd80.c984973.0.0.6be14023Dk5qSI</p>
<p>  (2)打开安装好的文件夹conf,application.properties文件,因为nacos里面有很多数据要持久化,所以我们要连接数据库,根据原本的配置去创建一个'nacos'数据库(记得数据库名字要和配置里面的一致)。把安装包的mysql-schema.sql文件在新建的这个数据库运行一下,就会产生很多个数据表。</p>
<p>  (3)打开安装包的bin,里面的startup.cmd文件(因为默认是集群方式启动),把'cluster'改为'standalone'(set MODE = 'standalone')。双击启动即可,端口是8848。接下来可以通过localhost:8848/nacos启动,就可以打开nacos的控制面板。&nbsp;</p>
<p>  (4)引入具体的Spring Cloud组件</p>
<div class="cnblogs_code">
<pre>&lt;dependency&gt;
    &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;
    &lt;artifactId&gt;spring-cloud-starter-alibaba-nacos-discovery&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
</div>
<p>&nbsp;  (5)在两个服务的启动类上加入@EnableDiscoveryClient, 表示当前应用启用了nacos的服务。</p>
<p>  (6)在两个服务的application.yml的文件中修改配置。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">spring:
    application:   
      name: order</span>-<span style="color: rgba(0, 0, 0, 1)">server
    cloud:
      nacos:
            discovery:
                server:addr: </span><span style="color: rgba(128, 0, 128, 1)">127.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.1</span>:<span style="color: rgba(128, 0, 128, 1)">8848</span></pre>
</div>
<p>  (7)启动项目,在nacos控制面板就可以看到两个服务。</p>
<p>&nbsp;  (8)服务发现集成Spring Cloud Loadbalancer组件(放在需要远程调用的消费者端order)。然后再OrderApplication中的public RestTemplate上加一个@LoadBalanced注解。然后在OrderController就可以通过服务名访问了。</p>
<div class="cnblogs_code">
<pre>&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
    &lt;artifactId&gt;spring-cloud-starter-loadbalancer&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">原来是写死的 </span><span style="color: rgba(0, 128, 0, 1); text-decoration: underline">http://localhost</span><span style="color: rgba(0, 128, 0, 1)">:8082/xxx,现在换成 服务名调用:</span>
<span style="color: rgba(0, 0, 0, 1)">@RestController
@RequestMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/order</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
public class OrderController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/add</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
    public String add() {
      System.out.</span><span style="color: rgba(0, 0, 255, 1)">println</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">下单成功!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里直接用服务名 "stock-service" 替代固定的IP和端口</span>
      String msg = restTemplate.getForObject(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">http://stock-service/stock/reduce</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, String.class);
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello World </span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)"> msg;
    }
}</span></pre>
</div>
<p>  (9)现在我们尝试调用下单接口,这时也会扣减库存,说明通过nacos管理服务的方式正确了。如果我们配置的是集群,那么这个服务在nocos面板中会显示集群数为10、实例数为10、健康数为10(全是健康的情况下),如果有五台down掉了,那么nacos不会去调用那五个服务,而是选择健康的实例。</p>
<h2>4、整合Openfeign进行远程调用</h2>
<p>  (1)集成OpenFeign</p>
<div class="cnblogs_code">
<pre>&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
    &lt;artifactId&gt;spring-cloud-starter-openfeign&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
</div>
<p>  (2)在启动类中加@EnableFeignClients</p>
<p>  (3)新建一个包feignService,<code data-start="876" data-end="900">StockFeignService.java</code>,用于声明要调用的远程接口:</p>
<div class="cnblogs_code">
<pre>@FeignClient(name = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">stock-service</span><span style="color: rgba(128, 0, 0, 1)">"</span>, path = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/stock</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
public </span><span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> StockFeignService {

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 调用库存服务的 /stock/reduct 接口</span>
    @RequestMapping(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/reduct</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
    String reduct();
}</span></pre>
</div>
<p>  这样,就等于给 <strong data-start="1117" data-end="1134">stock-service</strong> 定义了一个“本地接口代理”,底层由 Feign + Ribbon(或 LoadBalancer) 完成服务发现和调用。</p>
<p>  (4)在Controller中调用</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@RestController
@RequestMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/order</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
public class OrderController {

    @Autowired
    private StockFeignService stockFeignService;<br>  @Autowired<br>  StockFeignService stockFeighService;<br>
9、@RequestMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/add</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
    public String add() {
      System.out.</span><span style="color: rgba(0, 0, 255, 1)">println</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">下单成功!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      String msg </span>= stockFeignService.reduct(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 调用远程库存服务</span>
      <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello World </span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)"> msg;
    }
}</span></pre>
</div>
<h2>5、整合Nacos配置中心进行统一配置管理</h2>
<p><img src="https://img2024.cnblogs.com/blog/2297173/202509/2297173-20250928140401482-920021872.png"></p>
<p>&nbsp;  在这里可以创建一个通用的配置中心,新建一个配置。&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">在nacos中配一个author: yi</span>
<span style="color: rgba(0, 0, 0, 1)">
spring:
    application:   
      name: order</span>-<span style="color: rgba(0, 0, 0, 1)">server
    cloud:
      nacos:
            discovery:
                server</span>-addr: <span style="color: rgba(128, 0, 128, 1)">127.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.1</span>:<span style="color: rgba(128, 0, 128, 1)">8848</span><span style="color: rgba(0, 0, 0, 1)">
            config:
                server</span>-addr: <span style="color: rgba(128, 0, 128, 1)">127.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.1</span>:<span style="color: rgba(128, 0, 128, 1)">8848</span><span style="color: rgba(0, 0, 0, 1)">
    config:
      </span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)">:
            </span>- nacos: order-<span style="color: rgba(0, 0, 0, 1)">server.yaml

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这样就可以在OrderController中注入就可以用了<br>//加入</span>@RefreshScope注解可以实时更新</pre>
<pre><span style="color: rgba(0, 128, 0, 1)">@RefreshScope<br>public class OrderController{<br>  </span>@Value("${author}")<br>  String author;</pre>
<pre><span style="color: rgba(0, 128, 0, 1)">}</span></pre>
</div>
<p>&nbsp;</p>
<h2>6、整合GateWay网关进行路由分发</h2>
<p>  没有网关时,客户端需要<strong data-start="220" data-end="235">直接访问各个服务的端口</strong>。比如:</p>
<ul data-start="247" data-end="393">
<li data-start="247" data-end="317">
<p data-start="249" data-end="317">订单服务(order-service)在 8081 端口,访问路径是 <code data-start="284" data-end="317">http://localhost:8081/order/add</code></p>
</li>
<li data-start="320" data-end="393">
<p data-start="322" data-end="393">库存服务(stock-service)在 8082 端口,访问路径是 <code data-start="357" data-end="393">http://localhost:8082/stock/reduct</code></p>
</li>
</ul>
<p data-start="397" data-end="447">  这样客户端就需要知道 <strong data-start="408" data-end="422">每个服务的地址和端口</strong>,而且一旦端口变更,客户端也要跟着改,非常不方便。</p>
<ul>
<li data-start="831" data-end="870">
<p data-start="834" data-end="870"><strong data-start="834" data-end="844">客户端负担重</strong>:需要知道每个微服务的端口和路径,无法统一入口。</p>
</li>
<li data-start="871" data-end="913">
<p data-start="874" data-end="913"><strong data-start="874" data-end="883">安全性不足</strong>:没有统一的权限校验和鉴权机制,只能在每个服务单独实现。</p>
</li>
<li data-start="914" data-end="951">
<p data-start="917" data-end="951"><strong data-start="917" data-end="927">无法集中治理</strong>:像限流、熔断、灰度发布等能力没办法统一做。</p>
</li>
<li data-start="952" data-end="991">
<p data-start="955" data-end="991"><strong data-start="955" data-end="966">对外暴露风险大</strong>:每个服务都要直接暴露在公网,增加了安全风险。</p>
</li>
</ul>
<p>  <strong data-start="1009" data-end="1016">有网关</strong>:客户端只需要调用一个统一入口,比如 <code data-start="1035" data-end="1065">http://api.xxx.com/order/add</code>,网关负责路由到对应服务,还能顺带做鉴权、限流。</p>
<p>  做法:</p>
<p>  (1)网关是个单独的服务,我们要新建一个模块。</p>
<p>  (2)引入pom</p>
<div class="cnblogs_code">
<pre>&lt;dependencies&gt;
    &lt;!-- Spring Boot WebFlux(Gateway 基于 WebFlux,而不是 Spring MVC) --&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-webflux&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;!-- Spring Cloud Gateway 核心依赖 --&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
      &lt;artifactId&gt;spring-cloud-starter-gateway&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;!-- Nacos 服务发现(或其他注册中心,比如 Eureka、Consul,根据你的实际情况选择) --&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;
      &lt;artifactId&gt;spring-cloud-starter-alibaba-nacos-discovery&lt;/artifactId&gt;
    &lt;/dependency&gt;

    &lt;!-- Spring Boot Actuator(可选,但通常推荐,用于健康检查与监控) --&gt;
    &lt;dependency&gt;
      &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
      &lt;artifactId&gt;spring-boot-starter-actuator&lt;/artifactId&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</pre>
</div>
<p>  (3)在新建的这个模块的配置文件中配置Spring Cloud Gateway路由。如果请求的url中有/order/*的话,就把路由分发到以下那个order-server服务。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)"># 还需要注册中心、配置中心
spring:
    application:
      name: gateway
    cloud:
      gateway:
            #路由规则
            routes:
                </span>-<span style="color: rgba(0, 0, 0, 1)"> id: order route   # 路由的唯一标识,路由到order
                  url: lb.</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">order-server    # 需要转发的地址</span>
<span style="color: rgba(0, 0, 0, 1)">                  # 断言规则 用于路由规则的匹配
                  predictions:      
                        </span>- Path = /order<span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*</span></pre>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">spring:
    application:   
      name: order</span>-<span style="color: rgba(0, 0, 0, 1)">server
    cloud:
      nacos:
            discovery:
                server</span>-addr: <span style="color: rgba(128, 0, 128, 1)">127.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.1</span>:<span style="color: rgba(128, 0, 128, 1)">8848</span><span style="color: rgba(0, 0, 0, 1)">
            config:
                server</span>-addr: <span style="color: rgba(128, 0, 128, 1)">127.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.1</span>:<span style="color: rgba(128, 0, 128, 1)">8848</span><span style="color: rgba(0, 0, 0, 1)">
    config:
      </span><span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)">:
            </span>- optional: nacos: gateway.yaml      # 表示可有可无可选的</pre>
</div>
<p>注意:去除spring-boot-starter-web,不能同时和gateway出现。</p>
<p>  流程:用户请求首先经过 <strong data-start="28" data-end="37">Nginx</strong> 作为统一入口,Nginx 将流量转发到 <strong data-start="58" data-end="82">Spring Cloud Gateway</strong>。网关负责 <strong data-start="88" data-end="101">请求路由与负载均衡</strong>,并根据服务发现机制,从 <strong data-start="114" data-end="128">Nacos 注册中心</strong> 获取最新的微服务地址。随后,网关将请求分发到具体的 <strong data-start="155" data-end="164">微服务实例</strong>(如订单服务、库存服务),实现服务的动态调用。同时,微服务在启动时会向 Nacos <strong data-start="206" data-end="216">注册自身信息</strong>,Nacos 还能推送配置变化,从而保证整个系统在高并发下依然具备可扩展性与可管理性。</p>
<h2>7、整合Seata实现分布式事务</h2>
<p>  在单体应用里,<code data-start="7" data-end="23">@Transactional</code> 就像一把锁,可以保证这段业务里的数据库操作要么都成功,要么都回滚。但在微服务里,每个服务有自己的数据库,比如下单服务、库存服务、账户服务各管一摊,<code data-start="97" data-end="113">@Transactional</code> 只能管好自己那一摊,跨服务就管不了了。这样一来,下单成功了但库存没扣、余额没减的情况就可能发生,所以要用 Seata 这样的分布式事务工具来帮忙“统一指挥”,保证多个服务的数据一致。</p>
<p>  前置:在订单服务和库存服务的配置文件中进行补充数据库和mybatis的配置。</p>
<h3 data-start="294" data-end="313">  (1)Seata 工作原理</h3>
<p data-start="315" data-end="380">Seata 基于 <strong data-start="324" data-end="340">全局事务协调器 (TC)</strong> + <strong data-start="343" data-end="357">事务管理器 (TM)</strong> + <strong data-start="360" data-end="374">资源管理器 (RM)</strong> 实现。</p>
<ul data-start="382" data-end="539">
<li data-start="382" data-end="424">
<p data-start="384" data-end="424"><strong data-start="384" data-end="411">TM(Transaction Manager)</strong>:发起并结束全局事务。</p>
</li>
<li data-start="425" data-end="481">
<p data-start="427" data-end="481"><strong data-start="427" data-end="451">RM(Resource Manager)</strong>:管理分支事务(数据库操作),并向 TC 注册分支事务。</p>
</li>
<li data-start="482" data-end="539">
<p data-start="484" data-end="539"><strong data-start="484" data-end="515">TC(Transaction Coordinator)</strong>:全局事务协调器,负责全局事务的提交或回滚。</p>
</li>
</ul>
<p data-start="541" data-end="594">一句话总结:<br data-start="547" data-end="550"><strong data-start="553" data-end="591">👉 <strong data-start="13" data-end="70">TM 负责开启/提交/回滚全局事务,TC 负责全局事务的调度与协调,RM 负责具体数据库操作并上报给 TC</strong>。(<strong data-start="80" data-end="101">TM 发起,TC 指挥,RM 执行</strong>。)</strong></p>
<h3 data-start="601" data-end="614">  (2)引入依赖</h3>
<p data-start="616" data-end="643">  在订单服务、库存服务等需要分布式事务的模块中引入:</p>
<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary">
<div class="overflow-y-auto p-4" dir="ltr">
<div class="cnblogs_code">
<pre>&lt;dependency&gt;
    &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;
    &lt;artifactId&gt;spring-cloud-starter-alibaba-seata&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
</div>
<h3 data-start="794" data-end="811">  (3)配置 Seata</h3>
<p data-start="813" data-end="839">  在 <code data-start="815" data-end="832">application.yml</code> 中配置:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">spring:
cloud:
    alibaba:
      seata:
      tx</span>-service-group: my_test_tx_group</pre>
</div>
<p>然后在 <code data-start="944" data-end="955">resources</code> 目录下新建 <code data-start="962" data-end="973">file.conf</code> 和 <code data-start="976" data-end="991">registry.conf</code>,配置 Seata 注册中心(可以是 Nacos、Eureka 或 file 模式)。</p>
<code class="whitespace-pre! language-xml"><code class="whitespace-pre! language-xml"><span class="hljs-tag">  </span></code></code>
<h3 data-start="1043" data-end="1081">  (4)在业务中使用 <code data-start="1057" data-end="1079">@GlobalTransactional</code></h3>
<h4 data-start="1083" data-end="1094">  订单服务</h4>
</div>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@RestController
@RequestMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/order</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
public class OrderController {

    @Autowired
    private OrderService orderService;

    @RequestMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/add</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
    public String add() {
      orderService.createOrder();
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">下单成功!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private StockFeignService stockFeignService;

    <strong>@GlobalTransactional</strong>(name </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">order-create-tx</span><span style="color: rgba(128, 0, 0, 1)">"</span>, rollbackFor =<span style="color: rgba(0, 0, 0, 1)"> Exception.class)
    public void createOrder() {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 1. 新增订单</span>
      Order order = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Order();
      order.setProductId(</span><span style="color: rgba(128, 0, 128, 1)">9</span><span style="color: rgba(0, 0, 0, 1)">);
      order.setStatus(</span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">);
      order.setTotalAmount(</span><span style="color: rgba(128, 0, 128, 1)">100</span><span style="color: rgba(0, 0, 0, 1)">);
      orderMapper.insert(order);

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 2. 调用库存服务扣减库存</span>
<span style="color: rgba(0, 0, 0, 1)">      stockFeignService.reduct(order.getProductId());

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3. 模拟异常</span>
      <span style="color: rgba(0, 0, 255, 1)">int</span> a = <span style="color: rgba(128, 0, 128, 1)">1</span> / <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}</span></pre>
</div>
<p>库存服务:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@Service
public class StockService {

    @Autowired
    private StockMapper stockMapper;

    public void reduct(</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> productId) {
      stockMapper.reduce(productId);
      System.out.</span><span style="color: rgba(0, 0, 255, 1)">println</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">扣减库存成功</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    }
}</span></pre>
</div>
<h3 data-start="2194" data-end="2207">  (5)执行效果</h3>
<ul data-start="2209" data-end="2339">
<li data-start="2209" data-end="2250">
<p data-start="2211" data-end="2250">如果程序 <strong data-start="2216" data-end="2224">正常执行</strong>,订单库写入成功,库存库扣减成功,全局事务提交;</p>
</li>
<li data-start="2251" data-end="2339">
<p data-start="2253" data-end="2339">如果程序 <strong data-start="2258" data-end="2266">出现异常</strong>(比如 <code data-start="2270" data-end="2284">int a = 1/0;</code>),Seata 会自动通知各个分支事务回滚,订单库和库存库的数据都会恢复到原始状态,实现<strong data-start="2328" data-end="2336">强一致性</strong>。<strong data-start="211" data-end="219">订单服务</strong>在 <code data-start="221" data-end="248">orderMapper.insert(order)</code> 里写入订单数据。<strong data-start="263" data-end="271">库存服务</strong>在 <code data-start="273" data-end="310">stockFeignService.reduct(productId)</code> 里扣减库存。执行到 <code data-start="327" data-end="342">int a = 1 / 0</code> 抛异常。Seata 的 <strong data-start="361" data-end="377">全局事务协调器 (TC)</strong> 捕捉到异常,通知各个参与的分支事务(订单库 + 库存库)执行 <strong data-start="409" data-end="415">回滚</strong>。</p>
</li>
</ul>
<h3>  (6)原因</h3>
<p>  当<strong>库存服务</strong>的方法执行时,它其实是运行在Seate的RM管控下的,库存服务一启动,就会把它的数据源代理成Seata的DataSourceProxy。这样每一次数据库操作都会被拦截,并且记录到一个”<strong>回滚日志表</strong>“(undolog)。在执行时,库存服务会向全局事务协调器(TC)报告:我现在是订单事务里的一个分支事务,我做了哪些 SQL 改动,原始值是什么,新值是什么。</p>
<p>  当订单服务抛出异常时,TM(事务管理器)会告诉TC:全局事务失败,要回滚。TC通知所有已经注册过的分支事务(包括订单库和库存库)”你们刚刚执行的SQL全都撤销“。RM(资源管理器)根据之前记录的undolog,把数据恢复到更新前的样子。</p>
<h3 data-start="2346" data-end="2357">  (7)总结</h3>
<p data-start="2359" data-end="2503">  相比于本地事务的 <code data-start="2368" data-end="2384">@Transactional</code>,Seata 的 <code data-start="2393" data-end="2415">@GlobalTransactional</code> 可以让分布式场景下的多个服务 <strong data-start="2431" data-end="2447">像在一个数据库里操作一样</strong>,从而保证数据一致性。它非常适合应用在 <strong data-start="2467" data-end="2479">下单-库存-账户</strong> 这类 <strong data-start="2483" data-end="2500">跨服务、跨库的核心业务链路</strong>。</p>
<h2>8、整合Sentinel进行服务流控熔断降级</h2>
<p>&nbsp;  在分布式微服务架构中,高并发是常见场景。如果没有流控措施,突发流量会导致某个服务被打挂,从而引起系统雪崩。<br data-start="161" data-end="164">
  <strong data-start="164" data-end="176">Sentinel</strong> 就是阿里开源的流量治理框架,支持 <strong data-start="194" data-end="212">流量控制、熔断降级、系统保护</strong>等功能。</p>
<h3>  (1)引入依赖</h3>
<div class="cnblogs_code">
<pre>&lt;dependency&gt;
    &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;
    &lt;artifactId&gt;spring-cloud-starter-alibaba-sentinel&lt;/artifactId&gt;
&lt;/dependency&gt; </pre>
</div>
<h3>   (2)配置 Nacos + Sentinel</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">在 application.yml 中配置 Sentinel 控制台地址(先启动 sentinel-dashboard):</span>
<span style="color: rgba(0, 0, 0, 1)">
spring:
application:
    name: order</span>-<span style="color: rgba(0, 0, 0, 1)">server
cloud:
    nacos:
      discovery:
      server</span>-addr: <span style="color: rgba(128, 0, 128, 1)">127.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.1</span>:<span style="color: rgba(128, 0, 128, 1)">8848</span><span style="color: rgba(0, 0, 0, 1)">
    sentinel:
      transport:
      dashboard: localhost:</span><span style="color: rgba(128, 0, 128, 1)">8080</span><span style="color: rgba(0, 0, 0, 1)">   # Sentinel 控制台
      port: </span><span style="color: rgba(128, 0, 128, 1)">8719</span>                  # 与控制台通信端口</pre>
</div>
<h3>  (3)在业务方法上添加注解</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@RestController
@RequestMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/order</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
public class OrderController {

    @GetMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/add</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
    @SentinelResource(value </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">addOrder</span><span style="color: rgba(128, 0, 0, 1)">"</span>, blockHandler = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">handleBlock</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
    public String add() {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">下单成功!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    }

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 限流或熔断时执行</span>
<span style="color: rgba(0, 0, 0, 1)">    public String handleBlock(BlockException ex) {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">系统繁忙,请稍后再试~</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}<br>//这样,当 QPS 超过 Sentinel 配置的阈值时,就会自动调用 <code data-start="1141" data-end="1154">handleBlock</code> 方法,避免系统崩溃。<br></span></pre>
</div>
<blockquote>
<p>Queries Per Second,每秒请求数。</p>
<ul>
<li data-start="299" data-end="349">
<p data-start="301" data-end="349"><strong data-start="301" data-end="323">QPS = 每秒钟系统能处理的请求数</strong>,比如你的接口一秒钟能正常处理 100 次调用。</p>
</li>
<li data-start="350" data-end="392">
<p data-start="352" data-end="392">如果突然来了 500 次调用,系统可能直接挂掉(CPU 爆满、数据库崩溃)。</p>
</li>
<li data-start="393" data-end="472">
<p data-start="395" data-end="472">Sentinel 就像“限流器”,当检测到 <strong data-start="417" data-end="428">请求数超过阈值</strong>,会触发 <code data-start="433" data-end="448">handleBlock()</code>,给用户返回友好提示,而不是让系统直接崩溃。</p>
</li>
</ul>
</blockquote>
<h2>9、整合RocketMQ进行异步处理和流控制</h2>
<p>&nbsp;  在实际业务中,比如 <strong data-start="1214" data-end="1224">下单扣减库存</strong>,如果用户量突然激增,直接写库会给数据库造成很大压力。<br data-start="1251" data-end="1254">
  解决方案就是引入 <strong data-start="1263" data-end="1275">消息队列(MQ)</strong>,把请求写到队列里,由消费者异步慢慢处理,起到 <strong data-start="1299" data-end="1307">削峰填谷</strong>的作用。</p>
<h3>  &nbsp;(1)引入依赖</h3>
<div class="cnblogs_code">
<pre>&lt;dependency&gt;
    &lt;groupId&gt;org.apache.rocketmq&lt;/groupId&gt;
    &lt;artifactId&gt;rocketmq-spring-boot-starter&lt;/artifactId&gt;
    &lt;version&gt;<span style="color: rgba(128, 0, 128, 1)">2.2</span>.<span style="color: rgba(128, 0, 128, 1)">2</span>&lt;/version&gt;
&lt;/dependency&gt;</pre>
</div>
<h3>  (2)配置RocketMQ</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">spring:
application:
    name: order</span>-<span style="color: rgba(0, 0, 0, 1)">server
cloud:
    nacos:
      discovery:
      server</span>-addr: <span style="color: rgba(128, 0, 128, 1)">127.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.1</span>:<span style="color: rgba(128, 0, 128, 1)">8848</span><span style="color: rgba(0, 0, 0, 1)">
rocketmq:
    name</span>-server: <span style="color: rgba(128, 0, 128, 1)">127.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.1</span>:<span style="color: rgba(128, 0, 128, 1)">9876</span><span style="color: rgba(0, 0, 0, 1)">   # RocketMQ 服务地址
    producer:
      group: order</span>-producer-group</pre>
</div>
<h3>  (3)在下单服务中发送消息</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@RestController
@RequestMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/order</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
public class OrderController {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @GetMapping(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/add</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
    public String add() {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送下单消息到库存服务的 topic</span>
      rocketMQTemplate.convertAndSend(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">order-topic</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">下单一笔</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">下单请求已提交!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}</span></pre>
</div>
<h3>  (4)在库存服务中消费消息</h3>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">@Service
@RocketMQMessageListener(topic </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">order-topic</span><span style="color: rgba(128, 0, 0, 1)">"</span>, consumerGroup = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">stock-consumer-group</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
public class StockConsumer implements RocketMQListener</span>&lt;String&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    @Override
    public void onMessage(String message) {
      System.out.</span><span style="color: rgba(0, 0, 255, 1)">println</span>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">收到消息:</span><span style="color: rgba(128, 0, 0, 1)">"</span> +<span style="color: rgba(0, 0, 0, 1)"> message);
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 扣减库存逻辑</span>
<span style="color: rgba(0, 0, 0, 1)">    }
}</span></pre>
</div>
<p>  这样,<strong data-start="2443" data-end="2480">订单服务只负责把消息写入 MQ,真正的库存扣减逻辑由消费者异步执行</strong>,大大提升系统抗压能力。</p>
<h2>&nbsp;10、整合 SkyWalking 实现链路追踪</h2>
<p>  在微服务架构下,一个请求可能会经过 <strong data-start="181" data-end="208">网关 → 订单服务 → 库存服务 → 支付服务</strong> 等多个服务,如果某个环节出问题(慢、报错),很难排查到底是哪一段出了问题。<br data-start="246" data-end="249">
👉 这时候就需要用 <strong data-start="260" data-end="274">SkyWalking</strong> 这样的分布式链路追踪工具。</p>
<h3>  (1)原理</h3>
<ul>
<li data-start="316" data-end="364">
<p data-start="318" data-end="364">在每个服务中,SkyWalking 通过 <strong data-start="339" data-end="351">探针 Agent</strong> 自动拦截请求调用链。</p>
</li>
<li data-start="365" data-end="399">
<p data-start="367" data-end="399">每个请求会生成一个 <strong data-start="377" data-end="388">TraceId</strong>,贯穿整个调用链。</p>
</li>
<li data-start="400" data-end="455">
<p data-start="402" data-end="455">系统会把请求耗时、接口调用情况、异常信息都上报给 <strong data-start="427" data-end="452">SkyWalking OAP Server</strong>。</p>
</li>
<li data-start="456" data-end="557">
<p data-start="458" data-end="501">最终你可以在 <strong data-start="465" data-end="482">SkyWalking UI</strong> 上看到一条完整的调用链路,比如:</p>
<p>网关 → 订单服务 → 库存服务 → 数据库,每一环的耗时、错误一目了然。</p>
</li>
</ul>
<h3>  (2)环境准备</h3>
<ul>
<li>下载SkyWalking:https://skywalking.apache.org/downloads/</li>
<li>解压并启动:./bin/startup.sh。启动后默认 UI 控制台地址:http://localhost:8080</li>
</ul>
<h3>  (3)在微服务中集成Agent</h3>
<p>  在你的 <strong data-start="775" data-end="788">订单服务、库存服务</strong> 启动命令中,加上 SkyWalking Agent:</p>
<div class="cnblogs_code">
<pre>java -javaagent:/path/to/skywalking-agent/skywalking-<span style="color: rgba(0, 0, 0, 1)">agent.jar \
   </span>-Dskywalking.agent.service_name=order-<span style="color: rgba(0, 0, 0, 1)">service \
   </span>-Dskywalking.collector.backend_service=<span style="color: rgba(128, 0, 128, 1)">127.0</span>.<span style="color: rgba(128, 0, 128, 1)">0.1</span>:<span style="color: rgba(128, 0, 128, 1)">11800</span><span style="color: rgba(0, 0, 0, 1)"> \
   </span>-jar order-service.jar</pre>
</div>
<ul>
<li data-start="1040" data-end="1081">
<p data-start="1042" data-end="1081"><code data-start="1042" data-end="1054">-javaagent</code>:指定 SkyWalking 的探针 jar 包。</p>
</li>
<li data-start="1082" data-end="1116">
<p data-start="1084" data-end="1116"><code data-start="1084" data-end="1098">service_name</code>:服务的名称(区分不同微服务)。</p>
</li>
<li data-start="1117" data-end="1171">
<p data-start="1119" data-end="1171"><code data-start="1119" data-end="1136">backend_service</code>:SkyWalking OAP 的地址(默认 11800 端口)。</p>
</li>
</ul>
<h3>  (4)效果展示</h3>
<p data-start="1192" data-end="1201">假设用户下单:</p>
<ol data-start="1203" data-end="1451">
<li data-start="1203" data-end="1246">
<p data-start="1206" data-end="1246">请求先到 <strong data-start="1211" data-end="1222">Gateway</strong>,Gateway 转发到 <strong data-start="1235" data-end="1243">订单服务</strong>。</p>
</li>
<li data-start="1247" data-end="1281">
<p data-start="1250" data-end="1281">订单服务内部调用 <strong data-start="1259" data-end="1267">库存服务</strong>,库存服务再操作数据库。</p>
</li>
<li data-start="1282" data-end="1319">
<p data-start="1285" data-end="1319">SkyWalking 会自动收集这些调用链路,生成 Trace。</p>
</li>
<li data-start="1320" data-end="1451">
<p data-start="1323" data-end="1341">在 UI 上能看到一条完整链路:</p>
</li>
</ol>
<p>Gateway(20ms) → OrderService(50ms) → StockService(80ms) → MySQL(30ms)</p>
<p>  如果某个环节出错,会显示红色告警。</p>
<h3>  (5)优势</h3>
<ul>
<li data-start="1472" data-end="1496">
<p data-start="1474" data-end="1496"><strong data-start="1474" data-end="1483">可观测性强</strong>:能精确定位系统瓶颈。</p>
</li>
<li data-start="1497" data-end="1526">
<p data-start="1499" data-end="1526"><strong data-start="1499" data-end="1507">零侵入性</strong>:不需要改业务代码,只需挂载探针。</p>
</li>
<li data-start="1527" data-end="1563">
<p data-start="1529" data-end="1563"><strong data-start="1529" data-end="1542">适合排查分布式问题</strong>:在复杂微服务环境下快速定位错误环节。</p>
</li>
</ul>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>参考:</p>
<p>https://www.b ilibili.com/video/BV1apr6YyEGg?spm_id_from=333.788.player.switch&amp;vd_source=99ec55b57f4eeedd9ed62c43e87cb6ff&amp;p=3</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/xiaoqian01/p/19083401
頁: [1]
查看完整版本: Spring篇知识点(4)