Spring 5與Spring cloud的響應式程式設計之旅

banq發表於2018-07-03

全新的Spring Cloud Finchley GA版本是Reactive(響應式/反應式)微服務之旅的一個重要里程碑。下面是Spring的Josh Long有關Reactive Spring Cloud介紹文章:

Spring框架5.0釋出於2017年9月,這是首個引入新的Reactive程式設計支援的版本,它建立在Pivotal Reactor專案的基礎上,我們的響應流(reactive stream)是相容響應執行(reactive runtime)的,Spring Framework 5還包含大量新功能,這裡不一一列出,這裡只專注於響應式支援的功能。

什麼是響應式程式設計(Reactive Programming)?Spring為什麼要和這有關係?嗯,當你構建網路服務時,這就很重要啦。

簡而言之,Spring中服務整合的底層基礎設施已經更新,可以完全接受響應式程式設計。那麼,什麼是響應式程式設計?響應式程式設計又稱反應式式程式設計,當你開始通過網路傳輸更多資料時,比如更頻繁的REST API​​呼叫等會使得IO緩衝區飽和,IO會發生堵塞,產生時間延遲。

IO本身不是問題,傳統IO的使用卻是阻塞的 - 執行緒必須等待InputStream生成新的bytes。(通常迴圈執行read()讀取放入byte緩衝區)。當執行緒發生等待時,這個執行緒就不能做其他任何事情。執行緒很昂貴!

想想如何使用Java或任何其他具有相同執行緒處理方法的平臺實現傳統伺服器?如果Web伺服器tomcat的執行緒池最大有100個執行緒,當第101個請求到達時,那麼tomcat在完成處理現有100個請求之前,將沒有多餘執行緒提供第101個請求的處理。當然如果第101個請求到來之前其他請求已經處理完成(並且釋放佔有的執行緒),那就太好了!可能就不需要響應式程式設計了。如果在新請求到達之前更快地釋放執行緒,並且在這些執行緒中花費的時間主要是用於輸入/輸出,那麼就不需要響應式程式設計。

當使用微服務時,遭遇大資料以及長期會話(例如在websockets和伺服器傳送的事件以及任何其他長期維持伺服器狀態)等情況時,就會遭遇到IO堵塞了。

這種執行緒與IO的耦合其實是不必要的。幾十年來,作業系統一直支援“後臺管理”IO,並在前臺應該參與時通知前臺。實際上,Java 1.4(從2000年代早期開始)就支援NIO(Channels)了,它為我們提供了一種非同步IO機制。

在這個世界中,有專門管理IO的執行緒並在應該需要時回撥用程式碼。如果IO這裡存在延遲,那麼該執行緒可以自由移動並處理其他請求。它沒有被IO的延遲堵塞。你不需要從編寫程式碼從InputStream讀取位元組內容,位元組內容會被非同步推送給你。由此你高效率地反轉了與資料來源的互動方式。

許多專案比如來自@ NetflixOSS的RxJava、來自@Pivotal的@ProjectReactor、來自Eclipse的@vertx_project和來自@lightbend的@akkateam,都在尋求提供支援這種新非同步的響應式程式設計模型。它們都存在共同點,這些共同點都被編入了Reactive Streams規範中了,這些專案都支援這個規範。

Reactive Streams規範支援釋出者向訂閱者釋出內容。訂閱者呼叫方法onNextIT來消費使用這些被髮布的內容。訂閱者訂閱時,會給出一個Subscription物件,它用來表示可以處理多少條記錄。最後一位 - 能夠準確指定訂戶準備一次處理多少記錄的能力 - 也就是流量控制,釋出者因此不會在流量上壓垮訂閱者,提升了流處理的穩定性。在響應式程式設計的背景下,流量控制也稱為背壓backpressure。

有一個介面Processor,它只是一個橋樑; 它實現了釋出者和訂閱者兩個。Project Reactor支援兩種Publisher特殊化:

1. Flux: 傳送0-N項內容。

2. Mono:只傳送單項內容,或者沒有。

這是對IO使用方式的基本思考,因此需要在上面的每一層整合入這種新的思考方式,包括 資料訪問層、安全層、Spring Boot和微服務層。

Spring框架5.0還包括一個名為Spring WebFlux的全新的響應式Web模組(甚至支援Netty專案),它甚至提供新的函式性響應端點支援。

Spring WebFlux建立在Reactive Streams規範的基礎上,因此可以與任何其他支援庫互操作。使用響應式Spring Webflux可以和Lightbend的Akka Streams(以及Scala)進行互動。

新的Spring WebFlux元件模型首先是響應式和非同步的。它支援如websockets和伺服器傳送事件等非同步,使用方式與傳統上處理同步情況的方式相同。想要在幾納秒內傳送一條包含10條記錄的簡短JSON資料?那就用一個Publisher來做!

新增模組支援響應式程式設計

為支援響應式程式設計,新版本新增了一個名為新的響應式HTTP客戶端WebClient。

Spring Data Kay支援通過模板和儲存庫對具有非同步IO支援的資料訪問技術進行響應性資料訪問。比如下面這個程式碼可以實現Reactive Spring Data MongoDB的使用:

interface ReservationRepository extends ReactiveMongoRepository<Reservation, String> {

		Flux<Reservation> findByEmail(String email);
}

@Document
@AllArgsConstructor
@NoArgsConstructor
@Data
class Reservation {
		@Id
		private String id;
		private String email;
}
<p>

Spring Security 5支援對傳統用例(如下所示)和OAuth的響應式身份驗證和授權:

  @Bean
  MapReactiveUserDetailsService authentication() {
    // don't do this! this is a hardcoded username and password and it
    // would literally pain Spring Security lead @rob_winch to see this!
    //
    return new MapReactiveUserDetailsService(
      User.withDefaultPasswordEncoder().username("user").password("pw").roles("USER").build());
  }

  @Bean
  SecurityWebFilterChain authorization(ServerHttpSecurity security) {
  //@formatter:off
  return security
  .csrf().disable()
  .httpBasic()
  .and()
  .authorizeExchange()
    .pathMatchers("/proxy").authenticated()
    .anyExchange().permitAll()
  .and()
  .build();
  //@formatter:on
  }
<p>

無論選擇使用Spring WebFlux還是Spring MVC,Spring Boot 2將所有這些結合在一起:構建REST端點;使用Actuator;管理安全性以及其他等 。

從程式碼庫更改的角度來看,這也意味著Spring Cloud團隊需要進行多方面落地,這使得這個版本變得非常重要。

響應式程式設計與現有功能對接

新版本將響應式程式設計與現有功能進行了無縫對接:服務註冊,發現,安全性,CDC(T)和測試,訊息傳遞,微代理支援,斷路器等等。我們來看一些例子。

您可以使用新的響應式模組WebClient,它支援之前Spring Cloud的DiscoveryClient支援的任何服務註冊功能(Netflix Eureka,Hashicorp Consul,Apache Zookeeper,Cloud Foundry等)。

@Bean
WebClient client(LoadBalancerExchangeFilterFunction eff) {
  return WebClient.builder().filter(eff).build();
}
<p>

這樣你就使用這個響應式服務登錄檔WebClient了。在以下示例中,reservation-service是存在服務登錄檔中已經被註冊的服務,不是實際的主機名。

Publisher<String> emails = client
	.get()
	.uri("http://reservation-service/reservations")
	.retrieve()
	.bodyToFlux(Reservation.class)
	.map(Reservation::getEmail);
<p>

你可以使用Spring Cloud Stream中的響應功能來分別消費Kafka或RabbitMQ中的主題或佇列訊息,

@Configuration  
@EnableBinding(Sink.class)
public class MyStreamListener {

  @StreamListener
  public void incoming (@Input(Sink.INPUT) Flux<String> names ) {
    names
     .map ( x-> new Reservation( null, x))
     .flatMap ( this.reservationRepository::save )
     .subscribe( x -> log.info( "saved " + x.toString()));
   }
 }
 

你可以使用響應式Publisher物件來通過Hystrix斷路器保護和隔離可能發生錯誤的服務呼叫。

在以下示例中,我使用響應WebClient呼叫HTTP可能會失敗,如果失敗了,我們希望能夠提供對一個回退Publisher用來返回結果,這樣我的程式碼不會丟擲任何異常。它優雅地降級了。那個斷路器很聰明,它有自己的狀態監控,如果連續多次嘗試呼叫失敗,斷路器最終將直接切換到那個回退Publisher。如果下游服務重新聯機(如果使用Cloud Foundry將會重新啟動),那麼它最終會將重新註冊到登錄檔,登錄檔將發出心跳事件,並且心跳事件將把登錄檔中服務的本地檢視刪除,客戶端將看到登錄檔中有新的例項,它將重置斷路器,關閉它,並讓下一個呼叫成功通過。

Publisher<String> emails = client
  .get()
  .uri("http://reservation-service/reservations")
  .retrieve()
  .bodyToFlux(Reservation.class)
  .map(Reservation::getEmail);

Publisher<String> fallback = HystrixCommands
  .from( emails )
  .eager()
  .commandName("emails")
  .fallback ( Flux.just ("EEK!") )
  .build();

<p>

響應式新篇章

Spring開啟響應式程式設計的新開端,首先從這種新開端受益的是兩個新專案:Spring Cloud Gateway和Spring Cloud Function。

Spring Cloud Gateway是我們全新的響應式API閘道器。它建立在Spring的響應支援之上。它的功能是路由客戶端請求到下游服務。這其實響應應式程式設計的一個完美用例(需求)。

以下是使用Spring Cloud Gateway將請求代理:9999/proxy到服務(通過服務登錄檔解析和負載平衡)和速率受限的示例。(注意:此配置可以存在於Spring Cloud Config Server中的(可重新整理的)配置中,或者你可以建立的任何源Flux<Route>。)

此示例將每個經過身份驗證的使用者限制為每秒100個請求。您不需要通過Spring Security來協助閘道器做這件事,但是配置中已經暗暗地這樣做了:

@Bean
RouteLocator gateway (RouteLocatorBuilder rlb, RedisRateLimiter rrl) {
  return rlb
    .routes()
    .route( spec ->
      spec
       .path("/rl")
       .flters( fs -> fs
         .requestRateLimiter( c -> c.setRateLimiter( this.redisRateLimiter() ))
         .setPath("/reservations")
       )
       .uri("lb://reservation-service/")
    )
    .build();
}


@Bean // 100 reqs per second, burstable to 150
RedisRateLimiter redisRateLimiter (){
  return new RedisRateLimiter(100, 150);
}

<p>

函式即服務

Spring Cloud Function是我們新的 function-as-a-service(函式即服務)的抽象。它將plain-'-functions函式調整為不同的“函式即服務”執行時所需的型別。它可用於AWS Lambda,Microsoft Azure,當然還有我們自己的Project Riff,Project Riff是Apache 2許可的基於Kubernetes的多語言“函式即服務”執行環境。

使用起來可能不容易!您需要建立java.util.function.Function<I,O>例項。包括I和O,在這種情況下,可能是Publisher<X>:

package com.example.uppercase;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import reactor.core.publisher.Flux;

import java.util.function.Function;

@SpringBootApplication
public class UppercaseApplication {

		@Bean
		Function<Flux<String>, Flux<String>> uppercase() {
				return incoming -> incoming.map(String::toUpperCase);
		}

		public static void main(String[] args) {
				SpringApplication.run(UppercaseApplication.class, args);
		}
}
<p>

正如你現在所希望的那樣,響應式程式設計已經在Spring中真正實現了!Spring Cloud是最終全面支援的響應性程式設計的專案。

[該貼被admin於2018-07-04 20:10修改過]

相關文章