SpringCloud Alibaba学习记录

相关组件

  • Sentinel :把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳 定性。
  • Nacos :一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台,服务注册和配置中心。
  • RocketMQ :一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠 的消息发布与订阅服务。
  • Dubbo : Apache Dubbo™ 是一款高性能 Java RPC 框架。
  • Seata :阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
    Alibaba Cloud ACM :一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心 产品。
  • Alibaba Cloud OSS : 阿里云对象存储服务( Object Storage Service ,简称OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和 访问任意类型的数据。
  • Alibaba Cloud SchedulerX : 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精 准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
  • Alibaba Cloud SMS : 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速 搭建客户触达通道。

源码及文档

SpringBoot和SpringCloud版本约束

SpringCloud和Springboot之间版本对应:https://start.spring.io/actuator/info

SpringCloud停更替换方案 :

在这里插入图片描述

Maven公共依赖

SpringBoot+SpringCloud+SpringCloudAlibaba整合基础依赖(每个模块都需添加)

 <!--spring boot 2.2.2-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.2.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>Hoxton.SR1</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<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>

Nacos

Server端启动

Nacos官网直接下载和源码构建

启动 Server,进入解压后文件夹或编译打包好的文件夹,找到如下相对文件夹 nacos/bin,并对照操作系统实际情况之下如下命令。

  • Linux/Unix/Mac 操作系统,执行命令 sh startup.sh -m standalone
  • Windows 操作系统,执行命令 cmd startup.cmd

本地访问 http://localhost:8848/nacos,默认的账号密码是:nacos/nacos

Client端启动

中文官网

POM:

<!--nacos-discovery-->
 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 </dependency>
  <!--nacos-config-->
 <dependency>
     <groupId>com.alibaba.cloud</groupId>
     <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
 </dependency>
  <!--web + actuator-->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>

新建bootstrap.yml配置文档 :

# nacos配置
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
        #是否为临时节点,如果否则断线后不会删除,默认是临时节点(断线后自动删除)
        ephemeral: false
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置
        group: DEV_GROUP
        namespace: 7d8f0f5a-6a53-4785-9686-dd460158e5d4


# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yaml

# nacos-config-client-test.yaml   ----> config.info

springCloud 2021.1以后的版本需要额外引入spring-cloud-starter-bootstrap:


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

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

主启动类添加@EnableDiscoveryClient注解开启服务注册与发现功能

至此Nacos 服务端、客户端全部搭建完成

消费者搭建

alibaba已经不支持ribbon做负载均衡了,使用loadbalancer替换:

pom:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    <version>3.1.1</version>
</dependency>
</dependencies>

yml:

server:
  port: 9000
 
spring:
  application:
    name: nacos-consumer
  cloud:
    loadbalancer:
      ribbon:
        enable: false
    nacos:
      discovery:
        server-addr: localhost:8848
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
    return new RestTemplate();
}

	//调用示例
   @Resource
   private RestTemplate restTemplate;

   /**
    * 消费者去访问具体服务,这种写法可以实现
    * 配置文件和代码的分离
    */
   @Value("${service-url.nacos-user-service}")
   private String serverURL;


   @GetMapping(value = "consumer/nacos")
   public String getDiscovery(){
       System.err.println(serverURL);
       return restTemplate.getForObject(serverURL+"/mashibing",String.class);
   }

配置中心

POM :

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

bootstrap.yml:

# nacos配置
server:
  port: 6655

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置

application.yml

spring:
  profiles:
    active: dev # 表示开发环境

主启动同样加上@EnableDiscoveryClient

业务类 :

@RestController
@RefreshScope //支持Nacos的动态刷新功能
public class ConfigClientController {

    @Value("${config.info}")
    //@NacosValue(value = "${config.info:local}",autoRefreshed = true)
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo(){
        return configInfo;
    }

}

@NacosValue 同样可以实现动态刷新

Nacos配置规则

nacos官网配置

Nacos读取配置中心的文件规则:

${prefix}-${spring.profiles.active}.${file-extension}
  • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
  • spring.profiles.active 即为当前环境对应的 profile,注意:spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}(不能删除)
  • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 propertiesyaml 类型。

所以根据官方给出的规则我们最终需要在Nacos配置中心添加的配置文件的名字规则和名字为:${spring.application.name}-${spring.profiles.active}.${file-extension}

上面示例最终读取的文件为 : nacos-config-client-dev.yaml

通过 Spring Cloud 原生注解 @RefreshScope 可实现配置自动更新

Nacos 配置命名空间/分组

  • 命名空间(Namespace)
    用于进行租户粒度的配置隔离。不同的命名空间下,可以存在相同的 Group 或 Data ID 的配置。Namespace 的常用场景之一是不同环境的配置的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

  • 配置分组(Group)
    Nacos 中的一组配置集,是组织配置的维度之一。通过一个有意义的字符串(如 Buy 或 Trade )对配置集进行分组,从而区分 Data ID 相同的配置集。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP 。配置分组的常见场景:不同的应用或组件使用了相同的配置类型,如 database_url 配置和 MQ_topic 配置。

  • 配置集 ID(Data ID)
    ​ Nacos 中的某个配置集的 ID。配置集 ID 是组织划分配置的维度之一。Data ID 通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,每个配置集都可以被一个有意义的名称标识。Data ID 通常采用类 Java 包(如 com.taobao.tc.refund.log.level)的命名规则保证全局唯一性。此命名规则非强制。
    配置集:一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。例如,一个配置集可能包含了数据源、线程池、日志级别等配置项。

三者关系 : Namespace > Group > Data ID/Service


指定Namespace空间:

  • Nacos新建命名空间 :
    在这里插入图片描述
    在这里插入图片描述

  • bootstrap:

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置
        group: TEST_GROUP # 指定分组
        namespace: 71ed271f-5e26-49e4-8e2b-428488e1ecd3 #指定命名空间

指定Group:

  • Nacos:
    在这里插入图片描述
  • bootstrap:
spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
      config:
        server-addr: localhost:8848 #Nacos作为配置中心地址
        file-extension: yaml #指定yaml格式的配置
        group: TEST_GROUP #增加分组

配置集ID使用 spring.profile.active指定即可

Nacos开启权限控制

示例博客

Nacos数据库持久化

单机模式时Nacos使用的是内嵌数据库 Derby(Apache Derby)实现数据存储(就是自带一个微数据库)。0.7版本后增加了mysql集中式存储数据,目前Nacos仅支持Mysql数据库,且版本要求:5.6.5+

MySQL

nacos 内嵌了数据库实现持久化机制,在0.7版本后还支持了外部的mysql数据库(mysql版本必须为5.6.5+)

配置msyql持久化

  1. 数据库执行 conf/nacos-mysql.sql 文件

  2. 在conf/application.properties 文件内添加配置自己的数据库连接,用户名和密码

spring.datasource.platform=mysql
db.num=1 #数据库数量 单机为1个
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
db.user=root
db.password=root

修改数据库配置之后,原配置就不在了,所以提前做好备份,后面可以直接导入。每个命名空间都要单独导出。

重新登录后发现配置全没了,因为加入了新的数据源,Nacos从mysql中读取所有的配置文件,而我们刚刚初始化的数据库是干干净净的,自然不会有什么数据和信息显示。这时候再次新建的配置会发现存入我们的数据库了。也可导入之前的配置,按照原来的命名空间,重新创建。然后,在对应的命名空间下导入原备份的配置文件即可。‘

nacos内部使用的数据库驱动的5版本的,因此不支持Mysql8版本,如果你的Mysql版本为8则需要替换数据库驱动:

在nacos根目录创建 plugins/mysql 文件夹
并放入新的mysql驱动jar包,application.properties内的数据库连接也要换成Mysql8的连接

持久化示例博客

Nacos集群

要搭建nacos集群的话,必须要先配置其持久化。Nacos默认有自带嵌入式数据库derby,但是如果做集群模式的话,就不能使用自己的数据库不然每个节点一个数据库,那么数据就不统一了。

如果使用MySQL进行持久化 ,可以使用mysqldump命令备份数据库

mysqldump -u root -p --databases nacos > nacos.sql

一. 关闭单机持久化配置,开启集群:

###If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://192.168.117.128:3306/nacos?characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root

二、进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf:

修改内容为集群ip+端口

127.0.0.1:8845
127.0.0.1:8846
127.0.0.1:8847

将nacos文件夹复制三份,分别命名为:nacos1、nacos2、nacos3

然后分别修改三个文件夹中的application.properties,

nacos1:

server.port=8845

nacos2:

server.port=8846

nacos3:

server.port=8847

然后分别启动三个nacos节点:

startup.cmd

三、安装并配置Nginx

Nginx安装略。

upstream nacos-cluster {
    server 127.0.0.1:8845;
	server 127.0.0.1:8846;
	server 127.0.0.1:8847;
}

server {
    listen       80;
    server_name  localhost;

    location /nacos {
        proxy_pass http://nacos-cluster;
    }
}

而后在浏览器访问:http://localhost/nacos即可。

代码中application.yml文件配置如下:

spring:
  cloud:
    nacos:
      server-addr: localhost:80 # Nacos地址,指定为Nginx入口

集群及持久化原博客

Nacos底层通信

  1. nacos是一个分布式的配置中心和注册中心
  2. 服务启动的时候会通过grpc长连接告诉nacos,自己的信息,比如ip,端口号,服务名称
  3. 当服务与服务之间通信的时候,会向nacos发出请求
  4. 获取注册在nacos中的服务的信息
  5. 通过http://ip:端口/接口路径,请求到目标服务
  6. 当目标服务处理完之后,把结果返回给掉用方
  7. http的底层依赖tcp/ip协议
  8. nacos每隔5分钟会从注册中心和配置中心通过http定时拉取最新的服务列表和配置数据
  9. 服务每隔5秒会通过grpc长连接向nacos-server发送一条心跳包,表示自己在线
  10. 如果nacos-server在15秒内,没有收到某个服务的心跳,那么这个服务就会标记为不健康状态
  11. 如果心跳间隔超过30秒,那么这个服务就会从nacos的服务列表移除
  12. 当服务列表和配置数据发生变化的时候,nacos会通过grpc长连接,和udp短连接,来推送变更信息给注册的服务
  13. nacos的服务信息和配置信息,通过Raft协议将数据持久化在本地的Derby数据库中
  14. 也就是说nacos的底层基于多种通信协议

Sentinel

官方中文文档

SpringCloud整合Sentinel:

POM :

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

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
 </dependency>

yaml配置 :

server:
  port: 7001
spring:
  application:
    name: sentinel-user
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        ephemeral: false
    sentinel:
      eager: true #该配置保证启动立即注册
      transport:
        dashboard: 127.0.0.1:8080  #控制台地址


#暴露端点
management:
  endpoints:
    web:
      exposure:
        include: '*'

eager: true:不加需要手动调用一次服务的任意接口Sentinel控制台才能发现服务

原生手动配置示例

POM :

<!-- https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-core -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.0</version>
</dependency>

测试示例类 :

@RestController
@Slf4j
public class HelloController {

    private static final String RESOURCE_NAME = "hello";

    @RequestMapping("/hello")
    public String hello(){
        Entry entry = null;
		// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串
        try {
            entry = SphU.entry(RESOURCE_NAME);
        }catch (BlockException e1){
            // 资源访问阻止,被限流或被降级 进行相应的处理操作
            log.info("block异常");
        }catch (Exception ex){
            //如果需要配置降级规则,需要通过这种方式记录业务异常
            //Tracer.traceEntry(ex,entry);
        }
        finally {
            if(entry!=null){
                entry.exit();
            }
        }
        return "hello,sentinel";
    }
    
	//定义流控规则,类创建后马上执行
    @PostConstruct
    private static void init(){
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource(RESOURCE_NAME);
        // 设置流控规则 QPS
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置受保护的资源阈值
        rule.setCount(1);
        rules.add(rule);
        //加载配置好的规则
        FlowRuleManager.loadRules(rules);
    }

}

注解配置:

POM :

<!-- https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-annotation-aspectj -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.8.0</version>
</dependency>

测试用例 :

@RequestMapping("/findOrderByUserId/{id}")
@SentinelResource(value = "findOrderByUserId",fallback = "fallBack",fallbackClass = ExceptionUtil.class,
blockHandler = "blockBack",blockHandlerClass = ExceptionUtil.class)
public R findOrderByUserId(@PathVariable("id") Integer id){

    String url = "http://mall-order/order/findOrderByUserId/"+id;
    R result = restTemplate.getForObject(url,R.class);

    if(id==4){
        throw  new RuntimeException("非法参数异常");
    }
    return result;
}

配置服务端注册的IP

# 如果选择固定Ip注册可以配置
spring.cloud.nacos.discovery.ip = 127.0.0.1
spring.cloud.nacos.discovery.port = 9090

# 如果选择固定网卡配置项
spring.cloud.nacos.discovery.networkInterface = eth0

#配置使用内网前缀的ip
spring.cloud.inetutils.preferred-networks=10.25.14
#使用前缀为10.25.14的ip比如:10.25.14.12,10.25.14.13;

动态的IP配置:

其他相关配置:

# 如果想更丰富的选择,可以使用spring cloud 的工具 InetUtils进行配置
# 具体说明可以自行检索: https://github.com/spring-cloud/spring-cloud-commons/blob/master/docs/src/main/asciidoc/spring-cloud-commons.adoc
spring.cloud.inetutils.default-hostname
spring.cloud.inetutils.default-ip-address
spring.cloud.inetutils.ignored-interfaces[0]=eth0   # 忽略网卡,eth0
spring.cloud.inetutils.ignored-interfaces=eth.*     # 忽略网卡,eth.*,正则表达式
spring.cloud.inetutils.preferred-networks=10.34.12  # 选择符合前缀的IP作为服务注册IP
spring.cloud.inetutils.timeout-seconds
spring.cloud.nacos.discovery.server-addr  #Nacos Server 启动监听的ip地址和端口
spring.cloud.nacos.discovery.service  #给当前的服务命名
spring.cloud.nacos.discovery.weight  #取值范围 1 到 100,数值越大,权重越大
spring.cloud.nacos.discovery.network-interface #当IP未配置时,注册的IP为此网卡所对应的IP地址,如果此项也未配置,则默认取第一块网卡的地址
spring.cloud.nacos.discovery.ip  # 优先级最高
spring.cloud.nacos.discovery.port  # 默认情况下不用配置,会自动探测
spring.cloud.nacos.discovery.namespace # 常用场景之一是不同环境的注册的区分隔离,例如开发测试环境和生产环境的资源(如配置、服务)隔离等。

spring.cloud.nacos.discovery.access-key  # 当要上阿里云时,阿里云上面的一个云账号名
spring.cloud.nacos.discovery.secret-key # 当要上阿里云时,阿里云上面的一个云账号密码
spring.cloud.nacos.discovery.metadata    #使用Map格式配置,用户可以根据自己的需要自定义一些和服务相关的元数据信息
spring.cloud.nacos.discovery.log-name   # 日志文件名
spring.cloud.nacos.discovery.enpoint   # 地域的某个服务的入口域名,通过此域名可以动态地拿到服务端地址
ribbon.nacos.enabled  # 是否集成Ribbon 默认为true

动态注册IP

配置文件形式:

package com.zhongyi.doctor.config;

import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

/**
 * @author SymbolWong
 * @description
 * @date 2023/2/6 16:25
 */
@Configuration
@Slf4j
public class NacosDiscoveryConfig {
    /**
     * nacosConfigServerAaddr yml中配置的nacos配置中心地址
     */
    @Value("${spring.cloud.nacos.config.server-addr}")
    String nacosConfigServerAaddr;
    /**
     * nacosConfignamespace yml中配置的nacos配置中心命名空间
     */
    @Value("${spring.cloud.nacos.config.namespace}")
    String nacosConfignamespace;
    /**
     * getServerInternetIP 通过Nginx获取本机外网IP,需要Nginx配合配置
     * @author IPMan
     * @date 2022/7/10
     *
     * @return java.lang.String 返回本机外网IP
     */
    private String getServerInternetIPByNginx(){
        //通过配置中心地址构造查询IP请求地址
        String url="http://"+nacosConfigServerAaddr.split(":")[0]+"/getIp";
        //调试输出,这里不推荐err的方式输出,这样仅为测试使用,推荐采用日志实现或者不输出
        // System.err.println(url);
        log.warn("Nacos server addr is {}",url);
        //外网IP
        String internetIP="127.0.0.1";
        //这里一步完成了,构造一个RestTemplate对象,通过对指定URL执行GET请求来获取响应实体
        ResponseEntity<String> response =
                new RestTemplate()
                        .getForEntity(url, String.class);
        // //从响应实体对象中获取内容
        internetIP = response.getBody();
        // //调试输出,这里不推荐err的方式输出,这样仅为测试使用,推荐采用日志实现或者不输出
        // System.err.println(internetIP);
        log.warn("Internet ip is {}",url);
        return internetIP;
    }

    /**
     * nacosProperties Nacos 服务发现配置类,代替yml中spring.cloud.nacos.discovery:配置
     * @author IPMan
     * @date 2022/7/10
     *
     * @return com.alibaba.cloud.nacos.NacosDiscoveryProperties
     */
    @Bean
    public NacosDiscoveryProperties nacosProperties() {
        //new一个nacos服务发现配置对象
        NacosDiscoveryProperties properties = new NacosDiscoveryProperties();
        //设置发现注册的IP,即注册中心详情中的IP,这里很关键,默认是Inet4Address.getLocalHost(),即如果包含子网,则获取的是子网IP
        properties.setIp(getServerInternetIP());
        //设置注册中心地址
        properties.setServerAddr(nacosConfigServerAaddr);
        //设置注册中心命名空间
        properties.setNamespace(nacosConfignamespace);
        return properties;
    }

    private String getServerInternetIP() {
        String internetip = "";
        String url = "https://ip.3322.org";
        internetip = HttpRequest.get(url)
                .header(Header.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36")
                .execute().body();
        log.warn("Internet ip is {}",internetip);
        return internetip;
    }

    /**
     * nacosServiceDiscovery nacos 服务发现对象,这个对象构造完成后是无法设置配置的
     * @author IPMan
     * @date 2022/7/10
     *
     * @param discoveryProperties com.alibaba.cloud.nacos.NacosDiscoveryProperties
     * @param nacosServiceManager com.alibaba.cloud.nacos.NacosServiceManager
     * @return com.alibaba.cloud.nacos.discovery.NacosServiceDiscovery
     */
    @Bean
    public NacosServiceDiscovery nacosServiceDiscovery(
            NacosDiscoveryProperties discoveryProperties,
            NacosServiceManager nacosServiceManager) {
        return new NacosServiceDiscovery(discoveryProperties, nacosServiceManager);
    }
}

虽然能注册成功,但是请求是失败的,报错:Illegal character in authority at index 7

监听器形式:

package com.zhongyi.doctor.config;

import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.Ordered;

/**
 * @author SymbolWong
 * @description
 * @date 2023/2/18 9:13
 */
public class AfterConfigListener implements SmartApplicationListener, Ordered {


    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
        return (ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(aClass) ||
                ApplicationPreparedEvent.class.isAssignableFrom(aClass));
    }

    @Override
    public int getOrder() {
        return (ConfigFileApplicationListener.DEFAULT_ORDER + 1);
    }

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        String currentIp = getServerInternetIP();
        if (applicationEvent instanceof ApplicationEnvironmentPreparedEvent) {
            System.setProperty("spring.cloud.nacos.discovery.ip", currentIp);
        }
    }


    private String getServerInternetIP() {
        String internetip = "";
        String url = "https://ip.3322.org";
        internetip = HttpRequest.get(url)
                .header(Header.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36")
                .execute().body();
        return internetip.trim();
    }
}

Sentinl控制台介绍及配置

华为云博客原文

CSDN原博客

Sentinel 控制台包含如下功能:

  • 查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。

  • 监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信 息,最终可以实现秒级的实时监控。

  • 规则管理和推送:统一管理推送规则。

  • 生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。

Sentinel 2.0 将降级规则改名称为熔断规则

包含的模块 :

  • 实时监控:监控接口通过的QPS和拒绝的QPS
  • 簇拥链路:显示微服务的所监控的API(懒加载),在此配置各个入口的各种流控规则
  • 流控规则: 针对QPS或者线程数可以选择不同的流控模式进行链接流控
  • 熔断规则:再时间窗口期内针对不同的熔断策略(慢调用比例、异常比例、异常数)进行熔断,进而保护服务的稳定性。
  • 热点规则:热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用 进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效
  • 系统规则: Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平 均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
  • 授权规则: 很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控 制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请通过。
  • 集群流控:集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果
  • 机器列表:和Sentinel控制台保持通信的微服务机器列表

为什么要使用集群流控呢?假设我们希望给某个用户限制调用某个 API 的总 QPS 为 50,但机器数可能很 多(比如有 100台)。这时候我们很自然地就想到,找一个 server 来专门来统计总的调用量,其它的实例 都与这台 server通信来判断是否可以调用。这就是最基础的集群流控的方式。 另外集群流控还可以解决流量不均匀导致总体限流效果不佳的问题。假设集群中有 10台机器,我们给每台 机器设置单机限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。

规则配置

可通过在Controller层方法加上@SentinelResource指定资源名称,在Sentinel控制台通过资源名称配置限流,也可通过url路径配置

    @GetMapping("/byResource")
    @SentinelResource(value = "byResource",blockHandler = "handleException")
    public CommonResult byResource()
    {
        return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
    }
    public CommonResult handleException(BlockException exception)
    {
        return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
    }

上例配置:
在这里插入图片描述


    @GetMapping("/rateLimit/byUrl")
    //@SentinelResource(value = "byUrl")
    public CommonResult byUrl()
    {
        return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
    }

上例配置:在这里插入图片描述

流控规则

官方文档

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流 量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。同一个资源可以创建多条限流规则。FlowSlot 会对该资源的所有限流规则依次遍历,直到有规则触发限流 或者所有规则遍历完毕。一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果。

一条限流规则主要由下面几个因素组成:

  • resource:资源名,即限流规则的作用对象
  • limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
  • grade: 限流阈值类型(QPS 或并发线程数)
    • QPS(Query Per Second):每秒请求数,就是说服务器在一秒的时间内处理了多少个请求。
    • 并发线程数: 配置处理这个请求的线程数量,如果超过阈值,新的请求被拒绝。
  • count: 限流阈值
  • strategy: 调用关系限流策略
  • controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)

在这里插入图片描述

并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程 数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。

Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超 出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。

简而言之:就是配置处理这个请求的线程数量,如果超过阈值,新的请求被拒绝。

关联限制 :

   machine-root1       	 machine-root2
        |   				  |
        |      				  |
	 Entrance1   		  Entrance2
        |                     |
        |                     |
  DefaultNode(nodeA)    DefaultNode(nodeB)

示例两条线路分别需要访问nodeA、nodeB

设置资源名称为nodeA,设置关联链路为nodeB,那么当nodeB达到设置的阈值时会限制nodeA的访问,此时nodeB不受影响。两个资源之间具有资源争抢或者依赖关系的时候可使用。

链路限制 :

 	          machine-root
                /       \
               /         \
         Entrance1     Entrance2
            /             \
           /               \
  DefaultNode(nodeA)   DefaultNode(nodeA)

上例都调用的nodeA,此时设置链路的入口资源为Entrance2则表示只限制从Entrance2来的资源,Entrance1不限制。

导致链路流控不生效原因:

从1.6.3版本开始,Sentinel Web filter默认收敛所有URL的入口context,导致链路限流不生效。

解决方法:

从1.7.0版本开始,官方在CommonFilter引入了WEB_CONTEXT_UNIFY参数,用于控制是否收敛context,将其配置为false即可根据不同的URL进行链路限流。

引入sentinel­web­servlet依赖:

  <dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-web-servlet</artifactId>
  </dependency>

编写配置类:

    @Bean
    public FilterRegistrationBean sentinelFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new CommonFilter());
        registration.addUrlPatterns("/*");
        // 入口资源关闭聚合  解决流控链路不生效的问题
        registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
        registration.setName("sentinelFilter");
        registration.setOrder(1);

        return registration;
    }

流控效果

流量控制的效果包括以下几种:

  • 快速失败(直接 拒绝)
    默认的流量控制方式,当QPS超过任意规则的阈值后,新 的请求就会被立即拒绝,拒绝方式为抛出FlowException。
  • Warm Up(预热)
    让通过的流量缓 慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。冷加载因子: codeFactor 默认是3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS阈值。
  • 匀速排队(排队等待)。
    匀速排队方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。这种方式主要用于处理间隔性突发的流量,例如消息队列。设置的等待时间越长,拒绝的请求越少。

匀速排队模式暂时不支持 QPS > 1000 的场景

熔断规则

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们 需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。

熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。

降级规则有三种策略:

  • 分别是慢调用比例(SLOW_REQUEST_RATIO)
    选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。
    经过熔断时长后熔断器会进入探测恢复状态(HALF­OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例(ERROR_RATIO)
    当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。
  • 异常数 (ERROR_COUNT)
    当单位统计时长内的异常数目超过阈值之后会自动进行熔断。

配置异常比例和异常数经过熔断时长后熔断器会进入探测恢复状态(HALF­OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。

异常比例阈值的范围是 [0.0, 1.0],代表 0% -­ 100%。

热点参数

示例博客

测试方法:

    @RequestMapping("/testHotKey/{id}")
    @SentinelResource(value = "testHotKey",blockHandlerClass = CommonBlockHandler.class,blockHandler = "handleException2",fallbackClass = CommonFallback.class,fallback = "fallback")
    public String testHotKey(@PathVariable("id") Integer id){
        return "测试热点key"+id;
    }

热点规则需要使用@SentinelResource(“resourceName”)注解,否则不生效,’resourceName‘就是在页面指定配置的资源名
参数值必须是7种基本数据类型才会生效

公共兜底方法:

public class CommonBlockHandler {

    /**
     * 注意: 必须为 static 函数   多个方法之间方法名不能一样
     * @param exception
     * @return
     */
    public static String handleException(Map<String, Object> params, BlockException exception){
        return "被限流啦";
    }

    public static String handleException2(Integer id, BlockException exception){
        return "被限流啦";
    }

    public static String handleException3(BlockException exception){
        return "===被限流啦==="+exception;
    }
}

:兜底方法中需要满足

  1. 参数类型要和原方法保持一致

  2. 返回值类型要和原方法保持一致

  3. 参数多一个BlockException

  4. 方法是静态的。

授权规则

需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控 制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

Sentinel提供了RequestOriginParser这个接口中的parseOrigin方法来获取请求来源
但是RequestOriginParser来源解析器中parseOrigin方法默认情况下返回结果都是default,也就是说,无论请求是从哪里来的,得到的结果都是default, 在默认情况下Sentinel无法区分各个请求

只需要定义一个类来实现这个接口并实现其中的parseOrigin方法并将其交给spring管理,就可以拿到请求中我们自定义的origin的内容,从而区分来自不同地方的请求

需要注意的是,获取origin值的位置是我们自定义的,并不是一定要从请求头中获取,也可以从请求参数、cookie等能区分不同来源的请求的地方获取

1.8和1.7是不一样的

1.8:

编写RequestOriginParser实现类L

import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

//RequestOriginParser是com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser包下的 不要导错。
@Component
public class MyRequestOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String origin = request.getParameter("serviceName");
//        if (StringUtil.isBlank(origin)){
//            throw new IllegalArgumentException("serviceName参数未指定");
//        }
        return origin;
    }
}

配置流控:
在这里插入图片描述

访问路径中携带请求参数,然后会被拦截器校验

127.0.0.1:7001/user/test3/?serviceName=order

访问会被拦截,其他serviceName则不收影响

1.7及之前版本:

pom依赖(已引入忽略):,

 <dependency>
     <groupId>com.alibaba.csp</groupId>
     <artifactId>sentinel-web-servlet</artifactId>
 </dependency>

配置:

import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import javax.servlet.http.HttpServletRequest;

public class MyRequestOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String origin = request.getParameter("serviceName");
//        if (StringUtil.isBlank(origin)){
//            throw new IllegalArgumentException("serviceName参数未指定");
//        }
        return origin;
    }
}
    @Bean
    public FilterRegistrationBean sentinelFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new CommonFilter());
        registration.addUrlPatterns("/*");
        // 入口资源关闭聚合  解决流控链路不生效的问题
        registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
        registration.setName("sentinelFilter");
        registration.setOrder(1);

        //CommonFilter的BlockException自定义处理逻辑
        WebCallbackManager.setUrlBlockHandler(new MyUrlBlockHandler());

        //解决授权规则不生效的问题
        //com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser
        WebCallbackManager.setRequestOriginParser(new MyRequestOriginParser());

        return registration;
    }
系统规则

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平 均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系 统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

阈值类型:

  • 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 达到阈值即触发系统保护。

Sentinel整合RestTemplate

原文

Spring Cloud Alibaba Sentinel 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护,在构造 RestTemplate bean的时候需要加上 @SentinelRestTemplate 注解。

引入依赖:

<!--加入nocas-client-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>

<!--加入ribbon-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

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

<!--加入actuator-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

yml:

server:
  port: 8402

spring:
  application:
    name: mall-user-sentinel-ribbon-demo  #微服务名称
  #配置nacos注册中心地址
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        # 添加sentinel的控制台地址
        dashboard: 127.0.0.1:8080
        # 指定应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用的HttpServer
        port: 8719  
        
#暴露actuator端点   http://localhost:8800/actuator/sentinel
management:
  endpoints:
    web:
      exposure:
        include: '*'        

#true开启sentinel对resttemplate的支持,false则关闭  默认true
resttemplate: 
  sentinel: 
    enabled: true

配置:

@Bean
@LoadBalanced
@SentinelRestTemplate(
    blockHandler = "handleException",blockHandlerClass = GlobalExceptionHandler.class,
    fallback = "fallback",fallbackClass = GlobalExceptionHandler.class
)
public RestTemplate restTemplate() {
    return new RestTemplate();
}

Controller:

    @RequestMapping("/findOrderByUserId/{userId}")
    public String findOrderByUserId(@PathVariable("userId") String userId) {
        //模拟异常
        if ("5".equals(userId)) {
            throw new IllegalArgumentException("非法参数异常");
        }
        return userId;
    }

    //模拟消费者
    @RequestMapping(value = "/findOrderByUserIdConsumer/{id}")
    @SentinelResource(value = "findOrderByUserIdConsumer",
        fallback = "fallback",fallbackClass = ExceptionUtil.class,
        blockHandler = "handleException",blockHandlerClass = ExceptionUtil.class
    )
    public String findOrderByUserIdConsumer(@PathVariable("id") String id) {
        //ribbon实现
        String url = "http://127.0.0.1:8402/findOrderByUserId/" + id;
        String result = restTemplate.getForObject(url, String.class);
        return result;
    }

异常兜底类 :

import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class GlobalExceptionUtil {
    /**
     * 注意: static修饰,参数类型不能出错,注意导的包
     */
    public static SentinelClientHttpResponse handleException(HttpRequest request,
                                                             byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
        String result = "===被限流啦===";
        try {
            return new SentinelClientHttpResponse(new ObjectMapper().writeValueAsString(result));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static SentinelClientHttpResponse fallback(HttpRequest request,
                                 byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
        String result = "===被异常降级啦===";
        try {
            return new SentinelClientHttpResponse(new ObjectMapper().writeValueAsString(result));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

@SentinelResource@SentinelRestTemplate任选一个配置方式即可

配置Sentinel 后:

在这里插入图片描述

针对当前 GET:http://127.0.0.1:8402/findOrderByUserId/312配置限流规则后,通过restTemplate调用会被限流,而直接调用消费者不受影响

异常处理类定义需要注意的是该方法的参数和返回值跟 org.springframework.http.client.ClientHttpRequestInterceptor#interceptor 方法一致,其中参数多出了一个 BlockException 参数用于获取 Sentinel 捕获的异常。

Sentinel整合OpenFeign

Sentinel本身已经整合了OpenFeign组件,如下图

在这里插入图片描述

在setinel基础上新增依赖,pom:

 <!-- openfeign 远程调用 -->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-openfeign</artifactId>
 </dependency>

新增 yml:

feign:
  sentinel:
    enabled: true   #开启sentinel对feign的支持 默认false

主启动类添加 @EnableFeignClients;

FeignClient :

指定 fallbackFactory 方式

@FeignClient(value = "mall-order",path = "/order",fallbackFactory = FallbackOrderFeignServiceFactory.class)
public interface OrderFeignService {

    @RequestMapping("/findOrderByUserId/{userId}")
    public R findOrderByUserId(@PathVariable("userId") Integer userId);

}

降级类:

@Component
public class FallbackOrderFeignServiceFactory implements FallbackFactory<OrderFeignService> {
    @Override
    public OrderFeignService create(Throwable throwable) {
        return new OrderFeignServiceImpl(throwable);//也可使用匿名内部类
    }
}


    public class OrderFeignServiceImpl implements OrderFeignService {
        
        Throwable throwable;

        public OrderFeignServiceImpl(Throwable throwable) {
            this.throwable = throwable;
        }

        @Override
        public R findOrderByUserId(Integer userId) {
            if (throwable instanceof FlowException) {
                return R.error(100, "接口限流了");
            }
            return R.error(-1, "=======服务降级了========");
        }
    }

指定 fallback 方式

@FeignClient(value = "mall-order",path = "/order",fallback = FallbackOrderFeignService .class)
public interface OrderFeignService {

    @RequestMapping("/findOrderByUserId/{userId}")
    public R findOrderByUserId(@PathVariable("userId") Integer userId);

}

@Component   //必须交给spring 管理
public class FallbackOrderFeignService implements OrderFeignService {
    @Override
    public R findOrderByUserId(Integer userId) {
        return R.error(-1,"=======服务降级了========");
    }
}

Sentinel 添加流控规则后,违法流控规则走限流逻辑

Sentinel整合Dubbo

pom :

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--Sentinel 对 Dubbo的适配  Apache Dubbo 2.7.x 及以上版本-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-apache-dubbo-adapter</artifactId>
</dependency>

yml :

spring:
  cloud:
    sentinel:
      transport:
        # 添加sentinel的控制台地址
        dashboard: 127.0.0.1:8080

#暴露actuator端点   
management:
  endpoints:
    web:
      exposure:
        include: '*'

测试类:

@RequestMapping("/info/{id}")
    public User info(@PathVariable("id") Integer id) {
        User user = null;
        try {
            user = userService.getById(id);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return user;
    }

    @PostConstruct
    public void init() {
        DubboAdapterGlobalConfig.setConsumerFallback(
                (invoker, invocation, ex) -> AsyncRpcResult.newDefaultAsyncResult(
                        new User(0,"===fallback=="), invocation));
    }

详细略

整合Dubbo学习博客

自定义BlockException异常统一处理

Sentinel配置好规则后,当sentinel发生限流、降级、授权拦截等异常时触发了规则,都会抛出BlockException异常,Sentinel通过控制台配置规则作用于客户端是通过拦截器实现的,入口类是AbstractSentinelInterceptor,在preHandle方法中对目标方法进行了拦截器并进行规则校验,通过异常捕获,对微服务做出降级处理,对异常的处理是BlockExceptionHandler的实现类 ,我们这里自定义一个BlockExceptionHandler

//处理规则降级的统一异常类
@Slf4j
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        R r = null;
        if(e instanceof FlowException){
            r = R.error(100001,"接口已被限流");
        }
        if(e instanceof DegradeException){
            r = R.error(100002,"服务已被降级");
        }
        if(e instanceof ParamFlowException){
            r = R.error(100003,"热点参数被限流");
        }
        if(e instanceof SystemBlockException){
            r = R.error(100004,"触发系统保护规则");
        }
        if(e instanceof AuthorityException){
            r = R.error(100004,"未被授权,请稍后再试");
        }
        response.setStatus(500);
        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        new ObjectMapper().writeValue(response.getWriter(),r);
    }
}

内部方法handle的三个参数:

  • HttpServletRequest request : 请求对象
  • HttpServletResponse response : 响应对象
  • BlockException e : 被sentinel拦截时抛出的异常

三种自定义异常

持久化配置

一旦我们项目重启,sentinel 规则 将消失,生产环节需要将配置规则 进行持久化

Sentinel的三种配置管理模式

  • 原始模式:保存在内存
  • pull模式:保存在本地文件或数据库,定时去读取
  • push模式:保存在nacos,监听变更实时更新

将限流配置规则持久化进 Nacos 保存,只要刷新 8401 某个 rest 地址,Sentinel 控制台的流控规则 就能看到,只要 Nacos 里面的配置不删除,针对 8401 上的 Sentinel 的流控规则 就持续有效;

业务项目POM新增 :

<!-- 后续 持久化 用到 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

yml :

server:
  port: 8401
 
spring:
  application:
    name: cloudalibaba-myfirst-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719
      datasource:
        ds1:
          nacos:
            serverAddr: 114.215.173.88:8848
            dataId: ${spring.application.name}#nacos要读取的dataId配置名称
            groupId: DEFAULT_GROUP
            dataType: json
            ruleType: flow
 
management:
  endpoints:
    web:
      exposure:
        include: '*'

nacnos新增配置 :

在这里插入图片描述
dataId根据自己项目名称更改

在这里插入图片描述

流控规则 :

Field说明默认值
resource资源名,资源名是限流规则的作用对象
count限流阈值
grade限流阈值类型:QPS 模式(1)或并发线程数模式(0)QPS 模式
limitApp流控针对的调用来源default,代表不区分调用来源
strategy调用关系限流策略:直接、链路、关联根据资源本身(直接)
controlBehavior流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流直接拒绝
clusterMode是否集群限流

流控规则:

Field说明默认值
resource资源名,即规则的作用对象
grade熔断策略,支持慢调用比例/异常比例/异常数策略慢调用比例
count慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow熔断时长,单位为 s
minRequestAmount熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)5
statIntervalMs统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)1000ms
slowRatioThreshold慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)

详细及其他字段配置规则见官网

规则字段参考

学习实践博客

集群下的配置

Seata

Seata中文官方文档

官方配置文档

Seata(Simple Extensible Autonomous Transaction Architecture)是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA四种事务模式

Seata整体架构:

一个典型的分布式事务过程分为一ID(全局事务id)+三组件模型。

  • TC (Transaction Coordinator) - 事务协调者
    维护全局和分支事务的状态,驱动全局事务提交或回滚。

  • TM (Transaction Manager) - 事务管理器
    定义全局事务的范围:开始全局事务、提交或回滚全局事务。

  • RM (Resource Manager) - 资源管理器
    管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

在这里插入图片描述

TC(Server端)为单独服务端部署,负责维护分布式事务的运行状态,TM和RM(Client端)由业务系统集成,TM是一个分布式事务的发起者和终结者,而RM则负责本地事务的运行并上报。

一个典型的事务过程(AT模式):

  1. TM向TC申请开启一个全局事务 ,全局事务创建成功并生成一个全局唯一的XID;

  2. XID在微服务调用链路的上下文中传播;

  3. RM向TC注册分支事务,将其纳入XID对应全局事务的管辖;

  4. TM向TC发起针对XID的全局提交或回滚决议;

  5. TC调度XID下管辖的全部分支事务完成提交或回滚请求;

Seata分布式事务的四种模式

  • AT模式,默认、简单,需要增加undo_log表,生成反向SQL,性能高,会滚后,原来没数据的,现在还是没数据。
  • TCC模式:try confirm/cancel,三个阶段的代码都得自己实现,Seata只负责调度。对业务代码侵入性较强,必要时可能还要修改数据库。
  • SAGA模式,长事务解决方案,需要程序员自己编写两阶段代码(AT模式不需要写第二阶段),基于状态机来实现的,需要一个JSON文件(编写每个阶段的执行流程),可异步执行。
  • XA模式,XA协议是由X/Open组织提出的分布式事务处理规范,基于数据库的XA协议来实现2PC又称为XA方案,适用于强一致性的场景,比如金融、银行。

示例

启动及使用示例

AT模式使用

在需要发起全局事务的service方法上添加注解 @GlobalTransactional,

使用Fiegn、RestTemplate等方式发送请求,提供方只添加@Transactional保证本地事务

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

Yml 示例 :

server:
  port: 3011

spring:
  application:
    name: @artifactId@
  cloud:
    nacos:
      username: @nacos.username@
      password: @nacos.password@
      discovery:
        server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848}
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.0.35:3306/seata-at-demo?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: '1qaz@WSX'

seata:
  # 是否开启spring-boot自动装配,seata-spring-boot-starter 专有配置,默认true
  enabled: true
  # 是否开启数据源自动代理,seata-spring-boot-starter专有配置,默认会开启数据源自动代理,可通过该配置项关闭
  enable-auto-data-source-proxy: true
  # 配置自定义事务组名称,需与下方server.vgroupMapping配置一致,程序会通过用户配置的配置中心去寻找service.vgroupMapping
  tx-service-group: mygroup
  config: # 从nacos配置中心获取client端配置
    type: nacos
    nacos:
      server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848}
      group : DEFAULT_GROUP
      namespace: a4c150aa-fd09-4595-9afe-c87084b22105
      dataId: seataServer.properties
      username: @nacos.username@
      password: @nacos.username@
  registry: # 通过服务中心通过服务发现获取seata-server服务地址
    type: nacos
    nacos:
      # 注:客户端注册中心配置的serverAddr和namespace与Server端一致,clusterName与Server端cluster一致
      application: seata-server # 此处与seata-server的application一致,才能通过服务发现获取服务地址
      group : DEFAULT_GROUP
      server-addr: ${NACOS_HOST:nacos1.kc}:${NACOS_PORT:8848}
      userName: @nacos.username@
      password: @nacos.username@
      namespace: a4c150aa-fd09-4595-9afe-c87084b22105
  service:
    # 应用程序(客户端)会通过用户配置的配置中心去寻找service.vgroupMapping.[事务分组配置项]
    vgroup-mapping:
      # 事务分组配置项[mygroup]对应的值为TC集群名[default],与Seata-Server中的seata.registry.nacos.cluster配置一致
      mygroup : default
    # 全局事务开关,默认false。false为开启,true为关闭
    disable-global-transaction: false
  client:
    rm:
      report-success-enable: true
management:
  endpoints:
    web:
      exposure:
        include: '*'

logging:
  level:
    io.seata: info

# mybatis-plus配置控制台打印完整带参数SQL语句
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

  • 要求客户端配置(seata.tx-service-group: [事务分组配置项]、service.vgroupMapping.[事务分组配置项]: [TC集群名])与Seata-Server配置(service.vgroupMapping.[事务分组配置项]: [TC集群名] )在[事务分组配置项]和[TC集群名]配置一致,且上述[TC集群名]与seata.registry中的cluster配置一致

  • seata.config.type与seata.registry.type均为nacos

  • config与registry中nacos的配置,其中namespace与group须与Seata-Server的配置一致

要设置undo_log日志表的branch_id字段为主键(原来的脚本是没有设置的)

TCC模式

一个分布式的全局事务,整体是两阶段提交Try-[Comfirm/Cancel] 的模型。在Seata中,AT模式与TCC模式事实上都是两阶段提交的具体实现。

TCC 模式,不依赖于底层数据资源的事务支持:

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
  • 二阶段 commit 行为:调用 自定义的 commit 逻辑。
  • 二阶段 rollback 行为:调用 自定义的 rollback 逻辑。

所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

Seata四种事务模式介绍+示例代码

seata快速入门代码示例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值