SpringCloudAlibaba的学习笔记

SpringCloudAlibaba学习

Nacos学习

Naming Configuration 前两个字母, Service

是什么?

  • 一个更易于构建云原生应用的动态服务发现,配置管理和服务管理中心
  • Dynamic Naming and Configuration Service
  • Nacos就是注册中心+配置中心的组合 Eureka+Config+Bus

能干吗?

  • 替代Eureka做服务注册中心
  • 替代Config做服务配置中心

去哪下?

  • https://github.com/alibaba/Nacos
  • https://nacos.io/zh-cn/docs/

各种注册中心比较

服务注册与发现框架 CAP模型 控制台管理 社区活跃度
Eureka AP 支持
Zookeeper CP 不支持
Consul CP 支持
Nacos AP yes high

安装并允许Nacos

  • 本地Jdk 8 + Maven环境
  • 从官网去下载 https://github.com/alibaba/Nacos
  • 在windows环境下,解压安装包,直接允许bin目录下的 startup.cmd
  • 运行成功之后直接访问 http://localhost:8848/nacos 就可以看到

基于Nacos的服务提供者

  • 新建一个项目,由于这个是一个微服务,所以我们可以创建一个父项目来进行统一的版本管理

<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.0modelVersion>

    <groupId>com.atguigugroupId>
    <artifactId>cloud2021artifactId>
    <packaging>pompackaging>
    <version>1.0-SNAPSHOTversion>
    <modules>
        <module>cloudalibaba-provider-payment9001module>
        <module>cloudalibaba-provider-payment9002module>
        <module>cloudalibaba-consumer-nacos-order83module>
        <module>cloudalibaba-config-nacos-client3377module>
    modules>
    
    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <maven.compiler.source>1.8maven.compiler.source>
        <maven.compiler.target>1.8maven.compiler.target>
        <junit.version>4.12junit.version>
        <log4j.version>1.2.17log4j.version>
        <lombok.version>1.16.18lombok.version>
        <mysql.version>8.0.18mysql.version>
        <druid.verison>1.1.16druid.verison>
        <mybatis.spring.boot.verison>1.3.0mybatis.spring.boot.verison>
    properties>
    
    <dependencyManagement>
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-dependenciesartifactId>
                <version>2.2.2.RELEASEversion>
                <type>pomtype>
                <scope>importscope>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>Hoxton.SR1version>
                <type>pomtype>
                <scope>importscope>
            dependency>
            
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                <version>2.1.0.RELEASEversion>
                <type>pomtype>
                <scope>importscope>
            dependency>

            
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>${mysql.version}version>
            dependency>
            
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>druid-spring-boot-starterartifactId>
                <version>${druid.verison}version>
            dependency>
            
            <dependency>
                <groupId>org.mybatis.spring.bootgroupId>
                <artifactId>mybatis-spring-boot-starterartifactId>
                <version>${mybatis.spring.boot.verison}version>
            dependency>
            
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>${lombok.version}version>
            dependency>
            
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>${junit.version}version>
            dependency>
            
            <dependency>
                <groupId>log4jgroupId>
                <artifactId>log4jartifactId>
                <version>${log4j.version}version>
            dependency>
        dependencies>
    dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <fork>truefork>
                    <addResources>trueaddResources>
                configuration>
            plugin>
        plugins>
    build>
project>
  • 需要导入nacos的包,声明一下可以被发现
    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.62version>
        dependency>
    dependencies>
  • 需要写配置文件,声明这是一个需要被管理的服务,需要被哪个地址进行管理,需要被管理哪些东西
server:
  port: 9001

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

management:
  endpoints:
    web:
      exposure:
        include: '*'
  • 主启动类也要使用注解声明一下,这个服务可以作为一个客户端被Nacos发现
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001 {
     
    public static void main(String[] args) {
     
        SpringApplication.run(PaymentMain9001.class, args);
    }
}
  • 业务类我们可以写一个方法进行测试,将本机的端口暴露出去
@RestController
public class PaymentController
{
     
    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "/payment/nacos/{id}")
    public String getPayment(@PathVariable("id") Integer id)
    {
     
        return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
    }
}
  • 启动这个服务提供者,发现nacos控制台可以看到,安装同样的方式,我们再来一个9002的服务提供者,它们的服务名字都一样,但是端口却不一样,也就是说这是两个端口,这两个端口都可以提供同一个服务,
  • 接下来是服务消费者
    <dependencies>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
        <dependency>
            <groupId>com.atguigu.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>${project.version}version>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>
  • 配置文件
server:
  port: 83
spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
service-url:
  nacos-user-service: http://nacos-payment-provider
  • 这个主启动类和提供者差不多
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83 {
     
    public static void main(String[] args) {
     
        SpringApplication.run(OrderNacosMain83.class, args);
    }
}
  • 在服务调用者这里,我们可以使用一个模板进行调用,这个模板去调用提供者的方法,首先它们应该知道提供者的服务名称,然后还有它们的端口,自己需要准备参数,这些我们都可以获取到,我们还需要得到返回值,
  • 由于提供者有很多个,我们需要确定到底使用的是哪一个,这也是一个问题,这个时候需要使用到负载均衡策略,
@Configuration
public class ApplicationContextConfig {
     
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
     
        return new RestTemplate();
    }
}
  • 往容器中注入的这个Bean上面又声明了LoadBalanced注解,这表明我们的RestTemplate使用了一种负载均衡策略
  • 然后我们在消费者端就可以轮流调用提供者提供的服务了
@RestController
@Slf4j
public class OrderNacosController {
     
    @Resource
    private RestTemplate restTemplate;

    @Value("${service-url.nacos-user-service}")
    private String serverURL;

    @GetMapping(value = "/consumer/payment/nacos/{id}")
    public String paymentInfo(@PathVariable("id") Long id) {
     
        return restTemplate.getForObject(serverURL + "/payment/nacos/" + id, String.class);
    }
}
  • Nacos 其实是可以支持CP或者AP的,这两者之间可以相互切换
    • C是所有节点在同一时间看到的数据是一致的,而A的定义是所有的请求都会收到响应
    • 如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP默认当前主流的服务如SPring Cloud和Dubbo服务,都适合用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例
    • 如果需要在服务级别编写或存储配置信息,那么CP是必须的, k8s和DNS服务则适用于CP模式
    • CP模式下支持注册持久化实例,此时则是以Raft协议为集群运行模式该模式下注册实例之前需先注册服务,如果服务不存在,则会返回错误
    • curl -X PUT ‘$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP’

Nacos作为服务配置中心

我们可以在Nacos上搭建自己的配置文件,在自己的服务中声明自己想要使用哪个即可

  • 依赖引入
    <dependencies>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>
  • 配置文件需要配置两个
    • 为什么需要两个呢?
    • Nacos 和SpringCloud-config一样,在项目初始化时,需要先去配置中心进行配置拉取,然后才能保证项目的正常启动
    • 配置文件的加载存在优先顺序, bootstrap优先级高于application
  • 在远程配置的配置文件的命名格式都需要遵循一定的规则,要不然时无法获取到的
server:
  port: 3377

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

# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yml
  • 主启动类无变化
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377 {
     
    public static void main(String[] args) {
     
        SpringApplication.run(NacosConfigClientMain3377.class, args);
    }
}
  • 然后我们暴露一个端口,就可以看到在远程配置文件上面的信息,并且我们也可以设置自动刷新,这样当远程修改的时候,我们这个端口可以立即看到
@RestController
@RefreshScope
public class ConfigClientController {
     
    @Value("${config.info}")
    private String configInfo;

    @GetMapping("/config/info")
    public String getConfigInfo() {
     
        return configInfo;
    }
}
  • 主要有以下一些方面可以可以更换配置文件,
    • spring.application.name 项目的名称
    • spring.profile.active 项目的环境,一般有dev test等
    • spring.cloud.nacos.config.file-extension 项目的扩展文件名
    • 此外,还有group,namespace等
server:
  port: 3377

spring:
  application:
    name: nacos-config-client
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #服务注册中心地址
      config:
        server-addr: localhost:8848 #配置中心地址
        file-extension: yaml #指定yaml格式的配
        group: TEST_GROUP
        namespace: 76fce2f9-7d68-4729-a7d5-b6861295483d
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yml
  • 以上的这些信息可以唯一确定一个配置文件

Nacos持久化配置

这样一个微服务项目的配置文件一般都比较重要,如果万一挂掉了呢,所以我们需要非常谨慎的去处理配置信息,Nacos默认自带的嵌入式数据库,derby,

  • 我们也可以使用MySQL对配置文件进行持久化
  • 在config配置文件下有sql脚本,专门建立配置文件的数据表信息,
  • 修改application.properties ,咋i最后面加上一些信息
spring.datasource.platform=mysql

db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456
  • 下次就可以去mysql数据库里面找响应的消息了,
  • 如果要做持久化,我们仅仅需要一个mysql可能还不够,我们还需要高可用,把Nacos做一个集群,一个挂了另外一个可以顶上,它们共同使用一个mysql或者一个mysql的集群,
  • 我们模拟一下使用场景, 1个Nginx,+3个Nacos注册中心 + 1个Mysql
  • 我们先确定3台Nacos机器的不同服务端口号
  • 然后修改cluster.conf, 上面列出这个集群中所有的Nacos机器,
  • 再编写 Nacos的启动脚本 startup.sh ,使我们可以以不同的端口启动
  • 修改内容有些乱七八糟的,这里就不想再罗列出来了,等会儿说一个更加合适的方法
  • 另外我们的nginx也需要进行配置一下,然后暴露出来一个端口,监听集群里面的端口,然后负载均衡的分配给这些端口里面某一个确定的端口
upstream cluster{                                                        
 
    server 127.0.0.1:3333;
    server 127.0.0.1:4444;
    server 127.0.0.1:5555;
}
server{
                          
    listen 1111;
    server_name localhost;
    location /{
         proxy_pass http://cluster;
                                                        
    }
  • 启动nginx和我们的集群里面的端口即可
  • 我们在客户端修改一下服务器的ip,修改为我们nginx暴露出来的端口即可
  • 或者我们复制三个nacos,然后它们的cluster.xml 都是一样的,然后再把各自的端口修改一下,全部启动即可

Sentinel学习

Sentinel 主要提供 流量控制,熔断降级,系统负载保护,
可以理解为和Hystrix 的功能一致,但是这个比较优秀的是它可以 进行配置,
我们都知道,一般都是约定大于配置,配置大于编码,这就是它比较优秀的地方,可以再很多场景下面进行详细的配置

  • 服务使用中的各种问题:
    • 服务雪崩
    • 服务降级
    • 服务熔断
    • 服务限流
  • Sentinel分为两个部分
    • 核心库,不依赖任何框架库,能够运行于所有Java运行环境,同时对Dubbo、 SpringCloudd等框架也有比较好的支持
    • 控制台,基于SpringBoot开发,打包后可以直接运行,不需要web服务器
    • 我们去官网上面下载jar包,就可以使用java命令进行运行了,不过运行时需要注意,确保jdk8,8080端口不能被占用,然后就可以访问了
  • 假设现在nacos和sentinel都已经启动了,现在我们每创建一个微服务里面的接口都需要被监控到,
  • 需要做如下配置,我们现在创建一个8401端口的服务,看一下具体情况,

pom

    <dependencies>
        <dependency>
            <groupId>com.atguigu.springcloudgroupId>
            <artifactId>cloud-api-commonsartifactId>
            <version>${project.version}version>
        dependency>

        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>

        <dependency>
            <groupId>com.alibaba.cspgroupId>
            <artifactId>sentinel-datasource-nacosartifactId>
        dependency>

        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>4.6.3version>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

    dependencies>

配置文件

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719  #默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口

management:
  endpoints:
    web:
      exposure:
        include: '*'

主启动类

@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
     
    public static void main(String[] args) {
     
        SpringApplication.run(MainApp8401.class, args);
    }
}

然后我们建立一个测试的接口类进行测试

@Slf4j
@RestController
public class FlowLimitController {
     
    @GetMapping("/testA")
    public String testA() {
     
        return "------testA";
    }

    @GetMapping("/testB")
    public String testB() {
     
        return "------testB";
    }

    @GetMapping("/testD")
    public String testD() {
     
        try {
     
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }
        log.info("testD 测试");
        return "------testD";
    }
}
  • 由于Sentinel采用的懒加载的模式,我们需要被管理的接口除了再配置文件上声明过以外,还需要自己访问一次才可以进行管理,这样也是为了节省资源吧

![image.png](https://img-blog.csdnimg.cn/img_convert/a1700064b92d1d9dc33dee3611457010.png#align=left&display=inline&height=309&margin=[object Object]&name=image.png&originHeight=617&originWidth=854&size=46650&status=done&style=none&width=427)

流控模式的详细信息

  • 资源名,也就是相当于我们访问的接口的名字
  • 针对来源:sentinel可以针对调用者进行限流,填写微服务名字,默认是default,不区分来源
  • 阈值类型 :
    • QPS 每秒钟请求数量,当调用该api的QPS到达阈值的时候就会进行限流
    • 线程数:当调用该api的线程数到达阈值的时候,进行限流
    • 这两个怎么区分呢, 一个主机可以有很多个线程,这些线程可以同时调用一个api,QPS相当于在外部进行拦截,线程数相当于在系统的内部进行拦截
    • 是否集群:现在先不考虑集群
  • 流控模式:
    • 直接:api达到限流条件时,直接限流
    • 关联:当关联的资源达到阈值时,会限流自己
    • 链路:只记录指定链路上的流量
  • 流控效果
    • 直接:直接失败,抛出异常
    • 预热:阈值除以 cold Factor(默认为3),经过预热时长后才会达到阈值,默认每秒钟可以接收3个请求,逐步增加,经过预热时长之后,逐步升至设定的QPS阈值
    • 预热比较适合的应用场景时在秒杀系统开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是为了保护系统,慢慢的把流量放进来,慢慢把阈值增长到设置的阈值
    • 排队等待:让请求以均匀1的速度通过,对应的是漏桶算法,该方式主要用于处理间隔性突发的流量,例如消息队列,,在某一秒有大量的请求到来,而接下来几秒则处于空闲状态,我们希望系统能够在接下来的空闲时间逐渐处理这些请求,而不是一下子拒绝掉


![image.png](https://img-blog.csdnimg.cn/img_convert/86fb1faf43292b54c4a0cad630dabab1.png#align=left&display=inline&height=194&margin=[object Object]&name=image.png&originHeight=387&originWidth=848&size=26806&status=done&style=none&width=424)

降级规则

  • 降级策略 RT 平均响应时间,超出阈值,且在时间窗口内通过的请求大于5同时满足时触发
  • 异常比例: QPS大于5 且异常比例(秒级统计) 超过阈值,触发降级,时间窗口结束后关闭降级
  • 异常数,异常数超过阈值(分钟统计) 触发降级,时间窗口结束后,关闭降级
  • 我们在学习Hystrix的时候,知道它时是有一个半开的状态的,半开的状态系统自动去检车是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可以用,但是这里是没有半开这个状态的

![image.png](https://img-blog.csdnimg.cn/img_convert/f363c93b765bbcdd4e934436564378c5.png#align=left&display=inline&height=382&margin=[object Object]&name=image.png&originHeight=764&originWidth=854&size=61889&status=done&style=none&width=427)

热点参数限流

  • 热点,就是我们经常访问的数据,很多时候我们希望统计某一个热点数据中访问频次最高的数据,并对其访问进行限制,
  • 热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流,热点参数限流可以看作是一种特殊的流量控制,仅对包含热点参数的资源调用生效
  • Sentinel利用LRU策略统计最佳最常访问的热点参数,结合令牌桶算法来进行参数级别的流量防控,热点参数限流支持集群模式
    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey")
    public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                             @RequestParam(value = "p2",required = false) String p2) {
     
        //int age = 10/0;
        return "------testHotKey";
    }
  1. 我们这样写我们的接口,然后配置热点规则,当第一个参数的QPS超过1的时候就会进行报错,这个时候如果还是要继续访问就会返回错误页面,这个错误页面看着很不美观,我们也可以自己设置
    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
    public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                             @RequestParam(value = "p2",required = false) String p2) {
     
        //int age = 10/0;
        return "------testHotKey";
    }

    //兜底方法
    public String deal_testHotKey (String p1, String p2, BlockException exception){
     
        return "------deal_testHotKey,o(╥﹏╥)o";
    }
  1. 这个时候如果访问的频率违背了我们定义的热点规则,就会按照我们自定义的兜底方法来进行处理
  2. 但是如果我们不违背自己在Sentinel上面定义的热点规则,那么就什么事情也没有
  3. 我们定义的热点规则还可以支持高级配置,在原本的规则基础上,当参数类型和参数值确定了,再使用另外一套规则,使用不同的限流阈值
  4. 热点参数需要注意:参数类型必须是基本类型或者String
  5. @SentinelResource(value = “testHotKey”,blockHandler = “deal_testHotKey”) 这个处理的是Sentinel控制台配置违规的情况,有blockHandler方法配置的兜底方法,但是如果是方法内部运行时异常是无法控制的

系统规则

![image.png](https://img-blog.csdnimg.cn/img_convert/c0c591ca8af2f4e7c4e27521216094aa.png#align=left&display=inline&height=222&margin=[object Object]&name=image.png&originHeight=296&originWidth=835&size=21749&status=done&style=none&width=626)

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

  • 系统规则是从应用基本的入口流量进行控制,从load ,CPU使用率, 平均RT,入口QPS,和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性
  • load是一个结果,如果根据load的情况来调节流量的通过率,那么就始终有延迟,也就意味着通过率的任何调整,都会过一段时间才能看到结果,当前通过率是使load恶化的一个动作,那么也至少要过1s之后才可以观测到,同理,如果当前通过率调整时让load好转的当作,也需要1s之后才能继续调整,这样就浪费了系统的处理能力,所以我们看到的曲线,总是会有抖动
  • 总的来说,load不适合我们做及时的调整,,我们应该根据系统能够处理的请求,和允许进来的请求,来做平衡,而不是根据一个间接的指标来 load 做限流,最终我们追求的目标是,在系统不被拖垮的情况下,提高系统的吞吐率,而不是load一定要低于某个阈值,
  • CPU usage 当系统CPU使用率超过阈值即触发系统保护 ,比较灵敏
  • 平均RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护,单位是毫秒
  • 并发线程数:所有入口流量的并发线程数达到阈值即触发系统保护
  • 入口QPS:所有入口流量的QPS达到阈值即触发系统保护

按资源名称限流和按照Url地址进行限流

@GetMapping("/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handleException")
    public CommonResult byResource() {
     
        return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020, "serial001"));
    }
    @GetMapping("/rateLimit/byUrl")
    @SentinelResource(value = "byUrl")
    public CommonResult byUrl()
    {
     
        return new CommonResult(200,"按url限流测试OK",new Payment(2020,"serial002"));
    }

这两个方式其实都一样

自定义限流逻辑处理

原来的限流逻辑有一些缺点:

  1. 系统默认的,没有体现我们的业务要求
  2. 依照现有的条件我们自定义处理方法和业务方法都在一个类里面,耦合度太高,不太好
  3. 每个业务方法都有一个兜底方法,不太合适
  4. 全局统一的处理方法没有体现
    @GetMapping("/byResource")
    @SentinelResource(value = "byResource",
            blockHandlerClass = CustomerBlockHandler.class,
            blockHandler = "handleException")
    public CommonResult byResource() {
     
        return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020, "serial001"));
    }
public class CustomerBlockHandler {
     
    public static CommonResult handleException(BlockException e){
     
        return new CommonResult(2000,"客户自定义限流处理信息");
    }
}
  1. 按照上面的方法,我们就可以在一个统一的类里面写一些静态的方法,然后所有的兜底都在里面
  2. 需要注意的是,如果原来的方法有参数,那么兜底的方法也一定要有参数

Sentinel整合ribbon+openFeign+fallback

  • 创建两个服务提供者项目,
  • 然后消费者项目创建,
@RestController
@Slf4j
public class CircleBreakerController {
     

    public static final String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;


    @RequestMapping("/consumer/fallback/{id}")
    //@SentinelResource(value = "fallback") //没有配置
    //@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
    //@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
    @SentinelResource(value = "fallback",
            fallback = "handlerFallback",
            blockHandler = "blockHandler",
            exceptionsToIgnore = {
     IllegalArgumentException.class})
    public CommonResult<Payment> fallback(@PathVariable Long id) {
     
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);

        if (id == 4) {
     
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
        } else if (result.getData() == null) {
     
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return result;
    }

    //fallback
    public CommonResult handlerFallback(@PathVariable Integer id, Throwable e) {
     
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(444, "兜底异常handlerFallback,exception内容  " + e.getMessage(), payment);
    }

    //blockHandler
    public CommonResult blockHandler(@PathVariable Integer id, BlockException blockException) {
     
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException  " + blockException.getMessage(), payment);
    }
}
  • 原理上是 fallback会管理服务内部出现异常的回执,被限流降级而抛出的异常只会进入blockHandler处理逻辑
  • 具体的套路就是我们访问规则出错,使用blockHandler里面的方法,访问内部出错,使用fallback里面的回执方法,如果一直访问内部都出错,那么就用 blockHandler的方法,因为它首先是违反了我们定义的访问规则,然后再在内部出错的
  • 我们之前学习过Feign,现在再来回顾一遍,顺便将这个项目改造一下

在服务消费者引入feign

				<dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>

在配置文件上面开启对feign的支持

#对Feign的支持
feign:
  sentinel:
    enabled: true

远程调用,并声明降级方法

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService
{
     
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Integer id);
}

@Component
public class PaymentFallbackService implements PaymentService {
     
    @Override
    public CommonResult<Payment> paymentSQL(Integer id) {
     
        return new CommonResult<>(44444, "服务降级返回,---PaymentFallbackService", new Payment(id, "errorSerial"));
    }
}

在主启动类声明启用Feign远程调用

@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain84 {
     
    public static void main(String[] args) {
     
        SpringApplication.run(OrderNacosMain84.class, args);
    }
}
  • 熔断框架的比较
    | | Sentinel | Hystrix | resilience4j |
    | — | — | — | — |
    | 隔离策略 | 信号量隔离(并发线程数限流) | 线程池隔离/信号量隔离 | 信号量隔离 |
    | 熔断降级策略 | 基于响应时间,异常比率,异常数 | 基于异常比率 | 基于异常比率,响应时间 |
    | 实时统计实现 | 滑动窗口 LeapArray | 滑动窗口,基于 RxJava | Ring Bit Buffer |
    | 动态规划配置 | 支持多种数据源 | 支持多种数据源 | 有限支持 |
    | 扩展性 | 多个扩展点 | 插件的形式 | 的接口形式 |
    | 基于注解的支持 | 支持 | 支持 | 支持 |
    | 限流 | 基于QPS,支持基于调用关系的限流 | 有限的支持 | Rate Limiter |
    | 流量整形 | 支持预热模式,匀速器模式,预热排队模式 | 不支持 | 简单的Rate Limiter模式 |
    | 系统自适应保护 | 支持 | 不支持 | 不支持 |
    | 控制台 | 提高开箱即用的控制台,可配置规则,查看秒级监控,机器发现 | 简单的监控查看 | 不提高控制台,可对接其他监控系统 |

规则持久化

一旦我们重启应用,Sentinel规则就会消失,生产环境需要将配置规则进行持久化,
可以将限流配置规则持久化进 Nacos 保存,只要刷新 8401 某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对 8401 上 Sentinel上的 流控规则持续有效

  • 我们发现我们指定的规则在外面的服务停止之后再上来就没有了,针对这种情况,外面可以将外面的配置作为一个配置文件存储到nacos上面,而nacos外面前面已经学习过,它将信息存储到了数据库里面,所以外面就相当于间接的把配置信息存储到了数据库上面
  • 下次登录就会发现以前配置过的规则
   			<dependency>
            <groupId>com.alibaba.cspgroupId>
            <artifactId>sentinel-datasource-nacosartifactId>
        dependency>

配文件,需要声明外面存储到了哪个nacos声明,存储的格式,名字,组名,规则

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719  #默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

management:
  endpoints:
    web:
      exposure:
        include: '*'
      
      
spring:
   cloud:
    sentinel:
    datasource:
     ds1:
      nacos:
        server-addr:localhost:8848
        dataid:${
     spring.application.name}
        groupid:DEFAULT_GROUP
        data-type:json
            rule-type:flow     

外面配置过一个规则之后,配置的格式大致如此

[
    {
     
         "resource": "/retaLimit/byUrl",   
         "limitApp": "default",
         "grade":   1,
         "count":   1,
         "strategy": 0,
         "controlBehavior": 0,
         "clusterMode": false    
    }
]
resource: 资源名称,可以看作是我们的接口什么的
limitApp: 来源应用
grade: 阈值类型,0表示线程数,1表示QPS
count: 单机阈值
strategy: 流控模式:0表示直接,1表示关联,2表示链路
controlBehavior: 流控效果,0表示快速失败,1表示预热,2表示排队
clusterMode: 表示是否集群

分布式事务问题

Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务,Seata将为用户提供了AT,TCC,SAGA和XA事务模式

AT模式

  • 前提是基于本地ACID事务的关系型数据库,Java应用,通过JDBC访问数据库
  • 通过两个阶段提交协议:
    • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源
    • 二阶段,提交异步化,回滚通过一阶段的回滚日志进行反向补偿
  • 写隔离:
    • 一阶段本地事务提交之前,需要确保先拿到全局锁,
    • 拿不到全局锁就无法提交本地事务,
    • 拿全局锁的尝试被限制在一定范围内,超过范围将放弃,并回滚本地事务,释放本地锁,
  • 例子 一阶段:
    • 解析SQL,得到SQL的类型,表,条件等相关信息,
    • 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据,得到要更新这一行的数据的镜像
    • 执行业务SQL,更新这条记录,
    • 查询后镜像,根据前镜像的结果,通过主键定位数据,得到之后的镜像,
    • 插入回滚日志,把前后镜像数据以及业务SQL相关的信息组成一条回滚日志记录,插入到UNDO_LOG表中,
    • 提交之前,向TC注册分支,去申请要修改记录的全局锁,
    • 本地事务提交,业务数据的更新和前面步骤中生成的UNDO_LOG 一并提交
    • 将本地事务提交的结果上报给TC
  • 二阶段-回滚
    • 收到TC的分支回滚请求,开启一个本地事务,执行如下操作
    • 通过XID和BranchID查找相应的UNDO_LOG 记录
    • 数据校验:拿UNDO_LOG中的后镜像与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改,这种情况需要根据配置策略来做处理
    • 根据UNDO_LOG 中的前镜像和业务SQL的相关信息生成执行回滚的语句
    • 然后提交本地事务,并把本地事务的执行结果上报给TC
  • 二阶段-提交
    • 收到TC的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给TC,
    • 异步任务阶段的分支提交请求将异步和批量的删除相应UNDO_LOG记录

TCC模式

上一步AT模式的全局事务整体式两阶段提交,全局事务时由若干分支事务组成,分支事务 要满足两阶段提交的模型要求,即每个分支事务都具备自己的两个阶段

  1. 一阶段 prepare行为 : 在本地事务中,一并提交业务数据更新和相应回滚日志记录
  2. 二阶段 commit 或 rollback行为 : commit行为,马上成功结束,自动异步批量清理回滚日志
  3. 二阶段 rollback : 通过回滚日志,自动生成补偿操作,完成数据回滚

分布式事务的处理过程: ID+三组件模型

  • 全局唯一的事务ID
  • TC: Transaction Coordinator 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚
  • TM:Transaction Manager 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议
  • RM:Resource Manager 控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支(本地)事务dd1提交和回滚

处理过程

  1. TM向TC申请开启一个全局事务,全局事务创建成功,并生成一个全局唯一的XID
  2. XID在微服务调用链路的上下文中传播
  3. RM向TC注册分支事务,将其纳入XID对应全局事务的管辖,
  4. TM向TC发起针对XID的全局提交或回滚决议
  5. TC调度XID管辖的全部分支事务完成提交或回滚请求

这个太新了,我决定放弃!!!以后再来
























你可能感兴趣的:(java,spring,spring,cloud,alibaba,spring,cloud)