在微服务架构体系中,各服务中间的相互调用是常态,没有哪个服务能保证自身百分百不会出问题,然后再加上网络的波动以及环境等问题,服务间调用的稳定性无法保证,这时候就需要一个有容错能力的组件来介入,当调用出现问题时能够做出及时响应,确保用户的体验和服务本身不受影响;而hystrix就是这样一个具备容错能力的组件,可以通过hystrix来实现服务的熔断、降级和隔离等功能, 从而提升服务的可用性与容错性 ;下面先简单介绍下熔断、降级和隔离;
熔断
当服务调用出现问题时实现快速失败的一种手段,避免占用服务器资源造成宕机甚至雪崩的风险;
降级
当服务不可用时给客户端友好响应的一种处理手段,具体可根据实际业务需求角度来考虑降低的具体方案;
隔离
当请求量激增时为了保护整个微服务不被搞垮可以使用服务内的服务隔离来解决;hystrix的隔离策略分为信号量隔离和线程池隔离两种方式,默认使用的是线程池隔离;
信号量隔离维护的是web容器(如tomcat)的线程,不需要服务内部开启线程,更轻量;由于使用的是web容器的请求线程,导致其不支持异步调用,不能单独为其设置超时机制;而线程池隔离是由hystrix自己维护的线程进行远程调用,可以做成异步调用,但其又新增了线程的开销和维护;所以当服务属于io密集型时可以选择线程池隔离策略,而当服务更多的是执行本地计算等cpu密集型时可考虑使用信号量隔离;当请求达到信号量或是线程池设置的线程数的上限时,请求会被直接拒绝,等到信号量有余量或者线程池有空闲了再接纳请求,这样即使微服务中的某一请求出现异常了也不至于导致整个服务异常;
下面上源码来看看hystrix该如何配置上述功能
首先引入依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId> spring-cloud-starter-netflix-hystrix </artifactId> </dependency>
hystrix整合restTemplate
启动类添加@EnableCircuitBreaker注解,然后再需要做熔断的方法上添加@HystrixCommand注解并指定熔断后处理的方法即可,代码如下:
package com.darling.eureka.consumer.service.impl; import com.darling.eureka.consumer.service.TestRestTemplateService; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.net.URI; /** * @description: * @author: dll * @date: Created in 2021/9/23 11:21 * @version: * @modified By: */ @Service public class TestRestTemplateServiceImpl implements TestRestTemplateService { @Resource private RestTemplate restTemplate; @Override @HystrixCommand(fallbackMethod = "sayRestHiCallBack") public String sayRestHi(String name) { String url = "http://EUREKA-PROVIDER//serverApis/test/sayHi?name="+name; String object = restTemplate.getForObject(url, String.class); return object; } /** * 当sayRestHi方法发起远程调用失败时调用本方法 * @param name * @return */ public String sayRestHiCallBack(String name) { return "向"+name+"sayRestHi失败啦!"; } }
hystrix整合Feign
由于feign本身支持hystrix,所以只需在配置文件打开hystrix的开关即可:feign.hystrix.enabled=true;有两种整合方式,一种是回调普通类另一种是回调一个工厂类,具体配置如下:
回调普通类:
在@FeignClient注解上新增属性fallback,值为指定的处理熔断方法的类,该类需实现@FeignClient注解所在的service;配置代码如下:
package com.darling.eureka.consumer.service; import com.darling.api.service.UserService; import com.darling.eureka.consumer.model.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; /** * @description: 通过openFeign远程调用服务提供者 * @author: dll * @date: Created in 2021/9/14 12:29 * @version: 1.0 * @modified By: */ @FeignClient(name = "EUREKA-PROVIDER",fallback = UserClientFallback.class) public interface UserApiService extends UserService{ @GetMapping("/serverApis/test/sayHi?name={name}") String sayHi(@PathVariable String name); /** * 测试 插入一条信息 * @param user * @return */ @GetMapping("/serverApis/test/insertInfo") String insertInfo(@RequestBody User user); }
处理熔断方法的类代码如下:
package com.darling.eureka.consumer.service; import com.darling.api.model.UserInfo; import com.darling.eureka.consumer.model.User; import org.springframework.stereotype.Component; /** * @description: 基于htstrix封装的处理feign调用出错的熔断策略 * @author: dll * @date: Created in 2021/9/22 12:08 * @version: * @modified By: */ @Component public class UserClientFallback implements UserApiService { @Override public String sayHi(String name) { return "我被降级了。。。"; } @Override public String insertInfo(User user) { return null; } @Override public UserInfo test() { return null; } @Override public UserInfo getInfo() { UserInfo userInfo = new UserInfo(); userInfo.setServerPort("我被降级啦"); return userInfo; } }
回调工厂类:
在@FeignClient注解上新增属性fallbackFactory,值为指定的处理熔断方法的工厂类,该类需实现FallbackFactory;配置代码如下:
package com.darling.eureka.consumer.service; import com.darling.api.service.UserService; import com.darling.eureka.consumer.model.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; /** * @description: 通过openFeign远程调用服务提供者 * @author: dll * @date: Created in 2021/9/14 12:29 * @version: 1.0 * @modified By: */ @FeignClient(name = "EUREKA-PROVIDER",fallbackFactory = UserClientFallbackFactory.class) public interface UserApiService extends UserService{ @GetMapping("/serverApis/test/sayHi?name={name}") String sayHi(@PathVariable String name); /** * 测试 插入一条信息 * @param user * @return */ @GetMapping("/serverApis/test/insertInfo") String insertInfo(@RequestBody User user); }
工厂类代码如下:
package com.darling.eureka.consumer.service; import com.darling.api.model.UserInfo; import com.darling.eureka.consumer.model.User; import com.netflix.hystrix.exception.HystrixTimeoutException; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; import java.util.Objects; /** * @description: 基于htstrix封装的处理feign调用出错的熔断策略工厂 * @author: dll * @date: Created in 2021/9/22 13:06 * @version: * @modified By: */ @Component public class UserClientFallbackFactory implements FallbackFactory<UserApiService> { @Override public UserApiService create(Throwable throwable) { return new UserApiService() { @Override public String sayHi(String name) { System.out.println("throwable = " + throwable); if (throwable instanceof HystrixTimeoutException) { return "连接超时了"; } return "系统异常"; } @Override public String insertInfo(User user) { return null; } @Override public UserInfo test() { return null; } @Override public UserInfo getInfo() { UserInfo userInfo = new UserInfo(); userInfo.setServerPort("我被降级啦"); return userInfo; } }; } }
两种回调方法总结:
使用普通类回调无法定位具体错误,通过工厂类回调由于参数会传入一个Throwable对象,调用者可以根据不同的错误类型做不同的降级策略,个人认为会更友好;