写点什么

Spring cloud 之熔断机制

用户头像
Damon
关注
发布于: 2021 年 05 月 24 日

作者:Damon

博客:http://www.damon8.cn

程序猿 Damon | 微服务 | 容器化 | 自动化


前面讲过Spring cloud 之多种方式限流处理请求频繁的压力。大家都知道,多个微服务之间调用的时候,假设微服务 A 调用微服务 B 和微服务 C,微服务 B 和微服务 C 有调用其他的微服务,这就是所谓的 扇出,若扇出的链路上某个微服务的请求时间过长或者不可用,对微服务 A 的调用就会占用越来越多的时间以及更多资源,进而引起系统雪崩,即”雪崩效应”。


这个时候,需要一个机制来保证当某个微服务出现异常时(请求反应慢或宕机),其整个流程还是阔以友好滴进行下去。即向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就可以保证调用方的线程不会被长时间、无厘头滴占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。我们把这个机制,或者这种处理方式叫作“熔断器”。

熔断机制是应对雪崩效应的一种微服务链路保护机制,当整个链路的某个微服务异常时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回“合理”的响应信息。当检测到该节点微服务正常后恢复调用链路,在 Spring cloud 框架机制通过 Hystrix 实现,Hystrix 会监控微服务见调用的状况,当失败的调用到一个阈值,默认是 5 秒内 20 次调用失败就会启动熔断机制,熔断机制的注解是 @HystrixCommand。


最近研究了一下 Spring cloud 的熔断机制,特分享一些代码,以及实战中的坑。


在 Spring cloud 中,假设有几个微服务:用户管理服务、订单服务、鉴权中心、物流服务等。这时,订单服务中,某个接口请求用户管理服务,这个时候如果需要熔断机制,该怎么处理呢?

首先,订单服务引入依赖:


<dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>        </dependency>
        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>        </dependency>
复制代码

这个时候,订单服务启动类中需要引用熔断注解 @EnableCircuitBreaker,使其生效:

/** * @author Damon * @date 2020年1月13日 下午3:23:06 * */
@EnableOAuth2Sso@Configuration@EnableAutoConfiguration@ComponentScan(basePackages = {"com.damon"})@EnableDiscoveryClient@EnableCircuitBreakerpublic class OrderApp {
    public static void main(String[] args) {        SpringApplication.run(OrderApp.class, args);    }
}
复制代码

这里,不要忘记注解 @EnableDiscoveryClient 来相互暴露服务。

最后需要在调用用户管理服务的函数中,加入注解 @HystrixCommand:

@HystrixCommand(fallbackMethod = "admin_service_fallBack", commandProperties = {      @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000") })//隔离策略:execution.isolation.strategy =SEMAPHORE or THREAD(不配置默认)  @Override  public Response<Object> getUserInfo(HttpServletRequest req, HttpServletResponse res) {
    ResponseEntity<String> forEntity = restTemplate.getForEntity(envConfig.getAdmin_web_url() + "/api/user/getUserInfo", String.class);    HttpHeaders headers = new HttpHeaders();    MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");    headers.setContentType(type);    headers.add("Accept", MediaType.APPLICATION_JSON.toString());    headers.add("Authorization", "bearer " + StrUtil.subAfter(req.getHeader("Authorization"), "bearer ", false));    HttpEntity<String> formEntity = new HttpEntity<String>(null, headers);    String body = "";    try {      ResponseEntity<String> responseEntity = restTemplate.exchange("http://admin-web-service/api/user/getUserInfo",          HttpMethod.GET, formEntity, String.class);      if (responseEntity.getStatusCodeValue() == 200) {        logger.debug(String.format("request getUserInfo return: {}", JSON.toJSON(responseEntity.getBody())));        return Response.ok(responseEntity.getStatusCodeValue(), 0, "success", JSON.toJSON(responseEntity.getBody()));      }    } catch (Exception e) {      logger.error("loadJobDetail error");      logger.error(e.getMessage(), e);    }    return null;  }
  /**   * 熔断时调用的方法   *   * 参数要与被请求的方法的参数一致   *   * @return   */  private Response<Object> admin_service_fallBack(HttpServletRequest req, HttpServletResponse res) {    String token = StrUtil.subAfter(req.getHeader("Authorization"), "bearer ", false);    logger.info("admin_service_fallBack token: {}", token);    return Response.ok(200, -2, "用戶服務掛啦!", null);  }
复制代码

其中上面代码需要注意的是:注解中 fallbackMethod 的值指定了熔断后的处理函数,这个函数的参数与当前的调用方法的参数需要保持一致,否则报错:

com.netflix.hystrix.contrib.javanica.exception.FallbackDefinitionException:fallback method wasn't found.
复制代码

最后,配置 Hystrix 相关的参数配置 yaml:

hystrix.command.BackendCall.execution.isolation.thread.timeoutInMilliseconds: 5000hystrix.threadpool.BackendCallThread.coreSize: 5
复制代码

其中第一个配置在调用函数中其实也可以配置:

@HystrixCommand(fallbackMethod = "admin_service_fallBack", commandProperties = {      @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") })
复制代码

这里配置的 3000 毫秒生效后,如果配置文件中也配置了,则会被覆盖。

如果不加 @HystrixCommand 中的 commandProperties=@HystrixProperty 注解配置,下面的 FallBack 函数 admin_service_fallBack()是一个线程;@HystrixCommand()是一个隔离线程。若加上 commandProperties=@HystrixProperty 注解配置后,将 2 个线程合并到一个线程里。

这样到此为止,调用方就结束配置了,至于被调用方,相关配置与源码在Spring Cloud Kubernetes之实战二服务注册与发现 一文中,讲过被调用服务的相关,这里的 http://admin-web-service 为被调用服务,则在其服务启动类中需要注解 @EnableDiscoveryClient:

package com.damon;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;
import com.damon.config.EnvConfig;
/** * @author Damon * @date 2020年1月13日 下午3:23:06 * */
@EnableOAuth2Sso@Configuration@EnableAutoConfiguration@ComponentScan(basePackages = {"com.damon"})@EnableConfigurationProperties(EnvConfig.class)@EnableDiscoveryClientpublic class AdminApp {
    public static void main(String[] args) {        SpringApplication.run(AdminApp.class, args);    }
}
复制代码

另外,配置 RestTemplate 的 Bean 中加上注解 @LoadBalanced 需要作 LB,这样利用服务名来根据 LB 规则找到对应的其中一个服务,这样比较明显看出 LB 的效果:

package com.damon.config;
import javax.annotation.Resource;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.env.Environment;import org.springframework.http.client.SimpleClientHttpRequestFactory;import org.springframework.web.client.RestTemplate;
/** * @author Damon * @date 2018年2月2日 下午7:15:53 */@Configurationpublic class BeansConfig {  @Resource  private Environment env;
  @LoadBalanced//就不能用ip等形式来请求其他服务  @Bean  public RestTemplate restTemplate() {    SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();    requestFactory.setReadTimeout(env.getProperty("client.http.request.readTimeout", Integer.class, 15000));    requestFactory.setConnectTimeout(env.getProperty("client.http.request.connectTimeout", Integer.class, 3000));    RestTemplate rt = new RestTemplate(requestFactory);    return rt;  }
}
复制代码

最后如果没问题了,可以先暂停用户管理服务,然后运行订单服务时,返回熔断结果:

{"message":{"code":-2,"message":"用戶服務掛啦!","status":200}}
复制代码

OK,Spring cloud 熔断实战就结束了!


结束福利

开源实战利用 k8s 作微服务的架构设计代码:

https://gitee.com/damon_one/spring-cloud-k8shttps://gitee.com/damon_one/spring-cloud-oauth2
复制代码

欢迎大家 star,多多指教。

关于作者

  笔名:Damon,技术爱好者,长期从事 Java 开发、Spring Cloud 的微服务架构设计,以及结合 Docker、K8s 做微服务容器化,自动化部署等一站式项目部署、落地。目前主要从事基于 K8s 云原生架构研发的工作。Golang 语言开发,长期研究边缘计算框架 KubeEdge、调度框架 Volcano 等。公众号 程序猿Damon 发起人。个人微信 MrNull008,个人网站:Damon | Micro-Service | Containerization | DevOps,欢迎來撩。

欢迎关注:InfoQ

欢迎关注:腾讯自媒体专栏


精彩推荐

欢迎关注




发布于: 2021 年 05 月 24 日阅读数: 648
用户头像

Damon

关注

God bless the fighters. 2020.03.11 加入

欢迎关注公众号:程序猿Damon,长期从事Java开发,研究Springcloud的微服务架构设计。目前主要从事基于K8s云原生架构研发的工作,Golang开发,长期研究边缘计算框架KubeEdge、调度框架Volcano、容器云KubeSphere研究

评论

发布
暂无评论
Spring cloud 之熔断机制