一、Ribbon 负载均衡服务调用
1.1 概念
SpringCloud Ribbon 是基于Netflix Ribbon实现的一套 客户端负载均衡的工具
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端负载均衡算法和服务调用,Ribbon客户端组件提供一系列完善的配置项如 连接超时,超时重试等,简单来说,就是在配置文件中列出Load Balancer后面的所有机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接)去连接这些机器,我们很容易使用Ribbon实现自定义的负载均衡算法。
LB(负载均衡):
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到高可用HA
Ribbon本地负载均衡客户端 VS Nginx 服务端负载均衡区别
Nginx 是服务器负载均衡,客户端的所有请求都会交给Nginx,然后由nginx实现转发请求,即负载均衡是由服务端实现的。
Ribbon本地负载均衡,在调用微服务接口的时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术
- 集中式LB:即在服务的消费方和提供方之间使用独立的LB设施,可以是硬件,如F5也可以是软件,如nginx,由该设施负责把访问请求通过某种策略转发到服务的提供方。
- 进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器
- ribbon就属于进程LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
1.2 Ribbon简单负载均衡演示
Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他的所需请求的客户端结合使用,与eureka结合知识其一个实例
Ribbon 在工作的时候分为两步
- 第一步先选择EurekaServer,它优先选择在同一个区域内负载较少的Server
- 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址,其中Ribbon提供了多种策略:比如轮询,随机和根据响应时间加权。
在没有引入Ribbon jar包的时候也可以使用Ribbon 原因:
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
eureka客户端集成了Ribbon
Ribbon + RestTemplate
RestTemplate
- getForObject 方法 (返回对象为响应体中数据转换成的对象,基本上理解为Json)
- getForEntity方法 (返回对象为ResponseEntity对象,包含了响应中的一些重要的信息,如如响应头,响应状态码,响应体)
- postForObject
- postForEntity
- get请求方法
- post请求方法
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id){
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();
}else{
return new CommonResult(444,"操作失败");
}
}
1.3 Ribbon默认自带的负载均衡规则
1.3.1 IRULE: 根据特定的算法中从服务列表中选择一个要访问的服务
1.RoundRobinRule : 轮询
2. RandomRule: 随机
3. RetryRule : 先按照RoundRobinRule的策略获取服务,如果获取失败后则在指定时间内进行充实,获取可用的服务
4. WeightedResponseTimeRule: 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
5. BestAvailableRule: 会先过滤掉由于多次访问故障而处于短裤器跳闸状态的服务,然后选择一个并发量小的服务
6. AvailabilityFulteringRule : 先过滤掉故障实例,在选择并发较小的实例
7. ZoneAvoiddanceRule : 默认规则,复合判断server所在区域的性能和server 的可用性选择服务器
1.3.2 怎么替换LB策略
@springBootApplication 中的 @CompentScan 不能和Ribbon 的注解放在一起
- 新建myrule 包
- 在myrule包下新建MyselfRule类
@Configuration
public class MySelfRule {
@Bean
public IRule myRule() {
return new RandomRule();
}
}
- 主启动类添加@RibbonClient
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
1.3.3 负载均衡算法的原理,源码,手写
负载均衡算法 : rest接口的第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启动后rest几口计数从1开始。
list = 2 instance
1 % 2 = 1 -> index = 1 -> list.get(index)
2 % 2 = 2 -> index = 0 -> list.get(index)
3 % 2 = 1 -> index = 1 -> list.get(index)
源码 :
**nextServerCyclicCounter 是 AutomicInteger
compareAndSet CAS自选锁
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextServerCyclicCounter.get();
next = (current + 1) % modulo;
} while(!this.nextServerCyclicCounter.compareAndSet(current, next));
return next;
}
手写 :
原理 + JUC (CAS + 自选锁的复习)
@GetMapping("/payment/lb")
public String getPaymentLB() {
return SERVER_PORT;
}
- loadBalance接口
package com.xzq.springcloud.lb;
import org.springframework.cloud.client.ServiceInstance;
import java.util.List;
interface LoadBalancer {
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
package com.xzq.springcloud.lb;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;
import java.sql.SQLOutput;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class MyLB implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
private final int getAndIncrement() {
int current;
int next;
do {
current = this.atomicInteger.get();
next = current >= Integer.MAX_VALUE ? 0 : current + 1;
} while (!atomicInteger.compareAndSet(current, next));
System.out.println("第几次访问,次数next:" + next);
return next;
}
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
二、OpenFeign服务接口与调用
2.1 概述
Feign 是一个声明式的webService客户端,使用feign能让编写WebService客户端更加简单
它的使用方法是定义一个服务接口然后再上面添加注解,Feign也支持可拔插式的编码器和解码器,SpringCloud对Feign进行了封装,使用SpringMVC标准注解和HttpMessageConverters,Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
Feign能干什么 ?
Feign旨在编写Java Http 客户端变得更加容易
前面在使用Ribbon + RestTemplate 的时候,利用RestTemplate 对http 请求的封装处理,行程了一套模板化的调用方法,但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端来包装这些依赖服务的调用,所以Feign在此基础上做了进一步的封装,由他来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下,我们只需创建一个接口使用注解的方式来配置他(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用SpringCloud Ribbon时,自动封装服务调用客户端的开发量。
Feign 继承了Ribbon
利用ribbon 维护了Payment的服务列表信息,并且通过轮询的方式实现了客户端的负载均衡,与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
Feign与OpenFeign
- Feign 是SpringCloud组件中的一个轻量级的RestFul的Http客户端Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务.Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务
- OpenFeign 是SpringCloud在Feign的基础上支持了SpringMVC的注解,如@RequestMapping等等,OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping竹节虾的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
2.2 OpenFeign使用方式
微服务调用接口 + @FeignClient
1.新建cloud-consumer-feign-order80
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
主启动类添加@EnableFeignClients
@SpringBootApplication
@EnableFeignClients //开启Feign
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}
新增paymentService接口并新增注解@FeignClient
@Component
@FeignClient(value = "CLOUD-PROVIDER-SERVICE") //指定调用哪个微服务
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}") //哪个地址
CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();
}
编写controller 层
controller 通过 feignclient 找到服务端口地址
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){
//openfeign底层ribbon,客户端默认等待1秒钟
return paymentFeignService.paymentFeignTimeout();
}
}
2.3 OpenFeign超时控制
模拟调用超时
OpenFeign默认等待1秒钟,超过后报错。
- 8001 paymentCtroller
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout(){
//暂停几秒钟线程
try{
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
return serverPort;
}
- 80 OrderFeignController
@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){
//openfeign底层ribbon,客户端默认等待1秒钟
return paymentFeignService.paymentFeignTimeout();
}
- 80 : PaymentFeignService
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();
自定义超时时间解决方法 :
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
2.4 OpenFeign日志打印功能
Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节
说白了就是对Feign接口的调用情况进行监控和输出
NONE : 默认的,不显示任何日志
BASIC : 仅仅记录请求方法,URL,响应状态码及执行时间
HEADERS : 除了BASIC中定义的信息之外,还有请求和响应的头信息
FULL : 除了Headers中定义的信息之外,还有请求和响应的正文及元数据
public class FeignConf {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
logging:
level:
#feign日志以什么级别监控哪个接口
com.atguigu.springcloud.service.PaymentFeignService: debug
三、Hystrix 服务降级
3.1 分布式面临的一些问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在有些时候不可避免的失败
服务雪崩:
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的”扇出“,如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引发系统崩溃,所谓的雪崩效应。
对于高流量的应用来说,单一的后端依赖可能会导致所有的服务器上的所有资源都在几秒钟之内饱和,比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他的系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或者系统
所以,通常当你发现一个模块下的某个实例失败之后,这时候这个模块还接受流量,然后这个问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
3.2 Hystrix是什么
Hystrix : 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统当中,许多依赖不可避免的会调用失败,比如超时异常等,Hystrix能够保证在于一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
"断路器"本身就是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个符合预期的,可处理的备选响应,而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
3.3 Hystrix能干嘛
- 服务降级
- 服务熔断
- 接近实时的监控
3.4 Hystrix的重要概念
-
服务降级 fallback
服务器忙,请稍后再试,不让客户端等待并礼盒返回一个友好的提示
那些情况会触发降级 :
- 程序出现异常
- 超时
- 服务熔断出发服务降级
- 线程池、信号量打满也会导致服务降级 -
服务熔断 break
类比于保险丝达到最大服务访问后,直接拒绝方法,拉闸限电,然后调用服务降级的方法并返回友好提示 -
服务限流 flowlimit
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。
3.5 Hystrix案列
新建 cloud-provider-hystrix-payment8001
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
PaymentHystrixMain8001
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
PaymentService
- 正常的业务方法
- Timeout
public class PaymentService {
/**
* 正常访问
* @param id
* @return
*/
public String paymentInfo_OK(Integer id){
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id:"+id+"\t"+"O(∩_∩)O哈哈~";
}
/**
* @HystrixCommand报异常后如何处理:
* 一旦调用服务方法失败并抛出了错误信息后,
* 会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法
*
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
//设置这个线程的超时时间是3s,3s内是正常的业务逻辑,超过3s调用fallbackMethod指定的方法进行处理
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String paymentInfo_Timeout(Integer id){
int timeNumber = 5;
//int age = 10/0;
try{
TimeUnit.SECONDS.sleep(timeNumber);
}catch (InterruptedException e){
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,id:"+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒):"+timeNumber;
}
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+" 系统繁忙,请稍后再试,id:"+id+"\t"+"o(╥﹏╥)o";
}
}
PaymentController
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
return result;
}
@GetMapping(value = "/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_Timeout(id);
return result;
}
}
开启Jmeter,模拟20000个并发压死8001,20000个请求都去访问PaymentInfo_timeout
tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理
Jmeter压测结论:
上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死
3.5.1消费端80接入
cloud-consumer-feign-hystrix-order80
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
application.yml
server:
port: 80
eureka:
client:
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
feign:
hystrix:
enabled: true
主启动类
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
controller
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
/*@HystrixCommand(fallbackMethod = "paymentTimeOutFallBackMethod",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})*/
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
int age = 10/0;
return paymentHystrixService.paymentInfo_TimeOut(id);
}
}
20000个线程通过80调用 8001
- 要么转圈圈等待
- 要么 error page
8001 统一层次的其他服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕
80此时调用8001,客户端访问响应速度缓慢,转圈圈。
3.6 解决方法
- 超时导致服务器变慢 (超时不再等待)
- 出错(宕机或者程序运行出错) (出错要有兜底)
对方8001超时了,调用者80不能一直卡死等待,必须有服务降级
对方服务8001宕机了,调用者80不能一直卡死等待,必须有服务降级
对方服务8001 ok,调用者80自己出故障或有自我要求(自己的等待时间小于服务提供者)
降级配置
@HystrixCommand
返回一个备选响应
一旦调用服务失败并抛出了错误信息之后会自动调用@HystrixCommand标注皓的fallBackMethod调用类中的指定方法。
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
//设置这个线程的超时时间是3s,3s内是正常的业务逻辑,超过3s调用fallbackMethod指定的方法进行处理
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
//备选响应
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池:"+Thread.currentThread().getName()+" 系统繁忙,请稍后再试,id:"+id+"\t"+"o(╥﹏╥)o";
}
主启动类添加 : @EnableCircuitBreaker
对客户端进行80端: fallback降级保护
hystrix 既可以放在消费端都可以房子客户端
feign: hystrix: enabled: true
2.
@EnableHystrix
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
- OrderHystrixController
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallBackMethod",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})
//@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
int age = 10/0;
return paymentHystrixService.paymentInfo_TimeOut(id);
}
public String paymentTimeOutFallBackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对方支付系统繁忙,请稍后再试,o(╥﹏╥)o";
}
目前问题: 每个业务方法对应一个兜底的方法,代码膨胀
统一和自定义的分开
3.6.2 全局服务降级DefaultProperties
- 解决代码膨胀
1:N除了个别重要的核心业务有专门的降级处理方法
@DefaultProperties(defaultFallback = “payment_Global_FallbackMethod”)
/**
* 全局 fallback 方法
* @return
*/
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试。/(╥﹏╥)/~~";
}
-
和业务逻辑混合在一起
服务降级,客户端去调用服务端,碰上服务端宕机或关闭只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦
PaymentHystrixService 的实现类 PaymentFallbackService
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "----PaymentFallbackService fall back-paymentInfo_OK,o(╥﹏╥)o";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "----PaymentFallbackService fall back-paymentInfo_TimeOut,o(╥﹏╥)o";
}
}
PaymentHystrixService :
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,fallback = PaymentFallbackService.class)
3.7 服务熔断(保险丝)
服务的降级 -> 进而熔断 -> 恢复调用链路
熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务出错不可用或者响应时间不太长的时候,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
**当检测到该节点微服务调用响应正常之后,回复调用链路 Half open **
在SpringCloud框架当中,熔断机制通过Hystrix实现,Hystrix会监控微服务之间的调用的情况
当失败的调用达到一定的阈值的时候,缺省是5秒20次调用失败,就会启动熔断机制,熔断机制的注解是@HystrixCommand
修改 8001 的service
//服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",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(@PathVariable("id") Integer id){
if(id < 0){
throw new RuntimeException("******id 不能为负数");
}
String serialNumber = IdUtil.simpleUUID(); //UUID.randomUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id 不能负数,请稍后再试,o(╥﹏╥)o id:"+id;
}
Hystrix断路器使用时最常用的三个重要指标参数 在微服务中使用Hystrix
作为断路器时,通常涉及到以下三个重要的指标参数(这里是写在@HystrixProperties注解中,当然实际项目中可以全局配置在yml或properties中)
1、circuitBreaker.sleepWindowInMilliseconds 快照时间窗口
断路器的快照时间窗,也叫做窗口期。可以理解为一个触发断路器的周期时间值,默认为10秒(10000)。
2、circuitBreaker.requestVolumeThreshold 请求总数阈值
断路器的窗口期内触发断路的请求阈值,默认为20。换句话说,假如某个窗口期内的请求总数都不到该配置值,那么断路器连发生的资格都没有。断路器在该窗口期内将不会被打开。
3、circuitBreaker.errorThresholdPercentage 错误百分比阈值
断路器的窗口期内能够容忍的错误百分比阈值,默认为50(也就是说默认容忍50%的错误率)。打个比方,假如一个窗口期内,发生了100次服务请求,其中50次出现了错误。在这样的情况下,断路器将会被打开。在该窗口期结束之前,即使第51次请求没有发生异常,也将被执行fallback逻辑。
综上所述,在以上三个参数缺省的情况下,Hystrix断路器触发的默认策略为:
在10秒内,发生20次以上的请求时,假如错误率达到50%以上,则断路器将被打开。(当一个窗口期过去的时候,断路器将变成半开(HALF-OPEN)状态,如果这时候发生的请求正常,则关闭,否则又打开)
熔断打开 : 请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间)当打开时长达到所设钟则进入半熔断状态
熔断关闭 : 熔断关闭不会对服务进行熔断
熔断半开: 部分请求根据规则调用当前服务,如果请求成功符合规则则认为服务恢复正常,关闭熔断。
断路器打开之后 :
1: 再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟效果。
2. 原来的主逻辑要如何恢复呢?
对于这一问题,hystrix也为我们实现了自动恢复功能
3.8 服务限流
3.9 HystrixDashBoard
4、 Gateway 服务网关
4.1 概念简述
GateWay是在Spring生态系统之上构建的API网关服务,基于springboot2.0、spring webflux
Project reactor等技术
GateWay旨在提供一种简单的而有效的方式对API进行路由,以及提供一些强大的过滤器功能,例如熔断、限流、重试等
springCloud Gateway是基于webflux框架实现的,而webflux框架底层实现了高性能的Reactor模式通信框架Netty
能干什么 ?
- 反向代理
- 鉴权
- 流量控制
- 熔断
- 日志监控
有Zuul了怎么又出现了GateWay
一方面因为Zuul1.0已经进入了维护阶段,而且GateWay是SpringCloud团队研发的。
GateWay是基于异步非阻塞模型上进行开发的,性能方面不需要担心。
SpringCloud GateWay 居于如下特性
基于Spring Framework 5 ,Project Reactor 和SpringBoot2.0进行构建
动态路由:能够匹配任何请求属性
可以对路由指定Predicate(断言) 和 Filter(过滤器)
集成Hystrix 断路器功能
集成SpringCloud服务发现功能
请求限流功能
支持路径重写
zuu1是基于servlet之上的一个阻塞式的处理模型
传统的Web框架,比如说struts2, springmvc 等都是基于Servlet API与Servlet容器基础上进行的
但是 在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型的非阻塞异步的框架,它的核心是基于Reactor的相关API实现的,相对于传统的web框架来说,他可以运行在Netty。非阻塞式+ 函数式编程(spring5必须让你使用java8)
SpringWebflux 是Spring5.0引入的新的响应式框架,区别于SpringMVC,它不依赖于Servlet API 他是完全异步非阻塞的,并且基于Reactor 来实现响应式流规范。
4.2 三大核心概念
4.2.1 Route(路由)
路由是构建网关的基础模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true 则匹配该路由
4.2.2 Predicate (断言)
参考的是Java8的java.util.function.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
4.2.3 filter (过滤)
Web 请求 通过一些匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精细化的控制。
predicate 就是我们的匹配条件 而filter就可以理解为一个无所不能的拦截器,有了这两个元素,再加上目标uri,就可以实现一个具体的路由了。
客户端向Spring Cloud GateWay 发出请求。然后在GateWay Handler Mapping 中找到与请求相匹配的路由,将其发送到GateWay的Web Handler
Handler在通过制定的过滤器链来将请求发送到我们实际的服务业务逻辑,然后返回
Filter 在 pre类型的过滤器可以做参数校验,权限校验,流量控制、日志输出、协议转换等
在post类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控的等有着非常重要的作用
4.3 Gateway 9527
新建cloud-gateway-gateway9527
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
application.yml
在8001前面套用了一层9527网关
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-provider-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2 #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-provider-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
#- After=2020-03-15T15:35:07.412+08:00[GMT+08:00]
#- Cookie=username,zzyy
#- Header=X-Request-Id, \d+ #请求头要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Method=GET
#- Query=username, \d+ #要有参数名username并且值还要啥整数才能路由
eureka:
instance:
hostname: cloud-gateway-service
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
移除spring-boot-starter-web 依赖
淡化真实地址和端口号 使用9527地址和端口号转成统一的网关
添加网关前 : http://localhost:8001/payment/get/31
添加网关后 : http://localhost:9527/payment/get/31
GateWay网关路由有两种配置方式
- 在配置文件yml中配置
- 代码中注入RouteLocator的Bean
通过9527网关访问到外网的百度地址
@Configuration
public class GateWayConfig {
/**
* 配置一个id为route-name的路由规则,
* 当访问地址http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei
* @param routeLocatorBuilder
* @return
*/
@SuppressWarnings("JavaDoc")
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_atguigu",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String uname = exchange.getRequest().getQueryParams().getFirst("uname");//每次进来后判断带不带uname这个key
if(uname == null){
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); //uname为null非法用户
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
4.4 通过微服务名实现动态路由
默认情况下GateWay会根据注册中心注册的服务列表
以注册中心上的微服务名为路劲创建动态路由进行转发,从而实现动态路由的功能
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
uri: lb://cloud-provider-service #匹配后提供服务的路由地址
测试 : localhost:9527/payment/lb
4.5 Predicate 的使用
- After=2020-03-15T15:35:07.412+08:00[GMT+08:00]
在这个时间后才能正常访问 - Cookie=username,zzyy
- Header=X-Request-Id, \d+ #请求头要有X-Request-Id属性并且值为整数的正则表达式
- Host=**.atguigu.com
- Method=GET
- Query=username, \d+ #要有参数名username并且值还要啥整数才能路由
4.6 filter
路由过滤器可用于修改进入的HTTP请求和返回HTTP响应,路由过滤器只能指定路由进行使用
生命周期
pre:
post:
种类
单一的:
全局的:
filters:
- AddRequestParameter=X-Request-Id,1024
自定义过滤器 :
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String uname = exchange.getRequest().getQueryParams().getFirst("uname");//每次进来后判断带不带uname这个key
if(uname == null){
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); //uname为null非法用户
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
5 SpringCloud Config
微服务意味着将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务,由于每个服务都需要必要的配置信息才能运行,所以一套集中式的,动态的配置管理设施是必不可少的。
SpringCloud提供了ConfigServer来解决这个问题
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同的微服务应用的所有环境提供了一个中心化的外部配置
SpringCloud Config 分为服务端和客户端两部分
服务端也称为分布式配置中心,他是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密、解密等访问接口
客户端则是通过制定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
能干嘛 :
- 集中管理配置文件
- 不同环境不同配置,动态化的配置更新,分环境部署比如 dev/ test/prod/beta/release
- 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配合红心统一拉取配置自己的信息
- 当配置发生变动的时候,服务不需要重启即可感知到配置的变化并应用新的配置
- 将配置信息以REST接口的形式暴露
config服务端配置与测试
- 用你自己的账号在Github上新建一个名为springcloud-config的新Respository
- 本地硬盘目录上新建git仓库并clone
- 新建cloud-config-center-3344
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
application.yml
server:
port: 3344
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
uri: git@github.com:zzyybs/cloud-config.git #github仓库上面的git仓库名字
##搜索目录
search-paths:
- cloud-config
#读取分支
label: master
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka #注册进eureka
ConfigCenterMain3344
@SpringBootApplication
@EnableConfigServer
public class ConfigCenterMain3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain3344.class,args);
}
}
http:localhost:3344/master/config-dev.yml
label : 分支
name:服务名
profiles: 环境 dev / test / prod
config客户端
中心端3344
客户端3355 cloud-config-client-3355
<!--不带server了,说明是客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
bootstrap.yml
application.yml 是用户级别的资源配置项
bootstrap.yml 是系统级别,优先级更高
要将client模块下的application.yml文件改为bootstrap.yml
因为bootstrap.yml 是比application.yml先加载的
server:
port: 3355
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取 http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址
#服务注册到eureka地址
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
@RefreshScope
public class ConfigClientController {
@Value("${config.info}")
private String configInfo; //要访问的3344上的信息
@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}
分布式配置的动态刷新问题**
刷新3344 ,ConfigServer立马响应更新
刷新3355,ConfigSClient客户端没有任何响应,重启后才有响应
cloud-config-client-3366
1.pom 引入actuator 监控
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.修改YML 暴露监控端口
#暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
- @RefreshScope业务类Controller修改
- curl -X POST “http://localhost:3366/actuator/refresh”
避免了服务重启
自动刷新
->消息总线