SpringCloud (七)自定義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 微服務實戰》翟永超 著

通過繼承HystrixCommand來創建請求命令遇到的問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM