spring cloud下
六、服务网关
概述
- 服务网关进行日志、限流、权限、安全等等操作。
zuul
springcloud中集成的zuul版本,采用的是tomcat容器,使用的是传统的servlet io处理模型。
servlet是tomcat的核心组件之一,另外两个组件是监听器和过滤器。
servlet是由servlet container进行生命周期管理
servlet的生命周期:
- container启动时构造servlet对象并调用**servlet init()**进行初始化
- container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程),然后调用service()
- container关闭时调用**servlet destroy()**消费servlet。
servlet是一个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程,在并发不高的场景下,这种模型是适用的,但是一旦高并发,线程数量就会上涨,而线程资源代价是昂贵的(上下文切换造成内存消耗大),严重影响请求的处理时间。在一些简单业务场景下,不希望为每个请求分配一个线程,只需要1个或几个线程就能应对极大并发请求,这种业务场景下servlet模型没有优势。
所以Zuul 1.X是基于servlet的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet(DispatcherServlet),是阻塞式处理模型。
传统的Web框架,比如说strust2,springmvc等都是基于Servlet API与servlet容器基础之上运行的。
在servlet3.1之后有了异步非阻塞的支持,而webflux是一个典型异步非阻塞的框架,他的核心是基于Reactor的相关API实现的。
Spring WebFlux是spring5.0引入的新的响应式框架,区别于Spring MVC,它不需要依赖于Servlet API,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。
gateway
概述
springcloud gateway是springcloud生态系统中的网关,目标是替代zuul。
springcloud gateway是基于WebFlux框架实现的,WebFlux框架是非阻塞式的web框架,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Springcloud gateway的目标是提供统一的路由方式,且基于Filter链的方式提供了网关基本的功能,如安全、监控和限流。
可以理解为网关是所有微服务的入口。
gateway是基于异步非阻塞模型开发的。
特性
- 动态路由:能够匹配任何请求属性
- 可以对路由指定Predicate(断言)和Filter(过滤器)
- 集成Hystrix的断路器功能
- 集成Springcloud服务发现功能
- 请求限流功能
- 支持路径重写
三大核心概念
路由(Route)
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
断言(Predicate)
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
predicate就可以理解为匹配条件,定位到真正的服务节点。
Springcloud gateway包括许多内置的RoutePredicateFactories,所有这些Predicate都与Http请求的不同属性匹配,多个Predicate工厂可以进行组合。
过滤器(Filter)
使用过滤器,可以在请求被路由前或者后对请求进行修改。
gateway工作流程
客户端向spring cloud gateway发出请求,然后在gateway handler mapping中找到与请求相匹配的路由,将其发送到gateway web handler。
handler再通过指定的过滤器链将请求发送到我们实际的服务执行业务逻辑。
过滤器可能会在发送代理请求之前或之后执行过滤器逻辑。
“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等。
“post”类型的过滤器中可以做响应内容、响应头的修改,日志输出,流量监控等。
网关配置
在application.yml中配置
建module
修改pom
要注意网关作为一种微服务,也要注册进注册中心
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--引入公共实体类项目的jar包坐标--> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!--热部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </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>
注意,gateway网关微服务,不要引入以下jar包,不需要
<!--子类没有写版本号的,都继承了父类的版本号--> <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>
不然启动会报错
修改application.yml
server: port: 9527 spring: application: name: cloud-gateway eureka: instance: hostname: cloud-gateway-service client: ## 表示是否将自己注册进eureka server register-with-eureka: true ## 表示是否从eureka server抓取已有的注册信息,默认为true ## 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetchRegistry: true service-url: defaultZone: http://eureka7001.com:7001/eureka
主启动类
@SpringBootApplication @EnableEurekaClient public class GatewayMain9527 { public static void main(String[] args) { SpringApplication.run(GatewayMain9527.class, args); } }
配置好网关之后,如何做路由映射?
修改yml配置文件如下:
spring: application: name: cloud-gateway cloud: gateway: routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:8001 #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - id: payment_routh2 uri: http://localhost:8001 predicates: - Path=/payment/lb/**
做了以上配置,9527就挡在了8001的前面。
添加网关前:http://localhost:8001/payment/get/31
添加网关后:http://localhost:9527/payment/get/31
添加网关后,就通过网关作为微服务的入口,调用微服务。
代码中注入RouteLocator的Bean
添加配置类GateWayConfig
@Configuration public class GateWayConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes(); // 访问9527服务的path为guonei,则会转发到"http://news.baidu.com/guonei"这个url routes.route("path_route_atguigu1", r -> r.path("/guonei") .uri("http://news.baidu.com/guonei")).build(); return routes.build(); } }
动态路由
以前服务消费者直接调用服务提供者,我们通过Ribbon + RestTemplate来进行服务调用并实现负载均衡。或者通过OpenFeign来进行服务调用,Feign内置了Ribbon。
现在在服务提供者前面,有一个网关,我们通过网关,来进行服务调用,通过网关来作为微服务调用的入口,而网关的端口一般是9527,所以服务消费者访问的是9527端口,那么需要通过网关来做负载均衡。
服务的提供者,也就是微服务可以部署在多台服务器上,避免单点故障,都是分布式的部署方式。我们只需要找网关,由网关来做负载均衡,消费者只认挡在微服务前面的9527。
通过微服务名实现动态路由。
默认情况下GateWay会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。
修改gateway9527微服务的application.yml如下
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true #开启从注册中心获取注册服务列表,根据服务名创建动态路由的功能,利用微服务名进行路由 routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 ## uri: http://localhost:8001 #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - id: payment_routh2 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 ## uri: http://localhost:8001 predicates: - Path=/payment/lb/** eureka: instance: hostname: cloud-gateway-service client: ## 表示是否将自己注册进eureka server register-with-eureka: true ## 表示是否从eureka server抓取已有的注册信息,默认为true ## 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetchRegistry: true service-url: defaultZone: http://eureka7001.com:7001/eureka
把以前写死的uri地址,换成
lb
+“微服务名称”uri: lb://cloud-payment-service
测试
Predicate
Springcloud gateway包括许多内置的RoutePredicateFactories,所有这些Predicate都与Http请求的不同属性匹配,多个Predicate工厂可以进行组合。
可以把Route Predicate理解为路由匹配。多个路由匹配相组合。不同的路由匹配对应于Http请求的不同属性。
Springcloud gateway创建动态路由时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route。
Filter
概述
过滤器可用于修改http请求和返回的http响应。
Springcloud gateway内置了多种路由过滤器对象,他们都由GatewayFilter工厂来生产。
使用方式:
和predicate路由匹配一样,在yml配置文件中进行修改
spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true #开启从注册中心获取注册服务列表,根据服务名动态创建路由的功能,利用微服务名进行路由 routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 ## uri: http://localhost:8001 #匹配后提供服务的路由地址 filter: - AddRequestParameter=X-Request-Id,1024 #过滤器会在匹配的请求头加上一个键值对参数,名称为X-Request-Id,值为1024 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由
自定义过滤器
实际工作中,用自定义过滤器多一些。
自定义全局GlobalFilter,实现两个接口--GlobalFilter,Ordered。
@Component @Slf4j public class MyLogGatewayFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("********come in MyLogGatewayFilter:" + new Date()); String uname = exchange.getRequest().getQueryParams().getFirst("uname"); if (uname == null) { log.info("****用户名为null,非法用户"); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); return exchange.getResponse().setComplete(); } //通过之后,去下一个过滤器 return chain.filter(exchange); } @Override public int getOrder() { return 0; } }
exchange当作是request、response。
chain是过滤器链
如果request不带uname参数,那么就是非法用户。
2022-04-13 17:31:52.504 INFO 2180 --- [ctor-http-nio-2] c.a.s.filter.MyLogGatewayFilter : ****用户名为null,非法用户
注意:因为这是get请求,所以在浏览器的地址栏中能直接这样写,能直接通过浏览器地址栏去发送请求,那么是get请求,post请求不能通过这种方式去发送。
七、服务配置
概述
alibaba的nacos能做服务注册中心、服务配置、服务总线,所以到后面统一整理。
在实际生产中有很多微服务,我们要将他们的配置进行统一管理,因为可能他们用到的数据库、注册中心等配置是一样的,所以要统一管理,一处修改,处处生效。
实际开发中,有生产环境、测试环境、开发环境。
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息,所以一套集中式的、动态的配置管理设施是必不可少的。
问题:每一个微服务自己带着一个application.yml,实际开发中存在上百个文件的配置管理。
Springcloud提供了Config Server来解决这个问题。
Config Server
概述
springcloud config为微服务架构中的微服务提供集中化的外部配置支持,config server为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
公有的配置放在config server里,私有的配置,各微服务自己配置。
springcloud config分为服务端和客户端两部分。
服务端也就是分布式配置中心,它是一个独立的微服务应用,为客户端提供配置信息。
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。
配置中心默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便地管理和访问配置内容。
作用
- 集中管理配置文件
- 不同环境不同配置,比如dev/test/prod/beta/release
- 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息。
- 当配置发生变动时,服务不再需要重启即可感知到配置的变化并应用新的配置。
- 将配置信息以REST接口的形式暴露。
使用springcloud config
配置中心(服务端)
新建一个本地仓库项目,取名为springcloud-config,和git远程仓库相连,用来存储配置。
在此项目中,修改配置,并推送至远程仓库,实现配置的版本控制。
新建module:cloud-config-center-3344
修改pom文件
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</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-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </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>
修改application.yml
server: port: 3344 spring: application: name: cloud-config-center #注册进eureka注册中心的服务名 cloud: config: server: git: uri: https://github.com/shaileneF/springcloud-config.git ##搜索目录 search-paths: - springcloud-config username: xxx password: xxx #### 读取分支 label: main eureka: client: service-url: defaultZone: http://localhost:7001/eureka
主启动类
@SpringBootApplication @EnableConfigServer public class MainAppConfigCenter3344 { public static void main(String[] args) { SpringApplication.run(MainAppConfigCenter3344.class, args); } }
客户端
新建module
修改pom
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</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-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </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>
新建bootstrap.yml
application.yml是用户级的资源配置项
bootstrap.yml是系统级别的,优先级更高
boostrap属于高优先级,相当于客户端微服务通过bootstrap.xml读取共同的外部配置文件,application.yml则是自己私有的配置文件。
server: port: 3355 spring: application: name: config-client #注册进eureka注册中心的服务名 cloud: config: #### 读取分支 label: main #分支名称 name: config #配置文件名称 profile: dev #读取后缀名称 uri: http://localhost:3344 #配置中心地址 eureka: client: service-url: defaultZone: http://localhost:7001/eureka
主启动类
@SpringBootApplication @EnableEurekaClient public class ConfigClientMain3355 { public static void main(String[] args) { SpringApplication.run(ConfigClientMain3355.class, args); } }
业务类
@RestController public class ConfigClientController { @Value("${config.info}") private String configInfo; @GetMapping("/configInfo") public String getConfigInfo() { return configInfo; } }
测试
问题
- 修改github上的配置文件内容,config server配置中心立刻响应,config client客户端没有任何响应,难道每次修改github上的配置文件,客户端都要重启?
解决
修改config client
修改pom文件
添加以下两个依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--监控--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
修改bootstrap.yml文件,暴露监控端点
management: endpoints: web: exposure: include: "*"
Controller类添加此注解
@RefreshScope
运维发送post请求刷新config client
curl -X POST "http://localhost:3355/actuator/refresh"
八、服务总线
概述
什么是总线
在微服务架构的系统中,通常会使用轻量级的消息代理,来构建一个共用的消息主题,并让系统中所有微服务实例都**连接(订阅)**上来。由于该主题中产生的消息会被所有订阅的微服务实例监听和消费,所以称它为消息总线。
ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus),当一个服务刷新数据的时候,它会把这个消息生产到topic中,这样其他订阅此topic的微服务就能得到通知,然后去更新自己的配置。
Spring cloud Bus
概述
bus其实就是消息中间件,目前支持RabbitMQ、kafka
是用来将分布式系统的节点(各个微服务)与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。
springcloud config配合springcloud bus可以实现配置的动态刷新。
springcloud bus能管理和传播分布式系统间的消息,可以当作微服务间的通信通道。
通过bus实现全局广播
再新建一个config客户端
用一个config center和两个config 客户端做测试,通过bus实现全局广播通知
pom文件、yml文件、主启动类和之前一样写就可以了,没有什么变化。
controller层的写法发生一点改变,添加了端口,因为此时要启动多个config client。
public class ConfigClientController { @Value("${config.info}") private String configInfo; @Value("${server.port}") private String serverPort; @GetMapping("/configInfo") public String getConfigInfo() { return "serverport:" + serverPort + "\t" + "configInfo:" + configInfo; } }
设计思想
- 利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
- 利用消息总线触发一个服务端ConfigServer的/bus/refresh,而刷新所有客户端的配置。
第二点的架构显然更加合理。
第一点不合适的原因如下:
- 打破了微服务的职责单一性,客户端本来就是干活的,是业务模块,它本不应该承担配置刷新的职责。
- 破坏了微服务各节点的对等性
- 有一定的局限性, 例如微服务在迁移时,它的网络地址通常会发生变化,如果想通过客户端来做到自动刷新,那就会增加更多的修改,这样不合理。
我们用的是第二种设计思想,由总控,由configServer来通知、来广播其他config client。
设计实现
cloud-config-center-3344配置中心服务端
给cloud-config-center-3344配置中心服务端添加消息总线支持
pom文件引入新的依赖
<!--添加消息总线rabbitmq支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
yml文件
## rabbitmq相关配置 rabbitmq: host: 阿里云 port: 5672 username: admin password: 123 #暴露bus刷新配置的端点 management: endpoints: web: exposure: include: 'bus-refresh'
config client
pom文件
<!--添加消息总线rabbitmq支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
yml
## rabbitmq相关配置 rabbitmq: host: 42.192.182.4 port: 5672 username: admin password: 123
测试
修改配置
执行
curl -X POST "http://localhost:3344/actuator/bus-refresh"
实现一次修改,一次发送,处处生效。
注意发送这个post请求,是对config server,对配置中心发送,不是对客户端微服务。
所以只需要发送一次,只刷一个config server,所有config client都被广播到,所有config client的配置都更新了。
效果
执行了上面那条指令之后,也就是刷新了3344config server的配置之后,不用重启项目,不用重启config server,也不用重启两个config client,分别刷新以下三个浏览器界面,可以看到配置更新
我们是通过rabbitmq来实现广播通知的,原理就是客户端监听mq中的这个队列
动态刷新定点通知
比如只通知3355,不通知3366
指令
curl -X POST "http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}"
/bus/refresh请求不再发送到具体的服务实例上,而是发送给config server,并通过destination参数类指定需要更新配置的服务
destination:
spring.application.name:端口号
九、消息驱动
spring cloud stream
为什么引入stream?
在一个完整的项目中,Java后端使用的mq和大数据部分使用的mq可能不是同一个mq,可能存在两种mq,那么就涉及到mq的切换、维护等。
引入stream,让我们不再关注具体MQ的细节,我们只需要用一种适配绑定的方式,自动在各种MQ内切换。
stream屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型。
应用程序通过inputs或者outputs来与spring cloud stream中的binder对象交互
通过我们配置来binding,而spring cloud stream的binder对象负责与消息中间件交互
设计思想
比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同
像rabbitmq有exchange,kafka有topic和partitions分区的概念
现在用stream,来实现沟通,屏蔽差异,通过destination binder。
如果我们先用了一种消息队列,后面的业务需求想往另一种消息队列进行迁移,那么这是灾难性的,因为原先的消息队列和我们的系统是耦合的,这时候springcloud stream给我们提供了一种解耦合的方式。
在没有绑定器这个概念的情况下,我们的springboot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,他们的实现细节会有较大的差异,通过定义绑定器binder作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。
通过向应用程序暴露统一的Channel通道,使得应用程序不再需要考虑各种不同的消息中间件实现
通过定义绑定器binder作为中间层,实现了应用程序与消息中间件细节的隔离。
相当于消息队列中间件被绑定器挡在后面。
应用程序只需要和绑定器做交互。
stream中的消息通信方式遵循了发布-订阅模式
标准流程套路及注解
binding很方便的连接消息中间件,屏蔽消息中间件的差异
channel是信道,在消息通讯系统中就是实现存储和转发的媒介,通过channel对队列进行配置。
source和sink,简单地可以理解为输入输出,从stream发布消息就是输出,接收消息就是输入。
常用API和注解
案例说明
新建三个子module
- cloud-stream-rabbitmq-provider8801,作为生产者进行发消息模块
- cloud-stream-rabbitmq-consumer8802,作为消息接收模块
- cloud-stream-rabbitmq-consumer8803,作为消息接收模块
生产者
pom
<dependencies> <!--stream--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</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-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </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>
application.yml
server: port: 8801 spring: application: name: cloud-stream-provider rabbitmq: host: 阿里云 port: 5672 username: admin password: 123 virtual-host: / cloud: stream: binders: #在此处配置要绑定的rabbitmq的服务信息 defaultRabbit: #表示定义的名称,用于binding整合 type: rabbit ## 消息组件类型 bindings: ## 服务的绑定,整合 output: #表示这是服务的生产者 destination: studyExchange #表示要使用的Exchange名称 content-type: application/json #设置消息类型,本次为json,文本则设置“text/plain” binder: defaultRabbit eureka: client: service-url: defaultZone: http://localhost:7001/eureka instance: lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30秒) lease-expiration-duration-in-seconds: 5 instance-id: send-8801.com #在信息列表显示主机名称 prefer-ip-address: true #访问的路径变为ip地址
主启动类
@SpringBootApplication public class StreamMQMain8801 { public static void main(String[] args) { SpringApplication.run(StreamMQMain8801.class, args); } }
业务类
发送消息接口
public interface IMessageProvider { String send(); }
发送消息接口实现类
@EnableBinding(Source.class) //通过此注解定义消息生产者的发送管道(是源,source),channel和exchange绑定 public class IMessageProviderImpl implements IMessageProvider { @Resource private MessageChannel output; @Override public String send() { String serial = UUID.randomUUID().toString(); output.send(MessageBuilder.withPayload(serial).build()); System.out.println("***serial: " + serial); return null; } }
Controller
@RestController public class SendMessageController { @Resource private IMessageProvider iMessageProvider; @GetMapping(value = "/sendMessage") public String sendMessage() { return iMessageProvider.send(); } }
消费者
pom文件和生产者一样
application.yml
server: port: 8802 spring: application: name: cloud-stream-consumer rabbitmq: host: 阿里云 port: 5672 username: admin password: 123 virtual-host: / cloud: stream: binders: #在此处配置要绑定的rabbitmq的服务信息 defaultRabbit: #表示定义的名称,用于binding整合 type: rabbit ## 消息组件类型 bindings: ## 服务的绑定,整合 input: #表示这是服务的消费者 destination: studyExchange #表示要使用的Exchange名称 content-type: application/json #设置消息类型,本次为json,文本则设置“text/plain” binder: defaultRabbit eureka: client: service-url: defaultZone: http://localhost:7001/eureka instance: lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30秒) lease-expiration-duration-in-seconds: 5 instance-id: receive-8801.com #在信息列表显示主机名称 prefer-ip-address: true #访问的路径变为ip地址
主启动类
@SpringBootApplication public class StreamMQMain8802 { public static void main(String[] args) { SpringApplication.run(StreamMQMain8802.class, args); } }
消费者业务类
由于这是微服务,这是消费者,是消费消息的,就不写什么service业务层了,只写controller层来测试一下(但是要注意在实际开发中,一个微服务可能既是服务提供者,也是服务消费者,同时针对于消息队列,一个微服务可能既是消息生产者,也是消息的消费者。)
@RestController @Slf4j @EnableBinding(Sink.class) public class ReceiveMessageListenerController { @Value("${server.port}") private String serverPort; @StreamListener(Sink.INPUT) public void input(Message<String> message) { System.out.println("消费者1,----->接收到的消息 " + message.getPayload() + "\t" + "serverPort: " + serverPort); } }
要注意仍然用到@EnableBinding这个注解,只不过括号里的是消费端的Sink.class
总结:
消费者端用以下两个注解:
- @EnableBinding(Sink.class)
- @StreamListener(Sink.INPUT)
生产者用以下注解:
- @EnableBinding(Source.class)
分组消费
再写一个消费者module,端口号8803
运行后有两个问题:
重复消费
生产者发送消息,目前是8802和8803都收到了,存在重复消费问题。
如果一个订单同时被两个消费者获取到,那么会造成数据错误,因为消费者消费到消息,那么是要对消息做处理的,就会有两个消费者对同一条消息进行处理,所以要避免这个问题
导致原因:不同消费者微服务(应用程序)默认分组是不同的。
消息持久化问题
解决重复消费问题:
用stream中的消息分组来解决。
注意Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。
不同组是全面消费的(重复消费)
如果没有手动做分组的话,默认每一个消费者微服务(应用程序)的组都不一样,不同的组都要把消息消费一次,导致了重复消费。
现在自定义配置分组,自定义配置分为同一个组,解决重复消费问题
自定义配置分组
原理:微服务应用放置于同一个group中,就能够保证消息只被组中其中一个消费者消费一次。
不同的组是可以重复消费的,但是同一个组内会发生竞争关系,只有其中一个可以消费。
自定义配置分组就是在yml文件中配置,如下:
bindings: ## 服务的绑定,整合 input: #表示这是服务的消费者 destination: studyExchange #表示要使用的Exchange名称 content-type: application/json #设置消息类型,本次为json,文本则设置“text/plain” binder: defaultRabbit group: atguiguB
现在要实现每次只有一个消费者消费消息,8801模块发送的消息只能被8802或者8803其中一个接收到,这样就避免了重复消费。
那么把两个消费者配置成相同组。
把两个消费者都配置成
atguiguB
组效果如下:
8801发送6条消息
***serial: c5f4370d-a3f0-4303-93fd-6c3dee0e4df1 ***serial: a2d90834-4b5a-4219-8588-4e64dabee641 ***serial: afe5fb00-c5a7-4ee7-9e7f-d02fa801886f ***serial: b4450607-12ff-4323-a516-4f70b6749ea7 ***serial: 6a3d5224-6871-4ca9-ba3e-4c2daddd0583 ***serial: 3e891ece-bcc2-4931-a4b9-31f8fb59b6b8
8802和8803两个消费者微服务轮询接收消息
8802
消费者1,----->接收到的消息 c5f4370d-a3f0-4303-93fd-6c3dee0e4df1 serverPort: 8802 消费者1,----->接收到的消息 afe5fb00-c5a7-4ee7-9e7f-d02fa801886f serverPort: 8802 消费者1,----->接收到的消息 6a3d5224-6871-4ca9-ba3e-4c2daddd0583 serverPort: 8802
8803
消费者2,----->接收到的消息 a2d90834-4b5a-4219-8588-4e64dabee641 serverPort: 8803 消费者2,----->接收到的消息 b4450607-12ff-4323-a516-4f70b6749ea7 serverPort: 8803 消费者2,----->接收到的消息 3e891ece-bcc2-4931-a4b9-31f8fb59b6b8 serverPort: 8803
持久化
消息持久化问题:
停止8802消费者和8803消费者
8802消费者的分组被去掉,8803的分组不去掉,那么现在8802消费者是没有持久化的,但是8803消费者是持久化。
此时8801发送4条消息到rabbitmq
先启动8802,无分组属性配置,后台没有打出来消息
再启动8803,有分组属性配置,后台打出来了MQ上的消息。
group属性在避免消费者重复消费,和消息持久化,避免消息丢失上有着重要的作用。
Spring Cloud Alibaba
spring-cloud-alibaba/README-zh.md at 2.2.x · alibaba/spring-cloud-alibaba (github.com)
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
- 大致作用:
- 服务限流降级
- 服务注册与发现---eureka、zookeeper、consul等的作用
- 分布式配置管理
- 消息驱动能力
- 阿里云对象存储
- 分布式任务调度
十、Nacos
概述
nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台
nacos就是注册中心 + 配置中心的组合。
替代eureka做注册中心,替代spring cloud config做配置中心
nacos做注册中心
服务提供者
建module
改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-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </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>
application.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 PaymentMain9001 { public static void main(String[] args) { SpringApplication.run(PaymentMain9001.class, args); } }
业务类
@RestController public class PaymentController { @Value("${server.port}") private String port; @GetMapping(value = "/payment/nacos/{id}") public String getPayment(@PathVariable Integer id) { return "nacos registry, serverPort: " + port + "\t" + "id: " + id; } }
服务消费者
同样的方式建module、改pom,写application.yml,主启动类,以及业务类,进行服务的远程调用。
服务的远程调用可以通过openFeign,知道服务名,从注册中心进行服务发现,进而进行服务调用。
可以通过Ribbon+RestTemplate
为什么nacos自带负载均衡?
nacos集成了netflix的ribbon,ribbon是支持负载均衡的。
pom文件和服务提供者一样
application.yml
server: port: 83 spring: application: name: nacos-order-consumer cloud: nacos: discovery: server-addr: localhost:8848 #消费者将要去访问的微服务提供者的服务名称(注册进nacos的服务提供者) service-url: nacos-user-service: http://nacos-payment-provider
主启动类
@SpringBootApplication @EnableDiscoveryClient public class OrderNacosMain83 { public static void main(String[] args) { SpringApplication.run(OrderNacosMain83.class, args); } }
因为nacos自带ribbon,所以我们要写RestTemplate的配置类
@Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }
nacos做配置中心
概述
以前是通过springcloud config作为配置中心。
结合springcloud bus实现动态刷新配置,不用重启服务。
配置通过github来存储,并且实现配置的动态刷新。
现在是通过nacos来完成这一功能
基础配置
新建module
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>
配置文件要写bootstrap.yml和application.yml两个
nacos和springcloud-config一样,在项目初始化的时候,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动。
springboot中的配置文件加载是存在优先级的,bootstrap要高于application
相当于先有共性,后有个性
bootstrap.yml:
server: port: 3377 spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 config: server-addr: localhost:8848 file-extension: yaml
application.yml:
spring: profiles: active: dev #表示开发环境
Controller
@RestController @RefreshScope // 支持Nacos的动态刷新功能 public class ConfigClientController { @Value("${config.info}") private String configInfo; @GetMapping("/config/info") public String getConfigInfo() { return configInfo; } }
分类配置
nacos的namespace是可以用于区分部署环境的,group和dataID逻辑上区分两个目标对象。
比如说我们现在有三个环境:开发、测试和生产,我们就可以创建三个namespace,不同的namespace之间是隔离的。
一个namespace下面可以进行多个group分组。
group可以把不同的微服务划分到同一个分组里。
命名空间namespace:配置隔离
默认:public(保留空间),默认新增的所有配置都在public空间
开发、测试、生产:利用命名空间做环境隔离
要同时在bootstrap.yml中对namespace进行配置
每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置。
配置分组:
默认所有的配置集都属于:DEFAULT_GROUP
一个namespace下面可以进行多个group分组。
比如说某一个微服务,针对618、双11可以设置不同的分组。618、1111是同一个namespace下的多个group
分组配置如下:
通过namespace来区分配置环境
修改bootstrap.yml
spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 config: server-addr: localhost:8848 file-extension: yaml group: DEV_GROUP namespace: f0d42ed7-6cba-4fe0-be60-5e802b16886d
nacos集群和持久化配置
概述
nacos上一些重要的信息需要持久化,只是把信息配置进nacos里面,可能还不够,需要配置到数据库里,推荐的数据库是mysql,所以要进行linux的mysql安装配置
nacos在没有配置mysql数据之前,自带了一个内嵌式数据库。
内存中的东西,一断电就没有了,存入数据库,存入磁盘,才是持久化。
如果启动多个默认配置下的nacos节点,每个nacos都自带一份自己的小的嵌入式数据库derby,数据存储是存在一致性问题的,为了解决这个问题,nacos采用了集中式存储的方式来支持集群化部署,目前只支持mysql,保证了数据一致性。
我们需要使nacos不再使用它自带的嵌入式数据库,而应配置MySQL,使用MySQL。
配置
需要1个nginx,3个nacos,1个mysql
在linux服务器上安装mysql和nginx,这不是本章的重点,重点在于三者之间的配置,建立起他们之间的联系。
安装nacos linux版本。
mysql配置步骤
找到nacos的安装路径下的conf目录的nacos-mysql.sql文件
这是sql语句源文件,是在nacos中进行mysql配置的。
在linux服务器上的mysql,新建数据库nacos_config
执行命令
use nacos_config;
复制nacos-mysql.sql文件中的全部sql语句执行。
conf目录下的application.properties配置
spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://aliyun:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user=root db.password=mysql
linux服务器上,nacos集群配置cluster.conf(conf目录下)
先用
hostname -I
查询服务器ip地址[root@VM-0-2-centos conf]## hostname -I 172.17.0.2
配置
172.17.0.2:3333 172.17.0.2:4444 172.17.0.2:5555
经过以上两个步骤,配置好了nacos的集群,通过不同端口启动。
编辑nacos的启动脚本startup.sh,使它能够接受不同的启动端口
bin目录下有startup.sh
第一步
第二步
nginx配置
修改nginx配置文件
#gzip on; 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; #charset koi8-r; #access_log logs/host.access.log main; location / { ## root html; ## index index.html index.htm; proxy_pass http://cluster; }
测试通过nginx访问linux上的nacos集群
nacos的集群,三个nacos服务的端口分别是3333、4444、5555
但是现在不直接访问,通过nginx进行访问,nginx配置好的端口号是1111
分别启动三个nacos服务
./startup.sh -p 3333 ./startup.sh -p 4444 ./startup.sh -p 5555
启动nginx
./nginx -c /usr/local/nginx/conf/nginx.conf
访问
http://42.192.182.4:1111/nacos/#/login
发布配置
检查mysql
微服务进行注册
spring: application: name: nacos-payment-provider cloud: nacos: discovery: #server-addr: localhost:8848 #改成linux服务器上nginx的地址,通过nginx进行转发,服务器上有3个nacos服务,通过nginx做负载均衡 server-addr: 42.192.182.4:1111
Sentinel实现熔断与限流
概述
相当于hystrix的阿里版,做微服务,分布式系统的流量控制、熔断降级。
作用:
防止服务雪崩
做服务降级
服务熔断
服务限流
sentinel分为两个部分
- 核心库(Java客户端):不依赖任何框架、库,能够运行于所有的Java运行时环境,同时对Dubbo、springcloud等框架也有较好的支持。
- 控制台(Dashboard):基于springboot开发,打包后可以直接运行,不需要额外的tomcat等容器。
微服务集成sentinel
新建module
改pom
<dependencies> <!--nacos config 和 discovery--> <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> <!--sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--后续做持久化用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </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.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </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>
application.yml
server: port: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: ## nacos服务注册中心地址 server-addr: localhost:8848 sentinel: transport: #配置sentinel dashboard地址 dashboard: localhost:8080 ## 默认8719端口,假如被占用会自动从8719开始+1扫描,直至找到未被占用的端口 port: 8719 management: endpoints: web: exposure: include: '*'
流控规则
介绍
资源名:唯一名称,默认请求路径
针对来源:sentinel可以针对调用者进行限流,填写微服务名称,默认default(不区分来源)
阈值类型/单机阈值:
- QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流
- 线程数:当调用该API的线程数达到阈值的时候,进行限流。
是否集群:不需要集群
流控模式:
直接:api达到限流条件时,直接限流
关联:当关联的资源达到阈值时,限流自己
当与A关联的资源B达到阈值后,就限流A自己
链路:只记录链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)
流控效果:
快速失败:直接抛异常
warm up:
即预热/冷启动方式,当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮,通过冷启动,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
默认冷加载因子是3,即请求QPS从阈值/3开始,经预热时长逐渐升至设定的QPS阈值,相当于给系统一个预热缓冲的时间,请求限制阈值刚开始很低,经预热时长,才逐渐达到设定阈值。
排队等待:匀速排队,让请求匀速通过,阈值类型必须设置为QPS,否则无效。
这种方式主要用于处理间隔性突发的流量,例如消息队列。例如这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
熔断降级
RT:平均响应时间
时间窗口内通过的请求>=5,且平均响应时间均超过阈值,触发降级,接下来的时间窗口,对这个方法的调用,都自动地进行熔断。
窗口期过后,关闭断路器。
异常比例:
QPS >= 5,且异常比例(秒级统计),超过阈值时,触发降级(接下来的时间窗口);时间窗口结束后,关闭降级。
异常数
分钟统计,超过阈值,触发降级(接下来的时间窗口),时间窗口结束后,关闭降级。
sentinel的断路器是没有半开状态的。
sentinel熔断降级会在调用链路中某个资源出现不稳定状态(以上三种情况)时,对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源而导致级联错误。
当资源被降级后,在接下来的窗口时间内,对该资源的调用都自动熔断。
热点规则
何为热点?
热点就是经常访问的数据
之前的case,服务熔断降级后,或者服务限流了,都是用sentinel系统默认的提示,这个提示是可以自定义的,类似于hystrix兜底的方法,即fallback method
通过注解
从@HystrixCommand到@SentinelResource
注意:资源名,不带斜线,说明是配置的@SentinelResource注解的value值,限流信息是根据我们自定义的方法,即@SentinelResource注解的blockHandler的值,若带斜线,说明是根据url地址进行限流,限流信息是sentinel的默认信息
blockHandler相当于是@HystrixCommand注解的fallbackmethod
以上配置的意思是:1秒钟之内,请求次数超过1了,即QPS超过1,那么会执行blockHandler的自定义方法,相当于fallbackmethod,实现服务降级。
参数例外项
普通的配置就是,当带有某个参数的请求的QPS超过阈值,被限流,执行blockHandler的自定义方法
但是我们期望设定的参数是某个特殊值时,它的限流值和平时不一样。假如当p1的值是5时,阈值可以是200.
注意:
@SentinelResource
这个注解的blockHandler属性,请求会根据sentinel配置的流控规则,跳转到blockHandler的方法进行执行,相当于Hystrix的fallback method,但是当调用的方法内部出现了异常,并不会跳转到blockHandler的方法执行,而是会报异常,只会根据sentinel的配置进行跳转
如果是调用的方法内部出现了异常,又不想页面抛出异常,而是通过对应的方法进行处理,那么需要另外配置fallback属性。
兜底方案面临的问题
- 我们自定义的处理方法和业务代码耦合在一起
- 每个业务方法都添加一个兜底的,代码膨胀
- 全局统一的处理方法没有体现
解决上述问题
创建CustomBlockHandler类用于自定义限流处理逻辑,在这个类里统一处理跳转页面、服务降级的说明等等,来统一写限流的相关信息,将限流、降级与业务代码解耦
public class CustomBlockHandler { public static CommonResult handlerException(BlockException blockException) { return new CommonResult(4444, "按客户自定义,global handlerException---1"); } public static CommonResult handlerException2(BlockException blockException) { return new CommonResult(4444, "按客户自定义,global handlerException---2"); } }
@RestController public class RateLimitController { @GetMapping("/rateLimit/customBlockHandler") @SentinelResource(value = "customBlockHandler", blockHandlerClass = CustomBlockHandler.class, blockHandler = "handlerException2") public CommonResult customBlockHandler() { return new CommonResult(200, "按客户自定义", new Payment(2020L, "serial003")); } }
重要属性:
value:资源名称:必需项
blockHandler/blockHandlerClass:
blockHandler对应处理BlockException的函数名称,可选项。
blockHandler函数访问范围必须是public,返回类型需要与原业务方法相匹配,参数类型需要和原业务方法相匹配并且最后加一个额外的参数,类型为BlockException。blockHandler函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定blockHandlerClass为对应的类class对象,注意对应的函数必须为static函数,否则无法解析。
fallback:可选项,用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。
fallback函数签名和位置要求:
- 返回值类型必须和原业务方法返回值类型一致。
- 方法参数列表需要和原函数一致,或者可以额外多一个Throwable类型的参数用于接收对应的异常。
- fallback函数默认需要和原方法在同一个类中,若希望使用其他类的函数(使处理异常的代码和业务逻辑代码解耦合),则可以指定fallbackClass为对应的类的class对象,注意到对应的函数必须为staic函数,否则无法解析。
服务熔断功能
@SentinelResource的属性
- fallback管运行异常
- blockHandler管配置违规
若blockHandler和fallback都进行了配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑。
异常忽略