Spring Cloud&Spring Cloud Alibaba(一套标准化的微服务解决方案)
一、Spring Cloud基本介绍
1. 官方文档
2. 提出问题,引出微服务
- 思考一个问题,没有微服务技术,是不是程序员就不能开发大型项目?
- 是可以的,对大型项目进行模块划分,对各个模块进行实现,模块之间更多的是以API调用完成,耦合度较高,不利于扩展和维护(其实在以前没有微服务技术时,很多大型项目就已经使用了微服务的概念,只是当时不叫微服务)
- 再思考,标准的微服务解决方案(Spring Colud 和Cloud Alibaba)出现的原因和价值是什么
- ①微服务可以根据业务不同,将一个大项目,分解成不同的服务(微服务,比如搜索服务、网关服务、配置服务、存储服务、发现服务等等)②各个服务通过分布式方式进行工作,从而可以高效,快速,稳定的完成复杂的功能。③如果不理解,也可以理解成将原来的大项目的某些模块->抽出形成微服务->配合分布式工作方式->从而高效,快速,稳定的完成复杂业务功能
3.系统架构的演变过程
3.1 单机架构
3.2 动静分离架构:静态存储+文件存储
3.3 分布式架构:业务拆分+负载均衡
3.4 微服务架构:使用Spring Cloud
- ”微服务“一词源于Martin Fowler的名为Microservices的博文,简单地说,微服务是系统架构上的一种设计风格,它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务器都在各自独立的进程中运行,服务之间通过基于HTTP的RESTful API进行通信协作
- 被拆分成的每一个小型服务都围绕着系统中的某一项或一些耦合度较高的业务功能进行构建,并且每个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。由于有轻量级的通信协作基础,所以这些微服务可以使用不同的语言来编写
4. Spring Cloud全面说明
- Spring Cloud来源于Spring,是更高层次的、架构视角的综合性大型项目,目标旨在构建一套标准化的微服务解决方案,让架构师在使用微服务理念构建系统时,面对各个环节的问题都可以找到相应的组件来处理
- Spring Cloud是Spring社区为微服务架构提供的一个“全家桶”套餐。套餐中各个组件之间的配合,可以减少在组件的选型和整合上花费的精力,可以快速构建起基础的微服务架构系统,是微服务架构的最佳落地方案
- Spring Cloud天然支持Spring Boot(有版本对应要求),使用门槛较低
- 解决与分布式系统相关的复杂性-网络问题,延迟开销,带宽问题,安全问题
- 处理服务发现的能力-服务发现允许集群中的进程和服务找到彼此并进行通信
- 解决冗余问题-冗余问题经常发生在分布式系统重
- 解决负载平衡-改进跨多个计算资源(例如计算机集群,网络链接,中央处理单元)的工作负载分布
5. Spring Cloud核心组件图
5.1 Spring Cloud核心组件一览图

5.2 Spring Cloud Alibaba文档地址:https://github.com/alibaba/spring-cloud-alibaba
5.3 Spring Cloud Netflix地址:https://github.com/Netflix
5.4 Spring Cloud核心组件思维导图
6. Spring Cloud分布式示意图

- The distributed nature of microservices brings challenges. Spring helps you mitigate these. With several ready-to-run cloud patterns, Spring Cloud can help with service discovery, load-balancing, circuit-breaking, distributed tracing, and monitoring. It can even act as an API gateway.
- Spring Cloud是微服务的落地
- Spring Cloud体现了微服务的弹性设计
- 微服务的工作方式一般是基于分布式的
- Spring Cloud仍然是Spring家族一员,可以解决微服务的分布式工作方式带来的各种问题
- Spring Cloud提供很多组件,比如服务发现,负载均衡,链路中断,分布式跟踪和监控,甚至提供API gateway功能
6.1 Spring Cloud和Spring Boot版本对应关系
7. Spring Cloud组件选型
二、Spring Cloud Alibaba基本介绍
1. 官方文档
2. 分布式微服务技术选型
- Spring Cloud原生组件的几大痛点
- Spring Cloud 部分组件停止维护和更新,给开发带来不便
- Spring Cloud部分环境搭建复杂,没有完善的可视化界面,需要大量的二次开发和定制
- Spring Cloud配置复杂,难以上手
- Spring Cloud Alibaba的优势
- 阿里使用过的组件经历了考验【高并发、高性能、高可用】,性能强悍,设计合理,现在开源出来供大家使用
- 搭配完善的可视化界面给开发运维带来极大的便利搭建简单,学习曲线低
- 分布式微服务技术选型建议
- Spring Cloud Alibaba组件为主
- Spring Cloud为辅,比如(Spring Cloud-Ribbon:负载均衡、Spring Cloud-OpenFeign:调用远程服务、Spring Cloud-Gateway:API网关、Spring Cloud-Sleuth:调用链监控等)还是非常不错的。
三、微服务基础环境搭建
1. 创建父工程,用于聚合其它微服务模块
1.1 创建父项目,作为聚合其它微服务模块
1.2 项目设置
1.3 删除src,保留一个纯净环境
1.4 配置父工程pom.xml作为聚合其他模块
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
<!--表明是一个父工程,聚合管理其他模块-->
<packaging>pom</packaging>
<modules>
<module>member-service-provider-10000</module>
</modules>
<properties>
<junit.version>4.12</junit.version>
<!-- 使用最新版本的log4j , 防止安全漏洞-->
<log4j.version>2.17.2</log4j.version>
<lombok.version>1.18.20</lombok.version>
<mysql.version>8.0.26</mysql.version>
<druid.version>1.1.17</druid.version>
<mybatis.spring.boot.version>2.2.0</mybatis.spring.boot.version>
</properties>
<name>e-commerce-center</name>
<url>http://maven.apache.org</url>
<!--dependencyManagement 配置各个依赖和版本-->
<dependencyManagement>
<dependencies>
<!--配置Spring Boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<!--
解读:
1. type: pom 和 scope:import 配合使用
2. 表示 父项目的子模块, 在引入springboot 相关依赖时 锁定版本为2.2.2.RELEASE
3. 通过 pom+import 解决maven单继承机制
-->
<type>pom</type>
<scope>import</scope>
</dependency>
<!--配置Spring Cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--配置Cloud Alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--配置Mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--配置Druid数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!--配置Spring Boot整合mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<!--配置log4j-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!--配置Junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!--配置lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>e-commerce-center</finalName>
</build>
</project>
1.5 注意事项和细节
-
Maven使用dependencyManagement元素来提供了一种管理依赖版本号的方式。通常在项目packaging为POM,使用dependencyManadement元素
-
使用pom.xml中的dependencyManagement元素能让所有在子项目中引用一个依赖,Maven会沿着父子层次向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用这个dependencyManagement元素指定的版本号
-
好处:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,当升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要分别在子项目修改,另外如果某一个子项目需要另外的一个版本,只需要声明version就可以
-
dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖
-
如果不在子项目中声明依赖,是不会从父项目中继承下来的,只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且version和Scope都读取自父项目的pom文件
-
作用范围一览
Scope | 主代码 | 测试代码 | 打包 | 范例 |
---|---|---|---|---|
compile(默认) | Y | Y | Y | log4j |
test | Y | junit | ||
provided | Y | Y | serviet-api | |
runtime | Y | jdbc |
- 如果子项目中指定了版本号,那么会使用子项目中指定的jar版本
2. 创建会员中心微服务模块
2.1创建Module&完成配置
2.1.1 创建member-service-provider-10000微服务模块
2.1.2 修改member-service-provider-10000的pom.xml,加入相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-service-provider-10000</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入Spring Boot整合Mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--引入Druid场景驱动-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<!--引入Mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--引入spring-boot-starter-jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
2.1.3 创建resources/application.yml
server:
port: 10000
spring:
application:
name: member-service-provider-10000 #配置应用名称
datasource:
url: jdbc:mysql://localhost:3306/spring_boot?useSSL=true&userUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml #配置Mapper文件的文件扫描位置
type-aliases-package: com.leon.springcloud.entity #指定包类型别名,也就是javaBean所在的包
2.1.4 创建主启动类MemberApplication
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* ClassName:MemberApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/10 22:22
* @Version: 1.0
*/
@SpringBootApplication
public class MemberApplication {
public static void main(String[] args) {
SpringApplication.run(MemberApplication.class, args);
}
}
2.2 创建数据库
CREATE DATABASE e_commerce_center_db
USE e_commerce_center_db
CREATE TABLE `member`(
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` VARCHAR(64) COMMENT '用户名',
`pwd` VARCHAR(32) COMMENT '密码',
`mobile` VARCHAR(20) COMMENT '手机号码',
`email` VARCHAR(64) COMMENT '邮箱',
`gender` TINYINT COMMENT '性别',
PRIMARY KEY(id)
);
INSERT INTO member VALUES(NULL, 'smith', MD5('123'), '123456789000', 'smith@sohu.com', 1);
SELECT * FROM member
2.3 业务实现
2.3.1 entiity
package com.leon.springcloud.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* ClassName:Member
* Package:com.leon.springcloud.entity
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/11 9:57
* @Version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Member {
//id
private Long id;
//用户名
private String name;
//密码
private String pwd;
//电话
private String mobile;
//邮箱
private String email;
//性别
private Integer gender;
}
======================================================================================
package com.leon.springcloud.entity;
/**
* ClassName:Result
* Package:com.leon.springcloud.entity
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/11 10:00
* @Version: 1.0
*/
public class Result<T> {
//状态码
private Integer code;
//返回信息
private String message;
//返回的数据
private T data;
public Result() {
}
public Result(T data) {
this.data = data;
}
//请求成功后不返回数据
public static Result success() {
//创建返回对象
Result<Object> result = new Result<>();
//设置状态码
result.setCode(200);
//设置返回信息
result.setMessage("success");
return result;
}
//请求成功后返回数据
public static <T> Result<T> success(T data) {
//创建返回对象
Result<T> result = new Result<>(data);
//设置状态码
result.setCode(200);
//设置返回信息
result.setMessage("success");
return result;
}
//请求成功后返回数据
public static <T> Result<T> success(String message,T data) {
//创建返回对象
Result<T> result = new Result<>(data);
//设置状态码
result.setCode(200);
//设置返回信息
result.setMessage(message);
return result;
}
//请求失败后不返回数据
public static Result error(Integer code, String message) {
//创建返回对象
Result<Object> result = new Result<>();
//设置状态码
result.setCode(code);
//设置返回信息
result.setMessage(message);
return result;
}
//请求失败后返回数据
public static <T> Result<T> error(Integer code, String message, T data) {
//创建返回对象
Result<T> result = new Result<>(data);
//设置状态码
result.setCode(code);
//设置返回信息
result.setMessage(message);
return result;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
2.3.2 dao
package com.leon.springcloud.dao;
import com.leon.springcloud.entity.Member;
import org.apache.ibatis.annotations.Mapper;
/**
* ClassName:FurnMapper
* Package:com.leon.springcloud.dao
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/11 10:02
* @Version: 1.0
*/
@Mapper
public interface MemberMapper {
public Member queryMemberByFurnId(Long furnId);
public int insertMember(Member member);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.leon.springcloud.dao.MemberMapper">
<resultMap id="BaseResultMap" type="Member">
<result property="id" column="id" jdbcType="BIGINT"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="pwd" column="pwd" jdbcType="VARCHAR"/>
<result property="mobile" column="mobile" jdbcType="VARCHAR"/>
<result property="email" column="email" jdbcType="VARCHAR"/>
<result property="gender" column="gender" jdbcType="TINYINT"/>
</resultMap>
<select id="queryMemberByFurnId" parameterType="Long" resultMap="BaseResultMap">
select * from member where id = #{id}
</select>
<insert id="insertMember" parameterType="Member" useGeneratedKeys="true" keyProperty="id">
INSERT INTO member(name,pwd,mobile,email,gender)
VALUES(#{name}, MD5(#{pwd}), #{mobile}, #{email}, #{gender});
</insert>
</mapper>
2.3.3 service
package com.leon.springcloud.service;
import com.leon.springcloud.entity.Member;
/**
* ClassName:MemberService
* Package:com.leon.springcloud.service
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/11 10:17
* @Version: 1.0
*/
public interface MemberService {
public Member getMemberById(Long id);
public int saveMember(Member member);
}
package com.leon.springcloud.service.impl;
import com.leon.springcloud.dao.MemberMapper;
import com.leon.springcloud.entity.Member;
import com.leon.springcloud.service.MemberService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* ClassName:MemberServiceImpl
* Package:com.leon.springcloud.service.impl
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/11 10:18
* @Version: 1.0
*/
@Service
public class MemberServiceImpl implements MemberService {
@Resource
private MemberMapper memberMapper;
@Override
public Member getMemberById(Long id) {
return memberMapper.queryMemberByFurnId(id);
}
@Override
public int saveMember(Member member) {
return memberMapper.insertMember(member);
}
}
2.3.4 controller
package com.leon.springcloud.controller;
import com.leon.springcloud.entity.Member;
import com.leon.springcloud.entity.Result;
import com.leon.springcloud.service.MemberService;
import org.springframework.stereotype.Controller;
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.ResponseBody;
import javax.annotation.Resource;
/**
* ClassName:MemberController
* Package:com.leon.springcloud.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/11 10:19
* @Version: 1.0
*/
@Controller
public class MemberController {
@Resource
private MemberService memberService;
@GetMapping("/member/get/{id}")
@ResponseBody
public Result getMember(@PathVariable Long id) {
//查询
Member memberById = memberService.getMemberById(id);
//判断是否为空
if(memberById != null){
//返回成功信息
return Result.success(memberById);
}
//如果为空则查询失败
return Result.error(401,"查询失败",memberById);
}
@PostMapping("/member/save")
@ResponseBody
public Result saveMember(Member member) {
//保存信息
int effected = memberService.saveMember(member);
//判断是否保存成功
if(effected > 0){
//返回成功信息
return Result.success(effected);
}
//返回错误信息
return Result.error(401,"添加失败",null);
}
}
2.4 完成测试
package com.leon.springcloud;
import com.leon.springcloud.dao.MemberMapper;
import com.leon.springcloud.entity.Member;
import com.leon.springcloud.service.MemberService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
/**
* ClassName:MemberApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/11 10:25
* @Version: 1.0
*/
@SpringBootTest
public class MemberApplicationTest {
@Resource
private MemberService memberService;
@Resource
private MemberMapper memberMapper;
@Test
public void testService(){
//Member memberById = memberService.getMemberById(3L);
//
//System.out.println(memberById);
int effected = memberService.saveMember(new Member(null, "lili", "123", "13000000000", "tom@shouhu.com", 0));
System.out.println(effected);
}
@Test
public void testMapper(){
//Member member = memberMapper.queryMemberByFurnId(1L);
//
//System.out.println(member);
int effected = memberMapper.insertMember(new Member(null, "tom", "123", "13000000000", "tom@shouhu.com", 1));
System.out.println(effected);
}
}
2.5 注意事项和使用细节
- 前端如果是以JSON格式来发送请求添加信息,那么需要使用@RequestBody,才能将数据封装到对应的Bean,同时保证HTTP请求头的content-type是对应的
- 如果前端是以表单形式提交或是以parameter,则不需要使用@RequestBody,才会进行对对象数据封装,同时保证HTTP请求头的content-type是对应的
- 在进行Spring Boot应用程序测试时,引入的Junit是org.junit.juoiter.api.test包下的注解
- 运行程序时,一定要确保你的XxxMapper.xml文件被自动放到target目录下的classes指定的目录下
3.创建使用会员微服务模块
3.1 创建模块和完成配置
3.1.1 创建member-service-consumer-80微服务模块
3.1.2 修改member-service-consumer-80的pom.xml,加入相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-service-consumer-80</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
3.1.3 创建resources/application.yml
server:
port: 80
spring:
application:
name: member-service-consumer-80
3.1.4 创建主启动类
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
/**
* ClassName:MemberConsumerApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/12 19:52
* @Version: 1.0
*/
//加入排除DataSourceConfiguration自动配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class MemberConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(MemberConsumerApplication.class, args);
}
}
3.2 业务实现
3.2.1 创建entity
package com.leon.springcloud.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* ClassName:Member
* Package:com.leon.springcloud.entity
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/12 20:07
* @Version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Member {
//id
private Long id;
//用户名
private String name;
//密码
private String pwd;
//电话
private String mobile;
//邮箱
private String email;
//性别
private Integer gender;
}
package com.leon.springcloud.entity;
/**
* ClassName:Result
* Package:com.leon.springcloud.entity
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/12 20:09
* @Version: 1.0
*/
public class Result<T> {
//状态码
private Integer code;
//返回信息
private String message;
//返回的数据
private T data;
public Result() {
}
public Result(T data) {
this.data = data;
}
//请求成功后不返回数据
public static Result success() {
//创建返回对象
Result<Object> result = new Result<>();
//设置状态码
result.setCode(200);
//设置返回信息
result.setMessage("success");
return result;
}
//请求成功后返回数据
public static <T> Result<T> success(T data) {
//创建返回对象
Result<T> result = new Result<>(data);
//设置状态码
result.setCode(200);
//设置返回信息
result.setMessage("success");
return result;
}
//请求成功后返回数据
public static <T> Result<T> success(String message,T data) {
//创建返回对象
Result<T> result = new Result<>(data);
//设置状态码
result.setCode(200);
//设置返回信息
result.setMessage(message);
return result;
}
//请求失败后不返回数据
public static Result error(Integer code, String message) {
//创建返回对象
Result<Object> result = new Result<>();
//设置状态码
result.setCode(code);
//设置返回信息
result.setMessage(message);
return result;
}
//请求失败后返回数据
public static <T> Result<T> error(Integer code, String message, T data) {
//创建返回对象
Result<T> result = new Result<>(data);
//设置状态码
result.setCode(code);
//设置返回信息
result.setMessage(message);
return result;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
3.2.2 注入ResultTemplate
3.2.2.1 基本介绍
- RestTemplate是Spring提供的用于访问Rest服务的模板类
- RestTemplate提供了多种便捷访问远程HTTP服务的方法
- 说明
- 可以理解通过RestTemplate,我们可以发出http请求(支持Restful风格),去调用Controller提供的API接口,就像使用浏览器发出http请求,调用该API接口一样
3.2.2.2 官网和使用
3.2.2.3 配置RestTemplate
package com.leon.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* ClassName:MemberConsumerConfig
* Package:com.leon.springcloud.config
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/12 20:29
* @Version: 1.0
*/
@Configuration
public class MemberConsumerConfig {
@Bean
public RestTemplate restTemplate() {
//new一个RestTemplate对象并返回
return new RestTemplate();
}
}
3.2.3 创建Controller
3.3 完成测试
3.4 注意事项和使用细节
如果menber-service-consumer-80启动报错:Spring Boot 启动 if you want an embedded database(H2,HSQL or Derby),please put it on the classpath
package com.leon.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; /** * ClassName:MemberConsumerApplication * Package:com.leon.springcloud * Description: * * @Author: leon-->ZGJ * @Create: 2024/9/12 19:52 * @Version: 1.0 */ //加入排除DataSourceConfiguration自动配置 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class MemberConsumerApplication { public static void main(String[] args) { SpringApplication.run(MemberConsumerApplication.class, args); } }
添加会员数据库中为null的解决方案
3.5 开启RunBoard
3.5.1 介绍
当Spring Cloud的服务有多个时,管理多个服务的启动run很不好管理,这样就可以使用Run Dashboard
3.5.2 开启步骤
- 插入配置
<component name="RunDashboard">
<option name="configurationTypes">
<set>
<option value="SpringBootApplicationConfigurationType" />
</set>
</option>
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
- 插入位置演示
- 配置成功
4. 创建共用模块-供其他模块使用
4.1 创建Module&完成配置
4.1.1 创建e-commerce-center-common-api
4.1.2 修改e-commerce-center-common-api的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>e-commerce-center-common-api</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!--
解读<optional>true</optional>:
1. true 表示两个项目之间依赖不传递
2. 小伙伴可以这里理解<optional>true</optional>: 防止将该依赖传递到其他模块中
说的再具体一点,比如member-service-consumer-80模块依赖了本项目,
那么本项目不会把lombok 传递给 member-service-consumer-80
3. 不设置optional或者optional 是false,表示传递依
-->
<optional>true</optional>
</dependency>
</dependencies>
</project>
4.2 抽取共用API/类
package com.leon.springcloud.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* ClassName:Member
* Package:com.leon.springcloud.entity
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/13 22:15
* @Version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Member {
//id
private Long id;
//用户名
private String name;
//密码
private String pwd;
//电话
private String mobile;
//邮箱
private String email;
//性别
private Integer gender;
}
package com.leon.springcloud.entity;
/**
* ClassName:Result
* Package:com.leon.springcloud.entity
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/13 22:16
* @Version: 1.0
*/
public class Result<T> {
//状态码
private Integer code;
//返回信息
private String message;
//返回的数据
private T data;
public Result() {
}
public Result(T data) {
this.data = data;
}
//请求成功后不返回数据
public static Result success() {
//创建返回对象
Result<Object> result = new Result<>();
//设置状态码
result.setCode(200);
//设置返回信息
result.setMessage("success");
return result;
}
//请求成功后返回数据
public static <T> Result<T> success(T data) {
//创建返回对象
Result<T> result = new Result<>(data);
//设置状态码
result.setCode(200);
//设置返回信息
result.setMessage("success");
return result;
}
//请求成功后返回数据
public static <T> Result<T> success(String message,T data) {
//创建返回对象
Result<T> result = new Result<>(data);
//设置状态码
result.setCode(200);
//设置返回信息
result.setMessage(message);
return result;
}
//请求失败后不返回数据
public static Result error(Integer code, String message) {
//创建返回对象
Result<Object> result = new Result<>();
//设置状态码
result.setCode(code);
//设置返回信息
result.setMessage(message);
return result;
}
//请求失败后返回数据
public static <T> Result<T> error(Integer code, String message, T data) {
//创建返回对象
Result<T> result = new Result<>(data);
//设置状态码
result.setCode(code);
//设置返回信息
result.setMessage(message);
return result;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
4.3 使用Maven打包成jar
4.4 工程重构
4.4.1 在member-service-consumer-80引入e-commerce-center-common-api-1.0-SNAPSHOT.jar
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
4.4.2 在member-service-provider-10001引入e-commerce-center-common-api-1.0-SNAPSHOT.jar
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
四、Spring Cloud Eureka服务注册与发现
1.Eureka介绍
1.1 学Eureka前说明

- 从上图可以看出,目前主流服务注册&发现的组件是Nacos,但是Eureka作为一个老牌经典的服务注册&发现技术还是有必要学习一下原因:
- 一些早期的分布式-微服务项目使用的是Eureka,在工作中,完全有可能遇到这种情况
- 后期的服务注册&发现组件/技术,都参考了Eureka设计和理念,学习了Eureka后,我们上手Nacos容易很多,而且理解的更深刻
1.2 当前项目架构问题分析-引出Eureka
- 问题分析
- 在企业级项目中,服务消费访问请求会存在高并发
- 如果只有一个会议中心-提供服务,可用性差
- 所以,会议中心-提供服务往往是一个集群,也就是说会有多个会议中心-提供服务微服务模块
- 那么这个时候,就存在一个问题就是服务消费方,怎么去发现可以使用的服务
- 当服务消费方,发现了一个可以使用的服务后(可能是多个,又存在一个问题就是到底调用A服务还是B服务的问题,这就引出了服务注册和负载均衡)
- Eureka就可以解决上述问题
1.3 引入Eureka项目架构
- 解读上图
- 会员中心-提供服务,在项目中会做成集群,提供高可用
- Eureka Server有必要的话,也可以做成集群
- Eureka包含两个组件:Eureka Server和Eureka Client
- Eureka Server提供注册服务,各个服务节点通过配置启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到
- Eureka Client通过注册中心进行访问,是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)
1.4 服务治理介绍
- Eureka实现服务治理
- 在传统的rpc(Remote Procedure Call)远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理困难,所以需要使用服务治理服务之间依赖关系
- 服务治理实现服务调用、负载均衡、容错等,实现服务发现与注册
- 二说分布式开发:https://jingyan.baidu.com/article/46650658def479f549e5f83e.html
1.5 服务注册和发现
- Eureka采用了C/S的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。
- 系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接,通过Eureka Server来监控系统中的各个微服务是否正常运行
- 在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息,比如服务地址、通讯地址等以别名方式注册到注册中心上
- 服务消费者或者服务提供者,以服务别名的方式去注册中心上获取到实际的服务提供者通讯地址,然后通过RPC调用服务
2.创建单机Eureka Server-注册中心
2.1 创建Module&完成配置
2.1.1 创建e-commerce-eureka-server-9001微服务模块【作为注册中心】
2.1.2 修改e-commerce-eureka-server-9001的pom.xml,加入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>e-commerce-eureka-server-9001</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入eureka-server场景启动器starter-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2.1.3 创建resources/application.yml
server:
port: 9001
#配置Eureka-Server
eureka:
instance:
hostname: localhost #服务实例名s
client:
#配置不向注册中心注册自己
register-with-eureka: false
#表示自己就是注册中心,作用就是维护注册服务实例,不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互模块,查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
2.1.4 创建主启动类
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* ClassName:EurekaApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/14 15:32
* @Version: 1.0
*/
//@EnableEurekaServer 表示该程序作为Eureka Server
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
2.2 将member-service-provider-10000作为Eureka Client注册到e-commerce-eureka-server-9001成为服务提供者
2.2.1 架构示意图
2.2.2 修改member-service-provider-10000的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-service-provider-10001</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入Eureka-Client 场景启动器starter-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入Spring Boot整合Mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--引入Druid场景驱动-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<!--引入Mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--引入spring-boot-starter-jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2.2.3 修改member-service-provider-10000的resources/application.xml
server:
port: 10001
spring:
application:
name: member-service-provider-10001 #配置应用名称
datasource:
url: jdbc:mysql://localhost:3306/e_commerce_center_db?useSSL=true&userUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml #配置Mapper文件的文件扫描位置
type-aliases-package: com.leon.springcloud.entity #指定包类型别名,也就是javaBean所在的包
eureka:
client:
#将自己注册到Eureka-Server
register-with-eureka: true
#表示从Eureka-Server抓取信息
#如果是单节点,是可以不配置的,但是如果是一个集群,则必须配置true,才能配合Ribbon使用负载均衡
fetch-registry: true
#表示将自己注册到哪个Eureka-Server
service-url:
defaultZone: http://localhost:9001/eureka
2.2.4 member-service-provider-10000的MemberApplication.java
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* ClassName:MemberApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/10 22:22
* @Version: 1.0
*/
//@EnableEurekaClient 表示将该程序视为一个Eureka-Client
@EnableEurekaClient
@SpringBootApplication
public class MemberApplication {
public static void main(String[] args) {
SpringApplication.run(MemberApplication.class, args);
}
}
2.3 将member-service-consumer-80作为Eureka Client注册到e-commerce-eureka-server-9001成为服务提供者
2.3.1 架构示意图
2.3.2 修改member-service-consumer-80的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-service-consumer-80</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入Eureka-Server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2.3.3 修改member-service-consumer-80的resources/application.xml
server:
port: 80
spring:
application:
name: member-service-consumer-80
eureka:
client:
#将自己注册到Eureka-Server
register-with-eureka: true
#表示从Eureka-Server抓取信息
#如果是单节点,是可以不配置的,但是如果是一个集群,则必须配置true,才能配合Ribbon使用负载均衡
fetch-registry: true
#表示将自己注册到哪个Eureka-Server
service-url:
defaultZone: http://localhost:9001/eureka
2.3.4 member-service-consumer-80的MemberApplication.java
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* ClassName:MemberConsumerApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/12 19:52
* @Version: 1.0
*/
//@EnableEurekaClient 表示该程序视为一个Eureka-Client
@EnableEurekaClient
//加入排除DataSourceConfiguration自动配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class MemberConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(MemberConsumerApplication.class, args);
}
}
2.4 Server Consumer Server Provider Eureka Server的维护机制

2.5 Eureka自我保护模式
2.5.1 自我保护模式理论
在默认情况下,Eureka启动了自我保护模式(红色字体)
自我保护机制/模式说明
- 默认情况下Eureka Client定时向Eureka Server端发送心跳包
- 如果Eureka在Server端在一定时间内(默认90秒)没有收到Eureka Client发送的心跳包,便会直接从服务注册列表中剔除该模式(前提是关闭了:自我保护机制)
- 如果Eureka开启了自我保护模式/机制,那么在短时间(90秒)内丢失了大量的服务实例心跳,这个时候Eureka Server会开启自我保护机制,不会剔除该服务(该现象可能出现在网络不通或者阻塞)因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的
自我保护是属于CAP里面的AP分支,保证高可用和分区容错性
自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加健壮、稳定。参考网址:https://blog.csdn.net/wangliangluang/article/details/120626014
2.5.2 禁用自我保户模式(生产环境一般不禁用)
2.5.2.1 修改 e-commerce-eureka-server-9001的application.yml
server:
port: 9001
#配置Eureka-Server
eureka:
instance:
hostname: localhost #服务实例名s
client:
#配置不向注册中心注册自己
register-with-eureka: false
#表示自己就是注册中心,作用就是维护注册服务实例,不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互模块,查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
server:
#禁用自我保护模式
enable-self-preservation: false
#设置超时时间为2秒,在2秒钟收不到心跳包,就认为是超时
eviction-interval-timer-in-ms: 2000
2.5.2.2 修改member-service-provider-10000的application.yml
server:
port: 10001
spring:
application:
name: member-service-provider-10001 #配置应用名称
datasource:
url: jdbc:mysql://localhost:3306/e_commerce_center_db?useSSL=true&userUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml #配置Mapper文件的文件扫描位置
type-aliases-package: com.leon.springcloud.entity #指定包类型别名,也就是javaBean所在的包
eureka:
client:
#将自己注册到Eureka-Server
register-with-eureka: true
#表示从Eureka-Server抓取信息
#如果是单节点,是可以不配置的,但是如果是一个集群,则必须配置true,才能配合Ribbon使用负载均衡
fetch-registry: true
#表示将自己注册到哪个Eureka-Server
service-url:
defaultZone: http://localhost:9001/eureka
instance:
#客户端向服务端发送心跳的时间间隔1s(默认是30s)
lease-renewal-interval-in-seconds: 1
#服务端收到最后一次心跳后等待的时间上限
#时间单位(秒),默认是90秒,超时剔除服务
lease-expiration-duration-in-seconds: 2
3.搭建Eureka Server集群-实现负载均衡&故障容错
3.1 为什么需要集群Eureka Server
- 说明
- 微服务RPC远程服务调用最核心的是实现高可用
- 如果注册中心只有1个,它出故障,会导致整个服务环境不可用
- 解决办法:搭建Eureka注册中心集群,实现负载均衡+故障容错
3.2 需求分析/图解
3.3 搭建Eureka Server集群
3.3.1 创建e-commerce-eureka-server-9002微服务模块[作为注册中心]
3.3.1.1 创建e-commerce-eureka-server-9002
3.3.1.2 修改创建e-commerce-eureka-server-9002的pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>e-commerce-eureka-server-9002</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入eureka-server场景启动器starter-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
3.3.1.4 创建resources/application.yml
server:
port: 9002
#配置Eureka-Server
eureka:
instance:
hostname: eureka9002.com #服务实例名s
client:
#配置不向注册中心注册自己
register-with-eureka: false
#表示自己就是注册中心,作用就是维护注册服务实例,不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互模块,查询服务和注册服务都需要依赖这个地址
defaultZone: http://eureka9001.com:9001/eureka
3.3.2 修改e-commerce-eureka-server-9001微服务模块
server:
port: 9001
#配置Eureka-Server
eureka:
instance:
hostname: eureka9001.com #服务实例名s
client:
#配置不向注册中心注册自己
register-with-eureka: false
#表示自己就是注册中心,作用就是维护注册服务实例,不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互模块,查询服务和注册服务都需要依赖这个地址
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
defaultZone: http://eureka9002.com:9002/eureka
# server:
# #禁用自我保护模式
# enable-self-preservation: false
# #设置超时时间为2秒,在2秒钟收不到心跳包,就认为是超时
# eviction-interval-timer-in-ms: 2000
3.3.3 修改Host文件
文件位置:C:\Windows\System32\drivers\etc
把文件复制到桌面,然后再进行修改,再把文件复制到之前的目录进行覆盖
加入内容:
#配置Eureka主机和IP地址的映射 127.0.0.1 eureka9001.com 127.0.0.1 eureka9002.com
3.3.4 将member-service-provider-10000注册到Eureka Server集群
server:
port: 10001
spring:
application:
name: member-service-provider-10001 #配置应用名称
datasource:
url: jdbc:mysql://localhost:3306/e_commerce_center_db?useSSL=true&userUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml #配置Mapper文件的文件扫描位置
type-aliases-package: com.leon.springcloud.entity #指定包类型别名,也就是javaBean所在的包
eureka:
client:
#将自己注册到Eureka-Server
register-with-eureka: true
#表示从Eureka-Server抓取信息
#如果是单节点,是可以不配置的,但是如果是一个集群,则必须配置true,才能配合Ribbon使用负载均衡
fetch-registry: true
#表示将自己注册到哪个Eureka-Server
service-url:
defaultZone: http://eureka9001.com:9001/eureka,http://eureka9002.com:9002/eureka
# instance:
# #客户端向服务端发送心跳的时间间隔1s(默认是30s)
# lease-renewal-interval-in-seconds: 1
# #服务端收到最后一次心跳后等待的时间上限
# #时间单位(秒),默认是90秒,超时剔除服务
# lease-expiration-duration-in-seconds: 2
3.3.5 将member-service-consumer-80注册到Eureka Server集群
server:
port: 80
spring:
application:
name: member-service-consumer-80
eureka:
client:
#将自己注册到Eureka-Server
register-with-eureka: true
#表示从Eureka-Server抓取信息
#如果是单节点,是可以不配置的,但是如果是一个集群,则必须配置true,才能配合Ribbon使用负载均衡
fetch-registry: true
#表示将自己注册到哪个Eureka-Server
service-url:
defaultZone: http://eureka9001.com:9001/eureka,http://eureka9002.com:9002/eureka
3.4 搭建会员中心服务提供方-集群
3.4.1 架构示意图
3.4.2 创建member-service-provider-10002
- 参考member-service-provider-10000来创建member-service-provider-10002即可
- 创建好后,使用member-service-provider-10000的源码和配置替换member-service-provider-10002生成的代码
- 不要到磁盘整体拷贝,会出现关联到member-service-provider-10000的问题,很麻烦,可以创建好新项目的包,然后拷贝对应的文件,就不会出现问题
- 提醒,拷贝时不要忘记拷贝resources/mapper/MemberMapper.xml这些xxx.xml文件
3.4.3 创建resources/application.yml
server:
port: 10002
spring:
application:
name: member-service-provider #配置应用名称
datasource:
url: jdbc:mysql://localhost:3306/e_commerce_center_db?useSSL=true&userUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml #配置Mapper文件的文件扫描位置
type-aliases-package: com.leon.springcloud.entity #指定包类型别名,也就是javaBean所在的包
eureka:
client:
#将自己注册到Eureka-Server
register-with-eureka: true
#表示从Eureka-Server抓取信息
#如果是单节点,是可以不配置的,但是如果是一个集群,则必须配置true,才能配合Ribbon使用负载均衡
fetch-registry: true
#表示将自己注册到哪个Eureka-Server
service-url:
defaultZone: http://eureka9001.com:9001/eureka,http://eureka9002.com:9002/eureka
# instance:
# #客户端向服务端发送心跳的时间间隔1s(默认是30s)
# lease-renewal-interval-in-seconds: 1
# #服务端收到最后一次心跳后等待的时间上限
# #时间单位(秒),默认是90秒,超时剔除服务
# lease-expiration-duration-in-seconds: 2
3.4.4 创建主启动类
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* ClassName:MemberApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/10 22:22
* @Version: 1.0
*/
//@EnableEurekaClient 表示将该程序视为一个Eureka-Client
@EnableEurekaClient
@SpringBootApplication
public class MemberApplication10002 {
public static void main(String[] args) {
SpringApplication.run(MemberApplication10002.class, args);
}
}
3.4.5 注意事项和细节
- 因为member-service-provider-10000和因为member-service-provider-10002作为一个集群提供服务,因此需要将spring.application.name进行统一,这样消费方通过统一的别名进行负载均衡调用
3.5 配置服务消费端member-service-consumer使用会员中心服务集群
3.5.1 架构图
3.5.2 修改MemberConsumerController.java
package com.leon.springcloud.controller;
import com.leon.springcloud.entity.Member;
import com.leon.springcloud.entity.Result;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
/**
* ClassName:MemberConsumerController
* Package:com.leon.springcloud.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/12 20:31
* @Version: 1.0
*/
@RestController
public class MemberConsumerController {
@Resource
private RestTemplate restTemplate;
/*
* 1. MEMBER-SERVICE-PROVIDER就是服务提供方【集群】,注册到Eureka Server的名称
* 2. 也就是服务提供方【集群】对外暴露的名称为MEMBER-SERVICE-PROVIDER的程序
* 3. MEMBER-SERVICE-PROVIDER 目前两个Availability Zones member-service-provider:10000,member-service-provider:10002
* 4. 需要增加一个注解@LoadBalanced赋予RestTemplate负载均衡的能力,也就是会根据你的负载均衡算法
* 来选择某个服务去访问,默认是轮询算法,当然我们也可以自己配置负载均衡算法
* */
//定义MEMBER_SERVICE_PROVIDER_URL 这个基础url地址
private static final String MEMBER_SERVICE_PROVIDER_URL
= "http://MEMBER-SERVICE-PROVIDER";
@GetMapping("/member/consumer/get/{id}")
public Result<Member> getMember(@PathVariable("id") Long id) {
/*
* 1. MEMBER_SERVICE_PROVIDER_URL +"/member/get/"+ id 是表示完整的url
* 2. Result.class 是表示请求返回的数据类型
* */
return restTemplate.getForObject(MEMBER_SERVICE_PROVIDER_URL +"/member/get/"+ id, Result.class);
}
@PostMapping("/member/consumer/save")
public Result<Member> saveMember( Member member) {
/*
* 1. MEMBER_SERVICE_PROVIDER_URL +"/member/get/"+ id 是表示完整的url
* 2. member 是表示携带的数据
* 3. Result.class 是表示请求返回的数据类型
* */
return restTemplate.postForObject(MEMBER_SERVICE_PROVIDER_URL+"/member/save", member, Result.class);
}
}
3.5.3 修改CustomizationBean.java
package com.leon.springcloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* ClassName:MemberConsumerConfig
* Package:com.leon.springcloud.config
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/12 20:29
* @Version: 1.0
*/
@Configuration
public class MemberConsumerConfig {
@Bean
/*
* 1. @LoadBalanced注解标识赋予RestTemplate 负载均衡的能力
* 2. 默认是使用轮询算法来访问远程调用接口/地址
* */
@LoadBalanced
public RestTemplate restTemplate() {
//new一个RestTemplate对象并返回
return new RestTemplate();
}
}
3.5.4 交替访问member服务说明
- 注解@LoadBalanced底层是Ribbon支持算法
- Ribbon和Eureka整合后consumer直接调用服务而不用再关心地址和端口号,且该服务还有负载均衡功能
3.6 获取Eureka Server 服务注册信息-DiscoveryClient
3.6.1 需求分析/图解
-
这里以服务消费方,去获取Eureka Server 的服务注册信息为例
-
当然也可以在服务提供方获取Eureka Server的服务注册信息
3.6.2 代码实现
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* ClassName:MemberConsumerApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/12 19:52
* @Version: 1.0
*/
//@EnableEurekaClient 表示该程序视为一个Eureka-Client
@EnableEurekaClient
//加入排除DataSourceConfiguration自动配置
//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@SpringBootApplication
//启用服务发现
@EnableDiscoveryClient
public class MemberConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(MemberConsumerApplication.class, args);
}
}
package com.leon.springcloud.controller;
import com.leon.springcloud.entity.Member;
import com.leon.springcloud.entity.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.List;
/**
* ClassName:MemberConsumerController
* Package:com.leon.springcloud.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/12 20:31
* @Version: 1.0
*/
@RestController
@Slf4j
public class MemberConsumerController {
@Resource
private DiscoveryClient discoveryClient;
@Resource
private RestTemplate restTemplate;
/*
* 1. MEMBER-SERVICE-PROVIDER就是服务提供方【集群】,注册到Eureka Server的名称
* 2. 也就是服务提供方【集群】对外暴露的名称为MEMBER-SERVICE-PROVIDER的程序
* 3. MEMBER-SERVICE-PROVIDER 目前两个Availability Zones member-service-provider:10000,member-service-provider:10002
* 4. 需要增加一个注解@LoadBalanced赋予RestTemplate负载均衡的能力,也就是会根据你的负载均衡算法
* 来选择某个服务去访问,默认是轮询算法,当然我们也可以自己配置负载均衡算法
* */
//定义MEMBER_SERVICE_PROVIDER_URL 这个基础url地址
private static final String MEMBER_SERVICE_PROVIDER_URL
= "http://MEMBER-SERVICE-PROVIDER";
/*
* 1.produces = "application/json;charset=utf-8" 指定返回格式,因为引入Eureka Server
* 关联引入了xml格式转换的依赖,所以需要指定一下,返回格式是json
* */
@GetMapping(value = "/member/consumer/get/{id}",produces = "application/json;charset=utf-8")
public Result<Member> getMember(@PathVariable("id") Long id) {
/*
* 1. MEMBER_SERVICE_PROVIDER_URL +"/member/get/"+ id 是表示完整的url
* 2. Result.class 是表示请求返回的数据类型
* */
return restTemplate.getForObject(MEMBER_SERVICE_PROVIDER_URL +"/member/get/"+ id, Result.class);
}
@PostMapping("/member/consumer/save")
public Result<Member> saveMember( Member member) {
/*
* 1. MEMBER_SERVICE_PROVIDER_URL +"/member/get/"+ id 是表示完整的url
* 2. member 是表示携带的数据
* 3. Result.class 是表示请求返回的数据类型
* */
return restTemplate.postForObject(MEMBER_SERVICE_PROVIDER_URL+"/member/save", member, Result.class);
}
@GetMapping(value = "/member/consumer/discovery")
public Object discovery() {
//获取服务名称
List<String> services = discoveryClient.getServices();
//循环获取服务名称
for (String service : services) {
System.out.println("==========================" +service+ "==========================");
//获取服务信息
List<ServiceInstance> instances = discoveryClient.getInstances(service);
//循环迭代服务信息
for (ServiceInstance instance : instances) {
//获取服务id
System.out.print("id="+instance.getServiceId());
//获取服务主机
System.out.print(" host="+instance.getHost());
//获取服务端口
System.out.print(" port="+instance.getPort());
//获取服务的URI
System.out.println(" url="+instance.getUri());
}
}
return discoveryClient;
}
}
3.6.3 注意事项和细节
- 在引入DiscoveryClient时,不要引入错误的包
- 正确的包:import org.springframework.cloud.client.discovery.DiscoveryClient;
- 错误的包:import com.netfilx.discovery.DiscoveryClient;
- 在服务消费方使用DiscoveryClient来完成服务发现,在服务提供方/模块也可以
4. Eureka后续说明
五、Spring Cloud Ribbon
1. Ribbon介绍
1.1 Ribbon是什么
- Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端,负载均衡的工具
- Ribbon主要功能是提供客户端负载均衡算法和服务调用
- Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。
- Ribbon会基于某种规则(如简单轮询,随机连接等)去连接指定服务
- 程序员很容易使用Ribbon的负载均衡算法实现负载均衡
- 一句话:Ribbon:负载均衡+RestTemplate调用
1.2 官网
1.3 Ribbon进入维护状态
- Ribbon目前进入维护模式,未来替代方案是Spring Cloud LoadBalancer
1.4 LB(Load Balance)
- 集中式LB
- 即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,比如F5,也可以是软件,如nginx),由该设施负责把访问的请求通过某种策略转发至服务的提供方:
- LB(Load Balance 负载均衡)
- 进程内LB
- 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后再从这些地址中选择出一个合适的服务地址
- Ribbon就是属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址
2. Ribbon原理
2.1 Ribbon架构图&机制
2.2 Ribbon常见负载算法
策略名 | 描述 |
---|---|
BestAvailableRule | 选择一个最小的并发请求的Server。逐个考察Server,如果Server被tripped(跳闸)了,则忽略,再选择其中ActiveRequestCount最小的Server |
AvailabilityFilteringRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server(Active Connections超时配置的阈值) |
WeightedResponseTimeRule | 根据响应时间分配一个weight,响应时间越长,weight越小,选中的可能性越低 |
RetryRule | 对选定的负载均衡策略加上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的Server |
RoundRobinRule | 轮询index,选择inde对应位置的Server |
RandomRule | 随机选择一个Server。在index上随机,选择index对应位置的Server |
ZoneAvoidanceRule | 复合判断Server所在区域性能和Server的可用性选择Server |
3. 替换负载均衡算法-应用实例
package com.leon.springcloud.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* ClassName:RibbonRule
* Package:com.leon.springcloud.config
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/16 21:06
* @Version: 1.0
*/
@Configuration
public class RibbonRule {
//配置注入自己的负载均衡算法
@Bean
public IRule myRibbonRule() {
//指定一个随机算法
//return new RandomRule();
return new RoundRobinRule();
}
}
package com.leon.springcloud;
import com.leon.springcloud.config.RibbonRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
/**
* ClassName:MemberConsumerApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/12 19:52
* @Version: 1.0
*/
//@EnableEurekaClient 表示该程序视为一个Eureka-Client
@EnableEurekaClient
//加入排除DataSourceConfiguration自动配置
//@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@SpringBootApplication
//启用服务发现
@EnableDiscoveryClient
//指定Ribbon的负载均衡算法
@RibbonClient(name = "MEMBER-SERVICE-PROVIDER",configuration = RibbonRule.class)
public class MemberConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(MemberConsumerApplication.class, args);
}
}
六、Spring Cloud OpenFeign
1. OpenFeign介绍
1.1 OpenFeign是什么
- OpenFeign是一个声明式WebService客户端,使用OpenFeign让编写WebService客户端更简单
- 它的使用方法是定义一个服务接口然后在上面添加注解
- OpenFeign也支持可插拔式的编码器和解码器
- Spring Cloud对OpenFeign进行了封装使其支持了Spring MVC标准注解和HttpMessageConverters
- OpenFeign可以与Eureka和Ribbon组合使用以支持负载均衡
1.2官网
1.3 Feign和OpenFeign区别
Feign
Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端
Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务
Feign的使用方式是:使用Feign的注解定义接口,调用服务注册中心的服务
Feign支持的注解和用法请参考官方文档:https://github.com/OpenFeign/feign
引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency>
OpenFeign
OpenFeign是Spring Cloud在Feign的基础上支持了Spring MVC的注解,如RequestMapping等等。
OpenFeign的@FeignClient注解可以解析Spring MVC的@RequestMapping注解下的接口
OpenFeign通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务
引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
精简一句话:OpenFeign就是Feign基础上做了加强,有些程序员为了方便,说Feign就是指OpenFeign
2. OpenFeign-应用实例
2.1 需求分析/图解
2.2 创建服务消费模块-通过OpenFeign实现远程调用
2.2.1 创建 member-service-consumer-openfeign-80
2.2.2 修改member-service-consumer-openfeign-80的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-service-consumer-openfeign-80</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入OpenFeign场景驱动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--引入Eureka-Server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2.2.3 创建resource/application.yml
server:
port: 80
spring:
application:
name: member-service-consumer-openfeign-80
eureka:
client:
#将自己注册到Eureka-Server
register-with-eureka: true
#表示从Eureka-Server抓取信息
#如果是单节点,是可以不配置的,但是如果是一个集群,则必须配置true,才能配合Ribbon使用负载均衡
fetch-registry: true
#表示将自己注册到哪个Eureka-Server
service-url:
defaultZone: http://eureka9001.com:9001/eureka,http://eureka9002.com:9002/eureka
2.2.4 创建controller
package com.leon.springcloud.controller;
import com.leon.springcloud.entity.Result;
import com.leon.springcloud.service.MemberFeignService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* ClassName:MemberConsumerFeignController
* Package:com.leon.springcloud.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/17 10:20
* @Version: 1.0
*/
@RestController
public class MemberConsumerFeignController {
@Resource
private MemberFeignService memberFeignService;
@GetMapping(value = "/member/consumer/openfeign/get/{id}",produces = "application/json;charset=utf-8")
public Result getMemberById(@PathVariable Long id){
return memberFeignService.getMember(id);
}
}
2.2.5 创建service
package com.leon.springcloud.service;
import com.leon.springcloud.entity.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* ClassName:MemberService
* Package:com.leon.springcloud.service
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/17 10:08
* @Version: 1.0
*/
@Component
@FeignClient(value = "MEMBER-SERVICE-PROVIDER")
public interface MemberFeignService {
/*
* 1.@GetMapping 表示远程调用的方式是Get
* 2.@FeignClient+/member/get/{id} 表示远程调用的URL http://MEMBER-SERVICE-PROVIDER/member/get/{id}
* 3.MEMBER-SERVICE-PROVIDER 就是服务提供方在Eureka Server注册的服务
* 4.OpenFeign 会根据负载均衡来决定调用10000/10002-默认轮询
* 5.@PathVariable Long id 表示传递的参数
* 6.因为OpenFeign好处是支持了springmvc注解+接口解构
* */
@GetMapping("/member/get/{id}")
public Result getMember(@PathVariable("id") Long id);
}
2.2.6 创建调用接口
package com.leon.springcloud.service;
import com.leon.springcloud.entity.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* ClassName:MemberService
* Package:com.leon.springcloud.service
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/17 10:08
* @Version: 1.0
*/
@Component
@FeignClient(value = "MEMBER-SERVICE-PROVIDER")
public interface MemberFeignService {
/*
* 1.@GetMapping 表示远程调用的方式是Get
* 2.@FeignClient+/member/get/{id} 表示远程调用的URL http://MEMBER-SERVICE-PROVIDER/member/get/{id}
* 3.MEMBER-SERVICE-PROVIDER 就是服务提供方在Eureka Server注册的服务
* 4.OpenFeign 会根据负载均衡来决定调用10000/10002-默认轮询
* 5.@PathVariable Long id 表示传递的参数
* 6.因为OpenFeign好处是支持了springmvc注解+接口解构
* */
@GetMapping("/member/get/{id}")
public Result getMember(@PathVariable("id") Long id);
}
2.3 注意事项和细节
-
配置OpenFeign的使用特点是微服务调用接口+@FeignClient,使用接口进行解耦
-
@FeignClient(value="MEMBER-SERVICE-PROVIDER"),这里MEMBER-SERVICE-PROVIDER就是Eureka Server中的提供方注册的名称,不要写错了
-
接口方法上:value是不能乱写,远程调用的url为http://MEMBER-SERVICE-PROVIDER/member/get/{id}
@GetMapping("/member/get/{id}") public Result getMember(@PathVariable("id") Long id);
3.日志配置
3.1 基本介绍
- 说明:Feign提供了日志打印打印功能,可以通过配置来调整日志级别,从而对Feign接口的调用情况进行监控和输出
- 日志级别
- NONE:默认的,不显示任何日志
- BASIC:仅记录请求方法、URL、响应状态码及执行时间
- HEADERS:除了BASIC中定义的信息之外,还有请求和响应头信息
- FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
3.2 Spring Boot中application.yml的日志配置
- 常见的日志级别有5种,分别是:error、warn、info、debug、trace
- error:错误日志,指比较严重的错误,对正常业务有影响,需要运维配置监控的
- warn:警告日志,一般的错误,对业务影响不大,但是需要开发关注
- info:信息日志,记录排查问题的关键信息,如调用时间、出参入参等等
- debug:用于开发DEBUG的,关键逻辑里面的运行时数据
- trace:最详细的信息,一般这些信息只记录到日志文件中
3.3 配置日志-应用实例
3.3.1 创建配置类
package com.leon.springcloud.openfeign;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* ClassName:OPenFeignConfig
* Package:com.leon.springcloud.openfeign
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/17 11:01
* @Version: 1.0
*/
@Configuration
public class OPenFeignConfig {
@Bean
public Logger.Level loggerLevel(){
//返回日志级别
return Logger.Level.FULL;
}
}
3.3.2 配置application.yml
#配置输出日志
logging:
level:
com.leon.springcloud.service.MemberFeignService: debug
4. OpenFeign超时
4.1 先看一个问题
- 如果模拟超时,则会报错,因为OpenFeign默认超时时间是1秒,这里模拟一下
//模拟超时时间
try {
TimeUnit.MILLISECONDS.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
4.2 设置超时时间
4.2.1 配置application.yml文件
ribbon:
#1.设置Feign客户端超时时间(OpenFeign默认支持Ribbon)
#2.ReadTimeout:8000 建立连接从服务提供方获取可用资源的所用的全部时间
#3.时间单位是毫秒
ReadTimeout: 8000
#两端连接所用时间
ConnectionTimeout: 8000
七、Spring Cloud Gateway
1. Gateway介绍
1.1 看一个需求,引出网关服务
1.1.1 有一个前后端分离项目,分析
1.1.2 使用网关服务,重构项目架构
1.2 Gateway网络拓扑图
1.3 Gateway是什么
- GateWay是在Spring生态系统之上构建的API网关服务,基于Spring,Spring Boot和Project Reactor等技术
- Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等
1.4 官网
1.5 Gateway核心功能
- 鉴权
- 流量控制
- 熔断
- 日志监控
- 反向代理
1.6 Gateway VS Zuul
1.6.1 Gateway和Zuul区别
- Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Zuul
- Spring Cloud Gateway是基于Spring WebFlux框架实现的
- Spring WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty,提升了网关性能
1.6.2 Gateway特性
- Spring Cloud Gateway基于Spring Framework(支持Spring WebFlux),Project Reactor和Spring Boot进行构建,具有如下特性:
- 动态路由
- 可以对路由指定Predicate(断言)和Filter(过滤器)
- 集成Spring Cloud服务发现功能
- 请求限流功能
- 支持路径重写
2. Gateway基本原理
2.1 Gateway核心组件
2.1.1 一张图
2.1.2 Route(路由)
- 路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
2.1.3 Predicate(断言)
-
一句话:对HTTP请求中的所有内容(例如请求头或请求参数)进行匹配,如果请求与断言相匹配则进行路由
-
简单举例,比如配置路径
- Path=/member/get/** #断言,路径相匹配的进行路由转发,如果HTTP请求的路径不匹配,则不进行路由
2.1.4 Filter(过滤)
-
一句话:使用过滤器,可以在请求被路由前或之后对请求进行处理
-
你可以理解成,在对HTTP请求断言匹配成功后,可以通过网关的过滤机制,对HTTP请求处理
-
简单举例:
filters: - AddRequestParameter=color, blue #过滤器在匹配的请求头加上一对请求头,被称为color值为blue ,比如原来的HTTP请求是http://localhost:10000/member/get/1 == 过滤处理=>htpp://localhost:10000/member/get/1?color=bule
2.2 How It Works工作机制
- 梳理流程(How to work)
- 客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到Gateway Web Handler
- Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回
- 过滤器之间用虚线分开是因为过滤器可能会在发送请求之前(“Pre”)或之后(“Post”)执行业务逻辑
- Filter 在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等
- 在“post”类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等有着非常重要的作用。
- 一句话说:路由转发+执行过滤器链
3.搭建Gateway微服务
3.1 搭建Gateway-应用实例
3.1.1 需求分析/图解
- 通过网关暴露的接口,实现调用真正的服务
- 网关本身也是一个微服务模块
3.1.2 代码实现
3.1.2.1 创建member-gateway-20000
3.1.2.2 修改pom.xml,部分内容可以从member-service-consumer-80的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-gateway-20000</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入gateway-starter,网关场景启动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--引入Eureka-Server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--
1.不要引入spring-boot-starter-web和spring-boot-starter-actuator否则会出现冲突,然后报错
2.因为Gateway是一个服务网关,不需要web....
-->
<!--引入Spring-Boot-web场景驱动器 -->
<!--<dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!--</dependency>-->
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<!--<dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-actuator</artifactId>-->
<!--</dependency>-->
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
3.1.2.3 创建并配置resources/application.yml文件
server:
port: 20000
spring:
application:
name: member-service-gateway
#配置Gateway网关服务
cloud:
gateway:
#配置网关路由,可以配置多个路由,对应的映射方法的形参为:List<RouteDefinition> routes
#也就是说路由是一个集合,所以要使用配置集合的方式进配置
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
uri: http://localhost:10001
#配置断言,可以有多种形式
predicates:
- Path=/member/get/**
- id: member_service01
uri: http://localhost:10001
predicates:
- Path=/member/save
eureka:
instance:
hostname: e-member-service
client:
#将自己注册到Eureka-Server
register-with-eureka: true
#表示从Eureka-Server抓取信息
#如果是单节点,是可以不配置的,但是如果是一个集群,则必须配置true,才能配合Ribbon使用负载均衡
fetch-registry: true
#表示将自己注册到哪个Eureka-Server
service-url:
defaultZone: http://eureka9001.com:9001/eureka,http://eureka9002.com:9002/eureka
3.1.2.4 创建主启动类
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* ClassName:GatewayApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/20 14:41
* @Version: 1.0
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableEurekaClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3.1.3 注意事项和细节
-
因为我们的member的controller的方法使用了@RequestBody注解,所以请求时需要使用JSON格式传输数据
@PostMapping("/member/save") @ResponseBody public Result saveMember(@RequestBody Member member) { //保存信息 int effected = memberService.saveMember(member); //判断是否保存成功 if(effected > 0){ //返回成功信息 return Result.success(effected); } //返回错误信息 return Result.error(401,"添加失败",null); }
3.2 二说Gateway路由配置
3.2.1 方式一:application.yml中配置,了解看3.1
3.2.2 编写配置类注入【了解】
package com.leon.springcloud.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* ClassName:GatewayRouteConfig
* Package:com.leon.springcloud.config
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/20 15:39
* @Version: 1.0
*/
@Configuration
public class GatewayRouteConfig {
@Bean
public RouteLocator memberService04(RouteLocatorBuilder builder) {
//通过RouteLocatorBuilder获取RouteLocatorBuilder.Builder对象
RouteLocatorBuilder.Builder routes = builder.routes();
//配置路由以及断言
/*
* 1. "member_service04" 路由的ID
* 2. Function<PredicateSpec, Route.AsyncBuilder> fn 函数式接口
* 2.1 接收类型是PredicateSpec,返回类型是Route.AsyncBuilder
* 2.2 r->r.path("/member/get/**").uri("http://localhost:10001")).build() 是lambda表达式
* */
return routes.route("member_service04",r->r.path("/member/get/**").
uri("http://localhost:10001")).build();
}
@Bean
public RouteLocator memberService05(RouteLocatorBuilder builder) {
//通过RouteLocatorBuilder获取RouteLocatorBuilder.Builder对象
RouteLocatorBuilder.Builder routes = builder.routes();
//配置路由以及断言
/*
* 1. "member_service04" 路由的ID
* 2. Function<PredicateSpec, Route.AsyncBuilder> fn 函数式接口
* 2.1 接收类型是PredicateSpec,返回类型是Route.AsyncBuilder
* 2.2 r->r.path("/member/get/**").uri("http://localhost:10001")).build() 是lambda表达式
* */
return routes.route("member_service05",r->r.path("/member/save").
uri("http://localhost:10001")).build();
}
}
3.3 动态路由
3.3.1 需求分析/图解
3.3.2 代码实现-配置application.yaml
server:
port: 20000
spring:
application:
name: member-service-gateway
#配置Gateway网关服务
cloud:
gateway:
discovery:
locator:
#表示启用服务发现
enabled: true
#配置网关路由,可以配置多个路由,对应的映射方法的形参为:List<RouteDefinition> routes
#也就是说路由是一个集合,所以要使用配置集合的方式进配置
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
#uri: http://localhost:10001
#lb 是协议名表示:load balance 也就是负载均衡
#member-service-provider 表示注册到Eureka Server的服务名(小写)
uri: lb://member-service-provider
#配置断言,可以有多种形式
predicates:
- Path=/member/get/**
- id: member_service01
uri: lb://member-service-provider
predicates:
- Path=/member/save
eureka:
instance:
hostname: e-member-service
client:
#将自己注册到Eureka-Server
register-with-eureka: true
#表示从Eureka-Server抓取信息
#如果是单节点,是可以不配置的,但是如果是一个集群,则必须配置true,才能配合Ribbon使用负载均衡
fetch-registry: true
#表示将自己注册到哪个Eureka-Server
service-url:
defaultZone: http://eureka9001.com:9001/eureka,http://eureka9002.com:9002/eureka
3.3.3 注意事项和细节
-
配置好动态路由后Gateway会根据注册中心上微服务名,为请求创建动态路由,实现动态路由功能
-
使用的lb协议支持负载均衡-默认为轮询算法
-
配置自己的负载均衡算法
package com.leon.springcloud.config; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import com.netflix.loadbalancer.RoundRobinRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * ClassName:RibbonRule * Package:com.leon.springcloud.config * Description: * * @Author: leon-->ZGJ * @Create: 2024/9/16 21:06 * @Version: 1.0 */ @Configuration public class RibbonRule { //配置注入自己的负载均衡算法 @Bean public IRule myRibbonRule() { //指定一个随机算法 return new RandomRule(); //return new RoundRobinRule(); } }
4.Predicate/断言
4.1 基本介绍
- 一句话:Predicate就是一组匹配规则,当请求匹配成功,就执行对应的Route,匹配失败,放弃处理/转发
4.1.1 Route Predicate Factories
-
-
Spring Cloud Gateway包括许多内置的Route Predicate工厂,所有这些Predicate都与HTTP请求的不同属性匹配,可以组合使用
-
Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route
-
所有这些谓词都匹配HTTP请求的不同属性,多种谓词工厂可以组合
4.2 Route Predicate 和实例
4.2.1 After Route Predicate
- 需求:只有2022-11-18 12:35:50 之后的请求才进行匹配/转发,不满足该条件的,不处理
cloud:
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
#uri: http://localhost:10001
#lb 是协议名表示:load balance 也就是负载均衡
#member-service-provider 表示注册到Eureka Server的服务名(小写)
uri: lb://member-service-provider
#配置断言,可以有多种形式
predicates:
- Path=/member/get/**
- After=2024-11-18T12:35:50.000+08:00[Asia/Shanghai]
4.2.2 Before Route Predicate
- 需求:只有2022-11-18 12:35:50 之前的请求才进行匹配/转发,不满足该条件的,不处理
cloud:
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
#uri: http://localhost:10001
#lb 是协议名表示:load balance 也就是负载均衡
#member-service-provider 表示注册到Eureka Server的服务名(小写)
uri: lb://member-service-provider
#配置断言,可以有多种形式
predicates:
- Path=/member/get/**
- Before=2024-11-18T12:35:50.000+08:00[Asia/Shanghai]
4.2.3 Between Route Predicate
- 需求:只有2022-01-18 12:35:50和2022-11-18 12:35:50 之间的请求才进行匹配/转发,不满足该条件的,不处理
cloud:
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
#uri: http://localhost:10001
#lb 是协议名表示:load balance 也就是负载均衡
#member-service-provider 表示注册到Eureka Server的服务名(小写)
uri: lb://member-service-provider
#配置断言,可以有多种形式
predicates:
- Path=/member/get/**
- Between=2024-10-18T12:35:50.000+08:00[Asia/Shanghai], 2024-11-18T12:35:50.000+08:00[Asia/Shanghai]
4.2.4 Cookie Route Predicate
- 需求:请求带有cookie键:user 值:hsp才匹配/断言成功
cloud:
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
#uri: http://localhost:10001
#lb 是协议名表示:load balance 也就是负载均衡
#member-service-provider 表示注册到Eureka Server的服务名(小写)
uri: lb://member-service-provider
#配置断言,可以有多种形式
predicates:
- Path=/member/get/**
- Cookie=user, hsp
4.2.5 Header Route Predicate
- 需求:请求头Headers有X-Request-Id,并且值为hello才匹配/断言成功
cloud:
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
#uri: http://localhost:10001
#lb 是协议名表示:load balance 也就是负载均衡
#member-service-provider 表示注册到Eureka Server的服务名(小写)
uri: lb://member-service-provider
#配置断言,可以有多种形式
predicates:
- Path=/member/get/**
#值支持正则表达式
- Header=X-Request-Id, hello
4.2.6 Host Route Predicate
- 需求:请求Host是* * .leon. * *才匹配/断言成功,比如www.leon.com
cloud:
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
#uri: http://localhost:10001
#lb 是协议名表示:load balance 也就是负载均衡
#member-service-provider 表示注册到Eureka Server的服务名(小写)
uri: lb://member-service-provider
#配置断言,可以有多种形式
predicates:
- Path=/member/get/**
#host可以配置多个
- Host=**.leon.**, **.hspedu.**
4.2.7 Method Route Predicate
- 需求:请求是Get方式才匹配/断言成功
cloud:
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
#uri: http://localhost:10001
#lb 是协议名表示:load balance 也就是负载均衡
#member-service-provider 表示注册到Eureka Server的服务名(小写)
uri: lb://member-service-provider
#配置断言,可以有多种形式
predicates:
- Path=/member/get/**
#请求方式可以多个,使用逗号隔开,表示或
- Method=Get, Post
4.2.8 Path Route Predicate
cloud:
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
#uri: http://localhost:10001
#lb 是协议名表示:load balance 也就是负载均衡
#member-service-provider 表示注册到Eureka Server的服务名(小写)
uri: lb://member-service-provider
#配置断言,可以有多种形式
predicates:
#- Path=/member/get/**
- Path=/member/get/**,/member/save
4.2.9 Query Route Predicate
- 需求:请求参数email,并且满足电子邮件的基本格式,才能匹配/断言成功
cloud:
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
#uri: http://localhost:10001
#lb 是协议名表示:load balance 也就是负载均衡
#member-service-provider 表示注册到Eureka Server的服务名(小写)
uri: lb://member-service-provider
#配置断言,可以有多种形式
predicates:
- Path=/member/get/**
- Query=email, [\w-]+@([a-zA-Z]+\.)+[a-zA-z]+
4.2.10 RemoteAddr Route Predicate
- 需求:请求的IP是127.0.0.1,才匹配/断言成功
cloud:
routes:
#配置路由的ID或名称,要求唯一
- id: member_service01
#配置URI,gateway最终访问的url,Url=uri+Path
#匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com
#比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1
#如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1
#如果匹配失败,则有gateway返回404的错误信心页
#uri: http://localhost:10001
#lb 是协议名表示:load balance 也就是负载均衡
#member-service-provider 表示注册到Eureka Server的服务名(小写)
uri: lb://member-service-provider
#配置断言,可以有多种形式
predicates:
- Path=/member/get/**
#使用的是IPV6的地址
- RemoteAddr=0:0:0:0:0:0:0:1
4.2.11 weight Route Predicate
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
5.Filter过滤器
5.1 基本介绍
- 官网网址:https://docs.spring.io/spring-cloud-gateway/docs/3.0.8/reference/html/#gatewayfilter-factories
- 路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应
- Spring Cloud Gateway内置了很多种路由过滤器,他们都由GatewayFilter的工厂类来产生
5.2 类型
5.2.1 GatewayFilter
5.2.2 GlobalFilter
5.3 GatewayFilter使用
-
开发直接使用GatewayFilter较少,一般是自定义过滤器,配置yml文件
#配置Gateway网关服务 cloud: gateway: discovery: locator: #表示启用服务发现 enabled: true #配置网关路由,可以配置多个路由,对应的映射方法的形参为:List<RouteDefinition> routes #也就是说路由是一个集合,所以要使用配置集合的方式进配置 routes: #配置路由的ID或名称,要求唯一 - id: member_service01 #配置URI,gateway最终访问的url,Url=uri+Path #匹配后提供服务的路由地址:也可以是外网地址例如:htt://www.baidu.com #比如:客户端/浏览器请求 Url:http://localhsot:20000/member/get/1 #如果根据Path匹配成功,最终访问的URL就是url=http://localhost:10001/member/get/1 #如果匹配失败,则有gateway返回404的错误信心页 #uri: http://localhost:10001 #lb 是协议名表示:load balance 也就是负载均衡 #member-service-provider 表示注册到Eureka Server的服务名(小写) uri: lb://member-service-provider #配置断言,可以有多种形式 predicates: - Path=/member/get/** #可以设置多个 filters: - AddRequestParameter=color, blue - AddRequestParameter=addr, beijing
5.4 自定义ClobalFilter
5.4.1 需求
- 自定义一个全局GlobalFilter过滤器
- 如果请求参数user=hspedu,pwd=123456则放行,否则不能通过验证
5.4.2 代码实现
-
创建一个自定义的全局异常类:GlobalGatewayFilter
package com.leon.springcloud.filter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * ClassName:GlobalGatewayFilter * Package:com.leon.springcloud.filter * Description: * * @Author: leon-->ZGJ * @Create: 2024/9/20 20:56 * @Version: 1.0 */ @Component public class GlobalGatewayFilter implements GlobalFilter, Ordered { /* * 1.filter 是核心的方法,将我们的过滤的业务,写在该方法中 * * */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //获取到对应的参数值 String user = exchange.getRequest(). getQueryParams().getFirst("user"); String pwd = exchange.getRequest(). getQueryParams().getFirst("pwd"); //判断是否符合要求 if(!("hspedu".equals(user)&&"123456".equals(pwd))){ //进行响应 exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); return exchange.getResponse().setComplete(); } //将ServerWebExchange对象传递给下一Filter return chain.filter(exchange); } /* * 1.order表示过滤器执行的顺序,数字越小,优先级越高 * * */ @Override public int getOrder() { return 0; } }
八、Spring cloud Sleuth+Zipkin
1. Sleuth+Zipkin基础
1.1 官网
1.2 Sleuth+Zipkin是什么
1.2.1 概述
-
在微服务框架中,一个由客户发起的请求在后端系统中会经过多个不同的服务节点调用,来协同产生最后的请求结果,每一个请求都会形成一条复杂的分布式服务调用链路
-
链路中的任何一环出现高延时或错误都会引起整个请求最后的失败,因此对整个服务的调用进行链路追踪和分析就非常的重要
-
Sleuth和Zipkin的简单关系图
1.2.2 一句话:Sleuth提供了一套完整的服务跟踪的解决方案并兼容Zipkin
1.2.3 Sleuth做链路跟踪,Zipkin做数据收集/存储/可视化
1.3 Sleuth工作原理
-
Span和Trace在一个系统重使用Zipkin的过程-图形化
-
梳理
- 表示一请求链路,一条链路通过Trace Id 唯一标识,Span标识发起的请求信息,各Span通过parent id关联起来
- Trace:类似于树结构Span集合,表示一条调用链路,存在唯一标识
- Span:基本工作单元,表示调用链路来源,通俗的理解Span就是一次请求信息
-
Spans的parent/child关系图形化
2.Sleuth+Zipkin-搭建链路监控实例
2.1 安装/使用Zipkin
2.1.1 下载
2.1.2 运行
-
把zipkin-server-2.12.9-exec.jar放到指定的目录
-
在存放目录的导航地址输入cmd或直接通过指令方式切换到存放目录,然后执行运行指令:java -jar zipkin-server-2.12.9-exec.jar
2.1.3 访问
-
默认端口是9411,访问IP由部署或安装的主机IP为访问IP
2.2 服务提供微服务集成Sleuth/Zipkin
2.2.1 修改pom.xml文件添加Sleuth+Zipkin的依赖
<!--引入Sleuth+zipkin依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
2.2.2 修改application.yml文件添加Sleuth+Zipkin的配置
spring:
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
#配置采样率,在0-1之间,1表示全部采集,一般是填1
probability: 1
2.3 服务消费微服务集成Sleuth/Zipkin
2.3.1 修改pom.xml文件添加Sleuth+Zipkin的依赖
<!--引入Sleuth+zipkin依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
2.3.2 修改application.yml文件添加Sleuth+Zipkin的配置
spring:
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
#配置采样率,在0-1之间,1表示全部采集,一般是填1
probability: 1
2.4 查看监控以及结果分析
- 选择一个服务看结果
- 查看一次调用链路的深度,以及该链路包含请求,各个请求耗时,找到请求瓶颈,为优化提供依据(重要)
九、Spring Cloud Alibaba Nacos
1. Nacos 基础
1.1 官网
1.2 Nacos是什么
- 一句话:Nacos就是注册中心【替代Eureka】+配置中心【替代Config】
- Nacos:Dynamic Naming and Configuration Service
- Nacos:架构理论基础:CPA理论(支持AP和CP,可以切换)
1.3 Nacos下载和运行
- 下载网址:https://github.com/alibaba/nacos/releases/tag/1.2.1
- 环境要求:Java8以上/Maven3.2.x+
- 运行bin/startup.cmd
- 默认端口为8848
- 用户名/密码 为 nacos
2. 创建Nacos服务提供者
2.1 需求说明/图解
2.2 创建member-service-nacos-provider-10004并注册到NacosServer8848
2.2.1 创建member-service-nacos-provider-10004
-
参考member-service-nacos-provider-10000来创建member-service-nacos-provider-10004
-
创建好后,使用member-service-nacos-provider-10000的源码和配置替换member-service-nacos-provider-10004生成的代码
-
提醒,拷贝时不要忘记拷贝resources/mapper/MemberMapper.xml这些xxx.xml文件
2.2.3 修改本模块pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-service-nacos-provider-10004</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入Nacos场景启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入Spring Boot整合Mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--引入Druid场景驱动-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<!--引入Mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--引入spring-boot-starter-jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2.2.4 创建application.yml
server:
port: 10004
spring:
application:
name: member-service-nacos-provider #配置应用名称
datasource:
url: jdbc:mysql://localhost:3306/e_commerce_center_db?useSSL=true&userUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
#配置Nacos
cloud:
nacos:
discovery:
#配置Nacos Server的地址
server-addr: localhost:8848
#配置暴露所有的监控点
management:
endpoints:
web:
exposure:
include: '*'
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml #配置Mapper文件的文件扫描位置
type-aliases-package: com.leon.springcloud.entity #指定包类型别名,也就是javaBean所在的包
2.2.5 创建主启动类
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* ClassName:MemberNacosServerApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/24 15:07
* @Version: 1.0
*/
//@EnableDiscoveryClient 表示引入Nacos发现注解
@EnableDiscoveryClient
@SpringBootApplication
public class MemberNacosProviderApplication {
public static void main(String[] args) {
SpringApplication.run(MemberNacosProviderApplication.class,args);
}
}
2.3 创建member-service-nacos-provider-10006 并注册到NacosServer8848
2.3.1 创建member-service-nacos-provider-10006
2.3.3 修改本模块pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-service-nacos-provider-10006</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入Nacos场景启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入Spring Boot整合Mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--引入Druid场景驱动-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<!--引入Mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--引入spring-boot-starter-jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2.3.4 创建application.yml
server:
port: 10006
spring:
application:
name: member-service-nacos-provider #配置应用名称
datasource:
url: jdbc:mysql://localhost:3306/e_commerce_center_db?useSSL=true&userUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
#配置Nacos
cloud:
nacos:
discovery:
#配置Nacos Server的地址
server-addr: localhost:8848
#配置暴露所有的监控点
management:
endpoints:
web:
exposure:
include: '*'
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml #配置Mapper文件的文件扫描位置
type-aliases-package: com.leon.springcloud.entity #指定包类型别名,也就是javaBean所在的包
2.3.5 创建主启动类
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* ClassName:MemberNacosServerApplication
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/24 15:07
* @Version: 1.0
*/
//@EnableDiscoveryClient 表示引入Nacos发现注解
@EnableDiscoveryClient
@SpringBootApplication
public class MemberNacosProviderApplication10006 {
public static void main(String[] args) {
SpringApplication.run(MemberNacosProviderApplication10006.class,args);
}
}
3. 创建Nacos的服务消费者
3.1 需求说明/图解
3.2 创建member-service-nacos-consumer-80并注册到NacosServer8848
3.2.1 创建member-service-nacos-consumer-80
3.2.2 修改pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-service-nacos-consumer-80</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入alibaba-Nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
3.2.3 创建application.yml
server:
port: 80
spring:
application:
name: member-server-nacos-consumer-80
#配置Nacos
cloud:
nacos:
discovery:
#配置Nacos Server地址
server-addr: localhost:8848
3.2.4 创建主启动类
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* ClassName:MemberNacosConsumerApplication80
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/24 15:45
* @Version: 1.0
*/
@SpringBootApplication
//@EnableDiscoveryClient表示引入的是启动Nacos发现注解
@EnableDiscoveryClient
public class MemberNacosConsumerApplication80 {
public static void main(String[] args) {
SpringApplication.run(MemberNacosConsumerApplication80.class,args);
}
}
3.2.5 创建业务类
- MemberConsumerConfig.java
package com.leon.springcloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* ClassName:MemberConsumerConfig
* Package:com.leon.springcloud.config
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/12 20:29
* @Version: 1.0
*/
@Configuration
public class MemberConsumerConfig {
@Bean
/*
* 1. @LoadBalanced注解标识赋予RestTemplate 负载均衡的能力
* 2. 默认是使用轮询算法来访问远程调用接口/地址
* */
@LoadBalanced
public RestTemplate restTemplate() {
//new一个RestTemplate对象并返回
return new RestTemplate();
}
}
- MemberNacosConsumerController.java
package com.leon.springcloud.controller;
import com.leon.springcloud.entity.Member;
import com.leon.springcloud.entity.Result;
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.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
/**
* ClassName:MemberConsumerController
* Package:com.leon.springcloud.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/24 15:50
* @Version: 1.0
*/
@RestController
public class MemberNacosConsumerController {
/*
* 1.member-service-nacos-provider就是服务提供
* 方注册到Nacos Server的服务名,这里是小写,不可以大写否则会报错
* 2.
* */
private static final String MEMBER_SERVICE_NACOS_PROVIDER_URL=
"http://member-service-nacos-provider";
@Resource
private RestTemplate restTemplate;
@GetMapping(value = "/member/nacos/consumer/get/{id}",produces = "application/json;charset=utf-8")
public Result<Member> getMember(@PathVariable("id") Long id) {
return restTemplate.getForObject(MEMBER_SERVICE_NACOS_PROVIDER_URL +"/member/get/"+ id, Result.class);
}
@PostMapping("/member/nacos/consumer/save")
public Result<Member> saveMember( Member member) {
return restTemplate.postForObject(MEMBER_SERVICE_NACOS_PROVIDER_URL+"/member/save", member, Result.class);
}
}
3.2.6 修改负载均衡算法
package com.leon.springcloud.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* ClassName:RibbonRule
* Package:com.leon.springcloud.config
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/16 21:06
* @Version: 1.0
*/
@Configuration
public class RibbonRule {
//配置注入自己的负载均衡算法
@Bean
public IRule myRibbonRule() {
//指定一个随机算法
return new RandomRule();
//return new RoundRobinRule();
}
}
4. Nacos AP和CP切换
4.1 各种注册中心对比
4.2 选择AP还是CP?
- CP:服务可以不能用,但必须要保证数据的一致性
- AP:数据可以短暂不一致,但最终是需要一致的,无论如何都要保证服务的可用
- 取舍:只能在CP和AP选择一个平衡点,大多数都是选择AP模式
4.3 AP和CP切换
4.3.1 说明
- Nacos集群默认支持的是CAP原则中的AP原则,但是也可以切换为CP原则(一般不切换)
- CURL切换命令:curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP
- URL指令:$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP
4.3.2 参考
5. Nacos配置中心实例
5.1 需求分析/图解
5.2 在Nacos Server加入配置
-
进入Nacos Server
-
加入配置,特别提醒:文件后缀.yaml
5.3 创建Nacos配置客户端模块member-nacos-config-client-6000
5.3.1 创建Module
5.3.2 修改pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-nacos-config-client-6000</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入Nacos-config场景启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--引入alibaba-Nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
5.3.3 创建application.yml
spring:
profiles:
#指定环境,常见的环境dev[开发环境]/test[测试环境]/prod[生产环境]
active: dev
5.3.4 创建bootstrap.yml
server:
port: 6000
spring:
application:
#这个里配置的应用程序名字要参考,在Nacos 配置中心的Data Id
#当然也可以与Nacos配置中心的Data Id不一致,需要手动配置获取文件的文件名称前缀
name: e-commerce-nacos-config-client
#配置Nacos
cloud:
nacos:
discovery:
#配置服务注册中心地址
server-addr: localhost:8848
config:
#配置配置中心地址
server-addr: localhost:8848
#指定获取的配置文件的格式
file-extension: yaml
#解读
#1.Nacos 配置客户端/当前的微服务模块,会根据配置,找到配置中心的数据(配置文件)
#2.根据配置:config.server-addr: localhost:8848 可以找到配置中心
#3.spring.application.name 默认情况是与对应的是配置中心的 Data Id:e-commerce-nacos-config-client
#4.在application.yml 中配置的spring.profiles.active: dev 最后会同bootstrap.yml
# 配置文件中的配置结合使用,且bootstrap.yml加载优先于application.yml
#5.spring.cloud.nacos.config.file-extension 是表示配置配置中心要获取的配置文件的后缀名(文件类型)为.yml
#6.小结:根据配置,就是到ip为localhost:8848的配置中心,获取e-commerce-nacos-config-client-dev.yml的配置数据/信息
#7.默认的获取规则为:${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
5.3.5 创建主启动类
package com.leon.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* ClassName:MemberNacosConfigApplication6000
* Package:com.leon.springcloud
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/24 17:23
* @Version: 1.0
*/
@EnableDiscoveryClient
@SpringBootApplication
public class MemberNacosConfigApplication6000 {
public static void main(String[] args) {
SpringApplication.run(MemberNacosConfigApplication6000.class, args);
}
}
5.3.6 创建业务类
package com.leon.springcloud.controller;
import com.leon.springcloud.entity.Result;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* ClassName:MemberNacosConfigController
* Package:com.leon.springcloud.controller
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/9/24 17:25
* @Version: 1.0
*/
@RestController
public class MemberNacosConfigController {
/*
* 解读
* 1.client会拉去Nacos Server的 e-commerce-nacos-config-client-dev.yaml
* config:
ip: "128.8.8.8"
nanm: "leon_study"
* 2.@Value("${config.ip}") 表示会将config.ip复制给ip
* 3.{config.ip}不可以胡乱书写,要有依据不然会报错
* */
@Value("${config.ip}")
private String ip;
@Value("${config.name}")
private String name;
@GetMapping("/member/config/ip")
public Result getIp(){
return Result.success(ip);
}
@GetMapping("/member/config/name")
public Result getName(){
return Result.success(name);
}
}
5.3.7 注意事项和细节
-
@Value("${config.ip}") 是import org.springframework.beans.factory.annotation.Value包下,而不是lombok包下
-
配置文件application.yml和bootstrap.yml结合会得到配置文件/资源的地址
-
参考文档:https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html
-
注意在Nacos Server的配置文件的后缀是.yaml,而不是.yml
-
在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动,也就是说如果项目不能正确的获取到Nacos Server的配置数据,项目是启动不了的
-
Spring Boot中配置文件的加载是存在优先级顺序的,bootstrap.yml优先级高于application.yml
-
@RefreshScope是Spring Cloud原生注解,实现配置信息自动刷新,如果在Nacos Server修改了配置数据,Client端就会得到最新的配置
6. Nacos分类配置(实现配置隔离)
6.1 Data Id方案
6.1.1 需求分析/图解
6.1.2 解决方案分析
- 使用Data Id 方案解决
6.1.3 配置实现
- 在Nacos配置中心新创建一个e-commerce-nacos-config-client-test.yaml然后进行读取
6.1.4 修改application.yml
spring:
profiles:
#指定环境,常见的环境dev[开发环境]/test[测试环境]/prod[生产环境]
active: test
6.2 Group方案
6.2.1 需求分析/图解
6.2.2 解决方案分析
- 使用Group方案解决
6.2.3 配置实现
- 在Nacos配置中心,新创建order和seckill分组,然后创建一个e-commerce-nacos-config-client-dev.yaml配置文件
6.2.4 修改application.yml
spring:
profiles:
#指定环境,常见的环境dev[开发环境]/test[测试环境]/prod[生产环境]
active: dev
6.2.5 修改bootstrap.yml
spring:
application:
#这个里配置的应用程序名字要参考,在Nacos 配置中心的Data Id
#当然也可以与Nacos配置中心的Data Id不一致,需要手动配置获取文件的文件名称前缀
name: e-commerce-nacos-config-client
#配置Nacos
cloud:
nacos:
discovery:
#配置服务注册中心地址
server-addr: localhost:8848
config:
#配置配置中心地址
server-addr: localhost:8848
#指定获取的配置文件的格式
file-extension: yaml
#配置组名
group: seckill
6.3 Namepace方案
6.3.1 需求分析/图解
6.3.2 解决方案分析
- 使用NameSpace方案解决
6.3.3 配置实现
- 在Nacos配置中心创建两个NamSpace,在baidu的NameSpace中创建一个search分组,然后在分组中创建e-commerce-nacos-config-client-dev.yaml配置文件,再alibaba的NameSpace中创建一个order分组,然后在分组中创建e-commerce-nacos-config-client-dev.yaml配置文件
6.3.4 修改application.yml
spring:
profiles:
#指定环境,常见的环境dev[开发环境]/test[测试环境]/prod[生产环境]
active: dev
6.3.5 修改bootstrap.yml
server:
port: 7000
spring:
application:
#这个里配置的应用程序名字要参考,在Nacos 配置中心的Data Id
#当然也可以与Nacos配置中心的Data Id不一致,需要手动配置获取文件的文件名称前缀
name: e-commerce-nacos-config-client
#配置Nacos
cloud:
nacos:
discovery:
#配置服务注册中心地址
server-addr: localhost:8848
config:
#配置配置中心地址
server-addr: localhost:8848
#指定获取的配置文件的格式
file-extension: yaml
#配置组名
group: search
#配置NameSpace的id
namespace: 2458f863-49d0-4403-a89c-aa736215829a
6.4 Namepace/Group/Data Id关系
6.4.1 图解
6.4.2 详细介绍
-
Namepace/Group/Data Id关系
-
梳理
- Nacos默认的命名空间是public,NameSpace主要用来实现配置隔离,隔离范围大
- Group默认是DEFFAULT GROUP,Group可以把不同的微服务划分到同一个分组里去
- Service就是微服务,相同的Service可以是一个Cluster(簇/集群),Instance就是微服务的实例
十、Spring Cloud Alibaba Sentinel
1. Sentinel基础
1.1 官网
- Github:https://github.com/alibaba/Sentinel
- 快速开始:https://sentinelguard.io/zh-cn/docs/quick-start.html
- 中文手册:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
- 使用手册:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel
1.2 Sentinel是什么
1.2.1 Sentinel概述
-
Sentinel是什么
- 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel以流量为切入点,从流量控制、熔断降级、系统负载均衡保护等多个维度保护服务的稳定性。
-
Sentinel的主要特征
- 梳理:Sentinel可以完成的功能:绿色方框列出的部分
1.2.2 一句话:Sentinel:分布式系统的流量防卫兵,保护你的微服务
1.3 Sentinel核心功能
- 流量控制
- 熔断降级
- 在调用系统的时候,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积
- 解读
- 熔断降级可以解决这个问题,所谓的熔断降级就是当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障
- 系统负载保护
- 根据系统能够处理的请求,和允许进来的请求,来做平衡,追求的目标是在系统不被拖垮的情况下,提高系统的吞吐率
- 消息削峰填谷
- 某瞬时来了大流量的请求,而如果此时要处理所有请求,很可能会导致系统负载过高,影响稳定性。但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没有充分利用系统处理消息的能力
- Sentinel的Rate Limiter模式能在某一段时间间隔内以匀速方式处理这样的请求,充分利用系统的处理能力,也就是削峰填谷,保证资源的稳定性
1.4 Sentinel两个组成部分
- 核心库:(java客户端)不依赖任何框架/库,能够运行在所有java运行时环境,对Spring Cloud有较好的支持
- 控制台:(Dashboard)基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器
2. Sentinel控制台
2.1 需求分析/图解
- 需求:搭建Sentinel控制台,用于显示各个微服务的使用情况
2.2 下载
2.3 运行
- 指令:java -jar sentinel-dashboard-1.8.8.jar
- 注意:Sentinel控制台默认端口是8080
2.4 访问
- 浏览器访问:http://主机ip:8080
- 登录账号密码都是sentinel
2.5 注意事项和细节
- 更改控制台的端口号
- 指令:java-jar sentinel-dashboard-1.8.0.jar--server.port=指定的端口号
3. Sentinel控制微服务
3.1 需求分析/图解
- 需求:使用Sentinel控制台对member-service-nacos-provider-10004微服务进行实时监控
- 当调用member-service-nacos-provider-10004微服务是,可以监控到请求的url/QPS/响应时间/流量
3.2 代码/配置实现
3.2.1 修改pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>member-service-nacos-provider-10004</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--引入Sentinel starter场景启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--引入Nacos场景启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入Spring-Boot-web场景驱动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测
可以通过:https://localhost:10000/actuator 看到相关的连接和信息
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--引入Spring Boot整合Mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--引入Druid场景驱动-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<!--引入Mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--引入spring-boot-starter-jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--引入spring-boot-starter-test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--引入API-->
<dependency>
<groupId>com.leon.springcloud</groupId>
<artifactId>e-commerce-center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
3.2.2 修改application.yml文件
spring:
application:
name: member-service-nacos-provider #配置应用名称
cloud:
sentinel:
transport:
#配置指定Sentinel控制台的IP地址和端口号
dashboard: localhost:8080
#解读transport.port
#1.transport.port端口配置会在被监控的微服务对应的主机上启动 Http Server
#2.该Http Server会与Sentinel控制台进行交互
#3.比如Sentinel 控制台添加了一个限流规则,会把规则数据push给这个Http Server接收,Http Server再将这个规则注册到被监控的微服务的Sentinel中
#4.简单的讲:transport.port指定被监控的微服务应用于Sentinel控制台交互端口
#5.默认端口是8719,假如被占用了,就会自动的从8719开始依次+1扫描,知道找到一个没有被占用的端口
port: 8719
3.4 注意事项和细节
- QPS:Queries Per Second(每秒查询率/每秒钟请求数量),是服务器每秒响应的查询次数
- Sentinel采用的是懒加载,只有调用了某个接口/服务,才能看到监控数据
4. Sentinel流量控制
4.1 规则
-
先看一张图
-
对上图解读
- 资源名:唯一名称,默认请求路径
- 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认Default(不区分来源)
- 阈值类型/单机阈值:
- QPS(每秒钟的请求数量):当调用该API的QPS达到阈值的时候,进行限流
- 线程数:当调用该API的线程数达到阈值的时候,进行限流
-
解读QPS和线程数的区别,比如QPS和线程数设置阈值为1
- 对QPS而言,如果1秒内,客户端发出了2次请求,就达到阈值,从而限流
- 对线程数而言,如果在1秒内,客户端发出了2次请求,不一定达到线程限制的阈值,为什么呢?假设1次请求后台会创建一个线程,但是这个请求完成的时间是0.1秒(可以视为该请求对应的线程存活0.1秒),所以当客户端第二次请求时(比如客户端是在0.3秒发出的),这时第1个请求的线程就已经结束了,因此就没有达到线程的阈值,也不会限流
- 可以这样理解,如果1个请求对应的线程平均执行时间为0.1,那么相当于QPS为10
- 是否集群:不需要集群
- 流控模式:
- 直接:API达到限流条件时,直接限流
- 关联:当关联的资源达到阈值时,就限流自己
- 链路:当从某个接口过来的资源达到限流条件时,开启限流
- 流控效果:
- 快速失败:直接失败,抛异常
- Warm up:根据codeFactor(冷加载因子,默认3秒)的值,从阈值/codeFactor,经过预热时长,才到设置的QPS阈值
- 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效
4.2 流量控制实例-QPS
4.2.1 需求分析
- 需求:通过Sentinel实现流量控制
- 当调用member-service-nacos-provider-10004的/member/get/接口/API时,限制1秒内最多访问1次,否则直接失败,抛异常
4.2.2 配置实现步骤
- 添加流控规则
-
设置参数
4.2.3 注意事项和细节
-
流量规则改动,实时生效,不需要重启微服务,Sentinel控制台
-
在Sentinel配置流量规则时,如何配置通配符问题,比如/member/get/1和/member/get/2统一使用一个规则
-
方案1:在Sentinel中/member/get/?id=1和/member/get?id=2被统一认为是/member/get,所以只要对/member/get/限流就可以了
@GetMapping(value = "/member/get",params = {"id"}) @ResponseBody public Result getMember( Long id) { //查询 Member memberById = memberService.getMemberById(id); //判断是否为空 if(memberById != null){ //返回成功信息 //return Result.success("member-service-provider:10001 " + color + "="+ addr,memberById); return Result.success("member-service-nacos-provider:10004 ",memberById); } //如果为空则查询失败 return Result.error(401,"查询失败",memberById); }
-
方案2:URL资源清洗,可以通过UrlCleaner接口来实现资源清洗,也就是对于/member/get/{id}这个URL,可以统一归集至/member/get/*资源下,具体配置代码如下,实现UrlCleaner接口,并重写clean方法即可
package com.leon.springcloud.controller; import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Component; /** * ClassName:ConsumerUrlCleaner * Package:com.leon.springcloud.controller * Description: * * @Author: leon-->ZGJ * @Create: 2024/9/25 20:28 * @Version: 1.0 */ @Component public class ConsumerUrlCleaner implements UrlCleaner { /* * 1. originUrl是请求过来的url,也就是:http://localhost:10004/member/get/2中的/member/get/2 * */ @Override public String clean(String originUrl) { /* * public static boolean isBlank(String str) { int strLen; if (str != null && (strLen = str.length()) != 0) { for(int i = 0; i < strLen; ++i) { if (!Character.isWhitespace(str.charAt(i))) { return false; } } return true; } else { return true; } } * */ //isBlank方法就是判断originUrl != null && length > 0 && originUrl不是全部为空格" " if(StringUtils.isBlank(originUrl)){ return originUrl; } //判断是否以/member/get开头的Url if(originUrl.startsWith("/member/get")){ /* * 解读 * 1.如果请求的接口是/member/get开头的,比如/member/get/1,/member/get/10... * 2.给Sentinel放回资源名为/member/get/* * 3.在Sentinel对/member/get/*添加流控规则即可 * */ return "/member/get/*"; } return originUrl; } }
-
-
如果Sentinel流控规则没有持久化,当我们重启调用API所在微服务模块后,规则会丢失,需要重新加入
4.3 流量控制实例-线程数
4.3.1 需求分析
- 需求:通过Sentinel实现流量控制
- 当调用member-service-nacos-provider-10004的/member/get/接口/API时,限制只有一个工作线程,否则直接失败,抛异常
4.3.2 配置实现步骤
- 添加流控规则
- 设置参数
-
为了能展现效果,在controller的API中添加了睡眠
@GetMapping("/member/get/{id}") @ResponseBody public Result getMember(@PathVariable Long id, HttpServletRequest request) { //@GetMapping(value = "/member/get",params = {"id"}) //@ResponseBody //public Result getMember( Long id) { //System.out.println("------" +request.getRemoteHost()); //String color = request.getParameter("color"); //String addr = request.getParameter("addr"); //模拟超时时间 try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("enter getMemberById...当前线程id="+Thread.currentThread().getId()+"==时间=="+new Date()); //查询 Member memberById = memberService.getMemberById(id); //判断是否为空 if(memberById != null){ //返回成功信息 //return Result.success("member-service-provider:10001 " + color + "="+ addr,memberById); return Result.success("member-service-nacos-provider:10004 ",memberById); } //如果为空则查询失败 return Result.error(401,"查询失败",memberById); }
4.3.3 注意事项和细节
-
当请求一次微服务的API接口时,后台会启动一个线程
-
阈值类型QPS和线程数的区别讨论
- 如果一个线程平均执行时间为0.05秒,就说明在1秒钟,可以执行20次(相当于QPS为20)
- 如果一个线程平均执行时间为1秒,就说明在1秒钟,可以执行1次(相当于QPS为1)
- 如果一个线程平均执行时间为2秒,就说明在2秒钟内,才能执行1次请求
4.4 流量控制实例-关联
4.4.1 关联的含义
- 当关联的资源达到阈值时,就限流自己
4.4.2 需求分析
- 需求:通过Sentinel实现流量控制
- 当调用member-service-nacos-provider-10004的/t2 API接口时,如果QPS超过1,这时调用/t1 API接口时,否则直接失败,抛异常【/t2是关联的资源,限流的资源是/t1】
4.4.3 配置实现步骤
-
对/t1添加规则,不要对/t2添加规则
-
设置参数,并且关联的资源必须存在才会生效
-
因为是两个接口,所以使用Postman进行测试
-
添加请求图
-
保存请求图
-
选择集合运行图
-
配置参数图
-
开始运行图
- 运行结果图
4.4.4 注意事项和细节
- 在postman执行高并发访问/t2没有结束时,去访问/1才能看到流控异常出现
4.5 流量控制实例-Warm up
4.5.1 Warm up介绍
- 概述
- 当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的。
- 这个场景主要用于启动需要额外开销的场景,例如建立数据库连接等
- 梳理
- 通常冷启动的过程允许通过的QPS曲线图
- 默认coldFactor为3,即请求QPS从threshold / 3开始,经预热时长逐渐升至设定的QPS阈值
- 这里的threshold就是最终要达到的QPS阈值
- 文档
- 默认coldFactor为3,即请求QPS从threshold / 3 开始,经预热时长设定QPS阈值
- Warm Up称为冷启动/预热
- 应用场景:
- 秒杀在开启瞬间,大流量很热造成容易造成冲垮系统,Warm up可慢慢的把流量放入,最终将阈值增长到设置阈值
4.5.2 需求分析/图解
- 需求:通过Sentinel实现流量控制,演示Warm up。
- 调用member-service-nacos-provider-10004的/t2 API接口,将QPS设置为9,设置Warm up值为3
- 含义为请求/t2的QPS从threshold / 3 (9 / 3 = 3)开始,经预热时长(3秒)逐渐升至设定的QPS阈值(9)
- 为什么?9 / 3 ,这个是3就是默认冷启动加载因子coldFactor=3
- 测试预期效果:在前3秒,如果访问/t2的QPS超过3,会直接报错,在3秒后访问/t2的QPS超过3,小于等于9,是正常访问
4.5.3 配置实现步骤
-
添加流控规则
-
添加Warm Up流控规则
4.5.4 注意事项和细节
- 测试Warm Up效果不是很好测试,如果出不来可以尝试,调整流控规则:比如QPS为11,Warm up预热时间为6
- 如果请求停止(即:一段时间没有达到阈值),Warm up过程将重复,小伙伴可以理解是一个弹性过程
4.6 流量控制实例-排队
4.6.1 排队介绍
-
排队方式:这种方式严格控制了请求通过的时间间隔时间,也就是让请求以匀速通过,对应的是漏桶算法
-
一张图
-
这种方式主要用于处理间隔性突发的流量,例如消息队列,比如这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
-
匀速排队,阈值必须设置为QPS
4.6.2 需求分析/图解
- 需求:通过Sentinel实现流量控制-排队
- 调用member-service-nacos-provider-10004的/t2API接口,将QPS设置为1
- 当调用/t2的QPS超过1时,不拒绝请求,而是排队等待,依次执行
- 当等待时间超过10秒,则为等待超时。
4.6.3 修改业务类
-
在t2接口中添加一个睡眠时间,这样就能达到测试效果
//模拟超时时间 try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); }
4.6.4 配置实现步骤
-
添加流控规则
-
配置流控规则
5. Sentinel熔断降级
5.1 线程堆积引出熔断降级
- 一个服务常常调用别的模块,可能是另外的一个远程服务、数据库,或者第三方API【架构图】
- 例如,支付的时候,可能需要远程调用银联提供的API:查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务呢本身也变的不可用
- 这时,我们对不稳定的服务进行熔断降级,让其快速返回结果,不要造成线程堆积
5.2 基本介绍
-
解读:
- 现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路
- 链路调用中会产生放大的效果。复杂的链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。
- 因此需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩
-
熔断,降级,限流三者之间的关系
- 熔断强调的是服务之间的调用能实现自我恢复的状态
- 限流是从系统的流量入口考虑,从进入的流量上进行限制,达到保护系统的作用
- 降级,是从系统业务的维度考虑,流量大了或者频繁异常,可以牺牲一些非核心业务,保护核心流程正常使用
-
梳理
- 熔断是降级方式的一种
- 降级又是限流的一种的方式
- 三者都是为了通过一定的方式在流量过大或者出现异常时,保护系统的手段
5.3 熔断策略
5.3.1 慢调用比例
-
慢调用比例(SLOW_RESQUEST_RETIO):选择以慢调用比例作为阈值,需要设置允许的慢调用RT(即最大响应时间),请求的响应时间大于该值则统计为慢调用
-
当单位统计时长(statintervaMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内,请求会被自动熔断
-
熔断时长后,熔断器会进入探测恢复状态(HALF-OPEN状态),若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次被熔断
-
配置参考
5.3.2 异常比例
-
异常比例(ERROR_RATIO):当单位统计时长(statintervaIMS)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
-
经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN状态)
-
若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断
-
异常比率的阈值范围是[0.0,1.0],代表0% - 100%
-
配置参数
-
工作示意图
5.3.3 异常数
-
异常数(ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断
-
经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN状态)
-
若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断
-
配置参考
5.4 熔断降级实例-慢调用比例
5.4.1 需求分析/图解
- 需求通过Sentinel实现,熔断降级控制-慢调用比例
- 当调用member-service-nacos-provider-10004的/t3API接口时,如果在1秒内持续进入了5个请求,并且请求的平均响应时间超过200ms,那么就在未来10秒钟内,断路器打开,让/t3API接口微服务不可用
- 后面对/t3API接口访问降到1秒内1个请求,降低访问量了,断路器关闭,微服务恢复
5.4.2 修改业务类
- 添加测试接口
@GetMapping("/t3")
@ResponseBody
public Result t3(){
//设置睡眠时间
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return Result.success("t3()被执行");
}
5.4.3 配置实现步骤
-
配置熔断规则
-
配置熔断规则参数
5.4.4 注意事项和细节
- 平均响应时间超出阈值且在1s内通过的请求>= 两个条件同时满足后触发降级
- 熔断时间过后,关闭断路器,访问恢复正常
5.5 熔断降级实例-异常比例
5.5.1 需求分析/图解
- 需求通过Sentinel实现,熔断降级控制-异常比例
- 当调用member-service-nacos-provider-10004的/t4 API接口时,当资源的每秒请求量>=5,并且每秒异常总数占通过量的比值超过20%(即异常比例到20%),断路器打开(即:进入降级状态),让/t4API接口微服务不可用
- 后面对/t4API接口访问降到1秒内1个请求,降低访问量了,断路器关闭,5秒微服务恢复
5.5.2 修改业务类
-
增加一个接口t4
private static int num = 0 ; @GetMapping("/t4") @ResponseBody public Result t4(){ if(++num % 2 == 0){ System.out.println(3 / 0); } return Result.success("t4()被执行"); }
5.5.3 配置实现步骤
-
配置异常比例熔断参数
5.5.4 注意事项和细节
- 当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值,资源进入降级状态,需要两个条件都满足
- 测试时,如果熔断降级和恢复服务两个状态切换不明显,将时间窗口值调整大一点比如60,就可以了
5.6 熔断降级实例-异常数
5.6.1 需求分析/图解
- 需求通过Sentinel实现,熔断降级控制-异常数
- 当调用member-service-nacos-provider-10004的/t5 API接口时,当资源的每分钟请求量>=5,并且每分钟异常总数>=5,断路器打开(即:进入降级状态),让/t5 API接口微服务不可用
- 当熔断时间(比如20s)结束后,断路器关闭,微服务恢复熔断
5.6.2 修改业务类
-
增加一个接口t5
@GetMapping("/t5") @ResponseBody public Result t5(){ /* * 1. 这里设置一个10是因为需要测试,而且这个测试必须大于6 * */ if(++num <= 10 ){ System.out.println(3 / 0); } return Result.success("t5()被执行"); }
5.6.3 配置实现步骤
-
添加异常数熔断参数
5.6.4 注意事项和细节
- 资源在1分钟的异常数目超过阈值之后会进行熔断降级
- 如果在设计的统计时长内,结束熔断状态后,仍有可能进入熔断状态
6. Sentinel热点规则
6.1 一个问题引出热点key限流
- 热点:热点即经常访问的数据,很多时候我们希望统计热点数据中,访问频次最高Top k数据,并对其访问进行限制
- 比如某条新闻上热搜,在某段时间内高频访问,为了防止系统雪崩,可以对该条新闻进行热点限流
6.2 基本介绍
-
参考文档:https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81
-
图解
-
解读
- 热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流
- 热点参数限流可以看到是一种特殊的流量控制,仅对包含热点参数的资源调用生效
- Sentinel利用LRU策略统计最近最常访问的热点参数结合令牌桶算法来进行参数级别的流控,令牌桶算法参考网址:https://blog.csdn.net/qq_34416331/article/details/106668747
- 热点参数限流支持集群模式
6.3 热点key限流-实例
6.3.1 需求分析/图解
- 需求:通过Sentinel实现热点Key限流
- 对member-service-nacos-provider-10004的/news?id=x&Type=xAPI接口进行热点限流
- 假定id=10这一条新闻是当前的热点新闻,当查询新闻时,对通常的id请求QPS限定为2如果id=10QPS限定为10
- 如果访问超出了规定的QPS,触发热点限流机制,调用自定义的方法,给出提示信息
- 当对/news?id=x&type=xAPI接口降低访问量,QPS达到规定范围,服务恢复
6.3.2 修改业务类
6.3.3 配置实现步骤
-
添加热点规则
-
添加配置热点参数
-
添加热点参数
6.4 注意事项和细节
- 热点参数类型是(byte/int/long/float/double/char/String)
- 热点参数值,可以配置多个
- 热点规则只对指定的参数生效(比如本实例只对id生效,对type不生效)
7.系统规则
7.1一个问题引出系统规则
- 如果我们系统最大性能能抗100QPS,如何分配/t1/t2的QPS
- 方案1:/t1分配QPS=50 /t2分配QPS=50,问题,如果/t1当前QPS达到50,而/t2QPS才10,会造成没有充分利用服务器性能
- 方案1:/t1分配QPS=100 /t2分配QPS=100,问题,容易造成系统没有流量保护,造成请求线程堆积,形成雪崩
- 有没有对各个资源请求的QPS弹性设置,只要总数不超过系统最大QPS的流量保护规则?==>系统规则
7.2 基本介绍
-
一句话:系统规则作用,在系统稳定的前提下,保持系统的吞吐量
-
一张图
-
解读上图
- 系统处理请求的过程想象为一个水管,到来的请求是往这个水管灌水,当系统处理顺畅的时候,请求不需要排队,直接从水管中穿过,这个请求的RT是最短的
- 反之,当请求堆积的时候,那么处理请求的时间则会变为:排队时间 + 最短处理时间
-
系统规则
- Load自适应(仅对Linux/Unix-like机器生效):系统load1最为启发指标,进行自适应系统保护。当系统load1超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR阶段)。系统容量有系统的maxQPS*minRT估算得出。设置参考值一般是CPU cores * 2.5
- CPU usage(1.5.0+版本):当系统CPU使用率超过阈值即触发系统保护,(取值范围0.0 -1.0)比较灵敏
- 平均RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒
- 并发线程数:当单台机器上所有入口流量的并发线程达到阈值即触发系统保护
- 入口QPS:当单台机器上所有入口流量的QPS达到阈值触发系统保护
7.3 实例
7.3.1 需求分析/图解
- 需求:通过Sentinel实现系统规则-入口QPS
- 对member-service-nacos-provider-10004的所有API接口进行流量保护,不管访问哪个API接口,系统入口总的QPS不能大于2,大于2就进行限流控制
7.3.2 配置实现步骤
-
配置系统规则
-
添加系统规则参数
8.@SentinelResource
8.1 自定义全局限流处理类
8.1.1 需求分析/图解
-
先看一段代码
@GetMapping("/news") @ResponseBody /* * 1.SentinelResource指定Sentinel限流资源 * 2.value="news" 表示Sentinel限流资源的名称,由程序员指定 * 3.blockHandler = "newsBlockHandler" 表示当出现限流时,由newsBlockHandler方法执行 * */ @SentinelResource(value = "news",blockHandler = "newsBlockHandler") public Result news(@RequestParam(value = "id", required = false) String id, @RequestParam(value = "type",required = false) String type){ //模拟在数据库查询数据 log.info("到DB查询数据"); return Result.success("返回id= " + id + "新闻 来自数据库"); } //热点Key限制/限流异常处理方法 public Result newsBlockHandler(String id, String type, BlockException blockException){ return Result.error(400,"查询id= "+id+"新闻触发热点key限流保护sorry......."); }
- 说明:当配置的资源名news触发限流机制时,会调用newsBlockHandler方法
-
上面的处理方案存在一些问题
- 每个@SentibelResource对应一个异常处理方法,会造成很方法很多
- 异常处理方法和资源请求方法在一起,不利于业务逻辑的分离
- 解决方案->自定义全局限流处理类
-
需求:请编写一个自定义全局限流处理类,完成对异常处理
8.1.2 代码实现
-
全局限流异常
package com.leon.springcloud.handler; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.leon.springcloud.entity.Result; /** * ClassName:CustomGlobalBlockHandler * Package:com.leon.springcloud.handler * Description: * * @Author: leon-->ZGJ * @Create: 2024/9/29 16:08 * @Version: 1.0 */ /* * 1.CustomGlobalBlockHandler:全局限流处理类 * 2.在CustomGlobalBlockHandler类中,可以编写限流处理方法,但要求方法是static * */ public class CustomGlobalBlockHandler { public static Result blockHandler1(BlockException blockException) { return Result.error(400, "blockHandler1被执行"); } public static Result blockHandler2(BlockException blockException) { return Result.error(400, "blockHandler2被执行"); } }
-
修改conttroller类
@GetMapping("/t6") @ResponseBody /* * 1.value="t6" 表示Sentinel限流资源的名称 * 2.blockHandlerClass = {ClasspathURLStreamHandler.class},表示全局限流处理类 * 3. blockHandler = "blockHandler1"指定使用全局限流处理类哪个方法,来处限流信息 * * */ @SentinelResource(value = "t6", blockHandlerClass = {ClasspathURLStreamHandler.class}, blockHandler = "blockHandler1") public Result t6(){ @GetMapping("/t6") @ResponseBody /* * 1.value="t6" 表示Sentinel限流资源的名称 * 2.blockHandlerClass = {CustomGlobalBlockHandler.class},表示全局限流处理类 * 3. blockHandler = "blockHandler1"指定使用全局限流处理类哪个方法,来处限流信息 * * */ @SentinelResource(value = "t6", blockHandlerClass = {CustomGlobalBlockHandler.class}, blockHandler = "blockHandler1") public Result t6(){ return Result.success("t6()被执行"); }
8.1.3 配置实现
-
添加流控规则
-
配置流控参数
8.2 fallback
8.2.2 基本介绍
- blockHandler只负责Sentinel控制台配置违规
- fallback负责java异常/业务异常
8.2.3 需求分析/图解
- 需求:请编写一个自定以全局fallback处理类,处理java异常/业务异常
8.2.4 代码实现
-
创建全局fallback异常类
package com.leon.springcloud.handler; import com.leon.springcloud.entity.Result; import java.awt.geom.RectangularShape; /** * ClassName:CustomClobalFallBackHandler * Package:com.leon.springcloud.handler * Description: * * @Author: leon-->ZGJ * @Create: 2024/9/29 16:41 * @Version: 1.0 */ //CustomGlobalFallBackHandler 全局fallback处理类 //在CustomGlobalFallBackHandler类中,可以去编写处理java异常/业务异常方法需要是static的 public class CustomGlobalFallBackHandler { public static Result fallBackHandler1(Throwable e){ return Result.error(402,"fallBackHandler1被执行异常信息"+ e.getMessage()); } public static Result fallBackHandler2(Throwable e){ return Result.error(404,"fallBackHandler2被执行异常信息"+ e.getMessage()); } }
-
修改controller
@GetMapping("/t6") @ResponseBody /* * 1.value="t6" 表示Sentinel限流资源的名称 * 2.blockHandlerClass = {CustomGlobalBlockHandler.class},表示全局限流处理类 * 3.blockHandler = "blockHandler1"指定使用全局限流处理类哪个方法,来处限流信息 * 4.fallbackClass = {CustomGlobalBlockHandler.class} 表示全局fallback处理类 * 5.fallback = "fallBackHandler1"指定使用全局fallback处理类哪个方法来处理java异常/业务异常 * */ @SentinelResource(value = "t6", fallbackClass = {CustomGlobalFallBackHandler.class}, fallback = "fallBackHandler1", blockHandlerClass = {CustomGlobalBlockHandler.class}, blockHandler = "blockHandler1") public Result t6(){ if(++num % 5 == 0){ throw new RuntimeException("num 算数异常"+num); } return Result.success("t6()被执行"); }
8.3 exceptionsTolognore
-
如果希望忽略某个异常,可以使用exceptionsTolognore
-
代码实现
@GetMapping("/t6") @ResponseBody /* * 1.value="t6" 表示Sentinel限流资源的名称 * 2.blockHandlerClass = {CustomGlobalBlockHandler.class},表示全局限流处理类 * 3.blockHandler = "blockHandler1"指定使用全局限流处理类哪个方法,来处限流信息 * 4.fallbackClass = {CustomGlobalBlockHandler.class} 表示全局fallback处理类 * 5.fallback = "fallBackHandler1"指定使用全局fallback处理类哪个方法来处理java异常/业务异常 * 6.exceptionsToIgnore = {NullPointerException.class}表示指定的异常出现,则不处理,由系统默认的方式处理 * */ @SentinelResource(value = "t6", fallbackClass = {CustomGlobalFallBackHandler.class}, fallback = "fallBackHandler1", blockHandlerClass = {CustomGlobalBlockHandler.class}, blockHandler = "blockHandler1", exceptionsToIgnore = {NullPointerException.class} ) public Result t6(){ if(++num % 5 == 0){ throw new RuntimeException("num 算数异常"+num); } if (num % 2 == 0){ throw new NullPointerException("num 空指针"+num); } return Result.success("t6()被执行"); }
8.4 接入Sentinel的方式
8.4.1 代码方式(硬编码,侵入性强,不推荐)
8.4.2 注解方式(低侵入性,前面用过,推荐)
-
注解方式埋点不支持private方法
-
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource注解包含以下属性:
-
value:资源名称,必需项(不能为空)
-
entryType:entry 类型,可选项(默认为EntryType.OUT)
-
blockHandler / blockHandlerClass: blockHandler对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass为对应的类的Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
-
fallback /fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。 - fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
-
defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个 Throwable类型的参数用于接收对应的异常。
- defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的Class对象,注意对应的函数必需为 static 函数,否则无法解析。
-
exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
-
9.openFeign+sentinel对远程调用熔断降级
9.1 当前微服务基础环境
-
示意图
9.2 服务消费者整合OpenFeign
9.2.1 需求分析/图解
-
需求:在member-service-nacos-consumer-80整合OpenFeign实现远程调用
9.2.2 代码+配置实现步骤
-
修改pom.xml添加依赖
<!--引入OpenFeign 场景启动器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
创建服务接口
package com.leon.springcloud.service; import com.leon.springcloud.entity.Result; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; /** * ClassName:MemberOpenFeignService * Package:com.leon.springcloud.service * Description: * * @Author: leon-->ZGJ * @Create: 2024/9/29 17:48 * @Version: 1.0 */ @FeignClient(value = "member-service-nacos-provider") public interface MemberOpenFeignService { /* * 1.远程调用方式是Get * 2.远程调用的url为: http://member-service-nacos-provider/member/get/{id} * 3.member-service-nacos-provider是注册中心服务名称 * 4.OpenFeign会根据负载均衡算法决定调用10004/10006,默认是轮询算法 * 5.OpenFeign是通过接口调用的 * */ @GetMapping("/member/get/{id}") @ResponseBody public Result getMember(@PathVariable("id") Long id); }
-
修改controller
@Resource private MemberOpenFeignService memberOpenFeignService; @GetMapping("/member/openfeign/consumer/get/{id}") @ResponseBody public Result getMemberOpenFeign(@PathVariable Long id) { return memberOpenFeignService.getMember(id); }
9.3 服务消费者整合Sentinel
9.3.1 需求分析/图解
-
需求:在member-service-nacos-consumer-80整合Sentinel能被Sentinel监控
9.3.2 代码+配置实现步骤
-
修改pom.xml添加依赖
<!--引入Sentinel场景启动器--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
-
修改application.yam文件
#配置Nacos cloud: sentinel: transport: #指定Sentinel控制台地址(dash board) dashboard: localhost:8080 port: 8719 #设置暴露所有监控点 management: endpoints: web: exposure: include: '*'
9.4 OpenFeign+Sentinel对远程调用熔断降级
9.4.1 需求分析/图解
-
需求/如图:在member-service-nacos-consumer-80调用某个无效服务时,启动Sentinel的熔断降级机制,能够快速返回响应,而不是使用默认的超时机制(因为超时机制容易线程堆积,从导致雪崩)
-
先测试一下,关闭10004/10006,这时OpenFeign去调用会怎么样?(放回time out)
-
同学还可以测试一下,让10004服务对应的API执行时间很长(比如休眠2秒),这时OpenFeign去调用会怎么样(它会一直调用10006的接口)
9.4.2 代码+配置实现步骤
-
添加一个快速返回信息的处理类,要实现调用接口
package com.leon.springcloud.service; import com.leon.springcloud.entity.Result; import org.springframework.stereotype.Component; /** * ClassName:MemberFeignFallBackService * Package:com.leon.springcloud.service * Description: * * @Author: leon-->ZGJ * @Create: 2024/9/29 20:04 * @Version: 1.0 */ @Component public class MemberFeignFallBackService implements MemberOpenFeignService{ @Override public Result getMember(Long id) { return Result.error(500,"被调用的服务一异常,熔断降级,快速返回结果,防止雪崩"); } }
-
修改application.yml文件
#openfeign和sentinel整合,必须配置 feign: sentinel: enabled: true
9.4.3 注意事项和细节
-
因为member-service-nacos-consumer-80已经被Sentinel监控,所以我们可以加入相关的流控规则,比如为/member/openfeign/consumer/get/1加入流控规则QPS=1
-
测试/member/openfeign/consumer/get/1如果超过QPS=1 会出现什么
-
测试/member/openfeign/consumer/get/1没有超过QPS=1 会出现什么
10.规则持久化
10.1 规则没有持久化的问题、
- 如果Sentinel流控规则没有持久化,当重启调用API/接口所在微服务后,规则就会丢失,需要重新加入
- 解决方案:通过Nacos进行持久化
10.2 规则持久化方案
- 阿里云Ahas【最方便/付费】
- 在Nacos Server配置规则,完成持久化-官方推荐
- datasource支持nacos,redis,apollo,zk,file
10.3 Nacos Server配置中心-规则持久化实例
10.3.1 工作原理示意图
10.3.2 需求分析/图解
- 需求:member-service-nacos-consumer-80微服务的/member/openfeign/consumer/get/1API接口添加流控规则
- 要求将该流控规则加入到Nacos Server配置中心,实现持久化
10.3.3 代码+配置实现步骤
-
在Nacos Server配置中心增加Sentinel客户端/微服务模块的流控规则
-
在NacosServer 配置中心增加 Sentinel客户端/微服务模块 的流控规则参数说明
-
resource∶资源名称;
-
limlitApp∶ 来源应用;
-
grade∶阈值类型,0表示线程数,1表示QPS;
-
count∶单机阈值;
-
strategy∶流控模式,0表示直接,1表示关联,2表示链路;
-
controlBehavior∶流控效果,0表示快速失败,1表示WarmUp,2表示排队等待;
-
clusterMode∶是否集
-
修改pom.xml,添加依赖
<!--引入sentinel和Nacos持久化整合依赖--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
-
修改applicationyml
spring: application: name: member-server-nacos-consumer-80 #配置Nacos cloud: nacos: discovery: #配置Nacos Server地址 server-addr: localhost:8848 sentinel: transport: #指定Sentinel控制台地址(dash board) dashboard: localhost:8080 port: 8719 datasource: ds1: nacos: #指定Nacos Server配置中心的地址 server-addr: localhost:8848 #指定获取Nacos Server配置中心的哪个文件,也就是Data Id data-id: member-server-nacos-consumer-80 #指定组 group-id: DEFAULT_GROUP #指定配置流控规则的数据类型 data-type: json #指定是规则类型,例如流控规则,热点规则等 rule-type: flow
10.3.4 注意事项和细节
- 在Nacos Server 配置 Sentinel 流控规则的 Data Id 也可以自己指定,比如写成 hsp-id, 只要在Sentinel Client/微服务 的 applicaion.yml 的 datasource.ds1.nacos.dataId 的值保持一致即可
10.3.5 其它类型规则
-
在被监控保护的微服务模块,我们配置了如下:这里rule-type可以是:flow、degrade、param-flow、system等..
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package com.alibaba.cloud.sentinel.datasource; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.system.SystemRule; import java.util.Arrays; import java.util.Optional; import org.springframework.util.StringUtils; public enum RuleType { FLOW("flow", FlowRule.class), DEGRADE("degrade", DegradeRule.class), PARAM_FLOW("param-flow", ParamFlowRule.class), SYSTEM("system", SystemRule.class), AUTHORITY("authority", AuthorityRule.class), GW_FLOW("gw-flow", "com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule"), GW_API_GROUP("gw-api-group", "com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition"); private final String name; private Class clazz; private String clazzName; private RuleType(String name, Class clazz) { this.name = name; this.clazz = clazz; } private RuleType(String name, String clazzName) { this.name = name; this.clazzName = clazzName; } public String getName() { return this.name; } public Class getClazz() { if (this.clazz != null) { return this.clazz; } else { try { return Class.forName(this.clazzName); } catch (ClassNotFoundException var2) { ClassNotFoundException e = var2; throw new RuntimeException(e); } } } public static Optional<RuleType> getByName(String name) { return StringUtils.isEmpty(name) ? Optional.empty() : Arrays.stream(values()).filter((ruleType) -> { return name.equals(ruleType.getName()); }).findFirst(); } public static Optional<RuleType> getByClass(Class clazz) { return Arrays.stream(values()).filter((ruleType) -> { return clazz == ruleType.getClazz(); }).findFirst(); } }
-
其它规则的种类-配置文档:https://sentinelguard.io/zh-cn/docs/basic-api-resource-rule.html
十一、Spring Cloud Alibaba
1. Seata基础
1.1 先看一个问题,引出Seata
-
单机单库(多表)处理事务示意图
-
分布式微服务架构下的数据库事务示意图
-
梳理上图
- 用户购买商品的业务逻辑。整个业务逻辑由三个微服务提供支持
- 仓储服务:对给定的商品扣除仓库/商品数量
- 订单服务:根据采购需求创建订单
- 账户服务:从用户账户中扣除余额
-
问题分析
- 单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用独立的数据源
- 业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证
- 但是全局的数据一致性问题没办法保证
- 简单的说:一次性业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题
1.2 分布式事务问题&解决方案
- 分布式微服务架构下的全局数据一致性问题【即:分布式事务问题】
- 解决方案:Seata
1.3 官网
1.4 Seata是什么
- 一句话:Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
2. Seat Server安装
2.1 下载
2.2 安装和配置
-
将下载好的seata-server-0.9.0.zip解压到自己想要安装的目录
-
修改conf\file.conf文件,配置代码如下
transport { # tcp udt unix-domain-socket type = "TCP" #NIO NATIVE server = "NIO" #enable heartbeat heartbeat = true #thread factory for netty thread-factory { boss-thread-prefix = "NettyBoss" worker-thread-prefix = "NettyServerNIOWorker" server-executor-thread-prefix = "NettyServerBizHandler" share-boss-worker = false client-selector-thread-prefix = "NettyClientSelector" client-selector-thread-size = 1 client-worker-thread-prefix = "NettyClientWorkerThread" # netty boss thread size,will not be used for UDT boss-thread-size = 1 #auto default pin or 8 worker-thread-size = 8 } shutdown { # when destroy server, wait seconds wait = 3 } serialization = "seata" compressor = "none" } service { #vgroup->rgroup #vgroup_mapping.my_test_tx_group = "default" #----修改---- vgroup_mapping.my_test_tx_group = "leon_order_tx_group" #only support single node default.grouplist = "127.0.0.1:8091" #degrade current not support enableDegrade = false #disable disable = false #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent max.commit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" } client { async.commit.buffer.limit = 10000 lock { retry.internal = 10 retry.times = 30 } report.retry.count = 5 tm.commit.retry.count = 1 tm.rollback.retry.count = 1 } ## transaction log store store { ## store mode: file、db #mode = "file" #----修改---- mode = "db" ## file store file { dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions max-branch-session-size = 16384 # globe session size , if exceeded throws exceptions max-global-session-size = 512 # file buffer size , if exceeded allocate new buffer file-write-buffer-cache-size = 16384 # when recover batch read size session.reload.read_size = 100 # async, sync flush-disk-mode = async } ## database store db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. datasource = "dbcp" ## mysql/oracle/h2/oceanbase etc. db-type = "mysql" driver-class-name = "com.mysql.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata" #user = "mysql" #----修改---- user = "root" #password = "mysql" #----修改---- password = "root" min-conn = 1 max-conn = 3 global.table = "global_table" branch.table = "branch_table" lock-table = "lock_table" query-limit = 100 } } lock { ## the lock store mode: local、remote mode = "remote" local { ## store locks in user's database } remote { ## store locks in the seata's server } } recovery { #schedule committing retry period in milliseconds committing-retry-period = 1000 #schedule asyn committing retry period in milliseconds asyn-committing-retry-period = 1000 #schedule rollbacking retry period in milliseconds rollbacking-retry-period = 1000 #schedule timeout retry period in milliseconds timeout-retry-period = 1000 } transaction { undo.data.validation = true undo.log.serialization = "jackson" undo.log.save.days = 7 #schedule delete expired undo_log in milliseconds undo.log.delete.period = 86400000 undo.log.table = "undo_log" } ## metrics settings metrics { enabled = false registry-type = "compact" # multi exporters use comma divided exporter-list = "prometheus" exporter-prometheus-port = 9898 } support { ## spring spring { # auto proxy the DataSource bean datasource.autoproxy = false } }
-
在Mysql中创建数据库
CREATE DATABASE seata USE seata
-
在 seata 数据库创建表 , 使用 seata 提供的 sql 脚本即可, 在 seata 的 \conf\db_store.sql
-
修改seata 的 \conf\registry.conf , 配置注册中心nacosserver
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa #type = "file" #----修改---- type = "nacos" nacos { #serverAddr = "localhost" #----修改---- serverAddr = "localhost:8848" namespace = "" cluster = "default" } eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6379" db = "0" } zk { cluster = "default" serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } consul { cluster = "default" serverAddr = "127.0.0.1:8500" } etcd3 { cluster = "default" serverAddr = "http://localhost:2379" } sofa { serverAddr = "127.0.0.1:9603" application = "default" region = "DEFAULT_ZONE" datacenter = "DefaultDataCenter" cluster = "default" group = "SEATA_GROUP" addressWaitTime = "3000" } file { name = "file.conf" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "file" nacos { serverAddr = "localhost" namespace = "" } consul { serverAddr = "127.0.0.1:8500" } apollo { app.id = "seata-server" apollo.meta = "http://192.168.1.204:8801" } zk { serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } }
2.3 启动
- 启动Nacos Server 8848
- 双击Seata的bin\seata-server.bat,启动Seata Server
- 登录Nacos Server,查看Seata Server是否注册成功
- 启动问题解决分析
- 首先下载的Seata的版本是0.9.0
- 如果运行seata-server.bat直接闪退,是因为jdk版本过高,需要进行调换jdk,将jdk调到jdk8就可以运行,Seata的0.9.0版本
- 如果安装的Mysql版本大于5,那需要找到seata/lib目录下的mysql-connector-java-5.1.30替换成,下载的版本的驱动
3. Seata分布式事务-应用实例
3.1 需求分析/图解
-
需求:完成下订单功能,由三个微服务模块协同完成,涉及到多数据库,多张表
3.2 创建数据库和表
-
创建业务数据库和表
-- 订单微服务的数据库 CREATE DATABASE order_micro_service USE order_micro_service CREATE TABLE `order`( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id BIGINT DEFAULT NULL , product_id BIGINT DEFAULT NULL , nums INT DEFAULT NULL , money INT DEFAULT NULL, `status` INT DEFAULT NULL COMMENT '0:创建中;1:已完结' ); SELECT * FROM `order` -- 库存微服务的数据库`storage``order` CREATE DATABASE storage_micro_service USE storage_micro_service CREATE TABLE `storage`( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, product_id BIGINT DEFAULT NULL , amount INT DEFAULT NULL COMMENT'库存量' ); -- 初始化库存表 INSERT INTO `storage` VALUES(NULL, 1, 10); SELECT * FROM `storage` CREATE DATABASE account_micro_service USE account_micro_service CREATE TABLE `account`( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, user_id BIGINT DEFAULT NULL , money INT DEFAULT NULL COMMENT'账户金额' ); -- 初始化账户表 INSERT INTO `account` VALUES(NULL, 666, 10000) select * from `account`
-
分别为创建的数据库,创建对应的回滚日志表, 回滚日志表的sql文件在 seata 的 \conf\db_undo_log.sql
use order_micro_service
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY(`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
use storage_micro_service
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY(`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
use account_micro_service
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY(`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
3.3 开发seata-storage-micro-service-10010微服务
-
参考以前的方式,创建seata-storage-micro-service-10010微服务模块
-
修改pom.xml文件,添加相关依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.leon.springcloud</groupId> <artifactId>e-commerce-center</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>seata-storage-micro-service-10010</artifactId> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <!--引入seata starter场景启动器--> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <!--排除自带的seata-all,引入自己的版本,否则会出现冲突--> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> </exclusion> </exclusions> </dependency> <!--引入指定版本的seata-all--> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>0.9.0</version> </dependency> <!--引入OpenFeign starter场景启动器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--引入Nacos场景启动器--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId> </dependency> <!--引入Spring-Boot-web场景驱动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测 可以通过:https://localhost:10000/actuator 看到相关的连接和信息 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--引入Spring Boot整合Mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--引入Druid场景驱动--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency> <!--引入Mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--引入spring-boot-starter-jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--引入lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--引入spring-boot-starter-test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!--引入API--> <dependency> <groupId>com.leon.springcloud</groupId> <artifactId>e-commerce-center-common-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
-
创建application.yml文件,添加相关配置
server: port: 10010 spring: application: name: seata-storage-micro-service cloud: #配置seata alibaba: #配置指定事务组名,需要和seata-server中的conf/file.conf对应 #vgroup_mapping.my_test_tx_group = "leon_order_tx_group" seata: tx-service-group: leon_order_tx_group #配置Nacos nacos: discovery: #配置Nacos Server的服务地址 server-addr: localhost:8848 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/storage_micro_service?useSSL=true&userUnicode=true&characterEncoding=UTF-8 username: root password: root logging: level: io: #配置seata日志输出 seata: info mybatis: mapper-locations: classpath:mapper/*.xml
-
创建file.conf, 进行相关的配置, 说明:该文件从 seata 的\conf\file.conf拷贝,进行修改即可
transport { # tcp udt unix-domain-socket type = "TCP" #NIO NATIVE server = "NIO" #enable heartbeat heartbeat = true #thread factory for netty thread-factory { boss-thread-prefix = "NettyBoss" worker-thread-prefix = "NettyServerNIOWorker" server-executor-thread-prefix = "NettyServerBizHandler" share-boss-worker = false client-selector-thread-prefix = "NettyClientSelector" client-selector-thread-size = 1 client-worker-thread-prefix = "NettyClientWorkerThread" # netty boss thread size,will not be used for UDT boss-thread-size = 1 #auto default pin or 8 worker-thread-size = 8 } shutdown { # when destroy server, wait seconds wait = 3 } serialization = "seata" compressor = "none" } service { #vgroup->rgroup #vgroup_mapping.my_test_tx_group = "default" vgroup_mapping.my_test_tx_group = "default" #only support single node default.grouplist = "127.0.0.1:8091" #degrade current not support enableDegrade = false #disable disable = false #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent max.commit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" } client { async.commit.buffer.limit = 10000 lock { retry.internal = 10 retry.times = 30 } report.retry.count = 5 tm.commit.retry.count = 1 tm.rollback.retry.count = 1 } ## transaction log store store { ## store mode: file、db #mode = "file" mode = "db" ## file store file { dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions max-branch-session-size = 16384 # globe session size , if exceeded throws exceptions max-global-session-size = 512 # file buffer size , if exceeded allocate new buffer file-write-buffer-cache-size = 16384 # when recover batch read size session.reload.read_size = 100 # async, sync flush-disk-mode = async } ## database store db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. datasource = "dbcp" ## mysql/oracle/h2/oceanbase etc. db-type = "mysql" driver-class-name = "com.mysql.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata" #user = "mysql" user = "root" #password = "mysql" password = "root" min-conn = 1 max-conn = 3 global.table = "global_table" branch.table = "branch_table" lock-table = "lock_table" query-limit = 100 } } lock { ## the lock store mode: local、remote mode = "remote" local { ## store locks in user's database } remote { ## store locks in the seata's server } } recovery { #schedule committing retry period in milliseconds committing-retry-period = 1000 #schedule asyn committing retry period in milliseconds asyn-committing-retry-period = 1000 #schedule rollbacking retry period in milliseconds rollbacking-retry-period = 1000 #schedule timeout retry period in milliseconds timeout-retry-period = 1000 } transaction { undo.data.validation = true undo.log.serialization = "jackson" undo.log.save.days = 7 #schedule delete expired undo_log in milliseconds undo.log.delete.period = 86400000 undo.log.table = "undo_log" } ## metrics settings metrics { enabled = false registry-type = "compact" # multi exporters use comma divided exporter-list = "prometheus" exporter-prometheus-port = 9898 } support { ## spring spring { # auto proxy the DataSource bean datasource.autoproxy = false } }
-
创建registry.conf, 进行相关的配置, 说明:该文件从 seata 的\conf\registry.conf拷贝,进行修改即可
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa #type = "file" type = "nacos" nacos { #serverAddr = "localhost" serverAddr = "localhost:8848" namespace = "" cluster = "default" } eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6379" db = "0" } zk { cluster = "default" serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } consul { cluster = "default" serverAddr = "127.0.0.1:8500" } etcd3 { cluster = "default" serverAddr = "http://localhost:2379" } sofa { serverAddr = "127.0.0.1:9603" application = "default" region = "DEFAULT_ZONE" datacenter = "DefaultDataCenter" cluster = "default" group = "SEATA_GROUP" addressWaitTime = "3000" } file { name = "file.conf" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "file" nacos { serverAddr = "localhost" namespace = "" } consul { serverAddr = "127.0.0.1:8500" } apollo { app.id = "seata-server" apollo.meta = "http://192.168.1.204:8801" } zk { serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } }
-
创建 Storage.java
package com.leon.springcloud.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* ClassName:Storage
* Package:com.leon.springcloud.entity
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/10/3 17:11
* @Version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Storage {
private Long id;
private Long productId;
private Integer amount;
}
-
创建 StorageDao.java
package com.leon.springcloud.dao; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; /** * ClassName:StorageDao * Package:com.leon.springcloud.dao * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 17:13 * @Version: 1.0 */ @Mapper public interface StorageDao { //库存的修改 void reduce(@Param("productId") Long productId, @Param("nums") Integer nums); }
-
创建 resources/mapper/StorageMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.leon.springcloud.dao.StorageDao"> <resultMap id="baseStorage" type="com.leon.springcloud.entity.Storage"> <result property="id" column="id" jdbcType="BIGINT"/> <result property="productId" column="product_id" jdbcType="BIGINT"/> <result property="amount" column="amount" jdbcType="INTEGER"/> </resultMap> <update id="reduce"> UPDATE storage SET amount = amount- #{nums} WHERE product_id = #{productId} </update> </mapper>
-
创建 StorageService.java
package com.leon.springcloud.service; /** * ClassName:StorageService * Package:com.leon.springcloud.service * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 17:19 * @Version: 1.0 */ public interface StorageService { void reduce(Long productId,Integer nums); }
-
创建 StorageServiceImpl.java
package com.leon.springcloud.service.impl; import com.leon.springcloud.dao.StorageDao; import com.leon.springcloud.service.StorageService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * ClassName:StorageServiceIImpl * Package:com.leon.springcloud.service.impl * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 17:21 * @Version: 1.0 */ @Service @Slf4j public class StorageServiceImpl implements StorageService { @Resource private StorageDao storageDao; @Override public void reduce(Long productId, Integer nums) { log.info("==========seata_storage_micro_service-10010 start=========="); storageDao.reduce(productId,nums); log.info("==========seata_storage_micro_service-10010 end=========="); } }
-
创建 StorageController.java
package com.leon.springcloud.controller; import com.leon.springcloud.entity.Result; import com.leon.springcloud.service.StorageService; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * ClassName:StorageController * Package:com.leon.springcloud.controller * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 17:25 * @Version: 1.0 */ @RestController public class StorageController { @Resource private StorageService storageService; @PostMapping("/storage/reduce") public Result reduce(@RequestParam("productId") Long productId, @RequestParam("nums") Integer nums){ storageService.reduce(productId,nums); return Result.success("库存减扣成功"); } }
-
创建 MyBatisConfig.java
package com.leon.springcloud.config; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration; /** * ClassName:MybatisConfiig * Package:com.leon.springcloud.config * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 17:29 * @Version: 1.0 */ @Configuration @MapperScan({"com.leon.springcloud.dao"}) public class MybatisConfig { }
-
创建 DataSourceProxyConfig.java , 常规配置(拿来使 用即可)
package com.leon.springcloud.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
* ClassName:DataSourceProxyConfig
* Package:com.leon.springcloud.config
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/10/3 20:44
* @Version: 1.0
*/
/*
* 1.这里很重要:配置数据源的代理是Seata也就是使用Seata代理数据源
* 2.DataSourceProxy是引入的io.seata.rm.datasource
* */
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapper-locations}")
private String mapperLocations;
//配置DataSource
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
//配置DataSourceProxy-使用seata代理数据源
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource){
return new DataSourceProxy(dataSource);
}
//配置SqlSessionFactory
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
-
创建主启动类
package com.leon.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; /** * ClassName:SeataStorageMicroServiceApplication10010 * Package:com.leon.springcloud * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 20:58 * @Version: 1.0 */ /* * 1.注意:需要取消数据源的自动配置,而是使用Seata代理数据源,DataSourceProxy * */ @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableFeignClients @EnableDiscoveryClient public class SeataStorageMicroServiceApplication10010 { public static void main(String[] args) { SpringApplication.run(SeataStorageMicroServiceApplication10010.class,args); } }
3.4 创建seata-account-micro-service-10012微服务
-
参考以前的方式,创建seata-account-micro-service-10012微服务模块
-
修改pom.xml文件,添加相关依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.leon.springcloud</groupId> <artifactId>e-commerce-center</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>seata-account-micro-service-10012</artifactId> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!--引入seata starter场景启动器--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <!--排除自带的seata-all,引入自己的版本,否则会出现冲突--> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> </exclusion> </exclusions> </dependency> <!--引入指定版本的seata-all--> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>0.9.0</version> </dependency> <!--引入OpenFeign starter场景启动器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--引入Nacos场景启动器--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId> </dependency> <!--引入Spring-Boot-web场景驱动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测 可以通过:https://localhost:10000/actuator 看到相关的连接和信息 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--引入Spring Boot整合Mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--引入Druid场景驱动--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency> <!--引入Mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--引入spring-boot-starter-jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--引入lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--引入spring-boot-starter-test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!--引入API--> <dependency> <groupId>com.leon.springcloud</groupId> <artifactId>e-commerce-center-common-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
-
创建application.yml文件,添加相关配置
server: port: 10012 spring: application: name: seata-account-micro-service cloud: #配置seata alibaba: #配置指定事务组名,需要和seata-server中的conf/file.conf对应 #vgroup_mapping.my_test_tx_group = "leon_order_tx_group" seata: tx-service-group: leon_order_tx_group #配置Nacos nacos: discovery: #配置Nacos Server的服务地址 server-addr: localhost:8848 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/account_micro_service?useSSL=true&userUnicode=true&characterEncoding=UTF-8 username: root password: root logging: level: io: #配置seata日志输出 seata: info mybatis: mapper-locations: classpath:mapper/*.xml
-
创建file.conf, 进行相关的配置, 说明:该文件从 seata 的\conf\file.conf拷贝,进行修改即可
transport { # tcp udt unix-domain-socket type = "TCP" #NIO NATIVE server = "NIO" #enable heartbeat heartbeat = true #thread factory for netty thread-factory { boss-thread-prefix = "NettyBoss" worker-thread-prefix = "NettyServerNIOWorker" server-executor-thread-prefix = "NettyServerBizHandler" share-boss-worker = false client-selector-thread-prefix = "NettyClientSelector" client-selector-thread-size = 1 client-worker-thread-prefix = "NettyClientWorkerThread" # netty boss thread size,will not be used for UDT boss-thread-size = 1 #auto default pin or 8 worker-thread-size = 8 } shutdown { # when destroy server, wait seconds wait = 3 } serialization = "seata" compressor = "none" } service { #vgroup->rgroup #vgroup_mapping.my_test_tx_group = "default" vgroup_mapping.my_test_tx_group = "default" #only support single node default.grouplist = "127.0.0.1:8091" #degrade current not support enableDegrade = false #disable disable = false #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent max.commit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" } client { async.commit.buffer.limit = 10000 lock { retry.internal = 10 retry.times = 30 } report.retry.count = 5 tm.commit.retry.count = 1 tm.rollback.retry.count = 1 } ## transaction log store store { ## store mode: file、db #mode = "file" mode = "db" ## file store file { dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions max-branch-session-size = 16384 # globe session size , if exceeded throws exceptions max-global-session-size = 512 # file buffer size , if exceeded allocate new buffer file-write-buffer-cache-size = 16384 # when recover batch read size session.reload.read_size = 100 # async, sync flush-disk-mode = async } ## database store db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. datasource = "dbcp" ## mysql/oracle/h2/oceanbase etc. db-type = "mysql" driver-class-name = "com.mysql.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata" #user = "mysql" user = "root" #password = "mysql" password = "root" min-conn = 1 max-conn = 3 global.table = "global_table" branch.table = "branch_table" lock-table = "lock_table" query-limit = 100 } } lock { ## the lock store mode: local、remote mode = "remote" local { ## store locks in user's database } remote { ## store locks in the seata's server } } recovery { #schedule committing retry period in milliseconds committing-retry-period = 1000 #schedule asyn committing retry period in milliseconds asyn-committing-retry-period = 1000 #schedule rollbacking retry period in milliseconds rollbacking-retry-period = 1000 #schedule timeout retry period in milliseconds timeout-retry-period = 1000 } transaction { undo.data.validation = true undo.log.serialization = "jackson" undo.log.save.days = 7 #schedule delete expired undo_log in milliseconds undo.log.delete.period = 86400000 undo.log.table = "undo_log" } ## metrics settings metrics { enabled = false registry-type = "compact" # multi exporters use comma divided exporter-list = "prometheus" exporter-prometheus-port = 9898 } support { ## spring spring { # auto proxy the DataSource bean datasource.autoproxy = false } }
-
创建registry.conf, 进行相关的配置, 说明:该文件从 seata 的\conf\registry.conf拷贝,进行修改即可
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa #type = "file" type = "nacos" nacos { #serverAddr = "localhost" serverAddr = "localhost:8848" namespace = "" cluster = "default" } eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6379" db = "0" } zk { cluster = "default" serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } consul { cluster = "default" serverAddr = "127.0.0.1:8500" } etcd3 { cluster = "default" serverAddr = "http://localhost:2379" } sofa { serverAddr = "127.0.0.1:9603" application = "default" region = "DEFAULT_ZONE" datacenter = "DefaultDataCenter" cluster = "default" group = "SEATA_GROUP" addressWaitTime = "3000" } file { name = "file.conf" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "file" nacos { serverAddr = "localhost" namespace = "" } consul { serverAddr = "127.0.0.1:8500" } apollo { app.id = "seata-server" apollo.meta = "http://192.168.1.204:8801" } zk { serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } }
-
创建 Account.java
package com.leon.springcloud.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* ClassName:Account
* Package:com.leon.springcloud.entity
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/10/3 21:25
* @Version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
private Long id;
private Long userId;
private Integer money;
}
-
创建 AccountDao.java
package com.leon.springcloud.dao; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; /** * ClassName:AccountDao * Package:com.leon.springcloud.dao * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 21:28 * @Version: 1.0 */ @Mapper public interface AccountDao { void reduce(@Param("userId") Long userId, @Param("money") Integer money); }
-
创建 resources/mapper/AccountMapper.xm
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.leon.springcloud.dao.AccountDao"> <resultMap id="baseAccount" type="com.leon.springcloud.entity.Account"> <result property="id" column="id" jdbcType="BIGINT"/> <result property="userId" column="user_id" jdbcType="BIGINT"/> <result property="money" column="money" jdbcType="INTEGER"/> </resultMap> <update id="reduce"> UPDATE account SET money = money- #{money} WHERE user_id = #{userId}; </update> </mapper>
-
创建 AccountService.java
package com.leon.springcloud.service; /** * ClassName:AccountService * Package:com.leon.springcloud.service * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 21:31 * @Version: 1.0 */ public interface AccountService { void reduce( Long userId, Integer money); }
-
创建 AccountServiceImpl.java
package com.leon.springcloud.service.impl; import com.leon.springcloud.dao.AccountDao; import com.leon.springcloud.service.AccountService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * ClassName:AccountServiceImpl * Package:com.leon.springcloud.service.impl * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 21:32 * @Version: 1.0 */ @Service @Slf4j public class AccountServiceImpl implements AccountService { @Resource private AccountDao accountDao; @Override public void reduce(Long userId, Integer money) { log.info("========seata_account_micro_service-10012 扣减账户余额 start======"); accountDao.reduce(userId, money); log.info("========seata_account_micro_service-10012 扣减账户余额 end======"); } }
-
创建 AccountController.java
package com.leon.springcloud.controller; import com.leon.springcloud.entity.Result; import com.leon.springcloud.service.AccountService; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * ClassName:AccountController * Package:com.leon.springcloud.controller * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 21:35 * @Version: 1.0 */ @RestController public class AccountController { @Resource private AccountService accountService; @PostMapping("/account/reduce") public Result reduce(@RequestParam("userId") Long userId, @RequestParam("money") Integer money) { accountService.reduce(userId, money); return Result.success("扣减余额成功"); } }
-
创建 MyBatisConfig.java
package com.leon.springcloud.config; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration; /** * ClassName:MybatisConfiig * Package:com.leon.springcloud.config * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 17:29 * @Version: 1.0 */ @Configuration @MapperScan({"com.leon.springcloud.dao"}) public class MybatisConfig { }
-
创建 DataSourceProxyConfig.java , 常规配置(拿来使 用即可)
package com.leon.springcloud.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
* ClassName:DataSourceProxyConfig
* Package:com.leon.springcloud.config
* Description:
*
* @Author: leon-->ZGJ
* @Create: 2024/10/3 20:44
* @Version: 1.0
*/
/*
* 1.这里很重要:配置数据源的代理是Seata也就是使用Seata代理数据源
* 2.DataSourceProxy是引入的io.seata.rm.datasource
* */
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapper-locations}")
private String mapperLocations;
//配置DataSource
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource(){
return new DruidDataSource();
}
//配置DataSourceProxy-使用seata代理数据源
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource){
return new DataSourceProxy(dataSource);
}
//配置SqlSessionFactory
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
-
创建主启动类
package com.leon.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; /** * ClassName:SeataAccountMicroServiceApplication10012 * Package:com.leon.springcloud * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 21:38 * @Version: 1.0 */ @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableDiscoveryClient @EnableFeignClients public class SeataAccountMicroServiceApplication10012 { public static void main(String[] args) { SpringApplication.run(SeataAccountMicroServiceApplication10012.class, args); } }
3.5 创建seata-order-micro-service-10008微服务
-
参考以前的方式,创建seata-order-micro-service-10008微服务模块
-
修改pom.xml文件,添加相关依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.leon.springcloud</groupId> <artifactId>e-commerce-center</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>seata-order-micro-service-10008</artifactId> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!--引入seata starter场景启动器--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <!--排除自带的seata-all,引入自己的版本,否则会出现冲突--> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> </exclusion> </exclusions> </dependency> <!--引入指定版本的seata-all--> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>0.9.0</version> </dependency> <!--引入OpenFeign starter场景启动器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--引入Nacos场景启动器--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId> </dependency> <!--引入Spring-Boot-web场景驱动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--spring-boot-starter-actuator是Spring Boot程序的监控系统,可以实现系统的健康检测 可以通过:https://localhost:10000/actuator 看到相关的连接和信息 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--引入Spring Boot整合Mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--引入Druid场景驱动--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency> <!--引入Mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--引入spring-boot-starter-jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--引入lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--引入spring-boot-starter-test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!--引入API--> <dependency> <groupId>com.leon.springcloud</groupId> <artifactId>e-commerce-center-common-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
-
创建application.yml文件,添加相关配置
server: port: 10008 spring: application: name: seata-order-micro-service-10008 cloud: #配置seata alibaba: #配置指定事务组名,需要和seata-server中的conf/file.conf对应 #vgroup_mapping.my_test_tx_group = "leon_order_tx_group" seata: tx-service-group: leon_order_tx_group #配置Nacos nacos: discovery: #配置Nacos Server的服务地址 server-addr: localhost:8848 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/order_micro_service?useInformationSchema=false username: root password: root logging: level: io: #配置seata日志输出 seata: info mybatis: mapper-locations: classpath:mapper/*.xml
-
创建file.conf, 进行相关的配置, 说明:该文件从 seata 的\conf\file.conf拷贝,进行修改即可
transport { # tcp udt unix-domain-socket type = "TCP" #NIO NATIVE server = "NIO" #enable heartbeat heartbeat = true #thread factory for netty thread-factory { boss-thread-prefix = "NettyBoss" worker-thread-prefix = "NettyServerNIOWorker" server-executor-thread-prefix = "NettyServerBizHandler" share-boss-worker = false client-selector-thread-prefix = "NettyClientSelector" client-selector-thread-size = 1 client-worker-thread-prefix = "NettyClientWorkerThread" # netty boss thread size,will not be used for UDT boss-thread-size = 1 #auto default pin or 8 worker-thread-size = 8 } shutdown { # when destroy server, wait seconds wait = 3 } serialization = "seata" compressor = "none" } service { #vgroup->rgroup #vgroup_mapping.my_test_tx_group = "default" #vgroup_mapping.leon_order_tx_group 中的leon_order_tx_group要 #和application.yml文件中的spring.cloud.alibaba.seata.tx-service-group: leon_order_tx_group #的值一致 vgroup_mapping.leon_order_tx_group = "default" #only support single node default.grouplist = "127.0.0.1:8091" #degrade current not support enableDegrade = false #disable disable = false #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent max.commit.retry.timeout = "-1" max.rollback.retry.timeout = "-1" } client { async.commit.buffer.limit = 10000 lock { retry.internal = 10 retry.times = 30 } report.retry.count = 5 tm.commit.retry.count = 1 tm.rollback.retry.count = 1 } ## transaction log store store { ## store mode: file、db #mode = "file" mode = "db" ## file store file { dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions max-branch-session-size = 16384 # globe session size , if exceeded throws exceptions max-global-session-size = 512 # file buffer size , if exceeded allocate new buffer file-write-buffer-cache-size = 16384 # when recover batch read size session.reload.read_size = 100 # async, sync flush-disk-mode = async } ## database store db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc. datasource = "dbcp" ## mysql/oracle/h2/oceanbase etc. db-type = "mysql" driver-class-name = "com.mysql.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/seata" #user = "mysql" user = "root" #password = "mysql" password = "root" min-conn = 1 max-conn = 3 global.table = "global_table" branch.table = "branch_table" lock-table = "lock_table" query-limit = 100 } } lock { ## the lock store mode: local、remote mode = "remote" local { ## store locks in user's database } remote { ## store locks in the seata's server } } recovery { #schedule committing retry period in milliseconds committing-retry-period = 1000 #schedule asyn committing retry period in milliseconds asyn-committing-retry-period = 1000 #schedule rollbacking retry period in milliseconds rollbacking-retry-period = 1000 #schedule timeout retry period in milliseconds timeout-retry-period = 1000 } transaction { undo.data.validation = true undo.log.serialization = "jackson" undo.log.save.days = 7 #schedule delete expired undo_log in milliseconds undo.log.delete.period = 86400000 undo.log.table = "undo_log" } ## metrics settings metrics { enabled = false registry-type = "compact" # multi exporters use comma divided exporter-list = "prometheus" exporter-prometheus-port = 9898 } support { ## spring spring { # auto proxy the DataSource bean datasource.autoproxy = false } }
-
创建registry.conf, 进行相关的配置, 说明:该文件从 seata 的\conf\registry.conf拷贝,进行修改即可
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa #type = "file" type = "nacos" nacos { #serverAddr = "localhost" serverAddr = "localhost:8848" namespace = "" cluster = "default" } eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6379" db = "0" } zk { cluster = "default" serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } consul { cluster = "default" serverAddr = "127.0.0.1:8500" } etcd3 { cluster = "default" serverAddr = "http://localhost:2379" } sofa { serverAddr = "127.0.0.1:9603" application = "default" region = "DEFAULT_ZONE" datacenter = "DefaultDataCenter" cluster = "default" group = "SEATA_GROUP" addressWaitTime = "3000" } file { name = "file.conf" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "file" nacos { serverAddr = "localhost" namespace = "" } consul { serverAddr = "127.0.0.1:8500" } apollo { app.id = "seata-server" apollo.meta = "http://192.168.1.204:8801" } zk { serverAddr = "127.0.0.1:2181" session.timeout = 6000 connect.timeout = 2000 } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } }
-
创建 Order.java
package com.leon.springcloud.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * ClassName:Order * Package:com.leon.springcloud.entity * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 21:50 * @Version: 1.0 */ @Data @AllArgsConstructor @NoArgsConstructor public class Order { private Long id; private Long userId; private Long productId; private Integer nums; private Integer money; private Integer status; }
-
创建 OrderDao.java
package com.leon.springcloud.dao; import com.leon.springcloud.entity.Order; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; /** * ClassName:OrderDao * Package:com.leon.springcloud.dao * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 21:51 * @Version: 1.0 */ @Mapper public interface OrderDao { //新建订单 void save(Order order); //修改订单状态 void update(@Param("userId") Long userId, @Param("status") Integer status); }
-
创建 resources/mapper/OrderMapper.xm
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.leon.springcloud.dao.OrderDao"> <resultMap id="baseOrder" type="com.leon.springcloud.entity.Order"> <id column="id" property="id" jdbcType="BIGINT"/> <result column="user_id" property="userId" jdbcType="BIGINT"/> <result column="product_id" property="productId" jdbcType="BIGINT"/> <result column="nums" property="nums" jdbcType="INTEGER"/> <result column="money" property="money" jdbcType="INTEGER"/> <result column="status" property="status" jdbcType="INTEGER"/> </resultMap> <insert id="save"> insert into `order` (id,user_id,product_id,nums,money,status) values (null,#{userId},#{productId},#{nums},#{money},0); </insert> <update id="update"> update `order` set status = #{status} where user_id=#{userId} </update> </mapper>
-
创建 OrderService.java
package com.leon.springcloud.service; import com.leon.springcloud.entity.Order; /** * ClassName:OrderService * Package:com.leon.springcloud.service * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 21:55 * @Version: 1.0 */ public interface OrderService { void save(Order order); }
-
创建StorageService.java
package com.leon.springcloud.service; import com.leon.springcloud.entity.Result; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; /** * ClassName:StorageService * Package:com.leon.springcloud.service * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 21:56 * @Version: 1.0 */ @FeignClient(value = "seata-storage-micro-service") public interface StorageService { @PostMapping(value = "/storage/reduce") public Result reduce(@RequestParam("productId") Long productId, @RequestParam("nums") Integer nums); }
-
创建AccountService.java
package com.leon.springcloud.service; import com.leon.springcloud.entity.Result; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; /** * ClassName:AccountService * Package:com.leon.springcloud.service * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 21:56 * @Version: 1.0 */ @FeignClient(value = "seata-account-micro-service") public interface AccountService { @PostMapping(value = "/account/reduce") public Result reduce(@RequestParam("userId") Long userId,@RequestParam("money") Integer money); }
-
创建 OrderServiceImpl.java
package com.leon.springcloud.service.impl; import com.leon.springcloud.dao.OrderDao; import com.leon.springcloud.entity.Order; import com.leon.springcloud.service.AccountService; import com.leon.springcloud.service.OrderService; import com.leon.springcloud.service.StorageService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; /** * ClassName:OrderServiceImpl * Package:com.leon.springcloud.service.impl * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 21:55 * @Version: 1.0 */ @Service @Slf4j public class OrderServiceImpl implements OrderService { @Resource private OrderDao orderDao; @Resource private AccountService accountService; @Resource private StorageService storageService; @Override public void save(Order order) { log.info("=========开始新建订单 start =========="); //1.调用本地方法生成订单Order orderDao.save(order); log.info("=========减库存 start =========="); //2.远程调用Storage微服务扣减坤村 storageService.reduce(order.getProductId(),order.getNums()); log.info("=========减库存 end =========="); log.info("=========减账户金额 start =========="); //3.远程调用Account微服务扣减用户金额 accountService.reduce(order.getUserId(),order.getMoney()); log.info("=========减账户金额 end =========="); log.info("=========修改订单状态 start =========="); //4.调用本地方法修改订单状态0->1 orderDao.update(order.getUserId(), 1); log.info("=========修改订单状态 end =========="); log.info("=========下订单 end=========="); } }
-
创建 OrderController.java
package com.leon.springcloud.controller; import com.leon.springcloud.entity.Order; import com.leon.springcloud.entity.Result; import com.leon.springcloud.service.OrderService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * ClassName:OrderController * Package:com.leon.springcloud.controller * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 22:21 * @Version: 1.0 */ @RestController public class OrderController { @Resource private OrderService orderService; @GetMapping("/order/save") public Result saveOrder(Order order) { /* * 报错:Failed to fetch schema of `order` * 参考网址:https://blog.csdn.net/L1Ha1Y1/article/details/108681489 * */ orderService.save(order); return Result.success("订单生成成功"); } }
-
创建 MyBatisConfig.java
package com.leon.springcloud.config; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Configuration; /** * ClassName:MybatisConfiig * Package:com.leon.springcloud.config * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 17:29 * @Version: 1.0 */ @Configuration @MapperScan({"com.leon.springcloud.dao"}) public class MybatisConfig { }
-
创建DataSourceProxyConfig.java , 常规配置(拿来使 用即可)
package com.leon.springcloud.config; import com.alibaba.druid.pool.DruidDataSource; import io.seata.rm.datasource.DataSourceProxy; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.transaction.SpringManagedTransactionFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import javax.sql.DataSource; /** * ClassName:DataSourceProxyConfig * Package:com.leon.springcloud.config * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 20:44 * @Version: 1.0 */ /* * 1.这里很重要:配置数据源的代理是Seata也就是使用Seata代理数据源 * 2.DataSourceProxy是引入的io.seata.rm.datasource * */ @Configuration public class DataSourceProxyConfig { @Value("${mybatis.mapper-locations}") private String mapperLocations; //配置DataSource @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource(){ return new DruidDataSource(); } //配置DataSourceProxy-使用seata代理数据源 @Bean public DataSourceProxy dataSourceProxy(DataSource dataSource){ return new DataSourceProxy(dataSource); } //配置SqlSessionFactory @Bean public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations)); sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return sqlSessionFactoryBean.getObject(); } }
-
创建主启动类
package com.leon.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; /** * ClassName:SeataOrderMicroServiceApplication10008 * Package:com.leon.springcloud * Description: * * @Author: leon-->ZGJ * @Create: 2024/10/3 22:24 * @Version: 1.0 */ @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableDiscoveryClient @EnableFeignClients public class SeataOrderMicroServiceApplication10008 { public static void main(String[] args) { SpringApplication.run(SeataOrderMicroServiceApplication10008.class, args); } }
-
注意事项和细节
-
Mysql出现too many connections(1040)错误解决方法
- 在my.ini中设置
max_conenctions=1000
-
如果出现:service id not legal hostname
- 报错Service id not legal hostname的原因是服务名称不能带有下划线,可以使用中划线,Spring Cloud无法识别下划线,把下划线改成中划线就可以了
-
3.6 三个微服务协同完成-使用@GlobalTransactional完成分布式事务控制(出现异常,也能保证数据一致性)
-
在OrderServiceImpl.java中的save方法上添加注解
/* * 1. GlobalTransactional:表示分布式全局事务控制,在io.seata.spring.annotation包下 * 2. name = "leon-save-order" 事务名称,程序员自己指定,但是需要保证唯一性 * 3. rollbackFor = Exception.class 指定发生什么异常就回滚数据,这里表示只要发生异常我就回滚 * */ @GlobalTransactional(name = "leon-save-order", rollbackFor = Exception.class) @Override public void save(Order order) { log.info("=========开始新建订单 start =========="); //1.调用本地方法生成订单Order orderDao.save(order); log.info("=========减库存 start =========="); //2.远程调用Storage微服务扣减坤村 storageService.reduce(order.getProductId(),order.getNums()); log.info("=========减库存 end =========="); log.info("=========减账户金额 start =========="); //3.远程调用Account微服务扣减用户金额 accountService.reduce(order.getUserId(),order.getMoney()); log.info("=========减账户金额 end =========="); log.info("=========修改订单状态 start =========="); //4.调用本地方法修改订单状态0->1 orderDao.update(order.getUserId(), 1); log.info("=========修改订单状态 end =========="); log.info("=========下订单 end=========="); }
3.7 注意事项和细节
- 如果数据库/表使用到关键字,需要使用反引号
- OpenFeign在远程调用API接口时,默认超时时间为1秒
- 在resource目录下的file.conf文件要注意:
- vgroup_mapping.leon_order_tx_group 中的leon_order_tx_group要和application.yml文件中的spring.cloud.alibaba.seata.tx-service-group: leon_order_tx_group的值一致
- 参考网址:https://www.cnblogs.com/aclq/p/17903145.html#:~:text=Seata
- 在运行过程中报错:Failed to fetch schema of
order
,需要在application.yml文件的spring.data.url中添加语句:useInformationSchema=false
4. Seate 工作机制/反讲
4.1 分布式事务过程分析
-
Seata分布式事务处理过程-ID+三组件模型
-
示意图

-
梳理属于:XID、TC、RM、TM
- Transaction ID XID: 全局唯一的事务ID
- Transaction Coordinator(TC) : 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚
- Transaction Manager(TM) : 控制全局事务的边界,负责开启一个全局事务,并最终发 起全局提交或全局回滚的决议;
- Resource Manager(RM) : 控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
-
执行过程
- TM向 TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID
- XID在微服务调用链路的上下文中传播
- RM 向 TC注册分支事务,将其纳入 XID 对应全局事务的管辖
- TM 向 TC 发起针对 XID 的全局提交或回滚决议
- TC 调度 XID下管辖的全部分支事务完成提交或回滚请求。
4.2 Seata事务模式
- AT(默认模式)
- TCC
- SAGA
- XA
4.3 AT无侵入模式
-
文档
-
一阶段加载
-
在一阶段,Seata会拦截"业务SQL"
-
解析 SQL 语义,找到"业务 SQL"要更新的业务数据,在业务数据被更新前,将其保存成"before image" (前置镜像)
-
执行"业务 SQL"更新业务数据,在业务数据更新之后,其保存成"afterimage"/后置镜像
-
最后生成行锁
-
以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子
-
-
二阶段提交
- 二阶段如果是顺利提交
- 因为"业务 SQL"在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快 照数据和行锁删掉,完成数据清理即可
-
二阶段回滚
- 二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的"业务 SQL",还原业务数据
- 回滚方式便是用"beforeimage"还原业务数据;但在还原前要首先要校验脏写,对比"数 据库当前业务数据"和"afterimage 如果两份数据完全一致就说明没有脏写,可以还原业 务数据
- 如果不一致就说明有脏写,出现脏写就需要转人工处理。
4.4 AT事务模式Debug验证
-
Debug分析图
4.5 Seata的分布式交易解决方案
-
分布式事务二次梳理