一、场景
在分布式的环境下,多个服务之间的调用难免会出现异常、超时等问题,这些问题出现的时候,为了提高用户的体验,我们不能够直接将错误的信息展示给用户,而是在出现这种情况的时候,给用户返回一个友好的提示。服务降级的作用就在这里体现了。
二、初见
定义:服务降级通俗的讲就是服务之间调用出现错误或者异常时,可以返回一个友好提示,而不是直接将异常的信息返回。
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作。说白了,就是尽可能的把系统资源让给优先级高的服务在真正进行讲解服务降级之前,我们需要先理解什么是"扇出"和"服务雪崩"。
(一) 扇出:多个服务之间链接调用,如:A服务调用B服务,B服务调用C服务,C服务调用其他服务,就像扇子慢慢打开的效果一样,所以称为扇出。
(二) 服务雪崩:系统服务之间的调用总会存在超时或者异常的情况,服务调用时某个微服务的调用响应时间过长或者不可用,则它会占越来越多系统资源,进而引起系统崩溃,这个就是所谓的雪崩效应。
(三)实现服务降级技术:SpringCloud中提供了Hystrix组件来实现服务降级功能,它是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统不可避免出现调用超时、失败,它能够保证一个依赖出现问题的情况下,不会导致整个服务失败,避免了级联故障,提供分布式系统的弹性。:
三、出现场景
- 服务请求超时
- 服务请求异常
- 服务熔断触发服务降级
- 线程池/信号量打满导致服务降级
服务降级可以在服务提供方和服务消费方同时使用,但是一般是用于服务消费方,在使用服务降级的时候需要注意的事项如下:
1、使用时避免方法膨胀: 如果有多个方法需要使用的时候,可以将服务降级的兜底方法放在类上,使用DefaultProperties注解指定defaultFallback即可,然后在需要服务降级的方法上面添加HystrixCommand注解,而不需要在每个方法中都指定HystrixCommand中fallback,这样方法多了会出现方法膨胀。
2、服务降级注解defaultFallback指定的方法中,必须跟使用服务降级的方法有相同的参数,否则在执行的时候回报错。
3、服务降级在消费端: 如果消费端是通过feign进行访问服务提供方,可以通过创建一个实现绑定服务服务消费者接口的实现类,将服务降级的方法抽离出来,然后再指定FeignClient的fallback属性的值指定为实现类的class对象,这样就可以实现低耦合的服务降级,我们的controller便不需要添加服务降级的注解
四、实现案例
1、实现步骤
- Pom文件引入Hystrix依赖(消费者、服务者两个微服务都需要)
- 在application项目配置文件中开启FeignHystrix功能(只需要在服务消费者端开启即可)
- 创建绑定服务消费者的实现类
- 启动激活Hystrix组件
(1):服务消费者Pom文件引入Hystrix依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring_parent</artifactId>
<groupId>com.elvis.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-hystrix-payment8003</artifactId>
<dependencies>
<!-- hystrix服务降级-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- hystrix2.0版本不包含HystrixCommand注解,所以需要引入该依赖-->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</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.mybatis.spring.boot</groupId>-->
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>druid-spring-boot-starter</artifactId>-->
<!-- <!–如果没写版本,从父层面找,找到了就直接用,全局统一–>-->
<!-- <version>${druid.spring.boot.starter.version}</version>-->
<!-- </dependency>-->
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>provided</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>
</project>
(2):服务消费者在application项目配置文件中开启FeignHystrix功能
server:
port: 8009
spring:
application:
name: cloud-consumer-hystrix-order80
eureka:
client:
register-with-eureka: true # 是否注册到eureka中
fetch-registry: true # 是否获取注册到eureka的服务信息
service-url:
defaultZone: http://eureka7001.com:7001/eureka/ # eureka注册的地址
instance:
prefer-ip-address: true # 鼠标移动上去显示ip地址
instance-id: payment8003 # 服务的名称
#feign:
# hystrix:
# enabled: true # 开启feign的hystrix功能
(3):创建绑定服务消费者的实现类(服务消费方代码)
package com.springcloud.service;
import org.springframework.stereotype.Component;
/**
* 针对ConsumerService中绑定的服务的方法的服务降级类
*/
// 注入spring容器
@Component
public class ConsumerHystrixService implements ConsumerService {
@Override
public String ok() {
return "ok请求方法失败,进入服务熔断方法!";
}
@Override
public String timeOut(Integer sleepTime) {
return "timeOut请求方法失败,进入服务熔断方法!";
}
@Override
public String breakOut() {
return null;
}
}
// 绑定服务提供方的接口
package com.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
// 绑定服务名称为CLOUD-PROVIDER-HYSTRIX-PAYMENT8003的服务
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT8003",fallback = ConsumerHystrixService.class)
public interface ConsumerService {
@RequestMapping("/payment/ok")
public String ok();
@RequestMapping("/payment/timeout/{sleepTime}")
public String timeOut(@PathVariable("sleepTime") Integer sleepTime);
@RequestMapping("/payment/breakout")
public String breakOut();
}
// 服务消费者的启动类
package com.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
// 激活Hystrix
@EnableHystrix
// 激活Eureka客户端
@EnableEurekaClient
// 激活OpenFeign
@EnableFeignClients
public class HystrixConsumer80Application {
public static void main(String[] args) {
SpringApplication.run(HystrixConsumer80Application.class,args);
}
}
// 服务访问Controller
package com.springcloud.controller;
import com.springcloud.service.ConsumerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class ConsumerController {
@Autowired
private ConsumerService consumerService;
@RequestMapping("/consumer/payment/ok")
public String ok(){
String ok = consumerService.ok();
//int i = 10/0;
log.info(Thread.currentThread().getName()+",消费端获取到的信息:"+ok);
return ok;
}
@RequestMapping("/consumer/payment/timeout/{sleepTime}")
public String timeOut(@PathVariable("sleepTime") Integer sleepTime){
String s = consumerService.timeOut(sleepTime);
log.info(Thread.currentThread().getName()+",消费端获取到的信息:"+s);
return s;
}
@RequestMapping("/consumer/payment/breakout")
public String breakOut(){
try{
String s = consumerService.breakOut();
return s;
}catch (Exception e){
log.info(e.getMessage());
}
return "breakOut请求失败";
}
}
(4):服务提供方代码
// 配置文件
server:
port: 8003
spring:
application:
name: cloud-provider-hystrix-payment8003
eureka:
client:
fetch-registry: true # 从eureka中获取注册的信息
register-with-eureka: true # 注册到eureka中
service-url:
defaultZone: http://eureka7001.com:7001/eureka/ # eureka注册的地址
#defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ # eureka注册的地址
instance:
prefer-ip-address: true # 鼠标移动上去显示ip地址
instance-id: payment8003 # 服务的名称
// pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring_parent</artifactId>
<groupId>com.elvis.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-hystrix-payment8003</artifactId>
<dependencies>
<!-- hystrix服务降级-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- hystrix2.0版本不包含HystrixCommand注解,所以需要引入该依赖-->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</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.mybatis.spring.boot</groupId>-->
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba</groupId>-->
<!-- <artifactId>druid-spring-boot-starter</artifactId>-->
<!-- <!–如果没写版本,从父层面找,找到了就直接用,全局统一–>-->
<!-- <version>${druid.spring.boot.starter.version}</version>-->
<!-- </dependency>-->
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>provided</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>
</project>
// 服务提供者的controller层
package com.springcloud.controller;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.springcloud.service.PaymentService;
import com.springcloud.service.impl.PaymentServiceImpl;
import javafx.beans.DefaultProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
// 设置全局默认的服务降级响应
@DefaultProperties(defaultFallback = "globalHystrixFallback")
public class PaymentController {
@Autowired
private PaymentService paymentService;
// 直接反馈信息
@RequestMapping("/payment/ok")
// 不指定fallback属性时,默认使用DefaultProperties中指定的降级响应方法
@HystrixCommand
public String ok(){
int age = 10/0;
String ok = paymentService.ok();
return ok;
}
// 请求后睡眠指定时间再反馈
@RequestMapping("/payment/timeout/{sleepTime}")
// 注意: fallbackMethod指定的方法的参数列表要跟当前方法的参数列表的参数一样,否则会报错
@HystrixCommand(fallbackMethod = "specialHystrixFallback",commandProperties = {
// 设置最大的等待时间,超过这个时间获取反馈则认为是超时了
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String timeOut(@PathVariable("sleepTime") Integer sleepTime){
String timeOut = paymentService.timeOut(sleepTime);
return timeOut;
}
// 全局通用的Hystrix备选反馈
public String globalHystrixFallback(){
String logs = "全局通用的服务降级备选处理反馈!";
log.info(logs);
return logs;
}
// 特殊指定的Hystrix备选反馈
public String specialHystrixFallback(Integer abc){
String logs = "特殊指定的服务降级备选处理反馈!";
log.info(logs);
return logs;
}
@RequestMapping("/payment/breakout")
public String breakOut(){
return "测试服务宕机";
}
}
// 业务接口(面向接口开发)
package com.springcloud.service;
public interface PaymentService {
String ok();
String timeOut(Integer sleepTime);
String breakOut(Integer sleepTime);
}
// 业务实现类
package com.springcloud.service.impl;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class PaymentServiceImpl implements PaymentService {
@Override
public String ok(){
return Thread.currentThread().getName()+",请求ok服务一切正常";
}
@Override
public String timeOut(Integer sleepTimes){
int sleepTime = sleepTimes;
try {
Thread.sleep(sleepTime);
}catch (Exception e){
log.error(e.getLocalizedMessage());
}
return Thread.currentThread().getName()+",请求timeOut服务正常";
}
@Override
public String breakOut(Integer sleepTime) {
return "测试服务器宕机";
}
}
// 服务提供方启动类
package com.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
@EnableEurekaClient
// 激活Hystrix
@EnableHystrix
public class HystrixPayment8003Application {
public static void main(String[] args) {
SpringApplication.run(HystrixPayment8003Application.class,args);
}
}
(5):实现结果
五、出现场景
服务降级是分布式开发中防止程序出现异常时,不直接返回错误信息给使用者,而是返回指定的友好提示,提高了用户的使用体验。
"纸上得来终觉浅,觉知此事要恭行",无论文章看着再简单,我们也需要亲自实践过才能够真正的掌握,希望大家看完文章后可以亲自实践
作者:IT学习日记v
链接:https://juejin.cn/post/7109854199659102239
来源:稀土掘金
网友评论