降级是指,当请求超时、资源不足等情况发生时进行服务降级处理,不调用真实服务逻辑,而是快速失败直接返回托底数据,保证服务链条的完整,避免服务雪崩。
解决服务雪崩效应,都是避免客户端请求服务端时,出现服务调用错误或网络问题。所有的处理手法都是在客户端中实现。
超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。保证服务出现问题整个项目还可以继续运行。
本文示例是在OpenFeign教程上做修改,链接地址:https://blog.csdn.net/liwenyang1992/article/details/126167210
客户端添加依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
<version>2.2.10.RELEASEversion>
dependency>
注意:需要在启动类添加@EnableHystrix注解才能使hystrix生效。
降级示例
下面示例介绍通过@HystrixCommand注解实现降级方法:
package com.lwy.it.service;
import com.lwy.it.feign.BookFeignService;
import com.lwy.it.vo.BookVO;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class ClientBookService {
@Autowired
private BookFeignService bookFeignService;
/**
* 服务降级的注解 @HystrixCommand
* fallbackMethod:指降级回调方法,如果说这个方法出现问题或者抛出异常的话,会执行备用方法
*/
@HystrixCommand(fallbackMethod = "getBookFallback")
public BookVO getBook(int id) {
BookVO bookVO = bookFeignService.getBookById(id);
return bookVO;
}
/**
* 回调方法(备用方法),参数和返回值要相同
*/
private BookVO getBookFallback(int id) {
log.info("执行回调方法,参数:{}", id);
return new BookVO();
}
}
下面再提供一个Controller方法用于测试验证。
import com.lwy.it.service.ClientBookService;
import com.lwy.it.vo.BookVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ClientBookController {
@Autowired
private ClientBookService bookService;
@GetMapping("/hello")
public BookVO hello() {
// return bookService.getBook(0); 由于服务端没有这条数据,会导致500异常,会调用降级方法
return bookService.getBook(1);
}
}
思考:如果使用try catch包住异常是否会执行备用方法(不会,failed and fallback failed)
import com.lwy.it.feign.BookFeignService;
import com.lwy.it.vo.BookVO;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class ClientBookService {
@Autowired
private BookFeignService bookFeignService;
/**
* 服务降级的注解@HystrixCommand
* fallbackMethod:指降级回调方法,如果说这个方法出现问题或者抛出异常的话,会执行备用方法
*/
@HystrixCommand(fallbackMethod = "getBookFallback")
public BookVO getBook(int id) {
BookVO bookVO = null;
try {
bookVO = bookFeignService.getBookById(id);
} catch (Exception e) {
e.printStackTrace();
}
return bookVO;
}
/**
* 降级方法(回调方法、备用方法),返回值和参数要和原方法对应,可以定义为private
*/
private BookVO getBookFallback(int id) {
log.info("执行回调方法,参数:{}", id);
return new BookVO();
}
}
当一定时间内,异常请求比例(请求超时、网络故障、服务异常等)达到阈值时,启动熔断器,熔断器一旦启动,则会停止调用具体服务逻辑,通过fallback快速返回托底数据,保证服务链路的完整。
熔断有自动恢复机制,如:当熔断启动后,每隔5秒,尝试将新的请求发给服务提供方,如果可正常执行并返回结果,则关闭熔断器,服务恢复。如果仍然调用失败,则继续返回托底数据,熔断器持续开启状态。
降级与熔断的区别为:降级是出错了返回托底数据,而熔断是出错后如果开启了熔断将会一定时间不再访问原方法。熔断机制相当于电路的跳闸功能。例如:我们可以配置熔断策略为当请求错误比例在10s内>50%时,该服务将进入熔断状态,后续请求将都会进入fallback。通俗理解:熔断就是具有特定条件的降级,当出现熔断时在设定的时间内容就不再请求应用服务了。所以在代码上熔断和降级都是一个注解@HystrixCommand。
当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复。
保证:服务出现问题整个项目还可以继续运行。
熔断的实现是在调用远程服务的方法上增加@HystrixCommand注解。当注解配置满足则开启或关闭熔断器。
熔断示例:
import com.lwy.it.feign.BookFeignService;
import com.lwy.it.vo.BookVO;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class ClientBookService {
@Autowired
private BookFeignService bookFeignService;
/**
* HystrixPropertiesManager常用的常量:
* CIRCUIT_BREAKER_ENABLED:是否开启熔断策略,默认是true
* CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD:单位时间内,请求超时数超出则触发熔断策略,默认值为20次请求
* EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS:设置单位时间,默认是10秒,单位毫秒
* CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS:当熔断策略开启后,延迟多久尝试再次请求远程服务,默认是5秒,单位毫秒。这5秒内直接执行fallback方法,不再请求远程服务
* CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE:单位时间内,出现错误的请求百分比达到阈值,则触发熔断策略。默认为50%。
* CIRCUIT_BREAKER_FORCE_OPEN:是否强制开启熔断策略。即所有请求都返回fallback托底数据。默认值为false。
* CIRCUIT_BREAKER_FORCE_CLOSED:是否强制关闭熔断策略。即所有请求一定调用远程服务。默认为false。
*/
@HystrixCommand(fallbackMethod = "getBookFallback", commandProperties = {
// 开启熔断策略
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED, value = "true"),
// 设置单位时间内请求超时超出5次则触发熔断策略
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "5"),
// 设置单位时间为20秒
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "20000"),
// 设置容器策略开启后,5秒后尝试再次请求远程服务
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "5000"),
// 设置阈值为50%,即出现错误的请求百分比达到50%,则触发熔断策略
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "50"),
// 不强制开启熔断策略
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_FORCE_OPEN, value = "false"),
// 不强制关闭熔断策略
@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_FORCE_CLOSED, value = "false")
})
public BookVO getBook(int id) {
BookVO bookVO = bookFeignService.getBookById(id);
return bookVO;
}
/**
* 回调方法(备用方法),参数和返回值要相同
*/
private BookVO getBookFallback(int id) {
log.info("执行回调方法,参数:{}", id);
BookVO bookVO = new BookVO();
bookVO.setBookDescription("降级方法返回结果");
return bookVO;
}
}
使用jmeter工具进行10分钟压测验证,通过开启和关闭服务,可以更好的观察效果。
什么情况下使用请求合并?
在微服务架构中,我们将一个项目拆分成多个独立的项目,这些独立的项目通过远程调用来互相配合工作。但是,在高并发场景下,通信次数的增加会导致总的通信时间增加。同时,线程池的资源也是有限的,在高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题,我们可以使用Hystrix的请求合并。
请求合并的缺点?
设置请求合并之后,本来一个请求可能5ms就搞定了,但是现在必须再等10ms看看还有没有其他的请求一起的,这样一个请求的耗时就从5ms增加到15ms了。不过,如果我们要发起的命令本身就是一个高延迟的命令,那么这个时候就可以使用请求合并了,因为这个时候的时间窗的时间消耗就显得微不足道了,另外高并发也是请求合并的一个非常重要的场景。
添加@HystrixCollapser注解
被@HystrixCollapser注解标注的方法,返回类型必须为Future,使用异步方法,否则无法进行请求合并。
batchMethod:合并请求的方法,方法只能接收一个参数。如果你需要传递多个参数,那么请将它们封装成一个类参数。
scope:请求方式。分别为REQUEST(默认)、GLOBAL。REQUEST范围支队一个request请求内的多次服务请求进行合并;GLOBAL是多个应用中的所有线程的请求中的多次服务请求进行合并。
注意:请求合并需要服务端同步进行修改
隔离分为线程池隔离和信号量隔离。通过判断线程池或信号量是否已满,超出容量的请求直接降级,从而达到限流的作用。
为什么使用线程池隔离
没有线程池隔离的时候可能因为某个接口的高并发导致其它接口不可用。
使用线程池隔离,不同接口有自己独立的线程池。
即使某个线程池都被占用,也不影响其它线程。
Hystrix采用Bulkhead Partition舱壁隔离技术。舱壁隔离指的是船体内部分为多个隔舱,一旦其中某几个隔舱发生破损进水,水流不会在其它舱壁中流动,从而保证船舱依然具有足够的浮力和稳定性,降低沉船危险。
优点:
缺点:
/**
* groupKey:服务名(相同服务用一个名称,比如商品、用户等),默认值:getClass().getSimpleName()。在consumer里面为每个provider服务,设置group标识,一个group使用一个线程池
* threadPoolKey:线程池的名称:配置全局唯一标识线程池的名称,相同线程池名称的线程池是同一个,默认是分组名groupKey
* commandKey:接口(服务下面的接口,如购买商品),默认值:当前执行方法名。consumer的接口名称
* CORE_SIZE:线程池大小:并发执行的最大线程数。默认值:10
* MAX_QUEUE_SIZE:最大队列长度,默认值为-1,如果使用正数,队列将从同步队列SynchronousQueue改为阻塞队列LinkedBlockingDeque
* KEEP_ALIVE_TIME_MINUTES:线程存活时间,单位分钟。默认值1分钟
* QUEUE_SIZE_REJECTION_THRESHOLD:拒绝请求:设置拒绝请求的临界值,默认是5个。即使MAX_QUEUE_SIZE没有达到,达到QUEUE_SIZE_REJECTION_THRESHOLD该值后,请求也会被拒绝。
*/
@HystrixCommand(groupKey = "", commandKey = "", threadPoolKey = "", threadPoolProperties = {
@HystrixProperty(name = HystrixPropertiesManager.CORE_SIZE, value = "8"),
@HystrixProperty(name = HystrixPropertiesManager.MAX_QUEUE_SIZE, value = "64"),
@HystrixProperty(name = HystrixPropertiesManager.KEEP_ALIVE_TIME_MINUTES, value = "1"),
@HystrixProperty(name = HystrixPropertiesManager.QUEUE_SIZE_REJECTION_THRESHOLD, value = "100"),
})
public BookVO thread() {
BookVO bookVO = new BookVO();
bookVO.setBookDescription(Thread.currentThread().getName());
return bookVO;
}
更多学习:https://www.cnblogs.com/seifon/p/9921774.html
java.util.concurrent.Semaphore用来控制可同时并发的线程数,通过构造方法指定内部虚拟许可的数量。每次线程执行操作时先通过acquire方法获得许可,执行完毕之后再通过release方法释放许可。如果没有可用的许可,acquire方法将一直阻塞,直到其它线程释放许可。
如果采用信号量隔离技术,每接收一个请求,都是服务自身线程去直接调用依赖服务。信号量就相当于一道关卡,每个线程通过关卡后,信号量数量减去1,当为0时不再允许线程通过,而是直接执行fallback逻辑并返回,说白了仅仅做了一个限流。
同一个方法不能即是线程池隔离又是信号量隔离。
代码示例:
/**
* EXECUTION_ISOLATION_STRATEGY:隔离策略配置项,只有两种默认值THREAD(线程池隔离)和SEMAPHORE(信号量隔离),默认值THREAD(线程池隔离)
* EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS:超时时间,在THREAD模式下,达到超时时间,自动中断;在SEMAPHORE模式下,会等待执行完成后,再去判断是否超时。默认值1000ms
* EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT:是否打开超时线程中断,仅在THREAD模式下有效,默认值TRUE
* EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS:信号量最大并度,仅在SEMAPHORE模式下有效,默认值10
* FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS:fallback最大并发度,仅在SEMAPHORE模式下有效,默认值10
*/
@HystrixCommand(commandProperties = {
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "10"),
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT, value = "FALSE"),
@HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "10"),
@HystrixProperty(name = HystrixPropertiesManager.FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "10")
// 超过信号量数量执行降级方法
}, fallbackMethod = "semaphoreFallback")
public BookVO semaphore() {
BookVO bookVO = new BookVO();
bookVO.setBookDescription("信号量隔离");
// 为了展示效果,睡眠800ms
try {
Thread.sleep(800L);
} catch (InterruptedException exception) {
log.error("Exception:{}", exception);
}
log.info("执行了信号量隔离方法");
return bookVO;
}
public BookVO semaphoreFallback() {
BookVO bookVO = new BookVO();
bookVO.setBookDescription("信号量隔离Fallback方法");
log.info("执行了信号量隔离Fallback方法");
return bookVO;
}
编写Controller,通过jmeter调用
@GetMapping("/semaphore")
public BookVO semaphore() {
return bookService.semaphore();
}
# 开启OpenFeign的Hystrix配置
feign.circuitbreaker.enabled=true
通过fallback参数指定
import com.lwy.it.config.FeignConfiguration;
import com.lwy.it.vo.BookVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* name 指定调用rest接口所对应的服务名
* path 指定调用rest接口所在Controller指定的@RequestMapping
* configuration 局部配置,让调用的微服务生效,在@FeignClient注解中指定使用的配置类
* fallback:配置Hystrix降级实现类
*/
@FeignClient(name = "demo-server", path = "/server/book", configuration = FeignConfiguration.class, fallback = BookFeignServiceFallback.class)
public interface BookFeignService {
// 声明需要调用rest接口对应的方法
@GetMapping("/list")
List<BookVO> getAllBooks();
@GetMapping("/{bookId}")
BookVO getBookById(@PathVariable("bookId") Integer id);
/**
* 在OpenFeign中方法参数前如果没有注解,默认添加@RequestBody注解,最多只能有一个不带注解的参数
* 普通表单参数必须添加@RequestParam注解,如果变量名和参数名称对应可以不写name
*/
@GetMapping("/login")
String login(@RequestParam("username") String username, @RequestParam("password") Integer password);
@PutMapping("/saveBook")
BookVO saveBook(@RequestBody BookVO bookVO);
@GetMapping("/param")
BookVO getBook(@RequestParam int bookId, @RequestParam String bookName, @RequestParam String bookDescription, @RequestParam double bookPrice);
}
import com.lwy.it.vo.BookVO;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
/**
* 降级实现类
*/
@Component
public class BookFeignServiceFallback implements BookFeignService {
@Override
public List<BookVO> getAllBooks() {
return Collections.emptyList();
}
@Override
public BookVO getBookById(Integer id) {
return new BookVO();
}
@Override
public String login(String username, Integer password) {
return "OK";
}
@Override
public BookVO saveBook(BookVO bookVO) {
return bookVO;
}
@Override
public BookVO getBook(int bookId, String bookName, String bookDescription, double bookPrice) {
BookVO bookVO = new BookVO();
bookVO.setBookId(bookId);
bookVO.setBookName(bookName);
bookVO.setBookPrice(bookPrice);
bookVO.setBookDescription(bookDescription);
return bookVO;
}
}
编写Controller调用,分别开启和关闭服务提供方进行验证测试
@GetMapping("/all")
public List<BookVO> getAllBooks() {
return bookFeignService.getAllBooks();
}
服务A调用服务B,如果在A中添加请求缓存,第一次请求后走缓存了,就不再访问服务B了,即使出现大量请求时,也不会对B产生高负载。
请求缓存可以使用Spring Cache实现。减少对应用服务的调用。