完整2024 SpringCloudAlibaba 微服务从0到1企业级实战分享

2,314 阅读24分钟

序言

1.版本选型

2024年了,微服务已经不再是一个很新的技术了,老的那一套技术很多已经停止维护,社区也不在活跃,或者国内也没要流行起来,可以参考:

image.png

本篇文章所用的版本是:JDK17+Mysql8+SpringBoot3.0.x+SpringCloudAlibaba 2022.0.0.0-RC1+Maven3.9.9,版本尽量保持一致食用,不会涉及到很多基础的知识,默认已经有了SpringBoot,SSM,JAVAWeb基础

2.内容概要

红色标记的内容,是一定要掌握;蓝色浅色的内容,留给还有余力的兄弟食用 image.png

一 注册中心Nacos

1.单机部署Nacos2.4.2

所有的压缩包,文档都可以参考Nacos官网:nacos.io/ 下载当前最新的版本2.4.2,这里没有使用Docker部署,所以下载压缩包解压即可

image.png

jdk最低1.8,确保环境没有问题,解压文件,进入到Nacos的bin目录,切换到终端,执行单机运行命令 sh startup.sh -m standalone,如果是Win系统命令如下startup.cmd -m standalone,这些官网文档都可以参考

启动成功后,可以访问地址:http://127.0.0.1:8848/nacos 单机部署,不加任何配置,是不需要密码,所以直接就可以打开

image.png

看到页面出现,就表示Nacos部署成功了🎉。有一个注意事项,目前是学习阶段,实际生产并不会单机部署,需要注意

image.png 因为nacos也是java语言开发的,所以可以直接输入jps命令,也可以查看java服务,知识要灵活运用

image.png

2.SpringBoot3.0.x微服务环境构建

这里使用Maven聚合工程进行构建,在父工程pom中规定了所有依赖的版本,子工程引入父工程的依赖版本,通用的依赖全局引入

父工程pom文件核心内容

因为alibaba也是在原spingcloud上做的二次开发,所以也需要引入springcloud依赖,后续会用到 image.png

子工程pom文件依赖

image.png

yml文件配置信息

image.png

编写启动类

image.png

启动成功

image.png

在nacos中可以查询到服务

image.png 微服务环境基本搭建完成,后续的代码都是这个架构去编写

3.临时实例和永久实例

默认注册到nacos中的实例属于临时节点,一旦服务下线,就会被nacos剔除,如下可以看到,默认就是临时节点

image.png

如果要改成永久节点,可以修改配置文件,添加配置

image.png 重新启动时,发现服务一直无法启动,一直报错;csdn一篇文章可以参考 blog.csdn.net/javadream00… 当然,环境不一样可能解决方式也不同,我这里是重启启动了nacos,然后在重新启动服务,实例进行重新注册就可以了

image.png 可以看到已经不是临时实例了

image.png

如果是永久实例,即使服务挂了,也不会剔除,我这边手动停掉服务,进行测试

image.png ok,可以看到依然注册在nacos中,但是健康状态改成了false,重新启动服务就可以了

4.心跳机制和多网卡环境配置

nacos是如何检查注册的服务是正常还是宕机呢?有一个健康检查机制--心跳机制。如果注册到nacos中的实例是临时节点,临时节点会每隔几秒,向nacos注册中心发送数据,确保服务没有死亡;如果注册的是永久节点,nacos会主动向服务进行通信,如果可以正常的收发数据,则服务正常;所以临时和永久节点的心跳机制是存在区别的,可以简单理解成主动和被动; 这种日志就是在进行心跳数据包的收发

image.png

这里又会存在一个问题:如果是永久节点,nacos会主动向节点发送心跳包,既然涉及到发送数据,那么就需要知道服务的ip地址;如果电脑里面安装了多个网卡,比如虚拟机的虚拟网卡,可能会导致无法找到真实的ip。所以多网卡环境,需要手动去设置网段,比如我的mac电脑,可以看到当前的ip:

image.png 对应的网段:192.168.1 添加到yml配置文件中,不然服务的健康状态可能会是fasle

image.png 好了,这样就可以了

5.Nacos构建高可用集群

注意点,最后ncaos重新配置才解决

image.png

5.1 为什么需要使用Mysql

这里很多人可能会看不懂,这是官方文档推荐的部署内容。为什么会用到Mysql呢?其实很好理解,既然要部署集群,就会涉及到配置信息,通过Mysql将配置信息持久化到数据库,数据库版本这里使用Mysql8,最低也要大于5.6.5,目前也只支持Mysql

image.png

Nacos本身自带一个小型数据库,可以看到这是数据库的路径,但是官方推荐使用Mysql,所以这里就当知识补充,可以自行研究,本文会用Mysql8进行高可用集群搭建 image.png

5.2 数据库配置

无论是从官网还是下载的nacos文件,都可以获取数据库的脚本

image.png

image.png

下载的nacos数据库脚本路径

image.png

运行sql脚本,创建nacos需要的数据库表和信息,圈出来的都是nacos脚本生成的表

image.png

5.3 nacos数据库配置文件修改

找到对应的文件 image.png

配置用户名密码和数据库,因为我们单机本地部署,并没有开启鉴权

源文件已经注释,这里开启,表示使用Mysql做持久化
spring.sql.init.platform=mysql
源文件已经注释,这里开启,我们这里就用到了一个数据库,写1
db.num=1
设置数据库链接信息
db.url.0=jdbc:mysql://127.0.0.1:3306/cloud2024?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=nacos
db.password.0=nacos
开启鉴权
### If turn on auth system:
nacos.core.auth.enabled=true
自定义密码
nacos.core.auth.server.identity.key=supernacos
nacos.core.auth.server.identity.value=supernacos

## The default token (Base64 String): 
自定义key
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey0121222355890123199859012334346789487234567823131344343432


重新启动nacos,此时需要输入用户名和密码

5.4 自定义用户名和密码

自定义的密码需要使用nacos符合的加密规则 image.png 创建新用户和密码

image.png 登录成功

image.png 创建的新用户,没有默认的命名空间,需要新创建,命名空间作用后面介绍 可以参考文章 nacos.io/blog/faq/na…

如果配置了多个用户,需要在application.yml中明确用户名和密码,不然无法启动,提示找不到用户

image.png

5.5 搭建3个nacos集群

找到集群的配置文件,修改后缀名,源文件在conf目录下,cluster.conf就是修改后的文件

image.png 在文件内写入真实的ip和启动端口,端口号不能连续,因为nacos启动时会占用端口

image.png 复制三份nacos文件,记得修改application.properties,修改nacos的启动端口,

image.png 三个nacos服务,直接在bin目录下运行 sh startup.sh即可,nacos默认就是集群启动

image.png 可以看到默认集群启动

image.png

登录其中一个nacos就可以看到集群信息

image.png

修改java配置文件,使得服务接入nacos集群中,都是本机部署,所以直接写127.0.0.1或者localhost都行

image.png 可以看到服务已经注册到nacos集群中,任意一个节点都可以看到当前在线服务

image.png

6 Nacos一致性协议

Nacos的一致性协议在健康检测层面使用的是AP(高可用)Distro协议;在配置中心使用的是CP(强一致性)的Raft协议

6.1 阿里Distro AP协议

Distro 协议是 Nacos 社区自研的一种 AP 分布式协议,是面向临时实例设计的一种分布式协议,其保证了在某些 Nacos 节点宕机后,整个临时实例处理系统依旧可以正常工作。作为一种有状态的中间件应用的内嵌协议,Distro 保证了各个 Nacos 节点对于海量注册请求的统一协调和存储。

6.2 经典Raft CP协议

具体所有的内容,可以参考官网 nacos.io/docs/ebook/…

二 客户端负载均衡SpringCloud Loadbalancer

在SpringCloud2020版本之前,客户端负载均衡使用的Ribbon组件,现在已经不再维护;从2021,2022的SpringCloud开始,都是用新的LoadBalancer来做客户端的负载均衡,Ribbon已经过时了

1 客户端负载均衡LoadBalancer原理

image.png

所有的服务都会注册到nacos中,所以从nacos可以获取所有服务的信息,包括ip端口等;订单服务通过nacos可以调用用户服务,从而通过远程调用获取用户数据;如果用户服务存在多个节点,会走负载均衡去分发请求到user集群中的一个节点;负载均衡的策略有很多,比如常见的随机,轮训等;如果nacos中的服务挂掉,那么不健康的实例是不会作用远程调用服务的,nocas只会筛选健康的服务

2 Loadbalancer快速使用

SpringCloud Loadbalancer一般配合RestTemplate使用; 需求:查询订单数据和关联的用户数据;首先有两张表订单表和用户表。大致结构如下:用户表和订单时一对多的关系

image.png

创建对应的实体类,引入MybatisPlus,具体使用大家应该都会了,毕竟已经是cloud阶段了,基本代码写完了,并且开启了允许多实例,只需要分配一个没有使用的端口就行。如果代码不理解,也没有问题,都可以在gitee获取仓库,都是开源免费 image.png 注册中心可以看到有三个业务服务

image.png

image.png 配置RestTemplate和LoadBalancer,需要添加依赖,增加的依赖如图:order和user模块都需要引入

image.png 在order模块添加loadBalancer依赖

image.png 修改配置文件,比如添加数据库连接,mybatis-plus日志等

image.png 远程服务名调用业务逻辑,原始是通过ip端口调用,现在直接服务名就可以

image.png

postman测试

image.png 无论是9201端口服务还是9200都可以接收到请求,所有的请求会负载到两个节点上面

image.png

到这里,运行都没有问题,loadbalancer基本使用完成了

3.切换负载均衡策略

默认的负载均衡策略是轮训,所有的请求均等分配到服务节点上(概率上均等),如何配置策略呢,比如换成随机策略:

package com.light.order.config;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

public class CustomLoadBalancerConfiguration {
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(loadBalancerClientFactory
                .getLazyProvider(name, ServiceInstanceListSupplier.class),
                name);
    }
}

在上游服开启随机负载均衡,userService是下游服务名,这个不要写错地方

image.png

可以看到结果,一共发了四次请求,3个请求在userService9201节点,一个在userService9200节点,已经不在是轮训,改成随机访问了

image.png 负载均衡配置策略修改成功,如果要修改其他策略,参考这种方式即可

4.LoadBalancer缓存配置

4.1 自带的缓存方案

loadbalancer为了提高使用的效率,自带了缓存的配置使用,设置如下: 在orderServcei服务的配置文件中如下编写

image.png

4.2高性能缓存方案

利用Caffeine实现Loadbalancer缓存

Google的guava是早期流行的本地缓存方案,后来出来jdk8,很好的支持Lambda表达式和并行流式计算,所以集合了guava和jdk8新特性诞生了caffeine,号称java本地缓存之王 地址 github.com/ben-manes/c…

image.png

添加依赖

<dependencies>
  <dependency>
      <groupId>com.github.ben-manes.caffeine</groupId>
      <artifactId>caffeine</artifactId>
      <version>3.1.5</version>
  </dependency>
</dependencies>

什么都不需要编写,默认SpringCloud Balancer已经整合了Caffeine,只要引入依赖就行了,还是很方便

三 远程服务调用SpringCloud OpenFeign

既然已经有了RestTemplate+LoadBalancer可以实现服务的远程调用,那为什么还要学习Open Feign呢?因为Open Feign代码更简单,耦合性更低,代码好维护,可读性高

需求:将上述用RestTemplate+LoadBalancer的代码,改成OpenFeign

1. OpenFeign快速使用

OpenFeign需要结合LoadBalancer使用,底层也是LoadBalancer去做负载,只是语法和RestTemplate不一样,所以也很简单;在orderServce添加依赖 image.png 添加开启OpenFeign注解 image.png 定义OpenFeign远程接口调用,添加服务名和url image.png 使用很简单,替换原来的代码,当成SringBean对象注入即可 image.png postman测试 image.png

如果接口参数是一个对象,也是按照字符串JSON格式,需要添加注解即可

image.png

四 SpringCloud Gateway网关组件

网关可以理解为一个请求拦截器服务,所有的请求需要网关放行,才能到达后台的业务服务。比如可以用网关做统一的认证授权;黑白名单拦截;流量限流等;如图:

image.png

1.快速搭建网关实践

新增网关子模块 image.png

添加依赖 image.png

编写启动类 image.png

修改配置文件 image.png 其他的配置信息前面已经说过了,这里添加了红色圈起来的配置,可能很多人无法理解,http://127.0.0.1:9000/userService/user/findUserById/1 ,这是一个完整的请求。9000是网关端口,userService是服务名,/user/findUserById/1是url路径,这里并没有直接访问userService,而且通过网关进行自动映射,按照我给的请求就行拼接,服务名就可以通过网关进行自动映射

image.png 请求成功后,我们的网关初体验就完成了

2.网关自定义路由规则

网关自动进行路由映射,在实际工作中很少用,一般都是自定义路由规则,关闭自动映射 image.png 一个 - id 标识,就代表一个服务的网关配置,下面对配置就行讲解: image.png

  1. -id:服务网关路由的名称,不能重复,唯一标识,类似于数据库的主键ID
  2. uri: lb 负载均衡的意思,loadbalance的缩写,所以网关需要引入负载均衡依赖;orderService实际的服称,也就是需要转发的服务名,一般注册在nacos中
  3. predicates: Path属性代码访问的通过访问的url路径,比如 http://127.0.0.1:9000/orderService/order/list 这个就是通过网关进行访问的路径,满足Path开头的路径 都会分发到orderService上
  4. filters:StripPrefix = 1 表示会将请求第一个路径进行截取,可以看一下图就很好理解

image.png

基本常用的配置就是这些,如果要用别的配置可以自行谷歌学习

3.自定义谓词

首先什么是自定义谓词?可以看到predicates属性就是谓词,只是一个称呼而已。自定义谓词说白了,就是可以自定义属性

image.png

首先我们可以定义一个属性,但是不会生效;

image.png

自定义谓词,就是让我们自定义的属性生效,可以通过网关,对请求进行处理,其实这里就类似于拦截器的功能,创建实现类,实现类的命名=自定义谓词属性+ RoutePredicateFactory image.png

然后通过postman模拟请求 image.png 可以看到debug,获取到了所有请求头的数据,就有我们自己携带的数据,那么就可以对yml里面配置的数据和请求携带数据就行业务处理;返回true表示放行请求,false表示请求拦截;拦截会返回404;

image.png

4.自定义局部过滤器

通过对各个服务网关自定义谓词,实现局部过滤器;网关目前已经提供好了类似的功能。在网关对应各个服务中,添加如下配置:分别是请求拦截器和相应拦截器,也就是局部过滤器,只会对配置的服务进行生效,而不是对所有服务生效

image.png 创建对应的拦截器类,命名方式属性名+GatewayFilterFactory,这是写死的

package com.light.gateway.filter;


import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class RequestLoggingGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestLoggingGatewayFilterFactory.Config> {

    public RequestLoggingGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            log.info("Request URI: {}", request.getURI());
            return chain.filter(exchange);
        };
    }

    public static class Config {
    }
}
package com.light.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class ResponseLoggingGatewayFilterFactory extends AbstractGatewayFilterFactory<ResponseLoggingGatewayFilterFactory.Config> {

    public ResponseLoggingGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> chain.filter(exchange)
                .doFinally(signalType -> {
                    ServerHttpResponse response = exchange.getResponse();
                    log.info("Response headers: {}", response.getHeaders());
                });
    }

    public static class Config {
    }
}

可以看到访问订单服务的时候,控制台日志如下;但是访问别的服务不会有这个日志,需要自行配置

image.png

5.自定义全局过滤器

全局过滤器,顾名思义,所有的服务请求,都会优先走全局过滤器,具体配置如下:

package com.light.gateway.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component //自动实例化并被Spring IOC容器管理
//全局过滤器必须实现两个接口:GlobalFilter、Ordered
//GlobalFilter是全局过滤器接口,实现类要实现filter()方法进行功能扩展
//Ordered接口用于排序,通过实现getOrder()方法返回整数代表执行当前过滤器的前后顺序
public class MyGlobalFilter implements GlobalFilter, Ordered {
    //基于slf4j.Logger实现日志输出
    private static final Logger logger = LoggerFactory.getLogger(MyGlobalFilter.class);
    //起始时间属性名
    private static final String ELAPSED_TIME_BEGIN = "MyGlobalFilterTimeBegin";

    /**
     * 实现filter()方法记录处理时间
     *
     * @param exchange 用于获取与当前请求、响应相关的数据,以及设置过滤器间传递的上下文数据
     * @param chain    Gateway过滤器链对象
     * @return Mono对应一个异步任务,因为Gateway是基于Netty Server异步处理的,Mono对就代表异步处理完毕的情况。
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //Pre前置处理部分
        //在请求到达时,往ServerWebExchange上下文环境中放入了一个属性elapsedTimeBegin,保存请求执行前的时间戳
        exchange.getAttributes().put(ELAPSED_TIME_BEGIN, System.currentTimeMillis());


        //chain.filter(exchange).then()对应Post后置处理部分
        //当响应产生后,记录结束与elapsedTimeBegin起始时间比对,获取RESTful API的实际执行时间
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> { //当前过滤器得到响应时,计算并打印时间
                    Long startTime = exchange.getAttribute(ELAPSED_TIME_BEGIN);
                    if (startTime != null) {
                        logger.info(exchange.getRequest().getRemoteAddress() //远程访问的用户地址
                                + " | " + exchange.getRequest().getPath()  //Gateway URI
                                + " | cost " + (System.currentTimeMillis() - startTime) + "ms"); //处理时间
                    }
                })
        );
    }

    //设置为最高优先级,最先执行ElapsedFilter过滤器
    //return Ordered.LOWEST_PRECEDENCE; 代表设置为最低优先级
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

那么执行的顺序如何呢?这个是请求的执行顺序

image.png

响应的执行顺序刚好相反

image.png

那为什么要这么设计呢?其实很好理解,这么设计很合理;全局拦截器一定会优先拦截请求,比如做统一的认证授权等,然后在把请求发给某个服务;执行完请求后,某个服务执行响应,最后由全局拦截器进行最后的响应,可以进行统一的处理,比如敏感数据的加密和过滤等;

五 配置中心Nacos

本章很重要,nacos作为配置中心,也是很常用的功能

1.配置中心初体验

1.1 创建第一个配置文件

通过nacos创建一个配置文件,文件的命名是有规则,不是随意,一般是服务名+环境名+文件后缀;比如新建一个支付模块,起名是payService,如果是dev环境,一般我们的配置文件都是yml结尾,所以配置文件名就是 payService-dev.yml,这个规则一定要遵守

image.png 刚刚说的文件名,对应的就是Data ID,Group默认分组,可以自己定义,MD5是自动生成,类似于UUID,唯一值;配置内容就是我们正常在application.yml中写的内容,到时候写道nacos配置文件里面

image.png 最后点击发布就行了,在首页就可以看到我们的配置,点击详情可以看到内容,也支持在线编辑 image.png

1.2 搭建工程

为了演示配置中心的使用,这里单独搭建一个模块,就不在之前代码进行修改了,代码还是老规矩,改成一个SpringBoot服务就行 image.png 导入配置中心相关的依赖,这里除了服务发现依赖,还添加了ncaos配置依赖,别少加了 image.png

创建配置文件,这里配置文件已经写进了nacos中了,所以不需要在本地创建application.yml配置文件,这里需要创建bootstrap.yml文件;bootstrip.yml是用来读取nacos远程服务中配置文件,文件的内容如下

image.png 第一步 定位到nacos配置文件,所以需要输入服务名称和环境

第二步 定位到nacos服务ip,这里用的集群,所以输入集群配置和账号密码

启动进行测试

image.png 再来对比一下nacos配置中心的配置,很显然已经读取了nacos中的配置了 image.png

2.配置中心热更新

点击编辑,可以修改nacos中的配置文件

image.png nacos支持热更新,修改完后,配置文件可以自动生效,不需要重启服务,那如何实现呢?看我操作

编辑员配置文件,新增配置

image.png 点击发布 image.png 新建java文件读取配置

image.png

image.png

测试 image.png

@RefreshScope注解支持热更新,所以现在修改一下配置文件 image.png 这里并没有重启服务,直接读取到了新的配置文件 image.png

3.Nacos配置文件加载顺序

Nacos的dataId会按照优先级进行加载配置文件,优先级高的文件会对原文件属性进行覆盖,优先读取1 在 2 3 4 5等

  1. appliaction.yml
  2. appliaction.properties
  3. dataId={spring.application.name}
  4. dataId={spring.application.name}.yml
  5. dataId={spring.application.name}-{spring.profile.active}.yml

4.多环境配置切换

多环境配置一张图就可以搞定,通过设置NameSpace来区分各种环境;通过设置环境下面的分组,区分同一个环境,不同分组的配置 image.png 新建命名空间

image.png 创建命名空间下的配置文件,自定义分组

image.png 这有一个读取文件优先级的问题 我上面文章已经说过了,这里不需要借助spring.profile.active去区分了,namespace写PRD的UUID,然后写好分组就可以了

image.png

项目启动成功,端口也切换到PRD文件的9301 image.png 可以查询PRD环境下的服务

image.png

六 微服务保护组建 Alibaba Sentinel

1.Sentinel部署启动

sentinelguard.io/zh-cn/ 国内的中文官网,可以看考官网学习。Sentinel本身也是一个用java开发的服务,本地可以拉去源代码,进行服务本地启动;也可以去Sentinel Github上面下载最新的jar包文件,本地通过java -jar进行部署;这里使用Sentienl官方提供的github地址,下载最新的jar包,这里使用当前最新的版本1.8.8

image.png

image.png

官网也提供了启动脚本,自己拿来修改就行,比如端口等等

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

我本地启动修改了端口,改成了9400端口,启动时注意jar包名称,自行修改 image.png

image.png

登录地址 http://127.0.0.1:9400/#/login 控制台默认用户和密码都是sentinel image.png ok登录成功

image.png

2.微服务接入Sentinel

创建一个新的模块用于对Sentinel的学习测试,如何创建不解释了,已经到这里了,基础的问题不说了

image.png

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

配置文件输入sentinel的信息

image.png 启动项目 image.png 通过sentinel到控制台可以看到我们当前的服务 image.png 至此,微服务成功接入了Sentinel,点击簇点链路,可以对接口进行限流

image.png qps设置为1以后,当我在频繁访问接口,看来限流已经开启

image.png

七 阿里分布式事务Seata

1 分布式事务

如果只是单体项目,一般都是直接连接数据库,所有的事务回滚都是通过数据库自身的事务去实现;

image.png

但是现在需要把单体服务改成微服务,按照业务进行拆分,这里就会涉及到多个服务链路进行调用,数据库自带事务只能保证单个业务节点事务的一致性,如果要实现多个节点事务一致性,就需要分布式事务;说白了就是实现最终一致性

image.png

为了解决上述事务不一致问题,或者理解成所有节点事务一起回滚,一起提交。这里就需要用到了分布式事务,阿里目前开源的一款分布式事务的解决方案 seata.apache.org/zh-cn/ 国内用的也比较多,也是我今天需要分享的技术

2 分布式事务原理

2.1 两阶段提交

目前主流的分布式事务的解决方案,两阶段提交比较简单,但是性能普遍较差

image.png

**为了满足所有节点一起提交,一起回滚的机制,引入了协调者;**两阶段提交分为准备阶段和提交阶段

准备阶段:

image.png

提交阶段:

image.png

两阶段提交全称(2 Phase Commit)简称 2PC

2.2 三阶段提交

两阶段提交还是比较简单,但是在提交的过程中,如果出现了意外,比如网络不通等其他问题,事务是无法回滚,所以需要在提交前进行一些检测,比如环境网络配置,自定义校验等,说白了就是对事务的提交进行了更细粒度的区分和容错

准备阶段:

image.png

预提交阶段: 即预提交阶段。在预提交阶段,协调者会询问所有的参与者是否可以提交事务,如果有参与者无法提交,那么整个事务会被撤销。

image.png

提交阶段:

image.png

总的来说,二段式提交协议的优点是简单,但是缺点是可能会存在事务无法恢复的问题。而三段式提交协议相对来说更为复杂,但是可以避免这些问题。

3 Seata术语

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

image.png TC:事务协调者,开启一个分布式事务,所有的事务指令通过TC去分发,做事务的调度

TM:TC把事务指令发到TM,TM在对各个资源(RM)进行下发

RM:各个服务节点的事务,管理分支事务处理的资源,并且报告分支事务的状态,并驱动分支事务提交或者回滚。

4 Seata AT模式

4.1 整体机制

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

  • 二阶段:

    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

4.2 工作阶段

阶段1:准备阶段

image.png

阶段2:提交阶段

image.png

阶段3:回滚阶段

image.png

4.3 一个示例说明AT工作流程

订单表:

FieldTypeKey
idbigint(20)PRI
numint
pricedecimal(10,2)

AT分支事务逻辑:

update order set num = 10 where id = 1;
一阶段
  1. 解析SQL:得到SQL类型(UPDATE)表(Order)条件(Where id=1)等相关信息
  2. 查询前镜像:根据查询条件,生成查询语句,定位数据
select id, name, since from product where name = 'TXC';
  1. 执行SQL,更新前镜像数据
image.png

4.更新后镜像数据

image.png

5.插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中,日志内容格式大致如下

image.png
  1. 提交前,向 TC 注册分支:申请 order 表中,主键值等于 1 的记录的 全局锁
  2. 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交
  3. 将本地事务提交的结果上报给 TC。
二阶段-回滚
  1. 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作
  2. 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录
  3. 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改
  4. 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
update product set num = 12 where id = 1;
  1. 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
二阶段-提交
  1. 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC
  2. 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录

好累啊 ,完全写不动了,兄弟们

image.png

回滚日志表

UNDO_LOG Table:不同数据库在类型上会略有差别。

以MYSQL为例子

FieldType
branch_idbigint PK
xidvarchar(100)
contextvarchar(128)
rollback_infolongblob
log_statustinyint
log_createddatetime
log_modifieddatetime

4.4 微服务整合Seata AT模式

下载启动seata-server

seata.apache.org/unversioned… 下载最新版本

image.png 解压文件,打开bin目录

image.png

sh seata-server.sh

未完待续... ...