Spring Could(六)Alibaba

Spring Could主要内容结构图:
在这里插入图片描述





简介

Spring Cloud Netflix将不再开发新的组件,进入维护模式。

我们都知道Spring Cloud版本迭代算是比较快的,因而出现了很多重大ISSUE都还来不及Fix就又推另一个Release了。进入维护模式意思就是目前一直以后一段时间Spring Cloud Netfli提供的服务和功能就这么多了,不在开发新的组件和功能了。以后将以维护和Merge分支Full Request为主。

Spring Cloud Alibaba的作用:

  • 服务限流降级:默认支持Servlet、Feign、RestTemplate、Dubbo和RocketMQ限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级Metrics 监控。
  • 服务注册与发现:适配 Spring Cloud服务注册与发现标准,默认集成了 Ribbon的支持。
  • 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
  • 消息驱动能力:基于Spring Cloud Stream为微服务应用构建消息驱动能力。
  • 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  • 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于Cron表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有Worker (schedulerx-client)上执行。

Nacos服务注册和配置中心

名字的由来:前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service。

Nacos是什么:

Nacos就是注册中心+配置中心的组合

相当于:Nacos = Eureka + Config + Bus

各注册中心的比较:
在这里插入图片描述
注意:Nacos支持AP和CP模式的切换

在父工程pom文件中引入alibaba 依赖,以下所有案例都基于该父工程

<!--  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>

Navos服务提供者注册

pom文件:

    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

    </dependencies>

yml文件:

server:
  port: 9001
spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
management:
  endpoints:
    web:
      exposure:
        include: "*"

启动类:

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

controller:

@RestController
@RequestMapping("/payment")
public class PaymentController {

    @Value("${server.port}")
    public String serverPort;

    @RequestMapping("/getPayment/{id}")
    public String getPayment(@PathVariable("id") Integer id){
        return "Alibaba Nacos server "+ serverPort+"-----"+id;
    }

}

Nacos服务消费者的注册

pom文件:

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

yml文件:

server:
  port: 83

spring:
  application:
    name: cloud-nacos-order
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

#消费者将要去访问的微服务名称
server-url:
  nacos-user-service: http://nacos-payment-provider

启动类:

@SpringBootApplication
@EnableDiscoveryClient
public class NacosOrderMain83 {

    public static void main(String[] args) {
        SpringApplication.run(NacosOrderMain83.class,args);
    }
}

controller:

@RestController
public class OrderNacosController {

    @Resource
    private RestTemplate restTemplate;

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

    @GetMapping("/order/getPayment/{id}")
    public String getPaymentInfo(@PathVariable("id") Long id) {
        return restTemplate.getForObject(url+"/payment/getPayment/"+id,String.class);
    }
}

配置类:

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced  //负载均衡:轮询
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

注意:Nacos是默认整合了Ribbon的,能够自动实现负载均衡,所以需要添加@LoadBalanced 注解,否则会报错。

在这里插入图片描述





Nacos作为服务配置中心

pom文件:

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

配置文件:

Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。
springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application

bootstrap文件:

server:
  port: 3377

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: 7a901d46-e75e-4e6a-b186-5980cca4249b



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

application文件:

spring:
  profiles:
    active: dev

启动类:

@SpringBootApplication
@EnableDiscoveryClient
public class ConfigNacosMain3377 {

    public static void main(String[] args) {
        SpringApplication.run(ConfigNacosMain3377.class,args);
    }
}

controller

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

    @Value("${config.info}")
    private String configInfo;

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

Nacos界面配置对应:
在这里插入图片描述
最后公式:

${spring.application.name}${spring.profiles.active}.$(spring.cloud.nacos.config.file-extension}


在这里插入图片描述

启动项目,成功获取配置文件
在这里插入图片描述

多环境多项目管理

问题1:
实际开发中,通常一个系统会准备
dev开发环境
test测试环境
prod生产环境。
如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?

问题2:
一个大型分布式微服务系统会有很多微服务子项目,
每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境.
那怎么对这些微服务配置进行管理呢?

Namespace+Group+Data lD三者关系:

最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。
在这里插入图片描述
默认情况:
Namespace=public,Group=DEFAULT_GROUP,默认Cluster是DEFAULT

Nacos默认的命名空间是public,Namespace主要用来实现隔离。
比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。

Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去

Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。比方说为了容灾,将Seryice微服务分别部署在了杭州机房和广州机房,
这时就可以给杭州机房的Service微服务起一个集群名称(HZ) ,
给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。

最后是lnstance,就是微服务的实例。





## Nacos集群

默认Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点(Nacos集群),数据存储是存在一致性问题的。为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。

参考文章:

集群部署:link

集群配置:link





Sentinel实现熔断和限流

和Hystrix的对比

在这里插入图片描述

下载地址:
在这里插入图片描述

Sentinel分为两个部分:

  • 核心库(Java客户端)不依赖任何框架/库,能够运行于所有Java运行时环境,同时对 Dubbo /Spring Cloud等框架也有较好的支持。
  • 控制台(Dashboard)基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。

启动本地Sentinel后

搭建服务,并将服务入驻Sentinel控制台(Dashboard)

pom文件:

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
<!--        后续做持久化用到-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <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>

yml文件:

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

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

测试controler:

@RestController
public class FlowLimitController {

    @RequestMapping("testA")
    public String testA(){
        return "testA-------";
    }

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

启动服务,观察Sentinel控制台

注意:Sentinel是懒加载模式,需要被监控的服务有请求后,才会显示监控信息。

在这里插入图片描述

监控规则说明

用于对每一个接口进行配置,规定接口的访问限制,如果不符合条件则调用失败。相当于将Hystrix在代码注解中的配置,在控制界面进行。
在这里插入图片描述
解释说明:
在这里插入图片描述

控流模式

直接模式流控

如下图的配置表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
在这里插入图片描述

关联模式流控

当关联的资源达到阈值时,就限流自己

比如当与A关联的资源B达到阀值后,就限流A自己。

在这里插入图片描述

上图的设置效果:

当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名

控流效果

快速失败

默认的流控效果,直接返回失败

Warm Up

Warm Up ( RuleConstant.cONTROL_BEHAVIOR_NARA_up)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动"”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考流星控制- Warm Up文档,具体的例子可以参见

公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值

默认coldFactor为3,即请求QPS从(threshold / 3)开始,经多少预热时长才逐渐升至设定的QPS阈值。

案例:
阀值为10+预热时长设置5秒。系统初始化的阀值为10/3约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10
在这里插入图片描述

应用场景:

如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。

排队等待

匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。
设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒

在这里插入图片描述

在这里插入图片描述
应用场景:

这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一次直接拒绝多余的请求。

降级规则

和Hystrix相似,只是Sentinel是在监控界面进行配置,不需要在代码中使用注解进行配置。
在这里插入图片描述

Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。

当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

注意:Sentinel的断路器是没有半开状态的,和Hystrix区别开,只要在窗口时间内没有触发新的熔断,窗口事件过后就会恢复。

半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,
有异常则继续打开断路器不可用。具体可以参考Hystrix

RT(平均响应时间,秒级)降级策略

平均响应时间超出阈值且在时间窗口内通过的请求>=5,两个条件同时满足后触发降级窗口期过后关闭断路器
RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

在这里插入图片描述
案例:

在这里插入图片描述
假如一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,
如果超过20O0毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了

异常比列(秒级)降级策略

QPS >= 5且异常比例(秒级统计)超过阈值时,触发险级;时间窗口结束后,关闭降级

异常比例( DEGRADE_GRADE_EXCEPTION_RATIo):当资源的每秒请求量>=5,并且每秒异常总数占通过量的比值超过阈值( DegradeRule中的count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule中的timewindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0,1.0],代表0%到100%。

在这里插入图片描述
案例:
在这里插入图片描述
当一秒钟内访问量超过5,且调用失败的比例超多百分之20,则触发降级一秒钟,一秒钟后关闭降级。

异常数(分钟级)降级策略

异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

时间窗口一定要大于等于60秒。

异常数(DEGRADE_GRADE_EXCEPTION_COUNT ):当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timewindow小于60s,则结束熔断状态后可能再进入熔断状态。

在这里插入图片描述

案例:

在这里插入图片描述
http://localhost:8401/testE,第一次访问绝对报错,因为除数不能为零,我们看到error窗口,但是达到5次报错后,进入熔断后降级。

上述三种规则的测试代码:

    @RequestMapping("testC")
    public String testC(){
        try {
            TimeUnit.SECONDS.sleep(6);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "testC 测试RT-------";
    }

    @RequestMapping("testD")
    public String testD(){
        System.out.println("testD 异常比例测试");
        int age = 10/0;
        return "testD-------";
    }

    @RequestMapping("testE")
    public String testE(){
        System.out.println("testD 异常s数--测试");
        int age = 10/0;
        return "testD-------";
    }

热点key限流(热点规则)

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的Top K数据,并对其访问进行限制。比如:

  • 商品ID为参数,统计一段时间内最常购买的商品ID并进行限制
  • 用户ID为参数,针对一段时间内频繁访问的用户ID进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

在这里插入图片描述
在1秒钟内,携带第一个参数的请求超过1一次,则进行服务降级,调用指定方法返回,没有指定则会返回错误页面

测试代码:

    @RequestMapping("testHotKey")
    @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKet")
    public String testHotKey(
            @RequestParam(value = "p1",required = false) String p1,
            @RequestParam(value = "p2",required = false) String p2
    ){
        System.out.println("testHotKey 热点Key--测试");
        int age = 10/0;
        return "testHotKey-------";
    }

    public String deal_testHotKet(String p1, String p2, BlockException e) {
        return "----deal_testHotKet,------";
    }

方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理

参数列外项:

参数列外项可以满足我们比较复杂的参数限流设置

有时候我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样特例
假如当p1的值等于5时,它的阈值可以达到200

在这里插入图片描述
注意:

@SentinelResource
处理的是sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

@RuntimeException
比如int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管

总结
@SentinelResource主管配置出错,运行出错该走异常走异常

系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU使用率、平均RT、入口QPS和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量( EntryType.IN ),比如 Web服务或 Dubbo服务端接收的请求,都属于入口流量。

系统规则支持以下的模式:
在这里插入图片描述

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

@SentinelResource配置

注意:@SentinelResource不支持private方法

按资源名称限流

案例:

控制台配置:
在这里插入图片描述
接口代码:

    @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服务不可用");
    }

测试结果:
在这里插入图片描述

注意:如果服务关闭,对应的控制台配置规则也会消失

按照URL地址限流

案例:

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

代码:

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

需要注意的是:这里没有指定blockHandler,则返回Sentinel自带的限流处理结果

在这里插入图片描述

客户自定义限流

上面兜底方案面临的问题:

  1. 系统默认的,没有体现我们自己的业务要求。
  2. 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
  3. 每个业务方法都添加一个兜底的,那代码膨胀加剧。
  4. 全局统—的处理方法没有体现。

创建CustomerBlockHandler类用于自定义限流处理逻辑

public class Customerhandler {

    public static CommonResult handlerException(BlockException exception) {
        return new CommonResult(444,"按客户自定义,global handlerException",new Payment(2020L,"serial003"));
    }

    public static CommonResult handlerException2(BlockException exception) {
        return new CommonResult(444,"按客户自定义,global handlerException2",new Payment(2020L,"serial003"));
    }
}

注意:没有注入,需要使用static修饰方法

在接口上进行注解配置:

    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler",
        blockHandlerClass = Customerhandler.class,   // 指定自定义类
        blockHandler = "handlerException2")          // 指定类中的方法
    public CommonResult customerBlockHandler() {
        return new CommonResult(200,"按客户自定义OK",new Payment(2020L,"serial002"));
    }

服务熔断功能

配置fallback,处理业务异常

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback",fallback = "handlerFallback")  //fallback只负责业务异常
    public CommonResult<Payment> fallback(@PathVariable("id") 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;
    }

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

同时配置fallback和blockHandler

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

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler") 
    public CommonResult<Payment> fallback(@PathVariable("id") 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;
    }

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

    public CommonResult blockHandler(@PathVariable("id") Long id, BlockException exception) {
        Payment payment = new Payment(id,"null");
        return new CommonResult(445,"兜底异常blockHandler,exception内容"+exception.getMessage(),payment);
    }

fallback只处理业务异常

blockHandler只处理控制台配置的限流异常

若blockHandler和fallback 都进行了配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑。也就是说在即发生了业务异常又流量又超过了控制的限流配置,则会进行blockHandler的处理。

对应一些特定的异常我们不想进行降级的可以使用exceptionsToIgnore 进行忽略,如下配置当发生IllegalArgumentException异常时会返回错误页面不会调用handlerFallback进行处理。

    @GetMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler", exceptionsToIgnore = {IllegalAccessException.class}) //exceptionsToIgnore配置
    public CommonResult<Payment> fallback(@PathVariable("id") 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;
    }

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

    public CommonResult blockHandler(@PathVariable("id") Long id, BlockException exception) {
        Payment payment = new Payment(id,"null");
        return new CommonResult(445,"兜底异常blockHandler,exception内容"+exception.getMessage(),payment);
    }
sentinel整合ribbon+openFeign+fallback

调用方配置:

yml文件:

server:
  port: 84

spring:
  application:
    name: cloud-nacos-order
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719

#消费者将要去访问的微服务名称
server-url:
  nacos-user-service: http://nacos-payment-provider

feign:
  sentinel:
    enabled: true

config:

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

Open Feign:

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class )
public interface PaymentService {

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

降级处理类:

@Component
public class PaymentFallbackService implements PaymentService {

    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(444,"服务降级返回---PaymentFallbackService",new Payment(id,"errorService"));
    }
}

启动类:

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class NacosOrderMain84 {

    public static void main(String[] args) {
        SpringApplication.run(NacosOrderMain84.class,args);
    }
}

服务方配置:

yml文件:

server:
  port: 9004

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  application:
    name: nacos-payment-provider

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

controller:

@RestController
public class PaymentController {

    @Value("${server.post}")
    private String serverPost;

    public static HashMap<Long, Payment> hashMap = new HashMap<>();
    static {
        hashMap.put(1L,new Payment(1L,"000000000000000001"));
        hashMap.put(2L,new Payment(2L,"000000000000000002"));
        hashMap.put(3L,new Payment(3L,"000000000000000003"));
    }

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        Payment payment = hashMap.get(id);
        CommonResult<Payment> result = new CommonResult<>(200,"from mysql,serverPort:"+serverPost);
        return result;
    }
}

熔断框架对比:

在这里插入图片描述

Sentinel持久化规则

问题:

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

解决方案:

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

详情参考:link





分布式事务Seata

分布式事务是什么

单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源
业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据—致性问题没法保证

案例:
用户购买商品的业务逻辑。整个业务逻辑由3个微服务提供支持:

  • 仓储服务∶对给定的商品扣除仓储数里。
  • 订单服务:根据平购需求创建订单。
  • 账户服:从用户帐户中扣除余额。
    在这里插入图片描述

总结:一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。

Seata介绍

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

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

  1. TC-事务协调者心
    维护全局和分支事务的状态,驱动全局事务提交或回滚。
  2. TM-事务管理器
    定义全局事务的范围:开始全局事务、提交或回滚全局事务。
  3. RM-资源管理器
    管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

处理过程:

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

在这里插入图片描述

案例说明

这里创建三个服务,一个订单服务,一个库存服务,一个账户服务。

当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,
再通过远程调用账户服务来扣减用户账户里面的余额,
最后在订单服务中修改订单状态为已完成。

该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

订单服务:

pom文件:

    <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>seata-all</artifactId>
                    <groupId>io.seata</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <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>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

application.yml文件:

server:
  port: 2001


spring:
  application:
    name: seata-order-service
  cloud:
    alibaba:
      seata:
        tx-service-group: fsp_tx_group
    nacos:
      discovery:
        server-addr: localhost:8848
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://000.000.000.000:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456

feign:
  hystrix:
    enabled: false

logging:
  level:
    io:
      seata: info

mybatis:
  mapper-locations: classpath:mapper/*.xml

在application的同级目录下放入配置文件:

file.conf:

service {
  #transaction service group mapping
  vgroup_mapping.fsp_tx_group = "default"
  #only support when registry.type=file, please don't set multiple addresses
  default.grouplist = "127.0.0.1:8091"
  #disable seata
  disableGlobalTransaction = false
}

## transaction log store, only used in seata-server
store {
  ## store mode: file、db
  mode = "db"

  ## file store property
  file {
    ## store location dir
    dir = "sessionStore"
  }

  ## database store property
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
    datasource = "dbcp"
    ## mysql/oracle/h2/oceanbase etc.
    db-type = "mysql"
    driver-class-name = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://000.000.000.000:3306/seata"
    user = "root"
    password = "123456"
  }
}

registry.conf文件:

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
    serverAddr = "localhost:8848"
    namespace = ""
    cluster = "default"
  }
  eureka {
    serviceUrl = "http://localhost:8761/eureka"
    application = "default"
    weight = "1"
  }
  redis {
    serverAddr = "localhost:6379"
    db = "0"
  }
  zk {
    cluster = "default"
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  }
  consul {
    cluster = "default"
    serverAddr = "127.0.0.1:8500"
  }
  etcd3 {
    cluster = "default"
    serverAddr = "http://localhost:2379"
  }
  sofa {
    serverAddr = "127.0.0.1:9603"
    application = "default"
    region = "DEFAULT_ZONE"
    datacenter = "DefaultDataCenter"
    cluster = "default"
    group = "SEATA_GROUP"
    addressWaitTime = "3000"
  }
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "file"

  nacos {
    serverAddr = "localhost"
    namespace = ""
  }
  consul {
    serverAddr = "127.0.0.1:8500"
  }
  apollo {
    app.id = "seata-server"
    apollo.meta = "http://192.168.1.204:8801"
  }
  zk {
    serverAddr = "127.0.0.1:2181"
    session.timeout = 6000
    connect.timeout = 2000
  }
  etcd3 {
    serverAddr = "http://localhost:2379"
  }
  file {
    name = "file.conf"
  }
}

service方法进行全局事务处理:

@Service
@Slf4j
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderDao orderDao;
    @Resource
    private StorageService storageService;
    @Resource
    private AccountService accountService;

	// name保证全局唯一即可
    @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
    @Override
    public void create(Order order) {
        log.info("-------->开始创建新订单");
        orderDao.create(order);
        log.info("--------订单微服务开始调用库存,做扣减");
        storageService.decrease(order.getProductId(),order.getCount());
        log.info("-------订单微服务开始调用库存,做扣减end");

        log.info("-------订单微服务开始调用账户,做扣减");
        accountService.decrease(order.getUserId(),order.getMoney());
        log.info("-------订单微服务开始调用账户,做扣减end");
        log.info("-------修改订单状态");
        orderDao.update(order.getUserId(),0);
        log.info("-------修改订单状态结束");

        log.info("--------下订单结束了");
    }
}

Seata原理:

三方关系:

在这里插入图片描述
Seata将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造—站式的分布式解决方案。其中默认是AT模式。

AT模式

前提

  • 基于支持本地ACID事务的关系型数据库。
  • Java应用,通过JDBC访问数据库。

一阶段,Seata 会拦截“业务SQL”,

  1. 解析SQL语义,找到“业务SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,
  2. 执行“业务SQL”更新业务数据,在业务数据更新之后,
  3. 其保存成“after image”,最后生成行锁。
    以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

在这里插入图片描述

二阶段

如是顺利提交的话,

因为“业务SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

在这里插入图片描述

二阶段如果是回滚的话,
Seata就需要回滚一阶段已经执行的“业务SQl,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比"数据库当前业务数据”和“after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥自在”

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值