zoukankan      html  css  js  c++  java
  • 自定义HystrixCommand

    前提

    1、在继续学习Hystrix之前,向关注本人博客的各位致歉
    由于之前的项目起名以及服务之间的名称不是很规范,所以我修改了这些名称方便后来的代码管理,这些代码可以在本人github中找到,这里贴出该项目地址https://github.com/HellxZ/SpringCloudLearn.git
    2、如果不想使用最新的代码,也可以修改本来的代码,比较麻烦,再次致歉。
    3、本文假设读者已经有了注册中心、服务提供者,本次修改处为上一文项目修改而得

    本文内容

    1、自定义HystrixCommand(非注解)

    2、同步调用和异步调用的区别

    3、通过注解实现异步调用

    4、observe和toObserve方法简介

    5、结语

    自定义HystrixCommand

    自定义HystrixCommand需要继承HystrixCommand类,想让这个自定义的熔断执行,需要使用这个熔断器的对象去执行(同步方法为execute,异步为queue),会自动调用自定义对象的run方法,你们的请求就放在run方法中。解释好了,看代码:

    1、本地启动

    自定义HystrixCommand

    package com.cnblogs.hellxz.hystrix;
    
    import com.cnblogs.hellxz.entity.User;
    import com.netflix.hystrix.HystrixCommand;
    import com.netflix.hystrix.HystrixCommandGroupKey;
    import com.netflix.hystrix.HystrixCommandProperties;
    import org.springframework.web.client.RestTemplate;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.Future;
    
    /**
     * @Author : Hellxz
     * @Description:
     * @Date : 2018/4/25 09:47
     */
    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;
        }
    
        /**
         * 注意本地main方法启动,url请用http://localhost:8080/user
         * 通过controller请求启动需要改为服务调用地址:http://eureka-service/user
         */
        @Override
        protected User run() {
            //本地请求
    //        return restTemplate.getForObject("http://localhost:8080/user", User.class);
            //连注册中心请求
            return restTemplate.getForObject("http://eureka-service/user", User.class);
        }
    
        /**
         * 此方法为《spirngcloud微服务实战》中的学习部分,仅用于在此项目启动的之后调用本地服务,但是不能没有走注册中心。
         * 书中为我们留下了这个坑,详情请直接翻阅151页。
         * 问题解决请参考:https://blog.csdn.net/lvyuan1234/article/details/76550706
         * 本人在书中基础上已经完成调用注册中心服务的功能,见RibbonService类中具体实现
         */
        public static void main(String[] args) {
            //同步请求
            User userSync=new UserCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(
                    HystrixCommandGroupKey.Factory.asKey("")).andCommandPropertiesDefaults(
                    HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(5000)),
                    new RestTemplate(),0L).execute();
            System.out.println("------------------This is sync request's response:"+userSync);
            //异步请求
            Future<User> userFuture = new UserCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(
                    HystrixCommandGroupKey.Factory.asKey("")).andCommandPropertiesDefaults(
                    HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(5000)),
                    new RestTemplate(),0L).queue();
    
            User userAsync = null;
    
            try {
                userAsync = userFuture.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println("------------------This is async request's response:"+userAsync);
        }
    }
    

    上述代码中的main方法的作用?

    在这个方法中没有用到spring容器,仅仅是把服务提供者当做一个普通的springboot项目,但是要只启动服务提供者,会因为没有注册中心报错。所以这回我们仅仅使用main方法调用一下服务提供者的接口。这里将//本地请求下方的代码打开,注掉//连注册中心请求下方的代码,启动main方法,输出如下:

    16:10:24.252 [hystrix--1] DEBUG org.springframework.web.client.RestTemplate - Created GET request for "http://localhost:8080/user"
    16:10:24.327 [hystrix--1] DEBUG org.springframework.web.client.RestTemplate - Setting request Accept header to [application/json, application/*+json]
    16:10:24.374 [hystrix--1] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:8080/user" resulted in 200 (null)
    16:10:24.376 [hystrix--1] DEBUG org.springframework.web.client.RestTemplate - Reading [class com.cnblogs.hellxz.entity.User] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@4abb52c0]
    ------------------This is sync request's response:user:{name: hellxz, sex: male, phone: 123456789 }
    16:10:24.506 [hystrix--2] DEBUG org.springframework.web.client.RestTemplate - Created GET request for "http://localhost:8080/user"
    16:10:24.507 [hystrix--2] DEBUG org.springframework.web.client.RestTemplate - Setting request Accept header to [application/json, application/*+json]
    16:10:24.516 [hystrix--2] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:8080/user" resulted in 200 (null)
    16:10:24.516 [hystrix--2] DEBUG org.springframework.web.client.RestTemplate - Reading [class com.cnblogs.hellxz.entity.User] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@7a61c025]
    ------------------This is async request's response:user:{name: hellxz, sex: male, phone: 123456789 }
    
    Process finished with exit code 0
    

    2、使用容器请求调用

    这里将//本地请求下方的代码注掉, //连注册中心请求下方的代码
    新增service包,创建RibbonService,这里把之后的代码一并粘过来了

    package com.cnblogs.hellxz.servcie;
    
    import com.cnblogs.hellxz.entity.User;
    import com.cnblogs.hellxz.hystrix.UserCommand;
    import com.netflix.hystrix.HystrixCommandGroupKey;
    import com.netflix.hystrix.HystrixCommandProperties;
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import com.netflix.hystrix.contrib.javanica.command.AsyncResult;
    import org.apache.log4j.Logger;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.Random;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.Future;
    
    
    /**
     * @Author : Hellxz
     * @Description: Ribbon服务层
     * @Date : 2018/4/26 10:08
     */
    @Service
    public class RibbonService {
    
        private static final Logger logger = Logger.getLogger(RibbonService.class);
        @Autowired
        private RestTemplate restTemplate;
    
        /**
         * 使用Hystrix注解,声明回调类,此方法为同步请求,如果不指定回调方法会使用默认
         */
        @HystrixCommand(fallbackMethod = "hystrixFallback")
        public String helloService(){
            long start = System.currentTimeMillis();
            //设置随机3秒内延迟,hystrix默认延迟2秒未返回则熔断,调用回调方法
            int sleepMillis = new Random().nextInt(3000);
            logger.info("----sleep-time:"+sleepMillis);
    
            try {
                Thread.sleep(sleepMillis);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //调用服务提供者接口,正常则返回hello字符串
            String body = restTemplate.getForEntity("http://eureka-service/hello", String.class).getBody();
            long end = System.currentTimeMillis();
            logger.info("----spend-time:"+(end-start));
            return body;
        }
    
        /**
         * 调用服务失败处理方法:返回类型为字符串
         * @return “error"
         */
        public String hystrixFallback(){
            return "error";
        }
    
        /**
         * 使用自定义HystrixCommand同步方法调用接口
         */
        public User useSyncRequestGetUser(){
            //这里使用Spring注入的RestTemplate, Spring注入的对象都是静态的
            User userSync = new UserCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(
                    HystrixCommandGroupKey.Factory.asKey("")).andCommandPropertiesDefaults(
                            HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(5000)),
                    restTemplate ,0L).execute();
    
            return userSync;
        }
    
        /**
         * 使用自定义HystrixCommand异步方法调用接口
         */
        public User useAsyncRequestGetUser(){
    
            Future<User> userFuture = new UserCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(
                    HystrixCommandGroupKey.Factory.asKey("")).andCommandPropertiesDefaults(
                            HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(5000)),
                    restTemplate,0L).queue();
    
            User userAsync = null;
    
            try {
                //获取Future内部包含的对象
                userAsync = userFuture.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
            return userAsync;
        }
    }
    

    这里RestTemplete对象是spring注入的,所以我们将此时可以写RibbonController

    package com.cnblogs.hellxz.controller;
    
    import com.cnblogs.hellxz.entity.User;
    import com.cnblogs.hellxz.servcie.RibbonService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.Future;
    
    /**
     * @Author : Hellxz
     * @Description: Ribbon消费者controller
     * @Date : 2018/4/20 10:07
     */
    @RestController
    @RequestMapping("hystrix")
    public class RibbonController {
    
        @Autowired
        RibbonService service;
    
        @GetMapping("/invoke")
        public String helloHystrix(){
            //调用服务层方法
            return service.helloService();
        }
    
        /**
         * 发送同步请求,使用继承方式实现自定义Hystrix
         */
        @GetMapping("/sync")
        public User sendSyncRequestGetUser(){
            return service.useSyncRequestGetUser();
        }
    
        /**
         * 发送异步请求,使用继承方式实现自定义Hystrix
         */
        @GetMapping("/async")
        public User sendAsyncRequestGetUser(){
            return service.useAsyncRequestGetUser();
        }
    
    }
    

    启动这个项目(此项目为上一篇文中项目修改而来,详情见github),分别访问这下边两个接口,实测可以,这里就不贴了。

    同步调用和异步调用的区别

    上面说了那么多关于同步异步的说法,小伙伴们可别晕哦,本人理论不是很好,这里说说我的理解

    我的理解:

    同步调用:获取到结果直接返回并立即显示结果

    异步调用:获取到结果,延迟直到调用,结果才显示

    以本文举例,大家也可以试下异步的延迟加载,RibbonServcice中有这样一个方法useAsyncRequestGetUser这个方法中先接收到Future对象,其中的get方法不仅是返回User对象,还是调用这个异步的获取结果,查看这个get方法的源码,的确说是必要时加载

        /**
         * Waits if necessary for the computation to complete, and then
         * retrieves its result.
         *
         * @return the computed result
         * @throws CancellationException if the computation was cancelled
         * @throws ExecutionException if the computation threw an
         * exception
         * @throws InterruptedException if the current thread was interrupted
         * while waiting
         */
        V get() throws InterruptedException, ExecutionException;
    
    

    本想设计个实验的,想到些却没成功,这个方法还有一个设置延迟时间的重载方法,有兴趣的可以留言交流一下

    通过注解实现异步调用

    扩充RibbonService,这里自定义了回调方法

        /**
         * 使用注解实现异步请求调用
         *
         * 注意:此处AsyncResult为netfix实现,spring也做了实现,注意导包。
         */
        @HystrixCommand(fallbackMethod = "fallbackForUserTypeReturnMethod")
        public Future<User> asyncRequest(){
            return new AsyncResult<User>(){
                public User invoke(){
                    return restTemplate.getForObject("http://eureka-service/user", User.class);
                }
            };
        }
    
        /**
         * 调用服务失败处理方法:返回类型为User
         */
        public User fallbackForUserTypeReturnMethod(){
            return null;
        }
    

    扩充RibbonController,调用上边的方法

        /**
         * 使用注解发送异步请求
         */
        @GetMapping("/annotationasync")
        public User sendAsyncRequestByAnnotation(){
            Future<User> userFuture = service.asyncRequest();
            try {
                return userFuture.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
            return null;
        }
    

    observe和toObserve方法简介

    除了同步异步调用,还可以通过自定义HystrixCommand对象的observe方法和toObserve方法进行响应式编程,这两个方法都返回了一个Obserable对象

    • observe命令在调用的时候会立即返回一个Observable对象。
    • toObservable则不会立即返回一个Observable,订阅者调用数据的时候才会执行。

    引用《springcloud 微服务》书中对这两个方法的解释:

    observe()和toObservable虽然都返回了Observable,但是它们略有不同,前者返回的是一个Hot Observable,该命令会在observe()调用的时候立即执行,当Observable每次被订阅的时候会重放它的行为;而后者返回的是一个Cold Observable,toObservable执行之后,命令不会被立即执行,只有当所有订阅者都订阅它才会执行。

            //observe和toObservable方法
            UserCommand userCommand = new UserCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")), new RestTemplate(),1L);
            Observable<User> observe = userCommand.observe();
            System.out.println("------------------This is observe's response:"+observe);
            Observable<User> userObservable = userCommand.toObservable();
            System.out.println("------------------This is toObserve's response:"+userObservable);
    

    也可以使用注解的形式进行响应式编程

        /**
         * 注解实现Observable响应式开发
         */
        @HystrixCommand
        public Observable<User> observeByAnnotation() {
            return Observable.create(new Observable.OnSubscribe<User>() {
                @Override
                public void call(Subscriber<? super User> subscriber) {
                    if (!subscriber.isUnsubscribed()) {
                        User user = restTemplate.getForObject("http://eureka-service/user", User.class);
                        subscriber.onNext(user);
                        subscriber.onCompleted();
                    }
                }
            });
        }
    

    注解中也可以添加参数来确定是通过observe()还是toObserable()

    @HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER)表示使用observe模式来执行
    @HystrixCommand(observableExecutionMode = ObservableExecutionMode.LAZY)表示使用toObservable模式来执行
    

    注意:测试的时候,区分本地启动与容器启动,报错找不到那个主机url就请修改UserCommand的run()注释行

    结语

    自定义Hystrix请求就先记这些了,还是注解方便些。

    本文引用:

    《springcloud 微服务实战》翟永超 著

    https://www.cnblogs.com/hellxz/p/8961480.html

  • 相关阅读:
    Vista修改网卡MAC地址防御ARP
    MySQL
    FirstWebSite
    Java
    python
    编码风格 标识符命名
    计算机体系结构基础
    编码风格 indent工具
    Linux发布web app 到Tomcat
    编码风格 函数
  • 原文地址:https://www.cnblogs.com/smallfa/p/13133718.html
Copyright © 2011-2022 走看看