SpringCloud微服务知识整理五:服务容错保护 Spring Cloud Hystrix

什么是Spring Cloud Hystrix

在微服务架构中,我们将系统拆分为很多个服务,各个服务之间通过注册与订阅的方式相互依赖,由于各个服务都是在各自的进程中运行,就有可能由于网络原因或者服务自身的问题导致调用故障或延迟,随着服务的积压,可能会导致服务崩溃。为了解决这一系列的问题,断路器等一系列服务保护机制出现了。

Spring Cloud Hystrix 实现了断路器、线路隔离等一系列服务保护功能。
它也是基于 Netflix 的开源框架 Hystrix 实现的,该框架的目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力
Hystrix 具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。

一、快速入门

先搭建一个简单系统:
eureka-server 工程:服务注册中心,端口为8082。
hello-service 工程:HELLO-SERVICE 的服务单元,两个实例启动端口分别为 2221 和 2222.
ribbon-consumer 工程:使用 Ribbon 实现的服务消费者,端口为 3333
在这里插入图片描述
1、在 ribbon-consumer 工程的 pom.xml 的 dependency 节点中引入 spring-cloud-starter-hystrix 依赖:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

2、在 ribbon-consumer 工程的主类上使用 @EnableCircuitBreaker 注解开启断路器功能:

@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
	public class DemoApplication {
    	@Bean
    	@LoadBalanced
    	RestTemplate restTemplate(){
        	return new RestTemplate();
    	}
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

还可以使用 Spring Cloud 应用中的 @SpringCloudApplication 注解来修饰主类,该注解的具体定义如下。可以看到,该注解中包含了上述所引用的三个注解,这意味着一个 Spring Cloud 标准应用应包含服务发现以及断路器

3、改造服务消费方式,新增 HelloService 类,注入 RestTemplate 实例。然后,将在 ConsumerController 中对 RestTemplate 的使用迁移到 helloService 函数中,最后,在 helloService 函数上增加 @HystrixCommand 注解来指定回调方法。

@Service
public class HelloService {

    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "helloFallback")
    public String helloService(){
        return restTemplate.getForEntity("http://hello-service/index",
                String.class).getBody();
    }

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

4、修改 ConsumerController 类, 注入上面实现的 HelloService 实例,并在 helloConsumer 中进行调用:

@RestController
public class ConsumerController {

    @Autowired
    HelloService helloService;

    @RequestMapping(value = "ribbon-consumer", method = RequestMethod.GET)
    public String helloConsumer(){
        return helloService.helloService();
    }
}

5、对断路器实现的服务回调逻辑进行验证,重新启动之前关闭的 2221 端口的 hello-service,确保此时服务注册中心、两个 hello-service 和 ribbon-consumer 均已启动,再次访问 http://localhost:3333/ribbon-consumer 可以轮询两个 hello-serive 并返回一些文字信息。此时断开其中任意一个端口的 hello-service,再次访问,当轮询到关闭的端口服务时,输出内容为 error ,不再是之前的提示信息。

二、原理分析

工作流程:
在这里插入图片描述
1、创建HystrixCommand或者HystrixObservableCommand对象。用来表示对依赖服务的请求操作。使用的是命令模式。
其中HystrixCommand返回的是单个操作结果,HystrixObservableCommand返回多个结果

2、命令执行:共有4中方法执行命令。
execute() : 同步执行,从依赖的服务里返回单个结果或抛出异常
queue():异步执行,直接返回一个Future对象
observe():返回observable对象,代表了多个结果,是一个Hot Observable
toObservable():返回Observable对象,但是是一个 Cold Observable

3、结果是否被缓存。如果已经启用了缓存功能,且被命中,那么缓存就会直接以Observable对象返回

4、断路器是否已打开。没有命中缓存,在执行命令前会检查断路器是否已打开。断路器已打开,直接执行fallback。断路器关闭,继续往下执行

5、线程池And信号量Or请求队列是否已被占满 如果与该命令有关的线程池和请求队列,或者信号量已经被占满,就直接执行fallback

6、执行HystrixObservableCommand.construct () 或 HystrixCommand.run() 方法。如果设置了当前执行时间超过了设置的timeout,则当前处理线程会抛出一个TimeoutyException,如果命令不在自身线程里执行,就会通过单独的计时线程来抛出异常,Hystrix会直接执行fallback逻辑,并忽略run或construct的返回值。

7、计算断路器的健康值。

8、fallback处理。

9、返回成功的响应。

断路器原理:
在这里插入图片描述
依赖隔离:
Hystrix 使用 “舱壁模式” 实现线程池的隔离,它为每一个依赖服务创建一个独立的线程池,就算某个依赖服务出现延迟过高的情况,也不会拖慢其他的依赖服务。

三、使用详解

1、创建请求命令

继承方式实现HystrixCommand

public class UserCommand extends HystrixCommand<User> {

    private RestTemplate restTemplate;

    private Long id;

    public UserCommand(Setter setter, RestTemplate restTemplate, Long id) {
        super(setter);
        this.restTemplate = restTemplate;
        this.id = id;
    }
    
    @Override
    protected User run() throws Exception {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }

}

通过上面实现的UserCommand,我们即可以实现请求的同步执行也可以实现异步执行

public User getUserById(Long id) {
        HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("userKey");
        com.netflix.hystrix.HystrixCommand.Setter setter = com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(groupKey);
        UserCommand userCommand = new UserCommand(setter, restTemplate, id);
        // 同步执行获取结果
//        return userCommand.execute();
        // 异步执行获取结果
        Future<User> future = userCommand.queue();
        try {
            return future.get();
        } catch (Exception e) {
            logger.info("获取结果发生异常", e);
        }
        return null;
    }

注解方式使用HystrixCommand
同步:

 @HystrixCommand
    public User findUserById(Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }

异步:

 @HystrixCommand
    public Future<User> asyncFindUserFutureById(Long id) {
        return new AsyncResult<User>() {
            @Override
            public User invoke() {
                return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
            }
        };
    }

响应执行
还可以将HystrixCommand通过Observable来实现响应式执行方式。通过调用observe()和toObservable()可以返回Observable对象。

Observable<User> observe = userCommand.observe();
Observable<User> observe = userCommand.toObservable();

前者返回的是一个Hot Observable,该命令会在observe调用的时候立即执行,当Observable每次被订阅的时候都会重放它的行为。

后者返回的是一个Cold Observable,toObservable()执行之后,命令不会被立即执行,只有当所有订阅者都订阅他之后才会执行。

HystrixCommand具备了observe()和toObservable()的功能,但是它的实现有一定的局限性,它返回的Observable只能发射一次数据,所以Hystrix提供了另外的一个特殊命令封装HysrtixObservableCommand,通过命令可以发射多次的Observable。

响应执行自定义命令

public class UserObservableCommand extends HystrixObservableCommand<User> {

    private RestTemplate restTemplate;

    private Long id;

    public UserObservableCommand (Setter setter, RestTemplate restTemplate, Long id) {
        super(setter);
        this.restTemplate = restTemplate;
        this.id = id;
    }

    @Override
    protected Observable<User> construct() {
        return Observable.create(subscriber -> {
            if (!subscriber.isUnsubscribed()) {
                User user = restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
                subscriber.onNext(user);
                subscriber.onCompleted();
            }
        });
    }
}

响应执行使用注解@HystrixCommand

@Service
public class HelloService {

    private static final Logger logger = LoggerFactory.getLogger(HelloService.class);

    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand
    public Observable<User> observableGetUserId(Long id) {
        return Observable.create(subscriber -> {
            if (!subscriber.isUnsubscribed()) {
                User user = restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
                subscriber.onNext(user);
                subscriber.onCompleted();
            }
        });
    }

}

使用@HystrixCommand注解实现响应式命令,可以通过observableExecutionMode参数来控制是使用observe()还是toObservable()的执行方式。该参数有下面两种设置方式:

@HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER): EAGER是该参数的模式值,表示使用observe()执行方式。
@HystrixCommand(observableExecutionMode = ObservableExecutionMode.LAZY): 表示使用toObservable()执行方式。

2.定义服务降级

fallback是Hystrix命令执行失败时使用得后备方法,用来实现服务降级处理逻辑。
继承方式:

public class UserCommand extends HystrixCommand<User> {
 
    private RestTemplate restTemplate;
 
    private Long id;
 
    public UserCommand(Setter setter,RestTemplate restTemplate,Long id){
        super(setter);
        this.restTemplate = restTemplate;
        this.id = id;
    }
 
    @Override
    protected User run() throws Exception {
        return restTemplate.getForObject("Http://user-service/hello/user/{1}",User.class,id);
    }
 
    @Override
    protected User getFallback(){
        return new User();
    }
}

注解方式(含多次降级):


public class UserService {
 
    @Autowired
    private RestTemplate restTemplate;
 
    @HystrixCommand(fallbackMethod = "defaultUser")
    public User getUserById(Long id){
        return restTemplate.getForObject("Http://user-service/hello/user/{1}",User.class,id);
    }
    
    @HystrixCommand(fallbackMethod = "defaultUserSec")
    public User defaultUser(){
    		//此处可能是另外一个网络请求获取,也有可能失败
			return new User("First Fallback");
	}
	
    public  User dedalutUserSec(String id,Throwable e){
	       return new User("Second Fallback");
    }
}

3.异常处理

异常传播
在HystrixCommand实现的run()方法中抛出异常时,除了HystrixBadRequestException之外,其他异常均会被Hystrix认为命令执行失败并触发服务降级的处理逻辑,所以当需要在命令执行中抛出不触发服务降级的异常时来选择它。

在使用注解配置实现Hystrix命令时,可以忽略指定的异常类型,只需要通过设置@HystrixCommand注解的ignoreExceptions参数,如下:

  @HystrixCommand(fallbackMethod = "getDefaultUser", ignoreExceptions = NullPointerException.class)
    public User findUserById(Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
    }

异常获取
在继承实现Hystrix命令时,可以在getFallback()方法中通过getExecutionException()方法来获取具体的异常,然后通过判断来进入不同的处理逻辑。

在注解配置方式中,在fallback实现方法的参数中增加Throwable e对象的定义,这样在方法内部就可以获取触发服务降级的具体异常内容。

4.命令名称、分组和线程池划分

继承实现自定义命令
先调用了withGroupKey来设置命令组名,然后才通过调用andCommandKey来设置命令名。
还提供HystrixThreadPoolKey来对线程池进行设置,通过它可以实现更细粒度的线程池划分。

public UserCommand(RestTemplate restTemplate, Long id) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GroupName"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("CommandName"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ThreadPoolKey")));
    }

在没有指定HystrixThreadPoolKey的情况下,会使用命令组的方式来划分线程池。通常情况下,我们尽量使用HystrixThreadPoolKey来指定线程池的划分。 因为多个不同的命令可能从业务逻辑上来看属于同一个组,但是往往从实现本身上需要跟其他命令来进行隔离。

使用注解时只需要设置注解的commandKey、groupKey以及threadPoolKey属性即可,他分别表示了命令名称、分组以及线程池划分。

@HystrixCommand(fallbackMethod = "getDefaultUser", ignoreExceptions = NullPointerException.class,commandKey = "findUserById", groupKey = "UserGroup", threadPoolKey = "findUserByIdThread")
public User findUserById(Long id) {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
}

5.请求缓存

在高并发的场景之下,Hystrix中提供了请求缓存的功能,可以很方便的开启和使用请求缓存来优化系统,达到减轻高并发时的请求线程消耗、降低请求响应时间的效果。

开启:实现HystrixCommand时重载getCacheKey()
清理:HystrixRequsetCache.clear()

@Override
public String getCacheKey() {
       return String.valueOf(id); 
}
 public static void flushRequestCache(Long id){
        HystrixRequestCache.getInstance(
                HystrixCommandKey.Factory.asKey("test"), HystrixConcurrencyStrategyDefault.getInstance())
                .clear(String.valueOf(id));
}

使用注解实现:
@CacheResult
@CacheRemove
@CacheKey

6.请求合并

在高并发情况下,通信次数的增加会导致总的通信时间增加,同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题,我们需要Hystrix的请求合并。

1、假设微服务提供了两个接口,一个请求单个一个请求一个批量列表,服务消费端定义两个接口,一个是单个请求,第二个是批量请求,并且用restTemplate实现了远程调用。

public class UserServiceImpl implements UserService {
 
    @Autowired
    private RestTemplate restTemplate;
 
 
    @Override
    public User find(Long id) {
        return restTemplate.getForObject
                ("http//user-service/users/{1}",User.class,id);
    }
 
    @Override
    public List<User> findAll(List<Long> ids) {
        return restTemplate.getForObject
                ("http//user-service/users?ids={1}",List.class, StringUtils.join(ids,","));
    }
}

2、为请求合并实现一个批量请求

public class UserBatchCommand extends HystrixCommand<List<User>> {
 
    UserService userService;
    List<Long> userIds;
 
    public UserBatchCommand(UserService userService,List<Long> userIds){
        super(Setter.withGroupKey(asKey("userBatchCommand")));
        this.userIds = userIds;
        this.userService = userService;
    }
 
    @Override
    protected List<User> run() throws Exception {
        return userService.findAll(userIds);
    }
}

3、继承HystrixCollapser实现请求合并器:

public class UserCollapseCommand extends HystrixCollapser<List<User>,User,Long> {
 
    private UserService userService;
 
    private Long userId;
 
    public UserCollapseCommand(UserService userService,Long userId){
        super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("userBatchCommand")).andCollapserPropertiesDefaults(
                HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(100)//设置时间延迟属性
        ));
        this.userService = userService;
        this.userId = userId;
    }
 
    @Override
    public Long getRequestArgument() {
        return userId;//返回给定的单个请求参数
    }

    @Override
    protected HystrixCommand<List<User>> createCommand(Collection<CollapsedRequest<User, Long>> collection) {
        List<Long> userIds = new ArrayList<>(collection.size());
        userIds.addAll(collection.stream().map(CollapsedRequest::getArgument).collect(Collectors.toList()));
        return new UserBatchCommand(userService,userIds);
    }
 
    @Override
    protected void mapResponseToRequests(List<User> users, Collection<CollapsedRequest<User, Long>> collection) {
        int count = 0;
        for (CollapsedRequest<User,Long> collapsedRequest:collection){
            User user = users.get(count++);
            collapsedRequest.setResponse(user);
        }
    }
}

通过注解形式来实现

@Service
public class UserService {

    @Autowired
    RestTemplate restTemplate;
 
    @HystrixCollapser(batchMethod = "findAll",collapserProperties = {@HystrixProperty(name="timerDelayInMilliseconds",value = "100")})
    public User find(Long id){
    		return null;
    }
 
    @HystrixCommand
    public List<User> findAll(List<Long> ids){
        return restTemplate.getForObject("http://user-service/user?ids={1}",List.class, StringUtils.join(ids,","));
    }

四、属性详解

继承方法:Setter对象设置
注解方法:command-Properties

类型:
Execution:控制HystrixCommand.run() 的如何执行
Fallback: 控制HystrixCommand.getFallback() 如何执行
Circuit Breaker: 控制断路器的行为
Metrics: 捕获和HystrixCommand 和 HystrixObservableCommand 执行信息相关的配置属性
Request Context:设置请求上下文的属性
Collapser Properties:设置请求合并的属性
Thread Pool Properties:设置线程池的属性

五、Hystrix仪表盘与Turbine集群监控

度量指标都是HystrixCommand和HystrixObservableCommand实例在执行过程中记录的重要信息,它们除了在Hystrix断路器实现中使用之外,对于系统运维的帮助也很大。

度量指标会以“滚动时间窗”与“桶”结合的方式进行汇总,并在内存中驻留一段时间,以供内部或外部查询使用,Hystrix仪表盘就是这些指标内容的消费者之一。

在这里插入图片描述

不管是监控单体应用还是Turbine集群监控,我们都需要一个Hystrix Dashboard,单独创建一个新的工程专门用来做Hystrix Dashboard。
具体搭建略

在实际应用中,我们要监控的应用往往是一个集群,这个时候我们就得采取Turbine集群监控了。Turbine有一个重要的功能就是汇聚监控信息,并将汇聚到的监控信息提供给Hystrix Dashboard来集中展示和监控。
在这里插入图片描述
具体搭建略

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值