需要注意的是 本文不是真正的讨論如何做煎餅果子。
相信南方人都吃過煎餅果子——一種裹着生菜和火腿腸的雞蛋煎餅。雖然好吃,其制作過程卻也不比漢堡包簡單,讓我們一起來拆解下吧。

圖 1 ——煎餅果子拆解
從上圖(這個?沒有卷起來)我們可以看到,一個煎餅果子分為下面的幾個部分:
最下面的是雞蛋煎餅——雞蛋是打在煎餅上的
中間是一層生菜——若幹
在上面是一根火腿腸——隻有一根
最後把它卷起來,套上食品塑膠袋,就是煎餅果子了,如下圖:
看到如此美味的煎餅果子,是不是很想自己做來吃吃,甚至是去開個店,專門賣煎餅果子呢?當上CEO,迎娶白富美,這都不是夢啊~~。那麼,下面就讓我們一起看看如果做一個煎餅果子吧。
煎餅果子需要下面幾種食材:
面粉——我們需要做煎餅,
雞蛋N枚——取決于你要做的?的數量,一般一個煎餅果子,使用一枚?
生菜若幹——平均一個煎餅果子,使用1到2片生菜,
火腿腸N根——一般,都會加上火腿腸,可選,一般一個煎餅果子,一根火腿腸。
準備好了原材料,讓我們來做煎餅果子吧,
現在送脆餅
從上面的圖解中我們可以看到,流程是這樣的:
面粉 + 水 -> 面漿;面漿 + 烘焙 -> 煎餅
煎餅 + 雞蛋 + 烘焙 -> 雞蛋煎餅
雞蛋煎餅 + 生菜 -> 帶有生菜的雞蛋煎餅
帶有生菜的雞蛋煎餅 + 火腿腸 -> 帶有生菜和火腿腸的雞蛋煎餅
帶有生菜和火腿腸的雞蛋煎餅 + 卷曲 + 切斷 -> 煎餅果子
我們對上面的步驟進行化簡:
面粉 -> 煎餅
煎餅 + 雞蛋 -> 雞蛋煎餅
雞蛋煎餅 + 生菜 + 火腿腸 -> 煎餅果子
我們對上面的步驟繼續化簡
面粉 -> 煎餅 -> 雞蛋 -> 雞蛋煎餅 -> 生菜 -> 火腿腸 -> 煎餅果子
下面讓我們使用代碼先來模拟下,驗證怎麼樣才可以高效的做出煎餅果子吧。
實作代碼:
讓我們運作上面的代碼,其輸出結果如下:
如果我們偶爾做一次,其實還好啦,滿足,不過作為有夢想的程式員,我們要快點,這樣才有競争力啊,對,快,更快。
這就是我們牛逼的異步的方式了,即基于EventLoop/CSP的方式,事情都是一個人做,這個好了接着做下一個,在做煎餅的時候,不會等待煎餅烤熟。而是回去洗菜、準備火腿腸等。
這個時候,我們就會想,是不是多招聘幾個人呢?掐指一算,對,招聘4個人吧:
這裡我們,招聘了4個人,如下所示:
人手多了,讓我們來看看效果吧:
是的,我們變快了~~~,隻需要 5秒,我們投入了這麼大的人力成本不就是要,客戶第一,讓顧客更加快速地買到煎餅果子麼?
上面的方式稍顯老套,作為弄潮兒,最新的技術,搞搞搞,瞧,我們的透明後廚,簡潔着呢:
通過上面的方式,真的好簡潔,一套一套的,讓我們來看看效果吧:
額,竟然還是這樣?
感覺這個人有點多,生意也不夠好,還是換方式搞吧,某某某,你幫幫他吧,某某某,你也别閑着,把XXX也搞了。
不行,這樣不行,一定要從根本上解決問題,對,我們來梳理一下。
開店成功需要幾個要素?!
成本節約,在等待的時候,可以幫忙做點别的事情。
結構優化,效率提升,建立完整的上下行監管機制,消息順達。
精簡語義,使用專用詞溝通,減少自然語言表意不明。
客戶第一,提高服務品質,盡快傳回結果,不要超出購買者的能夠忍受的最長等待時間。
是以,對我們的例子使用Actor模型來進行模組化,然後應用CQRS來減少語義。當然,這裡我們的事件并沒有持久化,算是部分實作。
Actor模型 + Facade門面模式:
其中,使用 Ask 模式,我們接駁了傳統的門店服務,以及基于Actor 模型的店員。而對于我們的店員,我們又使用了Actor模型以及監管機制,同時使用 CQRS 來對指令和事件進行分離。
其中我們有指令:
在我們的門店服務内部,将會對這些指令進行處理,并且産生相應的事件。
再和門店的接駁處,使用了Ask模式:
而對于老闆/店長來說,他肯定是自己不處理的,是以他将任務,派發給了<code>煎餅果子大俠</code>,即:
注意,上面我們又一次使用了Ask模式,而非FSM。
這項任務就到達了我們的煎餅果子大俠了,他的任務可重了,因為他需要等待的東西有:
雞蛋煎餅
洗好的生菜
撕開好的火腿腸
是以:我們的煎餅果子大俠會分别和,雞蛋煎餅太郎、生菜小二哥以及火腿腸大叔形成依賴關系,并且會依賴于他們的結果,才可以做出一個完整的煎餅果子:
當收到老闆的指令的時候,他将任務進行了拆解,分發給了和他合作的其他店員:
這裡,我們搭配使用了Ask模式和Aggregator模式,将從各個部分收集到的結果,進行彙總,并産生了最終的美味的煎餅果子。
需要注意的是,我們在這裡,并沒有看到煎餅是怎麼來的,而是直接看到的是雞蛋煎餅,這就是DDD中的領域分層和依賴了:
我們的雞蛋煎餅太郎和煎餅西施之間是一個強依賴關系:
這裡,我們看到了一個奇怪的地方,即不對稱的逾時設定。因為太郎對西施特别好,是以頂住壓力,不管怎麼催他,他都會給西施說,别着急,慢慢來。
這就引發一個問題了,不正确的逾時設定,可能讓消費者非常不耐煩,本來消費者已經等了30s了,結果你對他說,哎呀,我們的雞蛋煎餅太郎太忙了,然後顧客灰溜溜的走了,丢失了大量的潛在客戶。
即,不合适的逾時配置,會造成服務品質的下降。
讓我們來把店開起來,并且提供服務吧
現在店開起來了,讓我們來看一看運作結果吧。
喔,完美的組織
架構和模式應用!當然,我們這裡沒有對Event進行持久化,這一點兒是不利于回溯的,同時也沒有應用斷路器模式,以及還有多處不合理的逾時配置。
上面的這些方式,都讓我們不難想,如何讓客戶更少的等待,如何提供更好的服務,如果我們的心更加大,如果我們要把店面做大,甚至要開連鎖店,或者開煎餅果子工廠呢?
技術,不是給業務以限制,而是助力其想象。
如果,我們想要将這個模式,複制到更多的場景,甚至開一條生産,N調生産線,如果我們要這些生産線能實作智能的調控,達到最小的資源占用,來達到最大的效力,那麼我們應該怎麼做呢?
對于這個問題,在2013年到2014年,業界也在思考,後來幾經波折,想到了一種基于流的拓撲描述的方式。下面就讓我們使用反應式流的方式,來實作上面的業務吧。
首先來一個不太清晰的例子,這個例子中,我們使用了Akka-Stream——一個ReactiveStream的實作。
在上圖中,我們對抽象進行了下面幾點改進:
我們的消費請求,被抽象為了一個流,這個流,類似于我們做的供給側改革,使用消費請求,來指導我們的生産。
我們的店員,不再是基本的店員了,把他們想成持續提供煎餅,雞蛋煎餅,生菜,和撕開的火腿腸,以及煎餅果子的流,即生産線。
我們可以動态地控制生産速度,如果消費者多,就在能力滿足的情況下,盡量地多生産,在某項能力不能滿足目前需求的情況下,就進行複制這些服務,對其進行複制,以提高更搞的生産力。比如,做煎餅是個比較緩慢的動作,那麼我們可以再加一條生産線,這個生産線專門生産煎餅。
有了這個流,我們發現,煎餅的生産和雞蛋煎餅的生産,總是強依賴的,那麼我們可以将他們部署到臨近的生産線,減少成本。
同上,我們發現雞蛋煎餅和生菜以及火腿腸的産生,也是最終要進行合并使用,那麼我們也可把這三種生産線,排布在一起,這樣降低了将這三種材料,運輸到煎餅果子大哥的時間。
如果我們發現煎餅果子大哥這個隻做煎餅的過程很慢了,這個時候我們可以隻在這個流程節點上,進行複制,進而加快煎餅果子的卷曲和打包過程。
我們如果一個發貨視窗發不過來,我們可以多開兩個門店/物流發貨視窗。
我們可以使用類似于由倉庫直接發貨的方式,将生産好的熱騰騰的煎餅果子,直接交給購買者,而不用通過我們的店長或者店面,在消息模式中,這叫做Forward模式。
即,我們描述了依賴,而再有了這樣的依賴之後,基于我們的需求和供應資訊。我們可以處處都進行複制流程,複制處理節點,進而做到最優化地讓熱騰騰的煎餅果子到達使用者的手裡。
當然,如果我們實在是,實在是不能再擴充生産線了,那麼我們就會進行回壓,回壓的時候,我們在最前面,就會讓客戶等待,或者在滿足SLA的情況下,進行一些政策,但是,我們整體的服務品質,依然是那麼得好,RS,就是雙向的流,控制流,資料流。
即,類似于下面的結構:
我們将生産好的結果,直接遞交給了消費者。
即,可以做到圖中的任意一個方框内的結構拓撲,都是可以單獨地進行複制,和調控的,甚至是智能地進行調控:)。智慧物流,我們也有智慧服務。
有了上面的實作,我們可以使用現在的一些語義化的工具來進行描述,比如:
好了,我們再看一種:
然後,我們再看一種:
然後仔細一下對比:
我們都需要煎餅Flow/Source,而且從煎餅Flow變成了雞蛋煎餅Flow/Source
我們都還需要生菜Flow/Source,以及火腿腸Flow/Source。
我們從雞蛋煎餅Flow/Source 、生菜Flow/Source以及火腿腸Flow/Source,建構了一條煎餅果子的Flow/Source。如果我們把Flow/Source看做生産線,喔喔,我們根據三個現有的Flow/Source,建構了一條煎餅果子的Flow/Source。
最後,我們隻從裡面拿了一個煎餅果子出來:)
上面我們還有一點,就是異步和并發怎麼控制?我如何并發動做一些事情呢?
或者
非常簡單,如果我們想要同步的呢?去掉紅框中的部分就好了。
也就是說,我們描繪了整個服務的編排,然後我們便可以友善地對任意特定的子拓撲進行優化了。開辟新的生産線,或者通過複制某個處理的過程來對某個流程進行并發執行,以提高其生産效率,當然在真的擴不了的情況下,保護我們的系統,保障我們SLA。
那麼,我們如何不斷地擷取我們的美味煎餅果子呢?早上喜歡吃煎餅果子的人太多,都要瘋掉了,好,請看:
剩下的留作練習:)
隻要我們的請求不斷,我們的
就會持續的産生結果。多麼簡單直接,而且非常的優雅。複用這樣的模式,開連鎖店,開工廠,智能化的工業生産,人生巅峰不是夢。
拓展思考?
基于這個例子,我們可以看到,如果我們的服務代碼,通過上面的方式來編寫,那麼勢必更加地簡潔優雅,而且我們也具備了更細膩的控制力,并且也有了更高屋建瓴的拓撲、思維模組化以及全局優化的可能。同時,我們的服務,都是由反應式流中流轉的信号量進行驅動的,進而實作了動态的推拉結合——對,沒錯,控制論中的知識:)
我相信,通過面向流的程式設計,從資料first,切換服務編排,請求/響應拓撲first的思維,将會大大地提高我們對鍊路的了解能力。同時,有了這些工具,我們常見的反應式設計模式,都可以非常友善地應用,并完全可以結合FP以及DDD的一些思路,打造更加清晰、明了、性能優異的系統。而且,我們僅僅描述了服務的編排,進而産生拓撲,而剩下的事情,隻需要動動手指,也許手指都不需要動:),這一切,都将會有下一代的架構來智能地保證。
從上面,大家已經看到了,我們的程式設計模式,是如何一步一步地從傳統的方式,變成我們的Reactive 化的方式。通過Reactive 的方式,我們可以友善的實作服務編排,有了服務編排之後,我們可以做更多的事情,服務将會是更加智能化的,而非一成不變,提升了客戶體驗、資源使用率,并降低了資源的浪費。