Talk about HystrixCommands of spring cloud netflix

  hystrix

Order

This article mainly studies the HystrixCommands of spring cloud netflix.

maven

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>

This component encapsulates hystrix, and 2.0.0.RELEASE fully supports Reactor’s Reactive Streams.

spring-cloud-starter-netflix-hystrix/pom.xml

spring-cloud-starter-netflix-hystrix-2.0.0.RELEASE.jar! /META-INF/maven/org.springframework.cloud/spring-cloud-starter-netflix-hystrix/pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <name>Spring Cloud Starter Netflix Hystrix</name>
    <description>Spring Cloud Starter Netflix Hystrix</description>
    <url>https://projects.spring.io/spring-cloud</url>
    <organization>
        <name>Pivotal Software, Inc.</name>
        <url>https://www.spring.io</url>
    </organization>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-archaius</artifactId>
        </dependency>
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-serialization</artifactId>
        </dependency>
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-metrics-event-stream</artifactId>
        </dependency>
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-javanica</artifactId>
        </dependency>
        <dependency>
            <groupId>io.reactivex</groupId>
            <artifactId>rxjava-reactive-streams</artifactId>
        </dependency>
    </dependencies>
</project>

The HystrixCommands to be mentioned here are in spring-cloud-netflix-core

HystrixCommands

spring-cloud-netflix-core-2.0.0.RELEASE-sources.jar! /org/springframework/cloud/netflix/hystrix/HystrixCommands.java

/**
 * Utility class to wrap a {@see Publisher} in a {@see HystrixObservableCommand}. Good for
 * use in a Spring WebFlux application. Allows more flexibility than the @HystrixCommand
 * annotation.
 * @author Spencer Gibb
 */
public class HystrixCommands {

    public static <T> PublisherBuilder<T> from(Publisher<T> publisher) {
        return new PublisherBuilder<>(publisher);
    }

    public static class PublisherBuilder<T> {
        private final Publisher<T> publisher;
        private String commandName;
        private String groupName;
        private Publisher<T> fallback;
        private Setter setter;
        private HystrixCommandProperties.Setter commandProperties;
        private boolean eager = false;
        private Function<HystrixObservableCommand<T>, Observable<T>> toObservable;

        public PublisherBuilder(Publisher<T> publisher) {
            this.publisher = publisher;
        }

        public PublisherBuilder<T> commandName(String commandName) {
            this.commandName = commandName;
            return this;
        }

        public PublisherBuilder<T> groupName(String groupName) {
            this.groupName = groupName;
            return this;
        }

        public PublisherBuilder<T> fallback(Publisher<T> fallback) {
            this.fallback = fallback;
            return this;
        }

        public PublisherBuilder<T> setter(Setter setter) {
            this.setter = setter;
            return this;
        }

        public PublisherBuilder<T> commandProperties(
                HystrixCommandProperties.Setter commandProperties) {
            this.commandProperties = commandProperties;
            return this;
        }

        public PublisherBuilder<T> commandProperties(
                Function<HystrixCommandProperties.Setter, HystrixCommandProperties.Setter> commandProperties) {
            if (commandProperties == null) {
                throw new IllegalArgumentException(
                        "commandProperties must not both be null");
            }
            return this.commandProperties(
                    commandProperties.apply(HystrixCommandProperties.Setter()));
        }

        public PublisherBuilder<T> eager() {
            this.eager = true;
            return this;
        }

        public PublisherBuilder<T> toObservable(Function<HystrixObservableCommand<T>, Observable<T>> toObservable) {
            this.toObservable = toObservable;
            return this;
        }

        public Publisher<T> build() {
            if (!StringUtils.hasText(commandName) && setter == null) {
                throw new IllegalStateException("commandName and setter can not both be empty");
            }
            Setter setterToUse = getSetter();

            PublisherHystrixCommand<T> command = new PublisherHystrixCommand<>(setterToUse, this.publisher, this.fallback);

            Observable<T> observable = getObservableFunction().apply(command);

            return RxReactiveStreams.toPublisher(observable);
        }

        public Function<HystrixObservableCommand<T>, Observable<T>> getObservableFunction() {
            Function<HystrixObservableCommand<T>, Observable<T>> observableFunc;

            if (this.toObservable != null) {
                observableFunc = this.toObservable;
            } else if (this.eager) {
                observableFunc = cmd -> cmd.observe();
            } else { // apply a default onBackpressureBuffer if not eager
                observableFunc = cmd -> cmd.toObservable().onBackpressureBuffer();
            }
            return observableFunc;
        }

        public Setter getSetter() {
            Setter setterToUse;
            if (this.setter != null) {
                setterToUse = this.setter;
            } else {
                String groupNameToUse;
                if (StringUtils.hasText(this.groupName)) {
                    groupNameToUse = this.groupName;
                } else {
                    groupNameToUse = commandName + "group";
                }

                HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey(groupNameToUse);
                HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(this.commandName);
                HystrixCommandProperties.Setter commandProperties = this.commandProperties != null
                        ? this.commandProperties
                        : HystrixCommandProperties.Setter();
                setterToUse = Setter.withGroupKey(groupKey).andCommandKey(commandKey)
                        .andCommandPropertiesDefaults(commandProperties);
            }
            return setterToUse;
        }

        public Flux<T> toFlux() {
            return Flux.from(build());
        }

        public Mono<T> toMono() {
            return Mono.from(build());
        }

    }

    private static class PublisherHystrixCommand<T> extends HystrixObservableCommand<T> {

        private Publisher<T> publisher;
        private Publisher<T> fallback;

        protected PublisherHystrixCommand(Setter setter, Publisher<T> publisher,
                Publisher<T> fallback) {
            super(setter);
            this.publisher = publisher;
            this.fallback = fallback;
        }

        @Override
        protected Observable<T> construct() {
            return RxReactiveStreams.toObservable(publisher);
        }

        @Override
        protected Observable<T> resumeWithFallback() {
            if (this.fallback != null) {
                return RxReactiveStreams.toObservable(this.fallback);
            }
            return super.resumeWithFallback();
        }
    }
}

It can be seen from the class annotation that this class is designed to facilitate the use of hystrix by webflux applications.

Example

    @Test
    public void testHystrixFallback() throws InterruptedException {
        Mono<String> delayMono = Mono.just("hello")
                .delayElement(Duration.ofMillis(500));
        Mono<String> result = HystrixCommands.from(delayMono)
                .commandName("demoCmd")
                .groupName("demoGroup")
                .eager()
                .commandProperties(HystrixCommandProperties.Setter()
                        .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
                        .withExecutionTimeoutInMilliseconds(1000)
                )
                .fallback(Mono.just("from fallback"))
                .toMono();

        System.out.println(result.block());

    }
  • From method can hystrix wrap Publisher.
  • CommandName specifies the commandname of the hystrix.
  • GroupName specifies the groupname of hystrix.
  • Eager is the default method, which means that it uses the observe () method, which is equivalent to hot Observable, and can only consume data after the subscription time. lazy uses the toObservable () method, which is equivalent to cold Observable, and can consume data before the subscription.
  • CommandProperties is used to specify properties of the command, such as executionIsolationStrategy, executionTimeoutInMilliseconds.
  • Fallback is used to specify fallback operations.

In addition, the configuration file can also specify default parameters, such as

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds : 6000
      circuitBreaker:
        sleepWindowInMilliseconds: 10000
      metrics:
        rollingStats:
          timeInMilliseconds : 18000

Summary

HystrixCommands is spring cloud’s packaging of netflix hystrix to facilitate the use of Hystrix in webflux, thus eliminating the need to use AOP technology.

doc