Spring-Cloud-Alibaba

Nacos(服务注册和配置中心)

一、安装

官网:https://nacos.io/zh-cn/

刚下完是集群模式,所以直接启动会显示"nacos is starting with cluster"

可以在bin/startup.cmd文件中修改:

set MODE="standalone"

修改完成后即可启动成功,在浏览器输入:

http://localhost:8848/nacos

即可进入nacos管理界面:

二、作为服务注册中心

以下配置均可在官网文档找到

1.创建两个module(端口:4001,4002)作为provider集群

  • pom

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

    # 基本配置
    server:port: 4001   # 集群时,注意端口号不一致spring:application:name: payment-alibaba-providercloud:nacos:discovery:server-addr: localhost:8848
    management:endpoints:web:exposure:include: "*"
    
  • 主启动类:添加注解:@EnableDiscoveryClient

  • controller

    @RestController
    public class PaymentController {@Value("${spring.application.name}")private String name;@Value("${server.port}")private String port;// 返回服务名称和当前实例的端口号,便于测试负载均衡@GetMapping("/get/nacos/{id}")public String getNacos(@PathVariable(value = "id")int id){return "get id from nacos: " + name + "----port:" + port;}
    }
    

    完成后即可在管理界面看到(注意需要先启动nacos,在启动微服务):

2.创建一个module(端口:81)作为consumer

  • pom,同上

  • yml,同上(注意微服务名称和端口)

  • 主启动类,同上

  • controller

    • 支持Restful,所以需要注入RestTemplate(代码略);因为nacos包含了ribbon,所以支持负载均衡,使用时与ribbon一样添加``@LoadBalanced`注解即可
    @RestController
    public class ConsumerController {// 此对象为官网使用方法,下面注释的方式配合该对象,可以不用添加@LoadBalanced注解也能开启负载均衡
    //    @Autowired
    //    private LoadBalancerClient loadBalancerClient;@Autowiredprivate RestTemplate restTemplate;@Value("${provider.service-url}")private String providerName;//    @GetMapping("consumer/get/nacos/{id}")
    //    public String getNacos(@PathVariable(value = "id")int id){//        ServiceInstance serviceInstance = loadBalancerClient.choose(providerName);
    //        return restTemplate.getForObject("http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() +
    //               " /get/nacos/" + id , String.class);
    //    }@GetMapping("consumer/get/nacos/{id}")public String getNacos(@PathVariable(value = "id")int id){return restTemplate.getForObject("http://"+providerName+"/get/nacos/"+id,String.class);}}
    

Nacos支持AP和CP的切换,Eureka只支持AP

三、作为配置中心

1.快速入门

  1. 新建module(端口2001)

  2. 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>
    
  3. yml(application、bootstrap), springCloud也是这样

    bootstrap:

    server:port: 2001spring:application:name: config-center-alibabacloud:nacos:discovery:server-addr: localhost:8848  # 服务注册config:server-addr: localhost:8848  # nacos作为配置中地址file-extension: yaml         # 指定配置文件类型

    application:

    spring:profiles:active: dev # 环境,与bootstrap中的配置可以结合起来
    
  4. 主启动类,注意添加EableDiscoverClient注解

  5. nacos配置中心创建配置文件:

    Data ID需要注意规范:
    ${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

    即:微服务名称-环境.配置文件类型====》对应案例:config-center-alibaba-dev.yaml

  6. 测试:

    @RestController
    @RefreshScope
    public class ConfigController {@Value("${config.info}")private String info;@GetMapping("/get/config")public String getInfo(){return info;}
    }
    

    访问:http://localhost:2001/get/info 即可得到配置信息

    Nacos具有自动的动态更新配置的功能,在controller上添加@RefreshScope注解即可,不需要和springcloud一样通过bus发送广播更新

tips:docker 安装nacos

  1. 安装docker(不作解释)

  2. docker search nacos
    
  3. docker pull nacos/nacos-server  # 默认最新版
    
  4. docker images  # 查看是否安装成功
    
  5. docker run -d -p 8848:8848 --env MODE=standalone  --name nacos  nacos/nacos-server
    
  6. http://IP+8848/nacos  登录
    
  7. 注意修改微服务中的配置文件的ip

2.分类配置

NameSpace、Group、Data Id

相当于java中包和类的关系

新建命名空间和配置文件:

微服务服务配置:

server:port: 2001spring:application:name: config-center-alibabacloud:nacos:discovery:server-addr: localhost:8848  # 服务注册config:server-addr: localhost:8848  # nacos作为配置中地址file-extension: yaml         # 指定配置文件类型group: DEFAULT_GROUP         # 指定分组namespace: ca6f1c91-fb7e-4a46-bf91-7f210bcef489   # 指定命名空间

四、Nacos集群和持久化

1.准备:

Nacos集群需要使用mysql:因为Nacos自带有小型的嵌入式数据库,所以在在集群的时候每个nacos都会有自己的数据库,这样就会导致数据的不一致,所以要切换到mysql将其统一持久到一个mysql数据库中。(目前只支持mysql)

在windows中配置mysql为数据源:

在ncaos中的conf文件夹下有:

nacos-mysql.sql文件:里面是数据库建表的语句,直接cv创建即可(数据库名称自定义)

application.properties文件修改如下(只需要将注解打开即可,注意修改数据库名称和密码,docker环境下就不需要修改这个文件了):

spring.datasource.platform=mysql### Count of DB:
db.num=1### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root

配置完成后,启动naco并登录管理界面,就会看到原来的配置文件不见了,再新建的配置文件信息可以再MySQL的自建的数据库的config_info表中看到,即配置成功。

在mysql版本过高可能会出现建表失败(时间的错误)

2.naocs集群配置(这里使用docker)

在上面已经下载nacos的docker版,只需在linux中配置mysql即可(如上,这里也是使用docker的mysql)。

创建容器命令(需要三份分别为:8846、8847、8848):

docker run -d \
-e PREFER_HOST_MODE=hostname \
-e MODE=cluster \
-e NACOS_APPLICATION_PORT=8847 \
-e NACOS_SERVERS="192.168.1.118:8846 192.168.1.118:8847 192.168.1.118:8848" \
-e SPRING_DATASOURCE_PLATFORM=mysql \
-e MYSQL_SERVICE_HOST=182.168.1.118 \
-e MYSQL_SERVICE_PORT=3307 \
-e MYSQL_SERVICE_USER=root \
-e MYSQL_SERVICE_PASSWORD=123456 \
-e MYSQL_SERVICE_DB_NAME=nacos_config \
-e NACOS_SERVER_IP=192.168.1.118 \
-p 8847:8847 \
--name my-nacos8847 \
nacos/nacos-server:1.4.2

简单解释:

  • 配置模式为:集群
  • nacos服务端口:8846
  • 集群的IP:3个
  • 使用数据库为mysql,在这里配置后,不需要修改application.properties文件了,将会自动绑定数据库
  • 数据库信息
  • 宿主机端口映射

**此时可以作个小测试:**

浏览器分别访问这三个nacos,在其中一个里面添加配置文件,刷新其他两个nacos管理界面,会出现同样的配置文件,即完成集群,且配置文件已经持久到mysql数据库中,可在数据库中查看。

注意:在登录管理界面时,可能出现默认账户密码错误,可以通过查看数据库中user表中是否有数据,大概率时没有导致的,只需要添加即可(密码是加密后的密码)

3.nginx配置(使用docker)

这里没有开启nginx的高可用

创建nginx容器:

docker run \
--name=nginx-MASTER \
-d -p 8000:80 \
-v $PWD/nginx.conf:/etc/nginx/nginx.conf:ro \
-v $PWD/conf.d:/etc/nginx/conf.d \
nginx

修改配置文件:

upstream cluster{         # nacos集群列表server 192.168.1.118:8846;server 192.168.1.118:8847;server 192.168.1.118:8848;
}
# server下
listen       80;     # nginx访问端口,docker映射为81:80
location / {proxy_pass http://cluster;
}

如上配置启动nginx,nacos;

访问:

http://192.168.1.118:81/nacos/#/lodin/

即可到nacos登录界面,且已经集群;在实际开发中,只能暴露nginx的虚拟ip,而nacos的IP都是不公开的。

遇到的坑:完成上述集群后,最后一步,需要注册微服务。但是始终无法注册进入nacos。调用服务注册api:

/nacos/v1/ns/instance

总会出现:server is DOWN now, please try again later!等字样,在java控制台中报错500或503.

查阅资料、官方文档、github上的issues,尝试过以下方法:

  • 修改docker中的nacos版本:latest、2.0.0、1.4.1
  • 设置linux的hostname即IP
  • 删除nacos里面的data中的protocol文件夹
  • 修改alibaba的依赖版本等

最终使用1.4.1版本使用/nacos/v1/ns/instance访问成功,并注册成功。具体原因还是没找到。个人认为是版本问题。在github上,官方说是由于(1)前置Filter拦截。(2)旧的jRaft的data数据会干扰本次jRaft的选举。总之还是使用RELEASE版本比较好

官网问题描述:https://github.com/alibaba/nacos/issues/5346

Sentinel(熔断与限流)

sentinal官网:

https://sentinelguard.io/zh-cn/

https://github.com/alibaba/Sentinel

功能:

下载:https://github.com/alibaba/Sentinel/releases/tag/1.8.1

下载的是一个jar包,所以世直接使用java -jar xxxx 直接运行即可,在浏览器访问http://localhost:8080可以看到dashboard(用户名密码默认为sentinel)

一、入门:对服务的监控

  1. 新建module

  2. 添加pom

            <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
    
  3. 配置yml

    server:port: 4003spring:application:name: alibaba-sentinel-servicecloud:nacos:discovery:server-addr: localhost:8848# sentinel监控该服务sentinel:transport:dashboard: localhost:8080port: 8719management:endpoints:web:exposure:include: "*"
  4. 创建主启动类

  5. 创建controller(随意创建mapping)

  6. 测试:在nacos中注册成功;sentinel中没有:因为sentinel使用的是懒加载,当进行一次访问该服务时就会加载

二、流控规则

流控规则添加:

解释:

  • 资源名:微服务中的Rest地址

  • 针对来源:

  • 阈值类型:QPS:访问的数量;线程数:服务端的线程数量

  • 单机阈值:QPS的请求阈值或线程数的数量

  • 是否集群:使用集群

  • 流控模式:

    1. 直接:达到限流条件时,直接限流自己

    2. 关联:A关联B,B达到设定阈值后,限流A自己

      如下:test1关联test2,当test2的请求QPS大于2时,test1就会被限流

    3. 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就可以限流)

  • 流控效果

    • 快速关闭:超过阈值直接返回错误
    • Warm up:预热,一个系统平时访问量很低,突然之间访问量暴增,超过阈值会使系统崩溃;使用预热留空会在设定时间内,将阈值提到上限;正常情况的阈值为:阈值上限/3 (3为预热因子,默认为3)
    • 排队等待:匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。

三、降级规则

1.慢调用比例策略

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

如图:对于/get/test3这个请求资源,满足如下规则触发熔断:

  • 在1000ms的时间内,最少需要有5次请求
  • 在1000ms内,超出RT的请求(即慢调用请求)的比例为1(100%)

就会触发2s的熔断。

**测试:**使用JMETER测试,给定每秒钟10个请求,controller中的处理时间为1s(超过RT,所以阈值为100%),此时使用浏览器访问该资源,会出现错误 Blocked by Sentinel (flow limiting)

    @GetMapping("/get/test3")public String test3(){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}return "-----test3----";}

2.异常比例

异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

如图,对于test4资源:

设置的比例阈值为0.5(50%),即:

  • 当1s内,最少有5次请求
  • 且,异常的比例为50%以上

则触发熔断。这里需要注意:这里的异常指的是业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效

    @GetMapping("/get/test4")public String test4(){int a=1/0;    // 错误率100%return "-----test4----";}

3.异常数

异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

异常数是按分钟为单位计数

如图:当窗口其内请求异常数超过10个就会触发熔断。

四、热点规则

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

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

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

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。

1.初步使用

业务代码:

这里使用到了新的标签@SentinelResource,该注解只适用于sentinel配置的异常,如服务限流、熔断、热点限流等;后台的程序异常使用,如:RuntimeException等

    @GetMapping("/hotKeyTest")@SentinelResource(value = "hotKeyTest", blockHandler = "deal_hotKeyTest")public String hotKeyTest(@RequestParam(value = "p1", required = false)String p1,@RequestParam(value = "p2", required = false)String p2){return "-----hotKeyTest----";}// blockHandler异常处理的方法public String deal_hotKeyTest(String p1, String p2, BlockException blockException){return "deal_hotKeyTest,  request error";}

普通Sentinel配置:

解释:资源名为hotKeyTest,而不是"/hotKeyTest";仅支持QPS的限流方式;参数索引为0,即第一个参数为热点参数;阈值:即统计时长内的请求。

所以,对于上面的热点规则,当在1s内超过一次请求带有参数p1则会触发异常,调用blockHandler方法;如果不带参数p1则不会触发。

参数例外项配置:

当某个热点参数的值为某个特定的值的时候,给与使用特定的规则,如下:

热点参数为p1,当p1的值为java.String类型的"zhangsan"的时候,允许其阈值为5,即可以在1s内访问5次,而其他带有p1参数的请求只能1s访问1次。

五、系统规则

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

如图:

当设置系统规则为入口QPS后,系统下的任何一个资源的访问大于1,都会被限流。

六、@SentinelResource

注解包含以下属性:

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT)
  • blockHandler / blockHandlerClass: blockHandler 对应处理BlockException的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class`对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback/fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable`类型的参数用于接收对应的异常。
    • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable`类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass为对应的类的 Class对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。

1.blockHandler / blockHandlerClass的使用

该属性用于BlockException异常的处理,即服务限流、降级相关的异常。

当出现这些异常的时候会使用指定的异常处理方法进行处理。配置如下(为了防止代码膨胀,将处理方法置于其他专门的类中):

controller:

    @GetMapping("/byResource/globalHandler")@SentinelResource(value = "globalHandler" ,  // 资源名称blockHandlerClass = CustomerBlockHandler.class,   // 指定异常处理所在类的classblockHandler = "globalHandler1")      // 异常处理方法名称public CommonResult globalHandler(){return new CommonResult(200,"request /byResource/globalHandler success");}

异常处理类:

// 异常处理类
public class CustomerBlockHandler {// 异常处理方法1public static CommonResult globalHandler1(BlockException e){return new CommonResult(444, "error by globalHandler1");}// 异常处理方法2public static CommonResult globalHandler2(BlockException e){return new CommonResult(444, "error by globalHandler2");}// 异常处理方法3public static CommonResult globalHandler3(BlockException e){return new CommonResult(444, "error by globalHandler3");}
}

注意:异常处理方法必须是public static修饰的,否则无法解析

因此,当对资源"globalHandler"限流的时候。超过阈值时就会进入"globalHandler1"方法并返回。

2.fallback / fallbackClass的使用

fallback用于处理程序运行时抛出的异常,测试如下:

@RestController
public class ConsumerController {@Autowiredprivate RestTemplate restTemplate;@Value("${provider.service-url}")private String providerName;// 调用微服务查询结果@GetMapping("/consumer/get/payment/{id}")// fallback可以处理运行(业务)的异常// @SentinelResource(value = "fallback",fallback = "fallback_method")// blockHandler不能处理运行异常,只能处理sentinel的配置异常// @SentinelResource(value = "fallback",blockHandler = "blockHandle_method")// 同时可以处理sentinel配置异常,也可以处理业务异常@SentinelResource(value = "fallback",fallback = "fallback_method",blockHandler = "blockHandle_method")public CommonResult getPayment(@PathVariable(value = "id")Long id){CommonResult res = restTemplate.getForObject("http://" + providerName + "/get/payment/" + id, CommonResult.class);Object data = res.getData();if(data != null){return res;}else{throw new NullPointerException("用户不存在");}}// 再次强调:fallback函数和blockHandle函数的格式必须严格遵守官网要求public static CommonResult fallback_method(@PathVariable(value = "id")Long id,Throwable e){return new CommonResult(444,"请求失败:id为:" + id + " 的用户不存在 ");}// 再次强调:fallback函数和blockHandle函数的格式必须严格遵守官网要求public static CommonResult blockHandle_method(@PathVariable(value = "id")Long id, BlockException e){return new CommonResult(444,"请求失败:被限流限流了");}}

上述代码对单独使用"fallback",单独使用"blockHandler",同时使用二者进行了测试,结果为:

  • 单独使用fallback,只能处理查询运行时抛出的异常
  • 单独使用blockHandler,只能处理sentinel配置后违规的异常
  • 同时使用,则所有异常都能处理,且当同时又两种异常发生时,会优先使用blockHandler的异常处理。

对于fallback,也可以将异常处理方法置于一个单独的类中,操作与1.一样。

3.defaultFallback

即:使用了@SentinelResource注解后,不添加fallback,则使用默认的方法;操作同上。

4.exceptionsToIgnore

忽略异常,即:当抛出的异常为指定的以场数时,不再做处理,将按照原样抛出;指定的参数为.class

七、服务熔断

Sentinel+OpenFeign实现服务熔断

  • POM添加依赖

                <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
    
  • 修改yml配置

    # 开启sentinel与feign的结合
    feign:sentinel:enabled: true
    
  • 主启动类添加注解@EnableFeignClients

  • 添加OpenFeign接口和实现

    接口:

    @Component
    @FeignClient(value = "payment-alibaba-provider", fallback = FeignServiceImpl.class)
    public interface FeignService {@GetMapping("/get/payment/{id}")public CommonResult getNacos(@PathVariable(value = "id")int id);
    }
    

    实现:

    @Component
    public class FeignServiceImpl implements FeignService {@Overridepublic CommonResult getNacos(int id) {return new CommonResult(444,"请求失败:id为:" + id + " 的用户不存在 ");}
    }
    
  • Controller

        @Autowiredprivate FeignService feignService;@GetMapping("/consumer/get/byFeign/{id}")public CommonResult getByFeign(@PathVariable(value = "id")int id){return feignService.getNacos(id);}
    
  • 测试:在断开服务后,consumer访问服务将会被openFeign的实现类中的方法处理。

八、Sentinel规则持久化

Seata(处理分布式事务)

分布式事务问题:

微服务的开发需要用到多个数据库保存不同的数据信息,也有许多不同的微服务调用,所以就需要事务来控制数据的一致性,所有需要使用到分布式事务。

一、Seata的基本概念

官网:https://seata.io/zh-cn/

Seata有3个基本组成部分:

  • **事务协调器(TC):**维护全局事务和分支事务的状态,驱动全局提交或回滚。
  • **事务管理器TM:**定义全局事务的范围:开始全局事务,提交或回滚全局事务。
  • **资源管理器(RM):**管理分支事务正在处理的资源,与TC进行对话以注册分支事务并报告分支事务的状态,并驱动分支事务的提交或回滚。

seata由一个XID+三个管理器组成

Seata管理的分布式事务的典型生命周期:

  1. TM要求TC开始一项新的全局事务。TC生成代表全局事务的XID。
  2. XID通过微服务的调用链传播。
  3. RM将本地事务注册为XID到TC的相应全局事务的分支。
  4. TM要求TC提交或回退相应的XID全局事务。
  5. TC驱动XID对应的全局事务下的所有分支事务,以完成分支的提交或回滚。

二、seata的配置与启动

下载地址:https://github.com/seata/seata/releases/tag/v1.4.2

1.配置文件修改

  • seata\seata-server-1.4.2\conf\file.conf

    1.  修改数据库
    mode = "db"2.  设置数据库连接
    driverClassName = "com.mysql.cj.jdbc.Driver"url = "jdbc:mysql://localhost:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC&useSSL=false"user = "root"password = "root"3.   添加设置,以下代码原来的文件中没有,需添加,指定事务组;重点关注"admin-tx_group"的值
    service {#transaction service group mappingvgroupMapping.my_test_tx_group = "admin-tx_group"#only support when registry.type=file, please don't set multiple addressesdefault.grouplist = "127.0.0.1:8091"#degrade, current not supportenableDegrade = false#disable seatadisableGlobalTransaction = false
    }
    

    这里需要注意驱动的使用:本次使用的版本是1.4.2,在lib目录下由jdbc目录,将里面的8.0的驱动拖到lib目录下,并按照上述配置修改即可(不这样操作会导致无法连接数据库而启动失败)

  • seata\seata-server-1.4.2\conf\registry.conf

    1.registry.type=nacos
    2.registry.nacos.serverAddr="localhost:8848" #此处为nacos地址端口
    
  • 创建数据库

    创建数据库,名称为上述配置中的名称(任意)。

    创建表:sql脚本地址:https://github.com/seata/seata/tree/develop/script/server/db

    创建完成后:

  • 测试:

    1.启动nacos

    2.启动seata

    3.注册成功

三、案例准备

  • 如图:当用户下单时,会在订单服务(order)创建一个订单,然后通过远程调用库存服务器来扣除库存;

  • 再通过远程调用账户服务来扣除用户账户余额;

  • 最后再订单服务中修改订单的状态为已完成。

1.创建数据库

  • order

    create table t_order(id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,user_id BIGINT(11) DEFAULT NULL,product_id BIGINT(11) DEFAULT NULL,count INT(11) DEFAULT NULL,money DECIMAL(11,0) DEFAULT NULL,status INT(1) DEFAULT NULL COMMENT '0:订单创建中 1:已完结'
    )ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;-- 日志表
    CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
  • storage

    create table t_storage(id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,product_id BIGINT(11) DEFAULT NULL,total INT(11) DEFAULT NULL,used INT(11) DEFAULT NULL COMMENT '已用库存' ,residue INT(11) DEFAULT NULL COMMENT '剩余'
    )ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;insert into seata_storage.t_storage(id,product_id,total,used,residue) values (1,1,100,0,100);select * from seata_storage.t_storage-- 日志表
    CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    
  • account

    create table t_account(id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,user_id BIGINT(11) DEFAULT NULL,total DECIMAL(11) DEFAULT NULL COMMENT '总额度度',used DECIMAL(11) DEFAULT NULL COMMENT '已用额度' ,residue DECIMAL(11) DEFAULT NULL COMMENT '剩余可用额度'
    )ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;insert into t_account(id,user_id,total,used,residue) values (1,1,10000,0,10000);select * from t_account-- 日志表
    CREATE TABLE `undo_log` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`branch_id` bigint(20) NOT NULL,`xid` varchar(100) NOT NULL,`context` varchar(128) NOT NULL,`rollback_info` longblob NOT NULL,`log_status` int(11) NOT NULL,`log_created` datetime NOT NULL,`log_modified` datetime NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

    日志表创建sql脚本:https://github.com/seata/seata-samples/blob/master/ha/src/main/resources/sql/undo_log.sql

2.order-Module创建

1.添加pom

    <dependencies><!--feign组件--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--nacos组件--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--seata组件--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><!--需要排除该依赖,再另外添加--><exclusions><exclusion><groupId>io.seata</groupId><artifactId>seata-all</artifactId></exclusion></exclusions></dependency><dependency><groupId>io.seata</groupId><artifactId>seata-all</artifactId><version>1.4.2</version></dependency><!--spring boot 组件--><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><!--mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!--jdbc--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.5</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

2.创建yml

server:port: 8011spring:application:name: seata-order-servicecloud:alibaba:seata:tx-service-group: admin-tx_group   # 需要与seata的服务端设置的事务组名称一致service:vgroup-mapping:admin-tx_group: default  #key与上面的tx-service-group的值对应nacos:discovery:server-addr: localhost:8848datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/seata_orderpassword: rootusername: rootfeign:hystrix:enabled: truemybatis:            # 整合mybatistype-aliases-package: com.zjj.cloud01.domian    # 别名所在包mapperLocations: classpath:mapper/*.xml

3.添加seata配置文件的conf

  • 直接将seata的file.conf和registry.conf放入resources目录下即可

  • cong.file

    # 添加如下配置信息,
    service {#transaction service group mappingvgroupMapping.admin-tx_group = "default"#only support when registry.type=file, please don't set multiple addressesdefault.grouplist = "127.0.0.1:8091"#degrade, current not supportenableDegrade = false#disable seatadisableGlobalTransaction = false
    }
    

    tips:这里需要注意,当前使用的1.4.2本版seata。在下载下来后,所提供的file.config文件中不存在”service“这个配置项,这个配置项主要用于定义事务组的名称。在使用时,服务端的seata定义了事务组的名称(详见本章"二"配置的详细内容),而客户端需要配置与其一致的事务组名称(如上添加的配置项)。如不配置,可能会出现提示:can not get cluster name in registry config ‘…’ please make sure registry config correct 的错误

  • registry.conf

    主要修改注册类型为nacos,修改地址和ip

4.创建domain包

  • 创建order的实体类:Order
  • 创建结果返回类:CommonResult

5.创建mapper

@Component
@Mapper
public interface OrderMapper {// 添加订单void createOrder(Order order);// 修改订单状态void updateOrderStatus(@Param(value = "userId")Long userId, @Param(value = "status")Integer status);}
<mapper namespace="com.zjj.cloud01.mapper.OrderMapper"><insert id="createOrder" parameterType="com.zjj.cloud01.domain.Order">insert into t_order values (null,#{userId},#{productId},#{count},#{money},0)</insert><update id="updateOrderStatus">update  t_order set status = 1 where user_id = #{userId} and status = #{status}</update></mapper>

6.创建service

  • OrderServiceImpl

    public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate AccountService accountService;@Autowiredprivate StorageService storageService;@Overridepublic void createOrder(Order order) {log.info("开始添加订单================");orderMapper.createOrder(order);log.info("开始扣减库存=================");storageService.decrease(order.getProductId(),order.getCount());log.info("扣减库存结束================");log.info("开始扣减余额==================");accountService.decrease(order.getUserId(),order.getMoney());log.info("扣减余额结束================");log.info("开始修改订单的状态=============");orderMapper.updateOrderStatus(order.getUserId(), 0);log.info("修改订单的状态结束================");log.info("添加订单结束================");}
    }
    
  • AccountService

    @FeignClient(value = "seata-account-service")
    public interface AccountService {@PostMapping("/account/decrease")CommonResult decrease(@RequestParam(value = "userId")Long userId, @RequestParam(value = "money")BigDecimal money);
    }
    
  • StorageService

    @FeignClient(value = "seata-storage-service")
    public interface StorageService {@PostMapping("/storage/decrease")CommonResult decrease(@RequestParam(value = "productId")Long productId, @RequestParam(value = "count")Integer count);
    }
    

7.创建controller

@RestController
public class OrderController {@Autowiredprivate OrderService orderService;@GetMapping("order/create")public CommonResult createOrder(Order order){orderService.createOrder(order);return new CommonResult(200,"添加订单成功~");}
}

8.创建主启动类

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) // 取消数据源的自动创建
@EnableDiscoveryClient
@EnableFeignClients
public class SeataOrderMain8011 {public static void main(String[] args) {SpringApplication.run(SeataOrderMain8011.class,args);}
}

9.创建config

  • MybatisConfig

    @Configuration
    @MapperScan({"com.zjj.cloud01.mapper.OrderMapper"})
    public class MyBatisConfig {}
  • DateSourceProxyConfig

    // 使用Seata对数据源管理
    @Configuration
    public class DateSourceProxyConfig {@Value("${mybatis.mapperLocations}")private String mapperLocations;@Bean@ConfigurationProperties(prefix = "spring.datasource")public DataSource druidDataSource(){return new DruidDataSource();}@Beanpublic DataSourceProxy dataSourceProxy(DataSource dataSource){return new DataSourceProxy(dataSource);}@Beanpublic SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSourceProxy);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());return sqlSessionFactoryBean.getObject();}
    }
    

3.storage-Module创建

步骤同上,只需要修改业务逻辑即可(修改库存操作)

4.account-创建

步骤同上,只需要修改业务逻辑即可(修改用户余额操作)

以上步骤全部完成后,测试:访问order,传入订单信息,将会相继调用微服务并修改数据库。

四、案例实践

当正常访问时,一切安好。

1.异常:

情况:当订单创建后(order表新建记录,状态为0),后面的任何一个环节出现故障,都会导致数据的不一致。

实例:修改AccountServiceImpl,为其设置超时(睡眠20s)

    @Overridepublic void updateAccount(Long userId, BigDecimal money) {log.info("扣款开始===========");try {Thread.sleep(20000);} catch (InterruptedException e) {e.printStackTrace();}accountMapper.updateAccount(userId,money);log.info("扣款完成===========");}

结果:当执行到account时,由于时间超时,所以在order端就会报错而不会执行下面的修改状态操作,最终导致用户余额减少,而订单状态处于未处理状态(数据不一致)。

2.解决

在OrderServiceImpl方法上添加@GlobalTransactional注解即可(在入口添加)

name属性值任取,rollbackFor表示何种情况回滚

 @Override@GlobalTransactional(name = "order-transaction", rollbackFor = Exception.class)public void createOrder(Order order) {log.info("开始添加订单================");orderMapper.createOrder(order);log.info("开始扣减库存=================");storageService.decrease(order.getProductId(),order.getCount());log.info("扣减库存结束================");log.info("开始扣减余额==================");accountService.decrease(order.getUserId(),order.getMoney());log.info("扣减余额结束================");log.info("开始修改订单的状态=============");orderMapper.updateOrderStatus(order.getUserId(), 0);log.info("修改订单的状态结束================");log.info("添加订单结束================");}

五、seata原理

ccountServiceImpl,为其设置超时(睡眠20s)

    @Overridepublic void updateAccount(Long userId, BigDecimal money) {log.info("扣款开始===========");try {Thread.sleep(20000);} catch (InterruptedException e) {e.printStackTrace();}accountMapper.updateAccount(userId,money);log.info("扣款完成===========");}

结果:当执行到account时,由于时间超时,所以在order端就会报错而不会执行下面的修改状态操作,最终导致用户余额减少,而订单状态处于未处理状态(数据不一致)。

[外链图片转存中…(img-FZGm2hHG-1620911972626)]

2.解决

在OrderServiceImpl方法上添加@GlobalTransactional注解即可(在入口添加)

name属性值任取,rollbackFor表示何种情况回滚

 @Override@GlobalTransactional(name = "order-transaction", rollbackFor = Exception.class)public void createOrder(Order order) {log.info("开始添加订单================");orderMapper.createOrder(order);log.info("开始扣减库存=================");storageService.decrease(order.getProductId(),order.getCount());log.info("扣减库存结束================");log.info("开始扣减余额==================");accountService.decrease(order.getUserId(),order.getMoney());log.info("扣减余额结束================");log.info("开始修改订单的状态=============");orderMapper.updateOrderStatus(order.getUserId(), 0);log.info("修改订单的状态结束================");log.info("添加订单结束================");}

springCloud-Alibaba相关推荐

  1. SpringCloud Alibaba微服务实战(七) - 路由网关(Gateway)全局过滤

    说在前面 全局过滤器作用于所有的路由,不需要单独配置,我们可以用它来实现很多统一化处理的业务需求,比如权限认证,IP 访问限制,监控,限流等等. 创建路由网关(Gateway)启动服务cloud-ac ...

  2. SpringCloud Alibaba微服务实战(五) - Sentinel实现限流熔断

    什么是Sentinel? 请查看文章:SpringCloud Alibaba微服务实战(一) - 基础环境搭建 构建服务消费者cloud-sentinel进行服务调用 服务创建请查看文章:Spring ...

  3. SpringCloud Alibaba微服务实战(四) - Nacos Config 配置中心

    说在前面 Nacos 是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现.配置管理和服务管理平台.Nacos Config就是一个类似于SpringCloud Config的配置中心. 一.启动N ...

  4. SpringCloud Alibaba微服务实战(三) - Nacos服务创建消费者(Feign)

    什么是Feign Feign 是一个声明式的伪 Http 客户端,它使得写 Http 客户端变得更简单.使用 Feign,只需要创建一个接口并注解.它具有可插拔的注解特性,可使用 Feign 注解和 ...

  5. SpringCloud Alibaba微服务实战(二) - Nacos服务注册与restTemplate消费

    说在前面 基础环境搭建,理论,请看上一篇,在这就不扯理论了,直接上代码. 项目结构 代码实现 第一步:在父pom的项目中引入dependencyManagement 在引入父pom之前咱们先来回顾下d ...

  6. SpringCloud Alibaba微服务实战(一) - 基础环境搭建

    说在前面 Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案.此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来 ...

  7. 给老板解释解释,为什么要用SpringCloud alibaba作为微服务开发框架???

    什么是微服务 提到微服务不得不提Martin Fowler在2014年3月25日发表的文章 Microservices,里面给出了微服务的定义.后续国内所有关于微服务的介绍都是基于这篇文章的翻译,或加 ...

  8. SpringCloud Alibaba Sentinel 流量控制规则介绍与配置

    概述:流量控制(flow control),其原理是sentinel断路器通过监控应用服务调用的QPS或调用并发线程数来实现调用控制.当QPS或线程数达到配置的阈值时,进行响应的服务降级功能,从而到达 ...

  9. SpringCloud Alibaba Sentinel 项目基础环境搭建

    本示例项目采用在maven父工程SpringCloud下面创建子module(sentinel_order_service_rest)形式. 文件清单列表: 父工程pom.xml 子工程pom.xml ...

  10. 为什么要用SpringCloud alibaba作为微服务开发框架?

    作者 | 飘渺Jam      责编 | 欧阳姝黎 什么是微服务 提到微服务不得不提Martin Fowler在2014年3月25日发表的文章 Microservices,里面给出了微服务的定义.后续 ...

最新文章

  1. [收集] Web服务相关的, 介绍框架(framework)类的论文
  2. python第三方开发软件_python开发者的必备工具(一)
  3. 51nod 1092 回文字符串
  4. 计算机系统的层次结构是缺一不可的吗,第1章 计算机组成与结构绪论.ppt
  5. nginx 同一个IP上配置多个HTTPS主机
  6. PHP支付接口教程,详解微信支付(二)
  7. 【Python CheckiO 题解】Between Markers (simplified)
  8. C#中判断服务器图片是否存在
  9. 计算机考试策略,计算机等级考试应试的策略.doc
  10. jQuery调用或获取iframe中的方法或控件值
  11. Vue 中的计算属性,方法,监听器
  12. 如何深入学习Android系统
  13. 标准电阻阻值速查表及由来
  14. 计算机图形学概论论文5000字,关于计算机图形学探究的论文
  15. 软件设计模式概念与意义
  16. Metasploit Framework —— Exploit
  17. 办公族如何防治鼠标手?
  18. CentOS 可以ping通IP和域名,但打开火狐却上不了网
  19. e^x导数为什么是e^x
  20. SOHO中国高管建“老鼠仓”吸钱 大企成空壳谁之责?

热门文章

  1. 顶尖高校【中国科学院大学】公布3届毕业生深造及就业情况!
  2. 正负±5V(-5V到+5V)转0-3.3V简单电路实现(其他输入输出值只需稍微更改)
  3. 如何查看小方侦测云存储_杭州雄迈信息技术有限公司
  4. Perconadb加载tokudb引擎时的问题
  5. [分享]英语面试问答大全
  6. android 动画方式,Android Activity进出动画三种方法
  7. linux重启was控制台,was控制台打不开了 详细
  8. 来了!8M/S+速度,Pdown复活!
  9. javascript网页设计大作业: HTML期末学生大作业 基于HTML+CSS+JavaScript通用的后台管理系统ui框架模板
  10. UnityShader入门精要——消融效果