"reactive programming"的概念

下面的內容大可能是翻譯來的。node

Reactive Programming?react

What is Reactive Programming?

爲了瞭解Reactive——從編程範式至其背後的動機,有必要了解如今的開發者和公司在十年前未曾面對的挑戰。git

遊戲的改變主要有兩大方面:github

  • 硬件的提高
  • 因特網

Why things are different now?

(原文對比了一遍2005年和2014年因特用戶的增加:10億至29.5億;Facebook用戶的增加:550萬至13億; twitter從無至有,0至2.7億用戶)web

時代改變了,如今一個網站就須要處理十年前整個因特網的流量。實下,容易發現軟件對於平常生活愈來愈重要。也容易發現,之前的一些範式並再也不適用於如今,更不適用於未來。編程

The four Reactive principles

Reactive applications 構建於四個指導性的原則。api

 

  • 目標是一個responsive的程序
  • 一個responsive的程序必定既scalable又resilent.responsiveness離開了scalability和resilence是不可能實現的
  • 一個message-driven的架構是scalale, resilent以及最終實現responsive系統的基礎

讓咱們在高層瞭解下每個原則,理解爲了開發一個適用於如今環境的軟件,爲何它們是缺一不可的。安全

Responsive

當咱們說一個程序是"responsive"的時候,是在說什麼?服務器

A responsive system is quick to react to all users — under blue skies and grey skies—in order to ensure a consistently positive user experience.網絡

一個responsive的系統是對全部用戶都能快速反應的——無論晴天陰天——來保證一個始終積極的用戶體驗。

在各類條件下都「快速」而「積級」的用戶體驗,好比外部系統出問題或者流量激增,依賴於Reactive application的兩個特質:resilence,以及scalability。一個消息驅動架構提供了一個responsive系統的基礎。

爲何消息驅動架構對於responsiveness如此重要?

這個世界是異步的。

 假如你如今來杯咖啡,卻發現糖和奶油沒了。

一種作法是:

  • 開始煮咖啡
  • 去商店買糖和奶沒
  • 回家
  • 啥都齊了,開始喝咖啡
  • 享受生活

另外一種作法是:

  • 去商店買糖和奶油
  • 回家
  • 煮咖啡
  • 等着咖啡煮好
  • 忍受咖啡因短缺
  • 崩潰

就像你看到的,一個消息驅動的架構提供給你異步邊界(asynchronous boundary),使你從時間和空間中解放出來。在這個文章的其他部分咱們將繼續深刻異步邊界的概念。(這段舉的例子挺好,但是看不出這個例子跟消息驅動有啥關係呀)

Consistency at Walmart Canada 一致性之於加拿大亞馬遜

(譯註:這個一致性和分佈式系統的一致性不同……)

在加入Typesafe公司以前,我是負責構建亞馬遜加拿大的新電商平臺的Play和Scala團隊的技術leader。

咱們的目標是提供一致的積極的用戶體驗,無論:

  • 不管什麼類型的物理設備被用來訪問walmart.ca,不設它是桌面電腦,平板仍是移動設備
  • 當前的流量峯值,無論它是因爲激增纔出來的,仍是持久的
  • 一個主要的基礎設施不可用,好比一整個數據中心。

響應時間和總體的用戶體驗必須保持一致,無論上面的狀況是否發生。一致性對於你的網站是相當重要的,由於當下,這就是你的品牌。一個很差的用戶體驗是難以被忘記或者忽略的,無論是發生在線上仍是在一個線下的商店。

Responsive retail at Gilt 

Gilt的Responsive零售。電商領域的一致性(的重要性)並非碰巧出現的。好比Gilt也是同樣,Gilt是一個閃購網站,天天中午當他們發佈了當日的銷售商品時流量就會激增。

讓我考慮下一個閃購網站的用戶體驗。若是你在11:58訪問了Gilt,12:01又訪問了它。那麼你但願兩次訪問都有積極的體驗,無論是否是Gilt在12:01那時候流量突增。

Gilt提供始終積極、responsive的用戶體驗,而且經過Reactive來實現這一切。經過這篇ReadWrite interview with Eric Bowman of Gilt 瞭解Gilt向基於Scala的微服務架構(Scala-based microservices architecture)的遷移。

Resilient

大部分的程序都被設計和開發成適用於「晴天」,可是事情可能往壞的方面發展。每隔幾天就會有報告說一個主要的應用不可用,或者因爲黑客的攻擊形成服務不可用、數據丟失、或者儲譽受損。

A resilient system applies proper design and architecture principles in order to ensure responsiveness under grey skies as well as blue.

一個彈性的系統採用了一些設計和架構的原則,使得無論外部狀況的好壞,都是responsive的

Java和JVM都是用來把一個程序無縫地佈署到多個操做系統,相似的是,201x年的內部互聯的應用程序都是關於應用程序層級的組合、鏈接和安全。(注:這句有點繞……忽略,看下邊的)

如今的程序大都由必定數量的其它程序組裝而成,經過web service和其它網絡協議整合在一塊兒。如今一個應用可能依賴於大量的其它服務——10,20或者更多,它所依賴的這些服務在本身的信賴防火牆(trust firewall)以外。它可能同時在爲大量外部用戶提供服務——既包含人,也包括其它系統。

考慮到整合的複雜性,有多少開發人員:

  • 分析和建模全部外在的依賴
  • 把整合進的每一個服務的理想響應時間整理成文檔,進行性能測試——在峯值和日常——來全面評估最初的指望
  • 把全部的預期,失敗狀況和其它非功能的需求編碼到系統的核心邏輯中
  • 分析和測試每一個服務的失敗情形的影響
  • 分析每一個外部依賴的安全,識別是否整合這個外部依賴是否給系統帶來最的安全隱患

Resiliency is one of the weakest links of even the most sophisticated application, 可是Resilency做爲一種過後的思考的狀況很快就會終結。現代的程序必須在其核心就是resilent的,以保持在現實世界多變的環境下,而不僅是理想狀況下,保持responsive。 性能、持久性、安全都是resilency的方面。你的應用必須在各個層次都是resilent的。

 Message-driven resiliency

在一個消息驅動的內核之上構建應用的美妙之處在於你天然地獲得了不少有價值的構造模塊。

Isolation 隔離性是一個自愈(self-heal)的系統所須要的。當隔離性在被恰當地使用,咱們能夠依據一些因素把不一樣類型的工做進行分離,好比失效的風險、不一樣的性能、CPU和內存的使用,等。一個isolation的組件的失效並不會影響整個系統的responsiveness, 而且給予了失效的那個系統一個被治癒的機會。

Location transparency 位置透明性使得咱們與不一樣集羣結點上的不一樣進程間的通訊就像與同一個VM的進程內通訊同樣。

A dedicated separate error channel 一個精心設計的分離的錯誤信息通道使得咱們能夠把錯誤信號重定向到其它地方,而不是拋回到調用者面前。(注:這個做用應該類型於Akka中的event stream這種東西,使得某些失敗的信息能夠被訂閱,使得不光在當前是調用了某個失敗組件的組件能夠知道它失敗了,而是全部相關的組件均可以瞭解到這個狀況,便於他們便是做出響應)

這些因素幫助咱們把健壯的錯誤處理和容錯合併進咱們的系統中。Akka的supervisor hierarchies的實現展現了這點。

消息驅動架構提供的這些核心的構建模塊有助於resiliency,進而有助於responsiveness——不只在理想狀況下,並且在各類並不是理想的、現實世界的狀況下。

 

The 440 million dollar resiliency mistake

來見識下騎士資本集團在2012年發生軟件故障的經歷(software glitch experienced by Knight Capital Group)。在一次軟件升級中,另外一個休眠的、被整合在一塊兒程序被不經意地喚醒,而且開始放大交易量。

接下來的45分鐘發生的事情是一場惡夢。

騎士資本的自動交易系統洪水般地向NASDAQ(注:納斯達克)進行錯誤交易,使得公司進入了一個須要付出數十億美圓代價的位置。過後,這家公司花了4.4億美圓來扭轉局勢。在這個故障中,騎士資本不能中止泛水般的錯誤交易,NASDAQ不得不終止騎士資原本中止這一切。騎士資本的股票在一天跌了63%, 幾乎不能作爲一個公司存活了下來,只能等着它的股票恢復了一些價值以後被一些投資者接管。

騎士資本的系統是高性能的,可是不是resilent的。高性能可是沒有resilience使得問題被放大,就像騎士資本發現的那樣。他們甚至沒有一箇中止開關來使得本身的系統在出現嚴重問題時中止交易, 因此,當真的出了問題時,他們的自動交易系統在45分鐘內耗盡了整個公司的資本。

這就是設計和開發只能用於"晴天「的程序的結果。現在,軟件是咱們的私人生活和生意的關鍵組成。若是咱們沒有爲」陰天「的狀況提早設計和準備,那麼即便這種狀況只存在了不到一個小時,也會給咱們形成巨大的損失。

 Scalable

Resililency和Scalability手拉手,一塊兒共建始終responsive的程序。

A scalable system is easily upgraded on demand in order to ensure responsiveness under varioius load conditions.

一個可擴展的系統能夠在面對不一樣的負載時輕鬆地進行升級,來保證respnsiveness

任何一個在網上賣過東西的人都明白一個事實:你的流量最大的時候就是你賣的東西最多的時候。除非流量的突增是因爲故意的黑客襲擊,否則指望流量突增是挺好的一件事。在一個突增的流量中,人們願意花錢給你。

那麼如何面對這種流量突增,或者是一個長久的可是大量的流量增加。

首先選擇你的範式(paradigm),而後選擇擁抱這種範式的語言和工具。不少狀況下,開發者只是簡單地選擇語言和框架……。一旦選擇了工具,就很難改回來. 因此,就像你在處理任何一個重大的投資同樣進行這些決定。若是你的技術選擇基於一些原則和分析,那麼你就已經領先一步了。

 Thread-based limitations to concurrency 基於線程的併發的不足

一個相當重要的技術選擇就是框架的併發模型。在較高的層次,有兩種併發模型:

  • 傳統的基於線程的併發模型,基於調用棧和共享內存
  • 消息驅動的併發

一些流行的MVC框架,好比Rails,是蔡於線程的。這類框架的典型特色包括:

  • 共享的可變狀態
  • 每一個請求一個線程
  • 對於可變狀態的併發訪問——變量以及對象實例——使用鎖和其它複雜的同步結構來進行管理

這些特色結合動態類型、解釋型語言好比Ruby,會迅速達到性能和擴展性的上限。你能夠認爲全部本質是腳本語言的語言都是如此。

Out or up?

讓咱們考慮擴展一個應用的不一樣方式。

向上擴展(scaling up)是關於最大化的利用一個CPU/服務器的資源,一般須要購買更強大的,專用的、更貴的機器。

向外擴展(scaling out)是關於把計算分佈到由廉價的commdity機器組成的集羣中。這樣在成本上更划算,可是若是你的系統是基於時間和空間概念的(注:」基於時間的「能夠認爲程序的各個動做是同步的,須要阻塞,等待,」基於空間的「能夠認爲是基於一個進程內部的內存共享,好比不線程間共享的變量),那麼向外擴展就很是困難。 就像咱們以前提到的那樣,一個消息驅動的架構提供了與時間和空間解耦合所須要的異步邊界(asynchronous boundary),提供了按需向外擴展(scale out on demand)的能力,這種能力也被叫作elasticity. Scaling up是關於更高效地利用已有的資源,而elasticity是關於在系統須要改變時按需增長新資源。可以」scale out, on demand"的能力,就是Reactive programming在擴展性方面的終極目標。

Reactive應用程序很難構建於基於線程的框架之上,想一想就知道把一個基於可變狀態,線程和鎖的的程序進行scale out有多困難。開發人員不只須要利用單個機器上多核的優劣,在某些時候,開發者也須要利用計算機集羣的力量。共享可變狀態也使得scale up變得困難,即便不是不可能。任何曾嘗試操做兩個線程共享的可變狀態的人都可以理解保證線程安全有多複雜,而且也可以理解爲了確保線程安全所須要花費的多餘努力所帶來的性能上的懲罰。

Message-driven

一個消息驅動的架構是Reactive應用程序的基礎。一個消息驅動的程序能夠是事件驅動的(event-driven),基於actor的(actor-based),或者二者的結合。

一個事件驅動的系統基於事件,這些事件被一個或更多監聽者監聽。這和imperative programming不一樣,所以調用者不須要阻塞以等待被調用的例程的響應。事件並非被導向特定的地址,而是被監聽,咱們將會更深刻地討論其隱含的意義。

基於actor的併發是消息傳遞架構的一種延伸,在基於actor的併發架構中,消息能夠跨越線程的邊界或者被傳遞到不一樣物理機器上的另外一個actor的郵箱(mailbox)中。這使得elasticity——scale out on demand——變得可能,由於actor能夠被分佈到網絡的各處,可是仍然能夠像他們在同一個VM裏同樣通訊。

消息和事件的不一樣在於消息是有方向的,而事件只是」發生「並無方向。消息有一個清晰的目的的,而event能夠被一個或者多的監聽者監聽。

讓咱們深刻探討下事件驅動和基於消息的併發。

Evnt-driven concurrency

典型的程序用命令的風格開發—一個命令的線性序列—而且是基於調用棧(call stack)的。調用棧的主要功能是追蹤例程(routine,感受應該是函數呀)的調用者,在阻塞調用者的同時執行被調用的例程,當執行完畢後把控制流交還給調用者,而且返回執行結果的值(或者不返回)

事件驅動的程序並不關注於調用棧,而是關注於觸發事件。事件能夠被編碼成消息,放在隊列裏,被一個或者更多監聽者監聽。事件驅動和命令式的巨大區別在於調用者不被阻塞,不用在等待響應的時候佔據一個線程(caller does not block and hold onto a thread while waiting for a response)。事件循環自己能夠是單線程的,可是仍然能夠實現併發,由於能夠在(有時是單線程 的)事件循環處理新的請求的同時調用例程來完成工做(注:由於對例程的調用被搞成了非阻塞的,即不會阻塞event-loop)。再也不是阻塞於一個請求,等待它被處理完,如今調用者的標識能夠和請求消息自己一塊兒被傳遞,使得(若是被調用的例程選擇這麼作)調用者能夠被回調,在回調時傳遞響應(the caller can be called back with a response)。

選擇一個事件驅動架構的主要結果是他們可有會遇到一個現象,叫作回調地獄(callback hell)http://callbackhell.com 。 回調地獄之因此發生,是由於消息的接受者是匿名函數而不是可尋址的接受者(addressable recipients).一般的解決方案僅僅關注於句法—aka, the Pyramid of Doom—而忽略了推導和調試代碼中表達的event所產生的後續事件的困難。

Actor-based concurrency

基於actor的程序以在多個actor之間異步的消息傳遞爲中心。

一個Actor由如下的屬性組成:

  • 用於接收消息的mailbox
  • actor的邏輯,它依賴於模式匹配(pattern matching)來決定這個actor如何處理不一樣類型的消息
  • 隔離的狀態(isolated state),而不是共享的狀態。使用隔離的狀態來存儲請求之間的上下文。

像事件驅動的併發同樣,基於actor的併發也避開了調用棧,轉向輕量級的消息傳遞。actor能夠返回以及發送消息,甚至傳遞消息給本身。一個消息能夠傳遞給本身消息,使得它先處理隊列裏的其它消息,而後再去結束一個長時間運行的請求。基於actor的併發的一個巨大好處是,它不只能夠有事件驅動的併發的優勢,並且可讓計算跨越網絡邊界變得更容易,而且能夠避免callback hell,由於消息被定向到actor。這是一個強大的概念,有助於構造易於設計、實施、維護的高可擴展性的程序。不用考慮「空間」和「時間」,也不用考慮深度嵌套的回調函數,你只須要考慮actor之間的消息流動。

另外一個基於actor的架構的主要好處是組件之間解耦合。調用者不用阻塞本身的線程等待響應,所以調用者能夠迅速地轉去進行其它工做。被調用的例程,封裝在一個actor中,只需在必須的回覆調用者。這開啓了不少可能性,好比分佈例程(routines)到一個集羣中,由於再也不有調用棧把程序耦合在單一的內存中,actor模型可使佈署一個拓撲(topology)只是一個虛擬的配置而再也不是編碼在程序裏。

Akka是一個基於actor的工具箱和運行時- Typesafe Reactive Platform的一部分,用來構造併發的、分佈式的、容錯的,基於actor的程序,運行於JVM。Akka還有其它的一些用於構造Reactive application的不可思憶的特性,像是用於rsilience的supervisor hierarchies,以及能夠把分佈工做來實現擴展性。對Akka的深刻研究超出了本文的範圍,可是我強烈推薦訪問Let it Crash blog 獲取更多關於Akka的內容。

我也強烈推薦閱讀Benjamin Erb’s Diploma Thesis,Concurrent Programming for Scalable Web Architectures, 被用於本節的參考。

Conclusion

以上的內容描述了時下軟件開發的的表面,引導咱們認識到爲何Reactive programming不只僅是一個潮流,並且是如今的軟件開發者須要學習的範式。無論你選擇什麼語言和工具,優先考慮scalability和resilient來實現responsiveness是惟一能知足用戶需求的方式。每過一年,這點就會變得更加劇要。

想要手把手的學習吧?下載Typesafe Activator ,在今天開始構建你的Reactive applications. (廣告呀……)

 


 

下面是另外一篇文章

  • Reactive

    is readily responsive to a stimulus

    • react to events (event-driven)
    • react to load (scalable)
    • react to failures (resilient)
    • react to users (responsive)

     

    • Event-Driven

      Systems are composed from loosely coupled event handlers.

      • Events can be handled asynchronously, without blocking.
    • Scalable

      An application is scalable if it is able to be expanded according to its
      usage.

      • scale up: make use of parallelism in multi-core systems
      • scale out: make use of multiple server nodes

      Important for scalability: Minimize shared mutable state.
      Important for scale out: Location transparency, resilience.

    • Resilient

      An application is resilient if it can recover quickly from failures.
      Failures can be:

      • software failures
      • hardware failures
      • connection failures

      Typically, resilience cannot be added as an afterthought; it needs to be
      part of the design from the beginning.
      Needed:

      • loose coupling
      • strong encapsulation of state
      • pervasive supervisor hierarchies
    • Responsive

      An application is responsive if it provides rich, real-time interaction withits users even under load and in the presence of failures.Responsive applications can be built on an event-driven, scalable, andresilient architecture. Still need careful attention to algorithms, system design, back-pressure, and many other details.

相關文章
相關標籤/搜索