Spring-cloud (7) Custom HystrixCommand

premise

1. Before continuing to learn Hystrix, I apologize to everyone who follows my blog.
Because the previous project names and the names between services are not very standardized, I have modified these names to facilitate subsequent code management. These codes can be found in my github , the project address is posted here https://github.com/HellxZ/SpringCloudLearn.git
2. If you don't want to use the latest code, you can also modify the original code, which is troublesome, and apologize again.
3. This article assumes that the reader already has a registration center and service provider. This modification is obtained from the modification of the previous article.

Content of this article

1. Custom HystrixCommand (non-annotation)

2. The difference between synchronous and asynchronous calls

3. Asynchronous calls through annotations 

4. Introduction to the observe and toObserve methods of Observable

5 Conclusion

Custom HystrixCommand

The custom HystrixCommand needs to inherit the HystrixCommand class. If you want this custom fuse to execute, you need to use the object of the fuse to execute (the synchronous method is execute, the asynchronous method is queue), and the run method of the custom object will be automatically called. Your request Just put it in the run method. Well explained, look at the code: 

1. Local start

自定义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);
    }
}

What does the main method in the above code do?

In this method, the spring container is not used, and the service provider is only regarded as an ordinary springboot project, but if only the service provider is started, an error will be reported because there is no registry. So this time we just use the main method to call the service provider's interface. Here //本地请求, open the code below, //连注册中心请求comment out the code below, start the main method, and the output is as follows:

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. Use the container to request the call

//本地请求Comment out the //连注册中心请求code below, add a
new servicepackage, create the code below, RibbonServiceand paste the following code here

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;
    }
}

The RestTempleteobject here is injected by spring, so we will be able to write at this RibbonControllerpoint

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();
    }

}

Start this project (this project is modified from the project in the previous article, see github for details ), and visit the two interfaces below. The actual measurement is OK, so I won't post it here.

Difference between synchronous and asynchronous calls

I have said so much about synchronization and asynchrony. Don't be confused, my friends, my theory is not very good, here is my understanding

My understanding:

Synchronous call: get the result and return it directly and display the result immediately

Asynchronous call: get the result, delay until the call, the result is not displayed

Taking this article as an example, you can also try asynchronous lazy loading. RibbonServciceThere is such a method. useAsyncRequestGetUserIn this method, the object is received first Future. The get method not only returns the Userobject, but also calls the asynchronous acquisition result. Check getthe source code of this method. It does say that it is loaded when necessary

    /**
     * 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;

I wanted to design an experiment, but I didn't succeed. This method also has an overloaded method to set the delay time. If you are interested, you can leave a message to communicate.

Asynchronous calls through annotations

Expansion RibbonService, the callback method is customized here

    /**
     * 使用注解实现异步请求调用
     *
     * 注意:此处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;
    }

Extend RibbonController, call the above method

    /**
     * 使用注解发送异步请求
     */
    @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;
    }

Introduction to the observe and toObserve methods of Observable

In addition to synchronous asynchronous calls, reactive programming is also possible through Observableobject observemethods and toObservemethods, both of which return an Obserable object

  • The observe command returns an Observable object immediately when called.
  • toObservable does not immediately return an Observable, it will only be executed when the subscriber calls the data.

Quoting the explanation of these two objects in the book "springcloud microservices":

Although both observe() and toObservable return Observable, they are slightly different. The former returns a Hot Observable. The command will be executed immediately when observe() is called, and it will be replayed every time the Observable is subscribed. The behavior of; the latter returns a Cold Observable, after toObservable is executed, the command will not be executed immediately, only when all subscribers subscribe to it will be executed.

        //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);

Reactive programming can also be done in the form of annotations

    /**
     * 注解实现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();
                }
            }
        });
    }

You can also add parameters to the annotation to determine whether to pass observe()ortoObserable()

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

Note: When testing, distinguish between local startup and container startup. If the host url cannot be found if the error is reported, please modify the run() comment line of UserCommand

Epilogue

Just remember these for custom Hystrix requests, or annotations are more convenient.

Quoted in this article:

"springcloud microservices in practice" by Zhai Yongchao

Problems encountered in creating request commands by inheriting HystrixCommand

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325015897&siteId=291194637