SpringCloud
SpringCloud组件预览
# 1. 快速入门
# 1.1 需求分析
现在有两个模块一个支付模块一个订单模块,需求为订单模块要去调用支付模块的接口完成支付
cloud-provider-payment8001
提供的接口
@Slf4j
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Autowired
private PaymentServcie paymentServcie;
@PostMapping("/create")
public Result<Payment> create(@RequestBody Payment payment) {
int i = paymentServcie.create(payment);
log.info("插入结果: " + i);
return i > 0 ? new Result<>(200, "插入成功") : new Result<>(400, "插入失败");
}
@GetMapping("/get/{id}")
public Result<Payment> getPaymentById(@PathVariable String id) {
Payment payment = paymentServcie.getPaymentById(id);
return payment != null ? new Result<>(200, "查询成功", payment) : new Result<>(400, "查询失败");
}
}
# 1.2 远程调用
订单模块中在容器中放入
RestTemplate
实例@Configuration public class ApplicationContextConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
订单模块
controller
使用restTemplate
进行调用@RestController @RequestMapping("/consumer") public class OrderController { private final static String PAYMENT_URL = "http://localhost:8001"; @Autowired private RestTemplate restTemplate; @PostMapping("/payment/create") public Result<Payment> create(@RequestBody Payment payment) { return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, Result.class); } @GetMapping("/payment/get/{id}") public Result<Payment> getPayment(@PathVariable String id) { return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, Result.class); } }
# 2. Eureka
Eureka
包含两个组件:Eureka Server
和Eureka Client
Eureak Server
提供服务注册服务,各个微服务节点通过配置启动后,会在EurekaServer
中进行注册,这样EurekaServer
中的服务注册表中存储所有可用的服务节点的信息,服务节点的信息可以在界面中直观看到。
Eureka Client
通过注册中心进行访问,它是一个java
客户端,用于简化Eureka Server
的交互,客户端同时也具备一个内置的、使用轮询round-robin
负载算法的负载均衡器。在应用启动后,将会向Eureka Server
发送心跳(默认周期为30秒)。如果Eureak Server
在多个心跳周期内没有接受到某个节点的心跳,Eureka Server
将会从服务注册表中把这个服务节点移除(默认90秒)。
# 2.1 Eureka Server配置
新建一个
maven
工程并引入如下依赖(目前工程名为cloud-eureka-server7001
)<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
application.yaml
配置server: port: 7001 eureka: instance: # eureka服务端实例名称 hostname: localhost client: # 不向注册中心注册自己 register-with-eureka: false # 表示自己是注册中心,不需要去检索服务 fetch-registry: false service-url: # 注册中心地址 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
启动类配置
@EnableEurekaServer @SpringBootApplication public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } }
# 2.2 Eureka Client配置
在需要注册进
Eureka Server
的项目中添加依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
并在启动类上添加注解
@EnableEurekaClient
配置文件中填写
Eureka Server
的地址eureka: client: # 注册EurekaServer register-with-eureka: true # 抓取已在EurekaServer的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetch-registry: true service-url: # 注册中心地址 defaultZone: http://localhost:7001/eureka/
# 2.2 Eureka 集群
修改
Eureka Server
配置文件server: port: 7001 eureka: instance: # eureka服务端实例名称 hostname: eureka7001.com client: # 不向注册中心注册自己 register-with-eureka: false # 表示自己是注册中心,不需要去检索服务 fetch-registry: false service-url: # 注册中心地址 defaultZone: http://eureka7002.com:7002/eureka/
server: port: 7002 eureka: instance: # eureka服务端实例名称 hostname: eureka7002.com client: # 不向注册中心注册自己 register-with-eureka: false # 表示自己是注册中心,不需要去检索服务 fetch-registry: false service-url: # 注册中心地址 defaultZone: http://eureka7001.com:7001/eureka/
修改
Eureka Client
配置文件eureka: client: # 注册EurekaServer register-with-eureka: true # 抓取已在EurekaServer的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetch-registry: true service-url: # 注册中心地址,多个注册中心以逗号隔开或者注册任一地址即可 defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/
cloud-consumer-order80
模块的restTemplate
开启负载均衡功能@Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
修改
controller
的默认远程调用地址为服务名称@RestController @RequestMapping("/consumer") public class OrderController { private final static String PAYMENT_URL = "http://COUD-PAYMENT-SERVICE"; @Autowired private RestTemplate restTemplate; @PostMapping("/payment/create") public Result<Payment> create(@RequestBody Payment payment) { return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, Result.class); } @GetMapping("/payment/get/{id}") public Result<Payment> getPayment(@PathVariable String id) { return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, Result.class); } }
(可选)修改
Eureka
显示的主机名称eureka: instance: # 实例名称 instance-id: payment8001 # 是否显示ip prefer-ip-address: true
(可选)获取
Eureka
上的服务信息所有的信息都封装在一个
DiscoveryClient
实例中@Autowired private DiscoveryClient discoveryClient;
启动类上需要添加注解
@EnableDiscoveryClient
# 2.3 Eureka保护模式
保护模式主(Eureka Server
独有)要用于一组客户端和Eureka Server
之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server
将会尝试保护其服务注册表中的信息,不再删除服务注册表中的信息,也就是不会注销任何服务
server
配置
eureka:
server:
# 关闭自我保护默认开启
enable-self-preservation: false
# 心跳包时间默认30s
eviction-interval-timer-in-ms: 2000
client
的配置
eureka:
instance:
# 客户端向服务端发送心跳的时间间隔:单位秒(默认30s)
lease-renewal-interval-in-seconds: 1
# Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认90s),超时将删除服务
lease-expiration-duration-in-seconds: 2
# 3. Zookeeper
安装,
Zookeeper Server
需要单独安装docker run --name zookeeper -d -e TZ="Asia/Shanghai" -p 2181:2181 -v ~/zookeeper:/data zookeeper:3.5.9
client
,在上述的例子中将Eureka
的依赖换成zookeeper
的依赖即可<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> </dependency>
启动类上需要添加注解
@EnableDiscoveryClient
配置文件
spring: application: name: cloud-provider-payment cloud: zookeeper: connect-string: 127.0.0.1:2181
远程调用的方式与[上述相同](#2.2 Eureka 集群)
# 4. Consul
# 4.1 安装
开发者方式启动
consul agent -dev
默认有一个
http://localhost:8500/
的访问地址
# 4.2 客户端
导入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency>
application.yaml
配置文件spring: application: name: consul-provider-payment cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
启动类需要添加注解
@EnableDiscoveryClient
远程调用的方式与[上述相同](#2.2 Eureka 集群)
# 5. Ribbon
Spring Cloud Ribbon
是Netflix Ribbon
提供的一套客户端负载均衡的工具
简单来说,Ribbon
是Netflix
发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon
客户端组件提供一系列完善的配置项如连接超时,重试等。简单来说,就是在配置文件中列出Load Balancer
后面所有的机器,Ribbon
会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon
自定义负载均衡算法
LB
负载均衡Load Balance
:简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA
(高可用)。常见的负载均衡有软件Nginx
,LVS
、硬件F5
等
Ribbon
本地负载均衡,在调用微服务接口的时候,会在注册中心上获取注册信息服务列表之后缓存到JVM
本地,从而在本地实现RPC
远程调用技术
eureka
已经自带了ribbon
的依赖,无需引入过多的依赖
# 5.1 负载均衡算法
ribbon
默认负载均衡算法为轮询,它自己提供了几种常用的负载均衡策略,也提供了相应的方法自定义负载均衡策略(注意所有针对负载均衡算法的改动都是针对调用端而言的)
常用策略
RoundRobinRule
:轮询Random
:随机RetryRule
:先按照RoundRobinRule
的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务WeightedResponseTimeRule
:对RoundRobinRule
的扩展,响应速度越快的实例选择权重越大,越容易被选择BestAvailableRule
:会先过滤掉由于多次访问故障而处于短路器跳闸状态的服务,然后选择一个并发量最小的服务AvailabilityFilteringRule
:先过滤掉故障实例,再选择并发较小的实例ZoneAvoidanceRule
:默认规则,复合判断server
所在区域的性能和server
的可用性选择服务器
# 5.2 修改负载均衡算法
修改负载均衡是相当于调用端来说的
在调用端注入想要使用的
Bean
,注意这个类不能在@ComponentScan
所扫描的包及其子包下@Configuration public class MySelfRule { @Bean public IRule mySelfRule() { return new RandomRule(); } }
在启动类上添加注解
// name 要调用的服务名称 // configuration 负载均衡算法 @RibbonClient(name = "COUD-PAYMENT-SERVICE", configuration = RandomRule.class)
# 5.3 自定义负载均衡算法
修改
restTemplate
,去掉@LoadBalanced
注解@Configuration public class ApplicationContextConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
自定义算法实现
public interface LoadBalancer { ServiceInstance instances(List<ServiceInstance> serviceInstances); } @Component public class MyLB implements LoadBalancer{ private AtomicInteger atomicInteger = new AtomicInteger(0); public final int getAndIncrement() { int current; int next; do { current = this.atomicInteger.get(); next = current >= Integer.MAX_VALUE ? 0 : current + 1; } while (!this.atomicInteger.compareAndSet(current, next)); return next; } @Override public ServiceInstance instances(List<ServiceInstance> serviceInstances) { int index = getAndIncrement() % serviceInstances.size(); return serviceInstances.get(index); } }
使用
@RestController @RequestMapping("/consumer") public class OrderController { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; @Autowired private LoadBalancer loadBalancer; @GetMapping("/payment/lb") public String getPaymentLb() { List<ServiceInstance> instances = discoveryClient.getInstances("COUD-PAYMENT-SERVICE"); if (instances == null || instances.size() <= 0) { return null; } ServiceInstance instance = loadBalancer.instances(instances); URI uri = instance.getUri(); return restTemplate.getForObject(uri + "/payment/lb", String.class); } }
# 6. OpenFeign
OpenFeign
是Spring Cloud
在Feign
的基础上支持了SpringMVC
的注解,如@RequestMapping
等,OpenFeign
的@FeignClient
注解可以解析SpringMVC
的@RequestMapping
注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务(⭐️Feign
针对调用端而言)
# 6.1 服务调用
导入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
编写服务调用接口
@FeignClient(name = "COUD-PAYMENT-SERVICE") // name为要调用的服务名称 public interface PaymentClient { @GetMapping("/payment/get/{id}") public Result<Payment> getPaymentById(@PathVariable("id") String id); }
主启动类上添加注解
@EnableFeignClients
使用
@RestController @RequestMapping("/consumer") public class OrderFeignController { @Autowired private PaymentClient paymentClient; @GetMapping("/payment/get/{id}") public Result<Payment> getPayment(@PathVariable("id") String id) { return paymentClient.getPaymentById(id); } }
# 6.2 超时控制
默认Feign
客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign
客户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign
客户端的超时控制。
ribbon:
# 建立连接后从服务器读取到可用资源所能够使用的最大时间
ReadTimeout: 5000
# 两端建立连接所能够使用的最大时间
ConnectTimeout: 5000
# 6.3 日志增强
Feign
提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign
中Http
请求的细节
总共提供了四个日志级别
NONE
:默认的,不显示任何日志BASIC
:仅记录请求方法、URL
、响应状态码及执行时间HEADERS
:除了BASIC
中定义的信息之外,还有请求和响应的头信息FULL
:除了HEADERS
中定义的信息之外,还有请求和响应的正文及元数据
修改配置文件
logging:
level:
# 指定具体的类的日志等级
com.meinil.springcloud.client.PaymentClient: debug
编写配置类
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
# 7. Hystrix
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的"扇出"。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,即"雪崩效应"
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源在几秒钟之内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
Hystrix
是一个用于分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix
能够保证在一个依赖出问题的情况下,不会导致整体服务徐失败,避免级联故障,以提高分布式系统的弹性。
"断路器"本身是一种开关,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack
),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
# 7.1 重要概念
服务降级
fallback
:当服务端出错时,不会让客户端无限等待下去,而是会立刻返回一个友好提示触发服务降级的情况
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量满载
服务熔断
break
:服务端达到最大访问量,调用服务降级的方法处理服务限流
flowlimit
:维持高并发操作的有序进行
# 7.2 服务降级
降级分为客户端降级可服务端降级,两者所用依赖相同
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
# 7.2.1 服务端降级
Controller
层@RestController @RequestMapping("/payment/hystrix") public class PaymentHystrixController { @Autowired private PaymentHystrixService paymentService; @GetMapping("/timeout/{id}") public String paymentError(@PathVariable String id) { return paymentService.paymentError(id); } }
service
层@Service public class PaymentHystrixService { @Value("${server.port}") private String serverPort; @HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = { @HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds", value = "3000") }) public String paymentError(String id) { try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } return "paymentError: \tThread: " + Thread.currentThread().getName() + "\tserverPort: " + serverPort + "\tid: " + id; } public String timeoutHandler(String id) { return "paymentTimeoutHandler: \tThread: " + Thread.currentThread().getName() + "\tserverPort: " + serverPort + "\tid: " + id; } }
HystrixCommand
指定了超时或者异常处理的方法timeoutHandler
启动类上需要添加注解
@EnableCircuitBreaker
# 7.2.2 客户端降级
降级一般是客户端做⭐️
依赖与上述相同
修改配置文件,开启熔断器
feign: hystrix: enabled: true
启动类上需要添加注解
@EnableHystrix
远程调用
@FeignClient(name = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") public interface PaymentHystrixClient { @GetMapping("/payment/hystrix/timeout/{id}") public String paymentError(@PathVariable("id") String id); }
Controller
层@RestController @RequestMapping("/consumer") public class PaymentHystrixController { @Autowired private PaymentHystrixClient paymentHystrixClient; @GetMapping("/payment/hystrix/timeout/{id}") @HystrixCommand(fallbackMethod = "timeoutHandler", commandProperties = { @HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds", value = "1500") }) public String paymentError(@PathVariable("id") String id){ return paymentHystrixClient.paymentError(id); } public String timeoutHandler(String id) { return "客户端降级: \tThread: " + Thread.currentThread().getName() + "\tid: " + id; } }
# 7.2.3 全局统一降级
以客户端为例
默认降级方法
@RestController @RequestMapping("/consumer") @DefaultProperties(defaultFallback = "globalHandler") public class PaymentHystrixController { @Autowired private PaymentHystrixClient paymentHystrixClient; @HystrixCommand @GetMapping("/payment/hystrix/timeout/{id}") public String paymentError(@PathVariable("id") String id){ return paymentHystrixClient.paymentError(id); } public String globalHandler() { return "全局统一降级处理"; } }
远程调用实现类降级
@FeignClient(name = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentHystrixClientFallback.class) public interface PaymentHystrixClient { @GetMapping("/payment/hystrix/timeout/{id}") public String paymentError(@PathVariable("id") String id); } @Component public class PaymentHystrixClientFallback implements PaymentHystrixClient { @Override public String paymentError(String id) { return "实现类降级"; } }
# 7.3 服务熔断
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后,恢复调用链路。也就是熔断会直接断开服务连接,使得请求无法到达服务端,进而保护服务端,防止过载。而降级不会
在SpringCloud
框架里,熔断机制通过Hystrix
实现。Hystrix
会监视微服务调用的状况,当失败的调用到达一定阈值时,缺省是5s内20次失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand
熔断器一般包含三个部分
Closed:熔断关闭不会对服务进行熔断
Open:请求不再调用当前服务,内部设置时钟一般为
MTTR
(平均故障处理时间),当打开时长达到所设时钟进入半熔断状态Half-Open:部分请求根据规则调用当前服务,如果请求成功且符合规则,则认为当前服务状态恢复正常,关闭熔断
# 7.3.1 实例演示
服务层
@Service public class PaymentHystrixService { @HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallback", commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 是否开启断路器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 请求次数 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), // 失败率达到多少熔断 }) public String paymentCircuitBreaker(Integer id) { if (id < 0) { throw new RuntimeException("*******id不能为负数"); } return "id: " + id + "\tThread: " + Thread.currentThread().getName() + "\tUUID: " + UUID.randomUUID().toString(); } public String paymentCircuitBreakerFallback(Integer id) { return "id不能为负数,请稍后再试\tid: " + id; } }
controller
层@RestController @RequestMapping("/payment/hystrix") public class PaymentHystrixController { @Autowired private PaymentHystrixService paymentService; @GetMapping("/breaker/{id}") public String breaker(@PathVariable Integer id) { return paymentService.paymentCircuitBreaker(id); } }
# 7.4 服务监控
除了隔离依赖服务的调用 意外,Hystrix
还提供了准实时的调用监控Hystrix Dashboard
,Hystrix
会持续地记录素哟偶通过Hystrix
发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求、多少成功、多少失败等。Netflix
通过hystrix-metrics-event-stream
项目实现了对以上指标的监控。Spring Cloud
也提供了Hystrix Dashboard
的整合,对监控内容转化成可视化界面
新建模块
cloud-consumer-hystrix-dashboard9001
导入依赖
<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.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
启动类
@SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardApplication9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboardApplication9001.class, args); } }
被监控的项目配置
/** * 此配置是为了服务监控为配置,与服务容错本身无关,SpringCloud升级后的bug * ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream" * z只要在自己的项目中配置如下的servlet即可 */ @Bean public ServletRegistrationBean getServlet() { HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/hystrix.stream"); registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; }
被监控的项目需要有如下的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
访问测试
http://localhost:9001/hystrix
输入监控的项目地址
http://localhost:8001/hystrix.stream
# 8. Gateway
SprngCloud Gateway
是SpringCloud
的一个全新项目,基于Spring5.0
、SprngBoot2.0
和Project Reator
等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API
路由管理方式。
SpringCloud Gateway
作为Spring Cloud
生态系统中的网关,目标是替代Zuul
,在Spring Cloud 2.0
以上版本中,没有对新版本的Zuul 2.0
以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x
非Reactor
模式的老版本。而为了提升网关的性能,SpringCloud Gateway
是基于WebFlux
框架实现的,而WebFlux
框架底层则使用了高性能的Reactor
模式通信框架Netty
。
Spring Cloud Gateway
的目标提供统一的路由方式且基于Filter
链的方式提供了网关的基本功能,例如:安全,监控/指标和限流
# 8.1 基本概念
路由Route
:路由是构建网关的基本模块,它由ID
,目标URI
,一系列的断言和过滤器组成,如果断言为true
则匹配该路由
断言Predicate
:开发人员可以匹配请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
过滤Filter
:指的是Spring
框架中的GatewayFilter
的实例,使用过滤器,可以在请求被路由前或者之后进行修改
# 8.2 案例
新建模块
cloud-gateway-gateway9527
导入依赖,注意:网关不能引入
web
的依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
配置文件
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: routes: - id: cloud-provider-payment1 # 路由的id,没有固定规则但要求唯一,建议配合服务名使用 uri: http://localhost:8001 # 匹配后提供服务的路由地址 predicates: - Path=/payment/get/** # 断言,路径相匹配的进行路由 - id: cloud-provider-payment2 # 路由的id,没有固定规则但要求唯一,建议配合服务名使用 uri: http://localhost:8001 # 匹配后提供服务的路由地址 predicates: - Path=/payment/lb/** # 断言,路径相匹配的进行路由 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/
代码方式配置
@Configuration public class GatewayConfig { @Bean public RouteLocator routes(RouteLocatorBuilder builder) { return builder .routes() .route( "path", r -> r.path("/guonei").uri("http://news.baidu.com/guonei") ).build(); } }
# 8.3 动态路由
修改配置文件即可
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: cloud-provider-payment1 # 路由的id,没有固定规则但要求唯一,建议配合服务名使用
uri: lb://cloud-provider-payment
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: cloud-provider-payment2 # 路由的id,没有固定规则但要求唯一,建议配合服务名使用
uri: lb://cloud-provider-payment
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/
# 8.4 断言
Spring Cloud Gateway
将路由匹配作为Spring WebFlux HandlerMapping
基础架构的一部分。Spring Cloud GateWay
包括许多内置的Route Predicate
工厂。素有这些Predicate
都与HTTP
请求的不同属性匹配。多个Route Predicate
工厂可以进行组合
Spring Cloud Gateway
创建Route
对象时,使用RoutePredicateFactory
创建Predicate
对象,Predicate
对象可以赋值给Route
。Spring Cloud Gateway
包含许多内置的Route Predicate Factories
所有这些谓词都匹配HTTP
请求的不同属性。多种谓词工厂可以组合,并通过逻辑and
在什么时间之后有效
predicates: - Path=/payment/lb/** - After=2022-03-13T22:40:32.588+08:00[Asia/Shanghai]
在什么时间之前有效
predicates: - Path=/payment/lb/** - Before=2022-03-13T22:40:32.588+08:00[Asia/Shanghai]
在什么时间之间有效
predicates: - Path=/payment/lb/** - Between=2022-03-13T22:40:32.588+08:00[Asia/Shanghai], 2022-03-13T22:50:32.588+08:00[Asia/Shanghai]
cookie
predicates: - Path=/payment/lb/** - Cookie=cookieName, RegExp
第一个参数为cookie名称,第二个为正则表达式
请求头
predicates: - Path=/payment/lb/** - Header=Authorization, \d+
限制主机请求
predicates: - Path=/payment/lb/** - Host=**.example.com
限定请求方法
predicates: - Path=/payment/lb/** - Method=GET,POST
请求参数
predicates: - Path=/payment/lb/** - Query=username, \d+
# 8.5 Filter
路由过滤器可用与修改进入的HTTP
请求和返回的HTTP
响应,路由过滤器只指定路由进行使用。Spring Cloud Gateway
内置了多种路由过滤器,它们都由GatewayFilter
的工厂类产生
过滤器的配置与断言相似,过滤器配置参考 (opens new window)
filters:
- AddRequestHeader=X-Request-red, blue
这个配置会在所有的请求上添加一个请求头
自定义过滤器
@Component
public class LogGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("===========拦截请求=============" + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null || uname.length() == 0) {
System.out.println("===========非法用户===========");
// 不通过返回
return exchange.getResponse().setComplete();
}
// 验证通过放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
# 9. Config
# 9.1 理论知识
SpringCloud Config
为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置
SpringCloud Config
分为服务端和客户端两部分。
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
客户端则通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容
# 9.2 服务端搭建
在github (opens new window)上新建一个仓库,有如下目录结构
config-dev.yaml config-test.yaml config-prod.yaml
新建模块
cloud-config-center3344
导入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
配置文件
server: port: 3344 spring: application: name: cloud-config-center cloud: config: server: git: uri: https://github.com/xxx/springcloud-config.git # 远程仓库地址 search-paths: springcloud-config # 配置文件所在目录 label: main # 分支名 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka/
启动类
@EnableConfigServer @EnableEurekaClient @SpringBootApplication public class ConfigCenterApplication3344 { public static void main(String[] args) { SpringApplication.run(ConfigCenterApplication3344.class, args); } }
测试
http://localhost:3344/main/config-dev.yaml http://localhost:3344/main/config-test.yaml http://localhost:3344/main/config-prod.yaml
# 9.3 客户端搭建
新建模块
cloud-config-client3355
导入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency>
新建一个
bootstrap.yaml
server: port: 3355 spring: application: name: cloud-config-client cloud: config: label: main # 分支名称 name: config # 配置文件名称 profile: dev # 环境 uri: http://localhost:3344 # 配置中心地址 # 自动拼接为 http://localhost:3344/main/config-dev.yaml
这样会存在一个问题,虽然我们能够读取配置中心的文件,但是它不会动态刷新,每次修改配置文件都要重启客户端(服务端无影响)
需要如下配置
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
修改
bootstrap.yaml
management: endpoints: web: exposure: include: "*"
在需要动态刷新的组件上添加注解
@RefreshScope
向客户端发送一个
POST
请求curl -X POST "http://localhost:3355/actuator/refresh"
# 10. BUS
SpringCloud Bus
配合Spring Cloud Config
使用可以实现配置的动态刷新
SpringCloud Bus
是用来将分布式系统的节点与轻量级消息系统连接起来的框架,它整合了Java
时间处理机制和消息中间件的功能。SpringCloud Bus
目前支持RabbitMQ
和Kafka
# 10.1 基本概念
总线:在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中素有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和浪费,所以称为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息
基本原理:ConfigClient
实例都监听MQ
中同一个topic
(默认是SpringCloudBus
).当一个服务刷新数据的时候,它会把这个信息放入到Topic
中,这样其他监听同一个Topic
的服务就能得到通知,然后更新自身的配置
两种不同的方式触发
- 客户端变动,导致整体变动
- 配置中心变动,导致整体变动
显然第二种架构更为合适
# 10.2 RabbitMQ
安装Erlang (opens new window),
RabbitMQ
依赖Erlang
,记得配置ERLANG_HOME
进入安装目录的
sbin
,执行如下命令rabbitmq-plugins enable rabbitmq_management
启动
RabbitMQ
rabbitmq-server.bat
访问测试
http://localhost:15672/
默认用户名密码为
guest
# 10.3 案例演示
目前需要完成的功能
配置中心以及被推送的模块都需要此依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
添加相关配置
spring: rabbitmq: host: localhost port: 5672 username: guest password: guest # 这个仅需配置中心配置 management: endpoints: web: exposure: include: "bus-refresh"
给配置中心发送一次
POST
请求,便会通知所有的客户端curl -X POST http://localhost:3344/actuator/bus-refresh
定点通知,仅通知某一个客户端
curl -X POST http://localhost:3344/actuator/bus-refresh/{destination}
destination
为服务名:端口号
# 11. Stream
消息驱动:屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
应用程序通过inputs
或者outputs
来与Spring Cloud Stream
中binder
对象交互。通过我们配置来绑定binding
,而Spring Cloud Stream
的binder
对象负责与消息中间件交互。所以,我们只需要搞清楚如何与Spring Cloud Stream
交互就可以方便使用消息驱动方式
通过使用Spring Integration
来连接消息代理中间件以实现消息事件驱动。Spring Cloud Stream
为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
目前仅支持RabbitMQ
、Kafka
# 11.1 常用概念
Binder
:用于连接各种中间件,屏蔽差异Channel
:通道,是队列Queue
的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel
对队列进行配置Source
和Sink
:简单的可理解为参照对象是Spring Cloud Stream
自身,从Stream
发布消息就是输出,接受消息就是输入
组成 | 说明 |
---|---|
Middleware | 中间件,目前仅支持Kafka |
Binder | Binder是应用与消息中间件之间的封装,目前实现了Kafka和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现 |
@Input | 注解标识输入通道,通过该输入通道接受到的消息进入应用程序 |
@Output | 注解标识输出通道,发布的消息将通过该通道离开应用程序 |
@StreamListener | 监听队列,用于消费者的队列的消息接受 |
@EnableBinding | 指信道和exchange绑定在一起 |
# 11.2 生产者
此次案例采用rabbitmq
新建模块
cloud-stream-rabbitmq-provider8801
导入依赖
<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.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
配置文件
server: port: 8801 spring: application: name: cloud-stream-provider cloud: stream: binders: # 此处配置要绑定的rabbitmq的服务信息 defaultRabbit: # 表示定义的名称,用于binding整合 type: rabbit # 消息组件类型 environment: # rabbitmq的相关环境配置 spring: rabbitmq: host: localhost port: 5672 username: guest password: guest bindings: # 服务的整合处理 output: # 一个通道的名称 destination: studyExchange # 要使用的Exchange名称定义 content-type: application/json # 设置消息类型,本次为json,文本则设置为 text/plain binder: defaultRabbit # 设置要绑定的消息服务的具体配置 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka/
service
层public interface MessageProvider { public String send(); } @EnableBinding(Source.class) // 消息推送的管道 public class MessageProviderImpl implements MessageProvider { @Autowired private MessageChannel output; // 消息发送管道 @Override public String send() { String content = UUID.randomUUID().toString(); output.send(MessageBuilder.withPayload(content).build()); return null; } }
controller
层@RestController public class SendMessageController { @Autowired private MessageProvider provider; @GetMapping("/sendMessage") public String sendMessage() { return provider.send(); } }
# 11.3 消费者
新建模块
cloud-stream-rabbitmq-consumer8802
导入依赖
<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.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
配置文件
server: port: 8802 spring: application: name: cloud-stream-consumer cloud: stream: binders: # 此处配置要绑定的rabbitmq的服务信息 defaultRabbit: # 表示定义的名称,用于binding整合 type: rabbit # 消息组件类型 environment: # rabbitmq的相关环境配置 spring: rabbitmq: host: localhost port: 5672 username: guest password: guest bindings: # 服务的整合处理 input: # 一个通道的名称 destination: studyExchange # 要使用的Exchange名称定义 content-type: application/json # 设置消息类型,本次为json,文本则设置为 text/plain binder: defaultRabbit # 设置要绑定的消息服务的具体配置 eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka/
接受消息
@Component @EnableBinding(Sink.class) public class MessageReceiverController { @Value("${server.port}") private String serverPort; @StreamListener(Sink.INPUT) public void input(Message<String> message) { System.out.printf("消费者%s, 接受到的消息为: %s\n", serverPort, message.getPayload()); } }
如果在生产者侧推送消息,则该组件会接收到推送的消息
# 11.4 分组
上述的案例中,如果消费者是集群部署的话,则会出现生产者推送一个消息,被所有消费者接收到,实际情况中我们需要避免这个问题
在Stream
中处于同一个group
中的多个消费者是竞争关系,就够保证消息只会被其中一个应用消费一次
分组只需在配置文件中修改
server:
port: 8803
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 此处配置要绑定的rabbitmq的服务信息
defaultRabbit: # 表示定义的名称,用于binding整合
type: rabbit # 消息组件类型
environment: # rabbitmq的相关环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 一个通道的名称
destination: studyExchange # 要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置为 text/plain
binder: defaultRabbit # 设置要绑定的消息服务的具体配置
group: MessageA # 分组
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
添加分组后,消息会自动持久化,即如果服务重启,在服务重启期间生产者的消息会在服务上线之后继续推送
# 12. Sleuth
分布式请求链路追踪:在微服务框架中,一个由客户端发起的请求在后端系统中会多个不同的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败
zipkin-server
是一个可执行jar
包,直接执行即可访问测试
http://127.0.0.1:9411/
被监控的服务需要引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
配置文件修改
spring: zipkin: base-url: http://localhost:9411 sleuth: sampler: probability: 1 # 采样率值介于0到1之间,1表示全部采集
# 13. Nacos
Nacos
支持AP
和CP
模式的切换
C
代表所有节点在同一时间看到的数据是一致的;而A
的定义是所有的请求都会收到响应
一般来说如果不需要存储服务级别的信息且服务实例是通过nacos-client
注册,并能够保持心跳上报,那么就可以选择AP
模式。当前主流的服务如Spring Cloud
和Dubbo
服务,都适用于AP
模式,AP
模式为了服务的可能性而减弱了一致性,因此AP
模式下只支持注册临时实例。
如果需要在服务级别编辑或者存储配置信息,那么CP
是必须的,K8S
和DNS
服务则适用于CP
模式。
CP
模式下支持注册持久化实例,此时则以Raft
协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误
# 13.1 注册中心
父工程中导入依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies>
新建一个模块
cloud-alibaba-provider-payment9001
导入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
修改配置文件
server: port: 9001 spring: application: name: nacos-provider-payment cloud: nacos: discovery: server-addr: 127.0.0.1:8848 # nacos的地址
提示:远程调用可以使用OpenFeign
# 13.2 配置中心
新建一个模块
cloud-alibaba-config-nacos-client3377
导入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
编写配置文件
bootstrap.yaml
server: port: 3377 spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: 127.0.0.1:8848 # nacos服务注册中心 config: server-addr: 127.0.0.1:8848 # nacos作为配置中心地址 file-extension: yaml # 指定yaml格式的配置
application.yaml
:需要指定配置文件环境spring: profiles: active: dev
Nacos
的配置文件的格式为${prefix}-${spring.profiles.active}.${file-extension} # 一般配置为服务名
以上面的配置为例,这个文件需要建在
nacos
的配置中心中# nacos-config-client-dev.yaml config: version: 1
新建一个
controller
@RefreshScope @RestController public class NacosConfigController { @Value("${config.version}") private String version; @GetMapping("/config") public String getConfig() { return version; } }
测试已经可以动态修改了
# 13.3 基本概念
默认情况:
Namespace=public
Group=DEFAULT_GROUP
Cluster=DEFAULT
Namespace
命名空间,主要用来实现隔离。比方说我们现在有三个环境:开发、测试、生产环境,我们可以创建三个
Namespace
,不同的Namespace
之间是隔离的。Group
默认是DEFAULT_GROUP
,Group
可以把不同的微服务划分到同一个分组里面去Service
就是微服务;一个Service
可以包含多个Cluster
(集群),Nacos
默认Cluster
是对指定微服务的一个虚拟划分。比方说为了容灾,将Service
微服务分别部署在了杭州机房和广州机房,这时候就可以给杭州机房的Service
微服务起一个集群名称HZ
,给广州机房的Service
微服务起一个集权名称HZ
,还可以尽量让同一个机房的微服务相互调用,以提升性能最后就是
instance
,就是微服务的实例
# 13.4 使用详解
DataId
dataId
就是在配置中心的文件名,可以根据spring.profiles.active
更换不同的文件group如果要更换组的话,直接在
config
下更换即可server: port: 3377 spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: 127.0.0.1:8848 # nacos服务注册中心 config: server-addr: 127.0.0.1:8848 # nacos作为配置中心地址 file-extension: yaml # 指定yaml格式的配置 group: DEV_GROUP # 指定分组
namespace
,更换命名空间server: port: 3377 spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: 127.0.0.1:8848 # nacos服务注册中心 config: server-addr: 127.0.0.1:8848 # nacos作为配置中心地址 file-extension: yaml # 指定yaml格式的配置 group: DEV_GROUP # 指定分组 namespace: c0553a5e-ce37-4b9c-ad18-b8a4ad97fcee # 指定命名空间
# 13.5 持久化
默认Nacos
使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos
节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos
采用了集中式存储的方式来支持集群化部署,目前仅支持MySQL
存储。
在
nacos
的安装目录下,会有一个conf
的文件夹,找到nacos-mysql.sql
将其导入修改
conf/application.properties
spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC db.user.0=nacos db.password.0=nacos
# 13.6 集群部署
请确保持久化工作已经完成
将
conf/cluster.conf.example
拷贝一份sudo cp cluster.conf.example cluster.conf
修改
cluster.conf
为自己的集群节点地址192.168.0.1:3344 192.168.0.1:4444 192.168.0.1:5555
注:不能填写
127.0.0.1
修改nginx配置文件
upstream cluster { server 127.0.0.1:3333; server 127.0.0.1:4444; server 127.0.0.1:5555; } server { listen 1111; server_name localhost; location / { proxy_pass http://cluster; } }
分别启动三个
nacos
,对应端口号为3333
、4444
、5555
访问
1111
测试
# 14 Sentinel
下载 (opens new window)下来是一个jar
包,直接使用java -jar
启动即可。注意:请使用java8
启动
启动后访问http://localhost:8080/,用户名密码默认为sentinel
# 14.1 实例演示
新建模块
cloud-alibaba-sentinel-service8401
导入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
由于
sentinel
使用懒加载机制,即服务需要被请求一次后才能在控制台监控到
# 14.2 流控规则
资源名:唯一名称,默认请求路径
针对来源:Sentinel
可以针对调用者进行限流,填写微服务名,默认default
(不区分来源)
阈值类型/单机阈值:
QPS
(每秒钟的请求数量):当调用该api
的QPS
达到阈值时,进行限流线程数:当调用该
api
的线程数达到阈值时,进行限流
是否集群:不需要集群
流控模式:
直接:
api
达到限流条件时,直接限流关联:当关联的资源达到阈值时,就限流自己
链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【
api
级别的针对来源】当B达到1时,会限制A的访问,应用场景:A为订单接口,B为支付接口,B因为流量过载,可以限制A的请求
流控效果:
快速失败:直接失败,抛异常
Warm up
:根据codeFactory
(冷加载因子, 默认是3)的值,从阈值/codeFactor
,经过预热时长,才达到设置的QPS
阈值排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置
QPS
,否则无效冷启动,在预热时长内仅能达到单机阈值/codeFactor(默认3),预热时长之后QPS应为单机阈值
排队等待,控制请求以相同的时间间隔处理
# 14.3 熔断规则
配置相应规则即可
# 14.4 热点key
编写controller
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "consumerHandler")
public String testHostKey(@RequestParam(name = "p1", required = false) String p1,
@RequestParam(name = "p2", required = false) String p2) {
return "-------TestHotKey";
}
public String consumerHandler(String p1,
String p2,
BlockException e) {
return "--------降级方法";
}
SentinelResource
指定的value
值就是需要在sentinel
中配置的资源名,blockHandler
为降级方法
也可以对于特定的值进行特定的限定
# 14.5 降级方法
自定义降级方法
public class CustomerBlockHandler { public static String handlerException(BlockException e) { return "全局异常处理: " + e.getMessage(); } }
controller
层@RestController public class RateLimitController { @GetMapping("/byResource") @SentinelResource( value = "byResource", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException") public String byResource() { return "按资源名称名称限流测试OK"; } }
# 14.6 异常处理
直接在SentinelResource
注解中指定fallback
的参数即可
@RestController
public class SentinelConsumerController {
@Autowired
private SentinelPaymentClient client;
@Value("${server.port}")
private String serverPort;
@GetMapping("/consumer/payment/{id}")
@SentinelResource(value = "fallback", fallback = "handlerPayment")
public String getPayment(@PathVariable(required = false) Integer id) {
if (id == 2) {
throw new RuntimeException("抛出异常");
}
return client.getPayment();
}
public String handlerPayment(Integer id) {
return String.format("serverPort: %s异常处理界面", serverPort);
}
}
如果fallback
与blockHandler
都配置的情况下,且达到流控的情况下,优先blockHandler
# 14.5 规则持久化
默认的流控的规则是跟随服务的生命周期
在需要持久化的服务里导入依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
配置文件
spring: application: name: cloud-alibaba-sentinel-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 sentinel: transport: # sentinel dashboard的地址 dashboard: 127.0.0.1:8080 # 如果8080被占用,会自动从8719开始查找未被占用的端口 port: 8719 # 持久化 datasource: ds1: nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} dataId: ${spring.application.name} groupId: DEFAULT_GROUP data-type: json rule-type: flow
nacos
中新建一个cloud-alibaba-sentinel-service
[ { "resource": "/testA", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "controlBehavior": 0, "clusterMode": false } ]
resource
:资源名称limitApp
:来源应用grade
:阈值类型,0
表示线程数,1
表示QPS
count
:单机阈值strategy
:流控模式,0
表示直接,1
表示关联,2
表示链路controlBehavior
:流控效果,0
表示快速失败,1
表示Warm Up
,2
表示排毒等待clusterMode
:是否集群
# 15. Seata
分布式事务:一次业务需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。
Seata
是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务
# 15.1 基本概念
Transaction ID
:全局唯一的事务ID
Transaction Coordinator(TC)
:事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚
Transaction Manager(TM)
:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议
Transaction Manager(RM)
:控制分支事务,负责分支注册、状态汇报,并接受事务协调器的指令,驱动分支(本地)事务的提交和回滚
TM
向TC
申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
;XID
在微服务调用链路的上下文中传播RM
向TC
注册分支事务,将其纳入XID
对应全局事务的管辖TM
向TC
发起针对XID
的全局提交或回滚决议TC
调度XID
下管辖的全部分支事务完成提交或回滚请求
# 15.2 下载配置
修改
conf/file.conf
# 更改为数据库存储 store.mode = "db" # 数据库配置 store.db
新建一个
seata
数据库,导入sql (opens new window)修改
conf/registry.conf
type="nacos" # 修改nacos相关的配置 nacos {}
先启动
Nacos
,在启动Seata
# 15.3 数据库搭建
创建三个库
CREATE DATABASE seata_order; USE seata_order; CREATE TABLE t_order( id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY , user_id BIGINT(11) DEFAULT NULL COMMENT '用户id', product_id BIGINT(11) DEFAULT NULL COMMENT '产品id', count INT(11) DEFAULT NULL COMMENT '数量', money DECIMAL(11,0) DEFAULT NULL COMMENT '金额', status INT(1) DEFAULT NULL COMMENT '订单状态:0创建中,1已完结' )ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8; CREATE DATABASE seata_storage; USE seata_storage; CREATE TABLE t_storage( id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY , product_id BIGINT(11) DEFAULT NULL COMMENT '产品id', total INT(11) DEFAULT NULL COMMENT '总库存', used INT(11) DEFAULT NULL COMMENT '已用库存', residue INT(11) DEFAULT NULL COMMENT '剩余库存' )ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8; INSERT INTO t_storage(id, product_id, total, used, residue) VALUES(1,1,100,0,100); CREATE DATABASE seata_account; USE seata_account; CREATE TABLE t_account( id BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY , user_id BIGINT(11) DEFAULT NULL COMMENT '用户id', total DECIMAL(10,0) DEFAULT NULL COMMENT '总额度', used DECIMAL(10,0) DEFAULT NULL COMMENT '已用额度', residue DECIMAL(10,0) DEFAULT 0 COMMENT '剩余可用额度' )ENGINE=InnoDB AUTO_INCREMENT=7 CHARSET=utf8; INSERT INTO t_account(id, user_id, total, used, residue) VALUES(1,1,1000,0,1000);
每个数据库下面都需要一张日志表 (opens new window)