技术文章
当前位置:首页 > Java技术文章 > Spring Cloud Hystrix 服务容错保护的原理

Spring Cloud Hystrix 服务容错保护的实现方法

  • 发布时间:
  • 作者:码农之家原创
  • 点击:176

这篇文章主要知识点是关于Spring、Cloud、Hystrix、服务容错保护、详解spring cloud hystrix缓存功能的使用 的内容,如果大家想对相关知识点有系统深入的学习,可以参阅以下java相关的电子书

Spring Cloud微服务架构进阶
  • 类型:微服务大小:219.4 MB格式:PDF出版:机械工业出版社作者:朱荣鑫
立即下载

更多Java相关的学习资源可以参阅 Java电子书程序设计电子书 等栏目。

Spring Cloud Hystrix 服务容错保护的原理

一、Hystrix 是什么

​ 在微服务架构中,我们将系统拆分成了若干弱小的单元,单元与单元之间通过HTTP或者TCP等方式相互访问,各单元的应用间通过服务注册与订阅的方式相互依赖。由于每个单元都在不同的进程中运行,依赖 远程调用 的方式执行,这样就可能引起因为网速变慢或者网络故障导致请求变慢或超时,若此时调用方的请求在不断增加,最后就会因等待出现故障的依赖方响应形成任务积压,最终导致自身服务的瘫痪。

Hystrix 是Netflix 中的一个组件库,它隔离了服务之间的访问点,阻止了故障节点之间可能会引起的雪崩效应,并提供了后备选项。

​ 在微服务架构中,存在着许多的服务单元,若单一节点的故障,就很容易因为依赖关系而引发故障的蔓延,最终导致整个生态系统的瘫痪。为了解决这样的问题,产生了 断路器 等一系列的保护机制措施。

​ 在 分布式架构中 ,断路器模式的作用也是类似的,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。

雪崩效应

​ 雪崩效应就像是水滴石穿,蝴蝶效应一样,是指微小的事物随着时间的推移,会变得越来越巨大,从而对整个环境造成影响的现象。例如:在生态系统中,某一类物种的灭绝可能对整个生态系统造成不了太大的损失,但是这类物种的灭绝可能会引发其他物种的死亡,其他物种的灭绝又会影响另外一种物种的灭亡,就像雪球越滚越大,最终会导致整个生态系统的崩溃。

Spring Cloud Hystrix 服务容错保护的原理实现

如上图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。

 雪崩效应产生场景

流量激增 : 比如异常流量,用户重试导致系统负载升高;

缓存刷新 : 假设A为 client 端,B为 Server 端,假设A系统请求都流向B系统,请求超出了B系统的承载能力,就会造成B系统崩溃

连接未释放 : 代码循环调用的逻辑问题,资源未释放引起的内存泄漏等问题;

硬件故障 : 比如宕机,机房断电等

线程同步等待 : 系统间经常采用同步服务调用模式,核心服务和非核心服务共用一个线程池和消息队列。如果一个核心业务线程调用非核心业务线程,这个非核心线程交由第三方系统完成,当第三方系统本身出现问题,导致核心线程阻塞,一直处于等待状态,而进程间的调用是有超时限制的,最终这条线程将断掉,也可能引发雪崩;

常见解决方案

​ 针对上述的雪崩问题,每一条都有一个自己的解决方案,但是任何一个解决方案能够应对所有场景

  • 针对流量激增,采用自动扩容以应对流量激增,或者在负载均衡器上安装限流模块
  • 针对缓存刷新,参考Cache应用的服务过载案例研究
  • 针对硬件故障,采用多机房灾备,跨机房路由
  • 针对同步等待,采用线程隔离,熔断器等机制

通过实践发现,线程同步等待是最常见引发的雪崩效应的场景。

二、Hystrix断路器搭建

​ 在开始使用Spring Cloud Hystrix断路器之前,我们先用之前实现的一些内容作为基础,构建一个如下图所示的服务调用关系:

Spring Cloud Hystrix 服务容错保护的原理实现

如图所示,上面需要的角色有三个,服务有四个

  • ribbon-connsumer: ribbon消费者,消费server-provider提供的服务
  • server-provider: 服务提供者,提供服务供消费者消费(有点像父母默默的付出一样),启动两个实例,还记得怎么启动吗?—server.port 启动
  • eureka-server: eureka注册中心,提供最基本的订阅发布功能。消费者和服务提供者都需要往注册中心注册自己

​ 依次启动上面的四个服务,发现注册中心已经成功注册了四个服务(包括自己)

Spring Cloud Hystrix 服务容错保护的原理实现

​调用http://localhost:9000/ribbon-consumer 发现能够通过Ribbon进行远端调用

在未加入断路器之前,关闭ribbon-consumer 的连接,再次调用http://localhost:9000/ribbon-consumer,发现服务无法提供(使用Postman 测试)

Spring Cloud Hystrix 服务容错保护的原理实现

下面开始引入Hystrix

在ribbon-consumer 工程的pom.xml的dependency节点下引入spring-cloud-starter-hystrix依赖

在ribbon-consumer 工程的 主加载类 中添加 @EnableCircuitBreaker 开启断路器的功能

注意:这里也可以使用@SpringCloudApplication注解来修饰应用主类,具体定义如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {}

SpringCloudApplication 注解上有@EnableCircuitBreaker 注解,用来开启断路器的功能,其他主要注解是@SpringBootApplication ,这个注解是SpringBoot的启动类注解, @EnableDiscoveryClient该注解可以发现Eureka注册中心

改造消费方式,新增 HystrixService 类,并且注入 RestTemplate 实例,然后,将在RibbonController中对RestTemplate 的使用迁移到hystrixService方法中,最后,在hystrixService上添加@HystrixCommand注解来指定回掉方法。

 // HystrixService
  @Service
 public class HystrixService {

   @Resource
   RestTemplate restTemplate;
      
    // 指定回掉方法是下面的hystrixCallback
   @HystrixCommand(fallbackMethod = "hystrixCallBack")
   public String hystrixService(){
     return restTemplate.getForEntity("http://server-provider/hystrix",String.class).getBody();
   }

   public String hystrixCallBack(){
     return "error";
   }
 }

服务提供者 的业务非常简单,具体代码如下

  @RequestMapping(value = "/hystrix", method = RequestMethod.GET)
  public String hystrix(){
    return "hystrix";
  }

下面来验证一下通过断路器的回掉实现,重启之前关闭的8081端口,恢复成为四个服务的状态,并确保http://localhost:9000/ribbon-consumer/ 能够提供服务,并且以轮询的方式循环访问8081 和 8082 端口的服务。此时断开8081端口,发现页面上展示的不再是 hystrix ,而是"error",而另一个服务是正常能够打印。

 三、断路器优化

​ 经过以上服务的搭建,相信你已经能够搭建出来最基本的Hystrix熔断器,并且实现了服务熔断机制,下面就来对断路器做一下简单的优化,来模拟 服务阻塞(长时间未响应) 的情况。

优化 server-provider 代码如下:

 @RequestMapping(value = "/hystrix", method = RequestMethod.GET)
  public String hystrix() throws InterruptedException {
    ServiceInstance serviceInstance = discoveryClient.getLocalServiceInstance();
    // 让线程等待几秒钟
    int sleepTime = new Random().nextInt(3000);
    Thread.sleep(sleepTime);
    System.out.println("weak up!!!");
    log.info("sleepTime = " + sleepTime);
    return "hystrix";
  }

依次启动所有的服务,在主页上访问 http://localhost:9000/ribbon-consumer ,多次刷新主页,发现error 和 hystrix 是交替出现的,这是为何?

因为hystrix断路器的 默认超时时间 是2000毫秒,所以这里采用了0 - 3000 的随机数,也就是访问请求在 0 -2000 毫秒内是不超时的,不会触发断路器,而> 2000 毫秒是超市的,默认会触发断路器。

详解spring cloud hystrix缓存功能的使用

hystrix缓存的作用是

- 1.减少重复的请求数,降低依赖服务的返回数据始终保持一致。
- 2.==在同一个用户请求的上下文中,相同依赖服务的返回数据始终保持一致==。
- 3.请求缓存在run()和construct()执行之前生效,所以可以有效减少不必要的线程开销。

1 通过HystrixCommand类实现

1.1 开启缓存功能

继承HystrixCommand或HystrixObservableCommand,覆盖getCacheKey()方法,指定缓存的key,开启缓存配置。

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixRequestCache;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault;
import com.szss.demo.orders.vo.UserVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestTemplate;

public class UserCacheCommand extends HystrixCommand<UserVO> {
  private static final Logger LOGGER = LoggerFactory.getLogger(UserCacheCommand.class);

  private static final HystrixCommandKey GETTER_KEY= HystrixCommandKey.Factory.asKey("CommandKey");
  private RestTemplate restTemplate;
  private String username;

  public UserCacheCommand(RestTemplate restTemplate, String username) {
    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("userCacheCommand")).andCommandKey(GETTER_KEY));
    this.restTemplate = restTemplate;
    this.username = username;
  }

  @Override
  protected UserVO run() throws Exception {
    LOGGER.info("thread:" + Thread.currentThread().getName());
    return restTemplate.getForObject("http://users-service/user/name/{username}", UserVO.class, username);
  }

  @Override
  protected UserVO getFallback() {
    UserVO user = new UserVO();
    user.setId(-1L);
    user.setUsername("调用失败");
    return user;
  }

  @Override
  protected String getCacheKey() {
    return username;
  }

  public static void flushCache(String username){
    HystrixRequestCache.getInstance(GETTER_KEY, HystrixConcurrencyStrategyDefault.getInstance()).clear(username);
  }
}

1.2 配置HystrixRequestContextServletFilter

通过servlet的Filter配置hystrix的上下文。

import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(filterName = "hystrixRequestContextServletFilter",urlPatterns = "/*",asyncSupported = true)
public class HystrixRequestContextServletFilter implements Filter {
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HystrixRequestContext context = HystrixRequestContext.initializeContext();
    try {
      chain.doFilter(request, response);
    } finally {
      context.shutdown();
    }
  }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {

  }

  @Override
  public void destroy() {

  }
}

在不同context中的缓存是不共享的,还有这个request内部一个ThreadLocal,所以request只能限于当前线程。

1.3 清除失效缓存

继承HystrixCommand或HystrixObservableCommand,在更新接口调用完成后,清空缓存。

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.szss.demo.orders.vo.UserVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.web.client.RestTemplate;

public class UserUpdateCacheCommand extends HystrixCommand<UserVO> {
  private static final Logger LOGGER = LoggerFactory.getLogger(UserUpdateCacheCommand.class);

  private static final HystrixCommandKey GETTER_KEY = HystrixCommandKey.Factory.asKey("CommandKey");
  private RestTemplate restTemplate;
  private UserVO user;

  public UserUpdateCacheCommand(RestTemplate restTemplate, UserVO user) {
    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("userUpdateCacheCommand")));
    this.restTemplate = restTemplate;
    this.user = user;
  }

  @Override
  protected UserVO run() throws Exception {
    LOGGER.info("thread:" + Thread.currentThread().getName());
    HttpEntity<UserVO> u = new HttpEntity<UserVO>(user);
    UserVO userVO=restTemplate.postForObject("http://users-service/user",u,UserVO.class);
    UserCacheCommand.flushCache(user.getUsername());
    return userVO;
  }

//  @Override
//  protected UserVO getFallback() {
//    UserVO user = new UserVO();
//    user.setId(-1L);
//    user.setUsername("调用失败");
//    return user;
//  }

  @Override
  protected String getCacheKey() {
    return user.getUsername();
  }
}

2 使用@CacheResult、@CacheRemove和@CacheKey标注来实现缓存

2.1 使用@CacheResult实现缓存功能

  @CacheResult(cacheKeyMethod = "getCacheKey")
  @HystrixCommand(commandKey = "findUserById", groupKey = "UserService", threadPoolKey = "userServiceThreadPool")
  public UserVO findById(Long id) {
    ResponseEntity<UserVO> user = restTemplate.getForEntity("http://users-service/user?id={id}", UserVO.class, id);
    return user.getBody();
  }

  public String getCacheKey(Long id) {
    return String.valueOf(id);
  }

@CacheResult注解中的cacheKeyMethod用来标示缓存key(cacheKey)的生成函数。函数的名称可任意取名,入参和标注@CacheResult的方法是一致的,返回类型是String。

2.2 使用@CacheResult和@CacheKey实现缓存功能

  @CacheResult
  @HystrixCommand(commandKey = "findUserById", groupKey = "UserService", threadPoolKey = "userServiceThreadPool")
  public UserVO findById2(@CacheKey("id") Long id) {
    ResponseEntity<UserVO> user = restTemplate.getForEntity("http://users-service/user?id={id}", UserVO.class, id);
    return user.getBody();
  }

标注@HystrixCommand注解的方法,使用@CacheKey标注需要指定的参数作为缓存key。

2.3 使用@CacheRemove清空缓存

  @CacheRemove(commandKey = "findUserById")
  @HystrixCommand(commandKey = "updateUser",groupKey = "UserService",threadPoolKey = "userServiceThreadPool")
  public void updateUser(@CacheKey("id")UserVO user){
    restTemplate.postForObject("http://users-service/user",user,UserVO.class);
  }

@CacheRemove必须指定commandKey,否则程序无法找到缓存位置。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持码农之家。

以上就是本次给大家分享的关于Java的全部知识点内容总结,大家还可以在下方相关文章里找到WebUploader实现分片断点上传、 swing组件JScrollPane滚动条实、 Java IO流之字符缓冲流的实、 等java文章进一步学习,感谢大家的阅读和支持。

上一篇:ArrayList源码和多线程安全问题详解

下一篇:快速创建spring boot项目的完整步骤

展开 +

收起 -

相关电子书
学习笔记
网友NO.328691

spring cloud 使用Hystrix 实现断路器进行服务容错保护的方法

在微服务中,我们将系统拆分为很多个服务单元,各单元之间通过服务注册和订阅消费的方式进行相互依赖。但是如果有一些服务出现问题了会怎么样? 比如说有三个服务(ABC),A调用B,B调用C。由于网络延迟或C本身代码有问题导致B迟迟得不到回应,这样B调用C的请求就会被挂起,等待。 在高并发的访问的情况下,这些挂起的线程得不到释放,使后续的请求阻塞,最终导致B也挂掉了。依次类推,A可能也会挂掉,进而使整个系统全部崩溃。 为了解决整个问题,Spring Cloud 使用Hystrix进行服务容错保护,包括断路器、线程隔离等一系列的保护功能,今天我们就来看下如何通过Hystrix实现断路器。 一、什么是Spring Cloud Hystrix?什么是断路器? Spring Cloud Hystrix是基于Netflix的开源框架Hystrix实现的,其目的是为了通过控制那些访问远程系统、服务和第三方的节……

网友NO.786230

详解springcloud Feign的Hystrix支持

本文介绍了springcloud Feign的Hystrix支持,分享给大家,具体如下: 一、Feign client中加入Hystrix的fallback @FeignClient(name="springboot-h2", fallback=HystrixClientFallback.class) //在fallback属性中指定断路器的fallback public interface UserFeignClient { // @GetMapping("/user/{id}") @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) User findById(@PathVariable("id") Long id); @RequestMapping(value="/users", method=RequestMethod.GET) ListUser findAll(); @RequestMapping(value="/post/user", method=RequestMethod.POST) User save(@RequestBody User user); } 二、编写HystrixClientFallback类 @Component //加入spring bean中 public class HystrixClientFallback implements UserFeignClient{ @Override public User findById(Long id) { User u = new User(); u.setName("临时名"); u.setUsername("匿名"); return u; } @Override public ListUser findAll() { return null; } @Override public User save(User user) { return null; } } 三、加入Hystrix支持 @……

网友NO.965704

springcloud 熔断器Hystrix的具体使用

说起springcloud熔断让我想起了去年股市中的熔断,多次痛的领悟,随意实施的熔断对整个系统的影响是灾难性的,好了接下来我们还是说正事。 熔断器 雪崩效应 在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。 如果下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。 熔断器(CircuitBreaker) 熔断器的原理很简单,如同电力过载保护器。它可以实现快速失败,如果它在一段时间内侦测到许多类似的错误,会强迫其以后的多个调用快速失败,不再……

网友NO.343131

SpringCloud Zuul在何种情况下使用Hystrix及问题小结

首先,引入 spring-cloud-starter-zuul 之后会间接引入: hystrix依赖已经引入,那么何种情况下使用hystrix呢? 在Zuul的自动配置类ZuulServerAutoConfiguration和ZuulProxyAutoConfiguration中总共会向Spring容器注入3个Zuul的RouteFilter,分别是 •SimpleHostRoutingFilter 简单路由,通过HttpClient向预定的URL发送请求 生效条件: RequestContext.getCurrentContext().getRouteHost() != null ​ RequestContext.getCurrentContext().sendZuulResponse() 1、RequestContext中的routeHost不为空,routeHost就是URL,即使用URL直连 2、RequestContext中的sendZuulResponse为true,即是否将response发送给客户端,默认为true •RibbonRoutingFilter 使用Ribbon、Hystrix和可插入的http客户端发送请求 生效条件: (RequestContext.getRouteHost() == null RequestContext.get(SERVICE_ID_KEY) != null ​ RequestContext.sendZuulResponse()) 1、RequestContext中的routeHost为空,即URL为空 2、RequestContext中的servi……

<
1
>

Copyright 2018-2020 xz577.com 码农之家

电子书资源由网友、会员提供上传,本站记录提供者的基本信息及资源来路

鸣谢: “ 码小辫 ” 公众号提供回调API服务、“ 脚本CDN ”提供网站加速(本站寻求更多赞助支持)

版权投诉 / 书籍推广 / 赞助:520161757@qq.com

上传资源(网友、会员均可提供)

查看最新会员资料及资源信息