Ribbon负载均衡服务调用、服务降级
LB(负载均衡)
集中式LB
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方.
进程内LB
将逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器.
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取服务提供方的地址.
就是 负载均衡+RestTemplate调用.
负载均衡演示
Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例.
架构:
Ribbon工作时分成两步
第一步先选择EurekaServer,它优先选择在同一区域内负载较少的server
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址.
其中Ribbon提供了多种策略:比如轮询,随机和根据响应时间加权.
新版的eureka整合了Ribbon
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
RestTemplate
getForObject和getForEntity:
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
@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()) {
log.info(entity.getStatusCode()+"\t"+entity.getHeaders());
return entity.getBody();
} else {
return new CommonResult<>(444,"操作失败");
}
}
IRule:根据特定算法中从服务列表中选取一个要访问的服务
修改cloyud-consumer-order80
@SpringBootApplication里有@ComponentScan注解,不能和主启动类放在同一包下
新建package com.kayleh.myrule
新建MySelfRule
package com.kayleh.myrule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: Wizard
* @Date: 2020/8/7 14:33
*/
@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);
}
}
测试
http://localhost/consumer/payment/get/1
Ribbon负载均衡算法
Ribbon手写轮询算法
OpenFeign
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。
它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
feign和OpenFeign
OpenFeign服务调用
接口+注解
新建cloud-consumer-feign-order80
pom.xml
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<groupId>com.kayleh.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
主启动类:
package com.kayleh.springcloud;
/**
* @Author: Wizard
* @Date: 2020/8/4 11:20
*/
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class, args);
}
}
Service:
package com.kayleh.springcloud.service;
@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
Controller
package com.kayleh.springcloud.controller;
@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);
}
}
测试
http://localhost/consumer/payment/get/1
OpenFeign超时控制
修改cloud-provider-payment8001的controller,添加
@GetMapping(value = "/payment/feign/timeout")
public String getPaymentFeignTimeout() {
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
cloud-consumer-feign-order80的PaymentFeignService接口:
@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
@GetMapping(value = "/payment/feign/timeout")
public String getPaymentFeignTimeout();
}
cloud-consumer-feign-order80的OrderFeignController,添加:
@GetMapping(value = "/consumer/payment/feign/timeout")
public String getPaymentFeignTimeout() {
//客户端默认等待1秒钟
return paymentFeignService.getPaymentFeignTimeout();
}
访问
http://localhost:8001/payment/feign/timeout
http://localhost/consumer/payment/feign/timeout #报错
OpenFeign默认支持Ribbon
OpenFeign默认等待1秒钟,超过后报错
默认Feign客户端只等待1秒钟,但是服务端处理需要等待超过1秒钟,导致Feign客户端不想等待了,直接返回报错.
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制.
yml文件中开启配置
修改cloud-consumer-feign-order80的yaml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
#设置feign客户端超时时间(OpenFeign默认支持Ribbon)
ribbon:
#指的是建立连接所用的时间,使用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务端读取到可用资源所用的时间
ConnectTimeout: 5000
再测试
http://localhost/consumer/payment/feign/timeout
OpenFeign日志打印功能
Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。说白了就是对Feign接口的调用情况进行监控和输出
在cloud-consumer-feign-order80的FeignConfig中添加
package com.kayleh.springcloud.config;
import feign.Logger;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @Author: Wizard
* @Date: 2020/8/4 12:54
*/
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
修改yaml文件
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
#设置feign客户端超时时间(OpenFeign默认支持Ribbon)
ribbon:
#指的是建立连接所用的时间,使用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务端读取到可用资源所用的时间
ConnectTimeout: 5000
logging:
level:
# feign以什么级别监控哪个接口
com.kayleh.springcloud.service.PaymentFeignService: debug
即可开启日志功能。
Hystrix服务调用
案例:
新建cloud-provider-hystrix-payment8001
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<groupId>com.kayleh.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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>
</dependency>
</dependencies>
yml
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
service
package com.kayleh.springcloud.service;
import com.kayleh.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentService {
public String paymentInfo_OK(Integer id) {
return "线程池: " + Thread.currentThread().getName() + " paymentInfo_OK,id: " + id + "\t" + "O(∩_∩)O哈哈~";
}
public String paymentInfo_TimeOut(Integer id) {
//暂停3秒钟
int timeNumber = 3;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: " + Thread.currentThread().getName() + " paymentInfo_TimeOut,id: " + id + "\t" + "o(╥﹏╥)o哭" + "耗时" + timeNumber + "秒钟";
}
}
controller
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@PostMapping(value = "/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfo_OK(id);
log.info("----------result:" + result);
return result;
}
@GetMapping(value = "/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfo_TimeOut(id);
log.info("----------result:" + result);
return result;
}
}
测试
localhost:8001/payment/hystrix/ok/{id}
localhost:8001/payment/hystrix/timeout/{id}
以上述为根基平台,从正确 –>错误 –> 降级熔断 –> 恢复
Jmeter压测测试
开启 Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut
http://localhost:8001/payment/hystrix/timeout/1
这时访问
http://localhost:8001/payment/hystrix/ok/1
访问开始变慢了
这只是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死。
新建cloud-consumer-feign-hystrix-order80
pom.xml
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<groupId>com.kayleh.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
# defaultZone: http://localhost:7001/eureka/
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
service接口,调用cloud-provider-hystrix-payment8001里的方法
package com.kayleh.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @Author: Wizard
* @Date: 2020/8/7 20:54
*/
@Component
@FeignClient("CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@GetMapping(value = "/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping(value = "/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
controller
package com.kayleh.springcloud.controller;
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentFeignService;
@GetMapping(value = "/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
return paymentFeignService.paymentInfo_OK(id);
}
@GetMapping(value = "/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
return paymentFeignService.paymentInfo_TimeOut(id);
}
}
测试
http://localhost/consumer/payment/hystrix/ok/1
导致原因
解决
超时导致服务器变慢(转圈)
- 超时不在等待
出错(宕机或程序运行出错)
- 出错要有兜底
方法:
对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
对方服务(8001)宕机了,调用者(80)不能一直卡死等待,必须有服务降级
对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级
服务降级
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
哪些情况会触发降级
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
降级配置
@HystrixCommand
从Hystrix-8001找问题,设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback
修改8001的Service
package com.kayleh.springcloud.service;
@Service
public class PaymentService {
/**
* 正常访问
*/
public String paymentInfo_OK(Integer id) {
return "线程池: " + Thread.currentThread().getName() + " paymentInfo_OK,id: " + id + "\t" + "O(∩_∩)O哈哈~";
}
/**
* 超时访问,演示降级
*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentInfo_TimeOut(Integer id) {
//暂停3秒钟
int timeNumber = 5;
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 "调用支付接口超时或异常:\t" + "\t当前线程池名字" + Thread.currentThread().getName();
}
}
一旦调用服务方法失败后并抛出错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中指定的方法。
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"
这行代码表示3秒以内都是正常的逻辑。
修改8001的主启动类,开启降级
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}
测试:
http://localhost:8001/payment/hystrix/timeout/1
注释超时异常,制造 10/0 的异常也会降级. 当前服务不可用了,做服务降级,兜底的方案都是paymentInfo_TimeOutHandler.
让支付模块也支持Hystrix
修改cloud-consumer-feign-hystrix-order80的yaml: 添加
feign:
hystrix:
enabled: true
修改启动类
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}
修改Controller(客户端的等待是1.5秒)
package com.kayleh.springcloud.controller;
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentFeignService;
@GetMapping(value = "/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
return paymentFeignService.paymentInfo_OK(id);
}
@GetMapping(value = "/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
return paymentFeignService.paymentInfo_TimeOut(id);
}
public String paymentTimeOutFallbackMethod(Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒钟后再试试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
}
测试:
http://localhost/consumer/payment/hystrix/timeout/1
如果修改paymentInfo_TimeOut超时错误为10/0,也会进入paymentTimeOutFallbackMethod
全局服务降级
目前问题:
每个业务方法对应一个兜底的方法,代码膨胀
统一和自定义的分开
解决问题:
@DefaultProperties(defaultFallback = “”)
修改cloud-consumer-feign-hystrix-order80的OrderHystrixController:
添加:@DefaultProperties(defaultFallback = “payment_Global_FallbackMethod”)
和方法payment_Global_FallbackMethod
package com.kayleh.springcloud.controller;
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentFeignService;
@GetMapping(value = "/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
return paymentFeignService.paymentInfo_OK(id);
}
@GetMapping(value = "/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) {
return paymentFeignService.paymentInfo_TimeOut(id);
}
public String paymentTimeOutFallbackMethod(Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒钟后再试试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
//全局fallback方法
public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后再试,(⊙o⊙)…";
}
}
再测试
http://localhost/consumer/payment/hystrix/timeout/1
---------------------
Global异常处理信息,请稍后再试,(⊙o⊙)…
通配服务降级FeignFallback
修改cloud-consumer-feign-hystrix-order80,根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里的方法进行异常处理。
访问异常就访问实现类下的方法。
新建实现类
package com.kayleh.springcloud.service;
/**
* @Author: Wizard
* @Date: 2020/8/8 22:54
*/
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "----------PaymentFallbackService fall back,o(╥﹏╥)o\tpaymentInfo_OK";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "----------PaymentFallbackService fall back,o(╥﹏╥)o\tpaymentInfo_TimeOut";
}
}
yml:添加
feign:
hystrix:
enabled: true
接口修改注解:
@FeignClient(value = “CLOUD-PROVIDER-HYSTRIX-PAYMENT”, fallback = PaymentFallbackService.class)
package com.kayleh.springcloud.service;
/**
* @Author: Wizard
* @Date: 2020/8/7 20:54
*/
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping(value = "/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping(value = "/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
测试:
http://localhost/consumer/payment/hystrix/ok/1 -正常访问
关掉微服务8001
再访问http://localhost/consumer/payment/hystrix/ok/1 -fallback
此时服务端provider已经宕机,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器。
服务熔断
类比保险丝达到最大服务器访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示,break
保险丝. 服务降级–>进而熔断–>恢复调用链路
熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后,恢复调用链路。
在SpringCloud框架里,熔断机制通过hystrix实现。hystrix会监视微服务间调用的状况,
当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand.
案例:
修改PaymentHystrixMain8001
修改PaymentService , 添加
/**
* 服务熔断
*/
@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();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能负数,请稍后再试,o(╥﹏╥)o id:" + id;
}
PaymentController,添加
// -----服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result:" + result);
return result;
}
测试
http://localhost:8001/payment/circuit/1
http://localhost:8001/payment/circuit/-1
熔断类型
熔断打开
请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态.
熔断关闭
熔断关闭不会对服务进行熔断
熔断半开
部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断.
断路器在什么情况下开始起作用:
断路器打开或关闭的条件:
断路器打开之后:
Hystrix图形化DashBoard
新建cloud-consumer-hystrix-dashboard9001
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency><!--引入自己定义的api通用包,可以使用Payment支付Entity-->
<groupId>com.kayleh.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-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-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
yml
server:
port: 9001
主启动类
/**
* @Author: Wizard
* @Date: 2020/8/3 18:10
*/
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboard9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboard9001.class, args);
}
}
修改cloud-provider-hystrix-payment8001的主启动类:添加
/**
* @Author: Wizard
* @Date: 2020/8/3 18:10
*/
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
/**
* 此配置是为了服务监控而配置,与服务容错本身无关,SpringCloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream"
* 只要在自己的项目配置上下面的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;
}
}
测试
http://localhost:9001/hystrix
配置9001监控8001
测试
http://localhost:8001/payment/circuit/1
http://localhost:8001/payment/circuit/-1
七色:
一圈:
一线:
整个图:
流程图: