天天看點

SpringCloud.Hoxton 版本, 全新熔斷器CircuitBreakerSpringCloud.Hoxton 版本, 全新熔斷器CircuitBreaker

SpringCloud.Hoxton 版本, 全新熔斷器CircuitBreaker

  • SpringCloud.Hoxton 版本, 全新熔斷器CircuitBreaker
    • 前置說明
    • 如何使用
    • 自動裝配
    • 總結

SpringCloud.Hoxton 版本, 全新熔斷器CircuitBreaker

前置說明

上文講到了Honxton 版本的新的負載均衡器, 這次準備講一下新的熔斷器. 其實也不是新的熔斷器, 是一個新的統一接口. 用過hystrix 的應該知道, 其實它整合到springcloud的時候, 其用法包括注解方式還是程式設計方式都是hystrix自帶的. 這樣的用法有個不好的地方就是, 如果我要切換新的熔斷器(hystrix官方不在更新,而是推薦我們使用Resilience4J, 雖然本身hystrix已經比較成熟了,不替換也不會有太多問題). 這就有點蛋疼了, 雖然網關那裡隻要改下依賴和配置就行了, 但是許多微服務中無論使用了注解的方式還是程式設計的方式,我都要一一替換.

是以在這個版本, springcloud提供了熔斷的統一接口(暫時好像沒有注解的方式, 但是我們可以自己用aop實作)

CircuitBreakerFactory

,

ReactiveCircuitBreakerFactory

. 這兩個接口分别用于非響應式和響應式程式設計.

主要的依賴, 也可以直接下載下傳我之前的 sample-springcloud 項目, 裡面有 h版本的分支demo, 連結放到評論區

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

如何使用

首先介紹一下非響應式的統一熔斷接口

CircuitBreakerFactory

// 基本用法
@Autowired
private CircuitBreakerFactory cbFactory;
@Autowired
private RestTemplate rest;

public String slow() {
        return cbFactory.create("slow").run(() -> rest.getForObject("/slow", String.class), throwable -> "fallback");
    }

           

首先很明顯最開始是一個工廠方法, 是以會調用create方法, 這個接口的參數是一個個

id

, 這個id是用來擷取對應的配置的.具體看下面,分别是

hystrix

resilience4j

create

方法

// org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerFactory#create
public HystrixCircuitBreaker create(String id) {
		Assert.hasText(id, "A CircuitBreaker must have an id.");
		HystrixCommand.Setter setter = getConfigurations().computeIfAbsent(id,
				defaultConfiguration);
		return new HystrixCircuitBreaker(setter);
	}

// org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory#create
	public Resilience4JCircuitBreaker create(String id) {
		Assert.hasText(id, "A CircuitBreaker must have an id.");
		Resilience4JConfigBuilder.Resilience4JCircuitBreakerConfiguration config = getConfigurations()
				.computeIfAbsent(id, defaultConfiguration);
		return new Resilience4JCircuitBreaker(id, config.getCircuitBreakerConfig(),
				config.getTimeLimiterConfig(), circuitBreakerRegistry, executorService,
				Optional.ofNullable(circuitBreakerCustomizers.get(id)));
	}
           

可以看到基本都是擷取相關的配置, 沒有就取預設的配置.

接下來run方法, 第一個參數就是原邏輯執行, 第二個參數是降級邏輯.

// org.springframework.cloud.netflix.hystrix.HystrixCircuitBreaker#run
public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {
		// 這裡的用法就是程式設計式的用法,沒啥差別
		HystrixCommand<T> command = new HystrixCommand<T>(setter) {
			@Override
			protected T run() throws Exception {
				return toRun.get();
			}

			@Override
			protected T getFallback() {
				return fallback.apply(getExecutionException());
			}
		};
		return command.execute(); //這裡就是hystrix的真正執行了, 底層是rxjava實作的
	}

// org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreaker#run
public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {
		TimeLimiter timeLimiter = TimeLimiter.of(timeLimiterConfig);
		Supplier<Future<T>> futureSupplier = () -> executorService.submit(toRun::get); // 将原邏輯丢到線程池, 獲得一個future
		Callable restrictedCall = TimeLimiter.decorateFutureSupplier(timeLimiter,
				futureSupplier); // 裝飾上 逾時邏輯, 其實很簡單哈 ,就是通過Future的逾時擷取就可以了

		io.github.resilience4j.circuitbreaker.CircuitBreaker defaultCircuitBreaker = registry
				.circuitBreaker(id, circuitBreakerConfig);
		circuitBreakerCustomizer
				.ifPresent(customizer -> customizer.customize(defaultCircuitBreaker));
		Callable<T> callable = io.github.resilience4j.circuitbreaker.CircuitBreaker
				.decorateCallable(defaultCircuitBreaker, restrictedCall);
		return Try.of(callable::call).recover(fallback).get(); // 執行邏輯和降級處理
	}


           

以上就是執行的過程, 可以看出

resilience4j

是要輕量很多, 而

hystrix

的源碼看下去能看上一整天 /(ㄒoㄒ)/~~

接下來是響應式接口的使用

@Autowired
    private WebClient webClient; //對應restTemplate響應式版本
    @Autowired
    private ReactiveCircuitBreakerFactory rcbFactory; //響應式熔斷工廠

public Mono<String> slow1() {
        return webClient.get().uri("/slow").retrieve() // 拿到DefaultResponseSpec 
        .bodyToMono(String.class) // 轉換Mono<ClientResponse> ->  Mono<String>
        .transform(  // 轉換 Mono<String> 增加 熔斷邏輯
                it -> rcbFactory.create("slow").run(it, throwable -> {
                    return Mono.just("fallback");
                }));
    }

           

具體還是看下 run 的實作

//org.springframework.cloud.netflix.hystrix.ReactiveHystrixCircuitBreaker#run(reactor.core.publisher.Mono<T>, java.util.function.Function<java.lang.Throwable,reactor.core.publisher.Mono<T>>)
public <T> Mono<T> run(Mono<T> toRun, Function<Throwable, Mono<T>> fallback) {
		HystrixObservableCommand<T> command = createCommand(toRun, fallback);
		
		return Mono.create(s -> {
		// 這裡也能看到 hystrix 底層就是響應式的, 隻不過可以通過阻塞方法适配成非響應式的調用, 不過因為spring用reactor 而 hystrix 用的rxjava, 是以需要通過标準接口(reactive stream規範)進行轉換一下
			Subscription sub = command.toObservable().subscribe(s::success, s::error,
					s::success);
			s.onCancel(sub::unsubscribe);
		});
	}

// org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreaker#run(reactor.core.publisher.Mono<T>, java.util.function.Function<java.lang.Throwable,reactor.core.publisher.Mono<T>>)
public <T> Mono<T> run(Mono<T> toRun, Function<Throwable, Mono<T>> fallback) {
		io.github.resilience4j.circuitbreaker.CircuitBreaker defaultCircuitBreaker = registry
				.circuitBreaker(id, config.getCircuitBreakerConfig());
		circuitBreakerCustomizer
				.ifPresent(customizer -> customizer.customize(defaultCircuitBreaker));
		Mono<T> toReturn = toRun
				.transform(CircuitBreakerOperator.of(defaultCircuitBreaker))
				.timeout(config.getTimeLimiterConfig().getTimeoutDuration()) // reactor 提供的逾時設定
				.doOnError(TimeoutException.class,
						t -> defaultCircuitBreaker.onError(config.getTimeLimiterConfig()
								.getTimeoutDuration().toMillis(), TimeUnit.MILLISECONDS,
								t));
		if (fallback != null) {
			toReturn = toReturn.onErrorResume(fallback);
		}
		return toReturn;
	}

           

可以看出來resilience4j的響應式還是通過spring去适配的, 本身沒有提供響應式的api.

自動裝配

先看下 hystrix 的自動裝配

@Configuration(proxyBeanMethods = false) // sb新特性, 減少運作時的類生成
@ConditionalOnClass({ Hystrix.class }) //必須引入hystrix依賴
@ConditionalOnProperty(name = "spring.cloud.circuitbreaker.hystrix.enabled",
		matchIfMissing = true) // 配置開啟, 預設就是開啟的
public class HystrixCircuitBreakerAutoConfiguration {
	// 對應的工廠類
	@Bean 
	@ConditionalOnMissingBean(CircuitBreakerFactory.class)
	public CircuitBreakerFactory hystrixCircuitBreakerFactory() {
		return new HystrixCircuitBreakerFactory();
	}
	// 響應式的工廠類
	@Bean
	@ConditionalOnMissingBean(ReactiveCircuitBreakerFactory.class)
	@ConditionalOnClass(
			name = { "reactor.core.publisher.Mono", "reactor.core.publisher.Flux" })
	public ReactiveHystrixCircuitBreakerFactory reactiveHystrixCircuitBreakerFactory() {
		return new ReactiveHystrixCircuitBreakerFactory();
	}
	// 自定義定制
	@Configuration(proxyBeanMethods = false)
	protected static class HystrixCircuitBreakerCustomizerConfiguration {

		@Autowired(required = false)
		private List<Customizer<HystrixCircuitBreakerFactory>> customizers = new ArrayList<>();

		@Autowired(required = false)
		private HystrixCircuitBreakerFactory factory;

		@PostConstruct
		public void init() {
			customizers.forEach(customizer -> customizer.customize(factory));
		}

	}
	// 自定義定制
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(
			name = { "reactor.core.publisher.Mono", "reactor.core.publisher.Flux" })
	protected static class ReactiveHystrixCircuitBreakerCustomizerConfiguration {

		@Autowired(required = false)
		private List<Customizer<ReactiveHystrixCircuitBreakerFactory>> customizers = new ArrayList<>();

		@Autowired(required = false)
		private ReactiveHystrixCircuitBreakerFactory factory;

		@PostConstruct
		public void init() {
			customizers.forEach(customizer -> customizer.customize(factory));
		}

	}

}

           

resilience4j的自動配置也是類似

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.circuitbreaker.resilience4j.enabled",
		matchIfMissing = true) //預設開啟
public class Resilience4JAutoConfiguration {
	// 工廠類
	@Bean
	@ConditionalOnMissingBean(CircuitBreakerFactory.class)
	public Resilience4JCircuitBreakerFactory resilience4jCircuitBreakerFactory() {
		return new Resilience4JCircuitBreakerFactory();
	}
	// 自定義配置,定制的
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass({
			"io.github.resilience4j.micrometer.tagged.TaggedCircuitBreakerMetrics",
			"io.micrometer.core.instrument.MeterRegistry" })
	public static class Resilience4JCustomizerConfiguration {

		@Autowired(required = false)
		private List<Customizer<Resilience4JCircuitBreakerFactory>> customizers = new ArrayList<>();

		@Autowired(required = false)
		private Resilience4JCircuitBreakerFactory factory;

		@PostConstruct
		public void init() {
			customizers.forEach(customizer -> customizer.customize(factory));
		}

	}
	// 用于監控的
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnBean({ MeterRegistry.class }) //springboot actuator這個依賴要有
	@ConditionalOnClass(name = {
			"io.github.resilience4j.micrometer.tagged.TaggedCircuitBreakerMetrics" })
	public static class MicrometerResilience4JCustomizerConfiguration {

		@Autowired(required = false)
		private List<Customizer<Resilience4JCircuitBreakerFactory>> customizers = new ArrayList<>();

		@Autowired(required = false)
		private Resilience4JCircuitBreakerFactory factory;

		@Autowired
		private MeterRegistry meterRegistry;

		@PostConstruct
		public void init() {
			customizers.forEach(customizer -> customizer.customize(factory));
			if (factory != null) {
				TaggedCircuitBreakerMetrics
						.ofCircuitBreakerRegistry(factory.getCircuitBreakerRegistry())
						.bindTo(meterRegistry);
			}
		}

	}

}

           

基本大同小異

總結

在H 版本以後, 熔斷提供了新的統一接口, 這樣就可以快速的替換底層的實作.

關于響應式: 因為spring開始推廣響應式程式設計(reactor), 是以在各個地方包塊webflux, webclient等都能見到響應式的使用. 響應式可以大大降低并發程式設計的困難, 但是它繁多的api和複雜的底層實作也會讓學習的曲線很陡. 當然響應式能不能提升性能這個就仁者見仁了.

關于hystrix vs resilience4j: 兩者都是很優秀的熔斷架構,簡單來說功能上兩者都有熔斷的功能, 逾時的功能, 降級的功能; 架構上resilience4j會稍微輕量很多; 性能上, resilience4j因為它的輕量級的實作, 是以性能會稍好(即使hystrix 底層用的rxjava, 但是我的了解是響應式并不能提升性能); 隔離性上我覺得hystrix基于線程池的隔離會更加好點.

分析到此結束了, 歡迎大家能互相交流

繼續閱讀