一文讀懂 Serverless,將配置化思想複用到平臺系統中

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者 | 春哥大魔王","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"來源 | ","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/AlcD8Hhq1ks8mRzpCz55qg","title":""},"content":[{"type":"text","text":"Serverless 公衆號","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"寫在前面","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 SaaS 領域 Salesforce 是佼佼者,其 CRM 的概念已經擴展到了 Marketing、Sales、Service 等領域。那麼 Salesforce 靠什麼變成了這三個行業的解決方案呢?得益於 Salesforce 強大的 ","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s?_biz=MzA4MTA0NTI1Mg==&mid=2650926599&idx=1&sn=6f0b0c40c9c81f2085fc229260d756e6&scene=21#wechatredirect","title":""},"content":[{"type":"text","text":"aPaaS 平臺","attrs":{}}]},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ISV、內部實施、客戶均可以從自己的維度基於 aPaaS 平臺構建自己的行業,實現業務定製,甚至是行業定製。因爲在此之前只有在 Sales 方向有專門的 SaaS 產品,而 Marketing 和 Service 都是由自己的 ISV 在各自行業的解決方案。所以 Salesforce 已經從一家 SaaS 公司變成了一家 aPaaS 平臺公司了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"搭建一個 aPaaS 平臺是需要很長時間的,當然也可以基於一些公有云產品的 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Serverless 方案","attrs":{}},{"type":"text","text":"實現現有系統的靈活性與擴展性,從而實現針對於不同客戶的定製。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"什麼是 Serverless","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Serverless 由兩部分組成,Server 和 Less。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前者可以理解爲其解決方案範圍處在服務端;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"後者可以譯爲少量的;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"組合起來就是較少服務端干預的服務端解決方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與 Serverless 相對的是 Serverfull,比較下對應的概念可能更便於理解。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Serverfull 時代","attrs":{}},{"type":"text","text":",研發交付流程一般有三個角色:RD,PM,QA。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RD 根據 PM 的 PRD 進行功能開發,交付到 QA 進行測試,測試完成之後發佈到服務器。由運維人員規劃服務器規格、數量、機房部署、節點擴縮容等,這種更多由人力處理的時代就是 Serverfull 時代。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"之後進入了 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"DevOps 時代","attrs":{}},{"type":"text","text":"。這個時代運維自己開發一套運維控制檯,可以讓研發同學在控制檯上自己進行服務觀測、數據查詢、運維處理等,運維同學的工作輕鬆了不少,這個階段主要釋放了運維同學的人力。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而到了 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Serverless 時代","attrs":{}},{"type":"text","text":",這套運維控制檯能力越來越豐富,可以實現按配置的自動擴縮容、性能監控、DevOps 流水線等,同時侵入到研發流程側,比如自動發佈流水線、編譯打包、代碼質量監測、灰度發佈、彈性擴縮等流程基本不需要人力處理了,這就是 Serverless 時代。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Serverless 怎麼用","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相信你有過這樣的經歷,在一個 Web 界面上,左側寫代碼,右側展示執行效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/56/5643e6126352ead2f811d13ea2d4dbc9.png","alt":"1.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寫的是代碼塊,代碼數量不會特別大;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼運行速度快;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持多種編程語言;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以支持不可預計的流量洪峯衝擊。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以阿里雲解決方案看下如何支持多語言架構:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/21/2175a0a30499d7aa36bdac8a82550198.png","alt":"2.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"抽象來說,前端只需要將代碼片段和編程語言的標識傳給 Server 端即可,等待響應結果。Server 端可以針對於不同編程語言進行 runtime 分類、預處理等工作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Serverless 怎麼做","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很多人把 Serverless 看做是 FC(function compute:函數計算),使用函數計算,無需業務自己搭建 IT 基礎設施,只需要編碼並上傳代碼。函數計算會按需爲你準備好計算資源,彈性、可靠地運行,並提供 trace、日誌查詢、監控告警等治理能力。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/49/495d501313e3a711c8da22409bacc590.png","alt":"3.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 FC 中有服務和函數之分。一個服務可以包含多個函數。我們可以用微服務理解,我們通過 golang 或 java 搭建了一個微服務架構,而 FC 服務就是其中的類,FC 函數是類中的一個方法:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f1/f1d1ef250d2e9dc993c3f6a6a714d4df.png","alt":"4.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"區別在於 Java 搭建的微服務只能運行 java 類代碼,golang 的類只能運行 go 寫的代碼,而 FC 函數可以安裝不同語言的 runtime,支持運行不同語言程序。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/37/37c27cbd0fe982cea7bbc11947d43959.png","alt":"5.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類比理解之後,我們再看下","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如何調用 FC 的函數","attrs":{}},{"type":"text","text":",一般的 FC 解決方案裏面都有一個觸發器的概念。比如 HTTP 觸發器、對象存儲觸發器、日誌服務觸發器、定時任務觸發器、CDN 觸發器、消息隊列觸發器等。觸發器是對於 FC 函數調用的抽象收口,比如 HTTP 觸發器一般都類比網關的一個 http 請求事件,或是指定對象存儲路徑下上傳了一個圖片,這些觸發事件的入口都可以是觸發器。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2c/2cc65af886a1329b7158cd18abca1404.png","alt":"6.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"觸發器產生事件之後可以調用 FC 函數,函數執行的邏輯可以是下載一張圖片或是註冊一個用戶。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣從觸發器到 FC 函數邏輯處理就是一個 FC 的生命週期了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"那麼 FC 是如何實現高可用的呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實每個函數底層代碼都是運行在一套 IaaS 平臺上,使用 IaaS 資源,我們可以爲每個函數設置運行代碼時需要的內存配置即可,比如最小 128M,最大 3G 等。研發人員不需要關心代碼運行在什麼樣的服務器上,不需要關心啓動了多少函數實例支持當前場景,不需要關注背後的彈性擴縮問題,這些都被收斂在 FC 之後。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e5/e50d9a5a55a12c09973679aa799bba1a.png","alt":"7.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如圖有兩種高可用策略:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"給函數設置併發實例數,比如 3 個,那麼當有三個請求進來時,該函數只啓動一個實例,但是會啓動三個線程來運行邏輯;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程達到上限後,會再拉起一個函數實例。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"類似於線程池的方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/18/1873a7c610a4a88f019bf6158b969a20.png","alt":"8.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"那麼 Serverless 如何提效呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"效率高","attrs":{}},{"type":"text","text":":如果新加了語言,只需要創建一個對應的 Runtime 的 FC 函數即可;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"高可用","attrs":{}},{"type":"text","text":":通過多線程、多實例兩種方式保障高可用,且函數實例擴縮容完全由 FC 自助處理,不需要運維做任何配置;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"成本低","attrs":{}},{"type":"text","text":":在沒有觸發器請求時,函數實例不會被拉起,也不會計費,所以在流量低谷期間或者夜間時,FC 消耗的成本是非常低的。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"如何在雲平臺創建一個 FC","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1. 創建服務","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先新建一個服務名稱;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"選定服務部署的地區(背後幫助你就近部署在目標機房);","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"選擇是否打開調試日誌(開發過程開啓,線上運行時可關閉)。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2. 創建函數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了服務之後就可以創建函數了,比如選擇基於 http 請求的函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"選擇函數綁定的服務;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"設置函數名稱;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"選擇 runtime 環境;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"是否要求函數實例彈性;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"函數入口(觸發器直接調用的目標方法);","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"函數執行內存;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"函數執行超時時間;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"設置實例併發度。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ba/ba3ad5f52c4e56d9c8f4acebfe7105e2.png","alt":"9.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"配置觸發器,比如選擇了 HTTP 觸發器,然後在觸發器上綁定函數名稱,由於是 http 訪問,可以選擇訪問的鑑權、認證方式,以及請求方式 POST or GET。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3. 代碼編寫","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當函數創建好了之後,進入函數,可以看到描述、代碼執行歷史、觸發器類型、日誌查詢頁等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果是 HTTP 觸發器,需要配置 http 觸發路徑。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/27/2789790ad26928a0198bfe579c75e3ac.png","alt":"10.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到就如前面介紹的那種,類似於類裏面的一個函數,上下文請求會打到這裏,直接執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Python 代碼爲例:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"# -*- coding: utf-8 -*-\nimport logging\nimport urllib.parse\nimport time\nimport subprocess\ndef handler(environ, start_response):\n context = environ['fc.context']\n request_uri = environ['fc.request_uri']\n for k, v in environ.items():\n if k.startswith('HTTP_'):\n pass\n try: \n request_body_size = int(environ.get('CONTENT_LENGTH', 0)) \n except (ValueError): \n request_body_size = 0 \n # 獲取用戶傳入的code\n request_body = environ['wsgi.input'].read(request_body_size) \n codeStr = urllib.parse.unquote(request_body.decode(\"GBK\"))\n # 因爲body裏的對象裏有code和input兩個屬性,這裏分別獲取用戶code和用戶輸入\n codeArr = codeStr.split('&')\n code = codeArr[0][5:]\n inputStr = codeArr[1][6:]\n # 將用戶code保存爲py文件,放/tmp目錄下,以時間戳爲文件名\n fileName = '/tmp/' + str(int(time.time())) + '.py'\n f = open(fileName, \"w\")\n # 這裏預置引入了time庫\n f.write('import time \\r\\n')\n f = open(fileName, \"a\")\n f.write(code)\n f.close()\n # 創建子進程,執行剛纔保存的用戶code py文件\n p = subprocess.Popen(\"python \" + fileName, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, encoding='utf-8')\n # 通過標準輸入傳入用戶的input輸入\n if inputStr != '' :\n p.stdin.write(inputStr + \"\\n\")\n p.stdin.flush()\n # 通過標準輸出獲取代碼執行結果\n r = p.stdout.read()\n status = '200 OK'\n response_headers = [('Content-type', 'text/plain')]\n start_response(status, response_headers)\n return [r.encode('UTF-8')]","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"流程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前端傳入代碼片段,格式是字符串;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 FC 函數中獲取到傳入的代碼字符串,截取 code 內容和 input 內容;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將代碼保存爲一個 py 文件,以時間戳爲文件命名,保存在 FC 函數的 /tmp 目錄下,每個函數有自己獨立的 /tmp 目錄;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"import time 庫代碼;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過 subprocess 創建子流程,以 shell 方式通過 py 命令執行保存在 /tmp 目錄下的 py 文件;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後讀取執行結果返回給前端。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前端調用 FC 函數:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/12/1290174c8525b2ea12cae69444da4f22.png","alt":"11.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整個過程只需要前端將代碼傳入到 FC 函數裏面,整個 Server 端各個環節都不需要研發與運維同學關心,體現了 Serverless 的精髓。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"用 Serverless 協調工作流","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"工作流可以用順序、分支、並行等方式來編排任務執行,之後流程會按照設定好的步驟可靠地協調任務執行,跟蹤每個任務的狀態切換,並在必要時執行定義的重試邏輯,確保流程順利執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"工作流流程通過記錄日誌和審計方式來監視工作流的執行,便於流程的診斷與調試。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/16/1684148cb5a8b8aa9c70cfb6818f33d2.png","alt":"12.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"系統靈活性與擴展性的核心是服務可編排,所以我們需要做的是將現有系統內部用戶希望定製的功能進行梳理、拆分、抽離、結合 FC 提供的無狀態能力,將這些功能點進行編排,實現業務流程的定製。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"需靈活配置工作流的業務","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"舉個例子,比如餐飲場景下不同商家可以配置不同的支付方式,可以走微信支付、銀聯支付、支付寶支付。可以同時支持三家,也可以某一家,可以到付,也可以積分兌換等。如果沒有一個好的配置化流程解決方案的話,系統中會出現大量硬編碼規則判斷條件,系統迭代疲於奔命,是個不可持續的過程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了 FC 搭建的工作流就可以很優雅地解決這種問題,比如規整流程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/82/82720c52b23ff4db0586f114d0d5adb4.png","alt":"13.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的流程是用戶側的流程,接下來需要轉換成程序側的流程,通過約束的 FDL 創建工作流,如圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/02/02869aaa707c46331bb13ca08eff50ea.png","alt":"14.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"FDL 代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"version: v1beta1\ntype: flow\ntimeoutSeconds: 3600\nsteps:\n - type: task\n name: generateInfo\n timeoutSeconds: 300\n resourceArn: acs:mns:::/topics/generateInfo-fnf-demo-jiyuan/messages\n pattern: waitForCallback\n inputMappings:\n - target: taskToken\n source: $context.task.token\n - target: products\n source: $input.products\n - target: supplier\n source: $input.supplier\n - target: address\n source: $input.address\n - target: orderNum\n source: $input.orderNum\n - target: type\n source: $context.step.name\n outputMappings:\n - target: paymentcombination\n source: $local.paymentcombination\n - target: orderNum\n source: $local.orderNum\n serviceParams:\n MessageBody: $\n Priority: 1\n catch:\n - errors:\n - FnF.TaskTimeout\n goto: orderCanceled\n -type: task\n name: payment\n timeoutSeconds: 300\n resourceArn: acs:mns:::/topics/payment-fnf-demo-jiyuan/messages\n pattern: waitForCallback\n inputMappings:\n - target: taskToken\n source: $context.task.token\n - target: orderNum\n source: $local.orderNum\n - target: paymentcombination\n source: $local.paymentcombination\n - target: type\n source: $context.step.name\n outputMappings:\n - target: paymentMethod\n source: $local.paymentMethod\n - target: orderNum\n source: $local.orderNum\n - target: price\n source: $local.price\n - target: taskToken\n source: $input.taskToken\n serviceParams:\n MessageBody: $\n Priority: 1\n catch:\n - errors:\n - FnF.TaskTimeout\n goto: orderCanceled\n - type: choice\n name: paymentCombination\n inputMappings:\n - target: orderNum\n source: $local.orderNum\n - target: paymentMethod\n source: $local.paymentMethod\n - target: price\n source: $local.price\n - target: taskToken\n source: $local.taskToken\n choices:\n - condition: $.paymentMethod == \"zhifubao\"\n steps:\n - type: task\n name: zhifubao\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan/functions/zhifubao-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n - condition: $.paymentMethod == \"weixin\"\n steps:\n - type: task\n name: weixin\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/weixin-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n - condition: $.paymentMethod == \"unionpay\"\n steps:\n - type: task\n name: unionpay\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/union-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n default:\n goto: orderCanceled\n - type: task\n name: orderCompleted\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/orderCompleted\n end: true\n - type: task\n name: orderCanceled\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/cancerOrder","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"示例體現了基於 Serverless 的 FC 可實現靈活工作流。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"流程如何觸發的呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/38/38144f65380dafdce2a3a7646b4271cb.png","alt":"15.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在用戶選擇完商品、填完地址之後,通過拉取商品、訂單上下文,可以自動化觸發流程了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在微服務背景下,很多能力不是閉環在單體代碼邏輯之內,很多時候是多個業務系統的連接,比如串聯多個 OpenAPI 接口實現全流程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/46/46ff1bacd3a3228bc4d873f07cae27bd.png","alt":"16.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如想使用流程引擎需要進行相關的備案鑑權:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"@Configuration\npublic class FNFConfig {\n @Bean\n public IAcsClient createDefaultAcsClient(){\n DefaultProfile profile = DefaultProfile.getProfile(\n \"cn-xxx\", // 地域ID\n \"ak\", // RAM 賬號的AccessKey ID\n \"sk\"); // RAM 賬號Access Key Secret\n IAcsClient client = new DefaultAcsClient(profile);\n return client;\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"startFNF 代碼裏面流程如何串聯起來:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸入要啓動的流程名稱,比如每次訂單編號作爲啓動流程實例名稱;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"流程啓動後的流程實例名稱;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"啓動輸入參數,比如業務參數,比如一個 json 裏面有商品、商家、地址、訂單等上下文信息。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"@GetMapping(\"/startFNF/{fnfname}/{execuname}/{input}\")\n public StartExecutionResponse startFNF(@PathVariable(\"fnfname\") String fnfName,\n @PathVariable(\"execuname\") String execuName,\n @PathVariable(\"input\") String inputStr) throws ClientException {\n JSONObject jsonObject = new JSONObject();\n jsonObject.put(\"fnfname\", fnfName);\n jsonObject.put(\"execuname\", execuName);\n jsonObject.put(\"input\", inputStr);\n return fnfService.startFNF(jsonObject);\n }","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再看下 fnfService.startFNF:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"@Override\n public StartExecutionResponse startFNF(JSONObject jsonObject) throws ClientException {\n StartExecutionRequest request = new StartExecutionRequest();\n String orderNum = jsonObject.getString(\"execuname\");\n request.setFlowName(jsonObject.getString(\"fnfname\"));\n request.setExecutionName(orderNum);\n request.setInput(jsonObject.getString(\"input\"));\n JSONObject inputObj = jsonObject.getJSONObject(\"input\");\n Order order = new Order();\n order.setOrderNum(orderNum);\n order.setAddress(inputObj.getString(\"address\"));\n order.setProducts(inputObj.getString(\"products\"));\n order.setSupplier(inputObj.getString(\"supplier\"));\n orderMap.put(orderNum, order);\n return iAcsClient.getAcsResponse(request);\n }","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一部分是啓動流程;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二部分是創建訂單對下,並模擬入庫。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"前端如何調用?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在前端當點擊選擇商品和商家頁面中的下一步後,通過 GET 方式調用 HTTP 協議的接口 /startFNF/{fnfname}/{execuname}/{input}。和上面的 Java 方法對應。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"fnfname:要啓動的流程名稱;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"execuname:隨機生成 uuid,作爲訂單的編號,也作爲啓動流程實例的名稱;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"input:將商品、商家、訂單號、地址構建爲 JSON 字符串傳入流程。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"submitOrder(){\n const orderNum = uuid.v1()\n this.$axios.$get('/startFNF/OrderDemo-Jiyuan/'+orderNum+'/{\\n' +\n ' \"products\": \"'+this.products+'\",\\n' +\n ' \"supplier\": \"'+this.supplier+'\",\\n' +\n ' \"orderNum\": \"'+orderNum+'\",\\n' +\n ' \"address\": \"'+this.address+'\"\\n' +\n '}' ).then((response) => {\n console.log(response)\n if(response.message == \"success\"){\n this.$router.push('/orderdemo/' + orderNum)\n }\n })\n }","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1. generateInfo 節點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先看下第一個 FDL 節點定義:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"- type: task\n name: generateInfo\n timeoutSeconds: 300\n resourceArn: acs:mns:::/topics/generateInfo-fnf-demo-jiyuan/messages\n pattern: waitForCallback\n inputMappings:\n - target: taskToken\n source: $context.task.token\n - target: products\n source: $input.products\n - target: supplier\n source: $input.supplier\n - target: address\n source: $input.address\n - target: orderNum\n source: $input.orderNum\n - target: type\n source: $context.step.name\n outputMappings:\n - target: paymentcombination\n source: $local.paymentcombination\n - target: orderNum\n source: $local.orderNum\n serviceParams:\n MessageBody: $\n Priority: 1\n catch:\n - errors:\n - FnF.TaskTimeout\n goto: orderCanceled","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"name:節點名稱;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"timeoutSeconds:超時時間,節點等待時長,超過時間後跳轉到 goto 分支指向的 orderCanceled 節點;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"pattern:設置爲 waitForCallback,表示需要等待確認;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"inputMappings:該節點入參;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - taskToken:Serverless 工作流自動生成的 Token;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - products:選擇的商品;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - supplier:選擇的商家;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - address:送餐地址;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - orderNum:訂單號;","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"outputMappings:該節點的出參;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - paymentcombination:該商家支持的支付方式;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - orderNum:訂單號;","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"catch:捕獲異常,跳轉到其他分支。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Serverless 工作流支持多個雲服務集成,將其他服務作爲任務步驟的執行單元。服務集成方式通過 FDL 表達式實現,在任務步驟中,可以使 用resourceArn 來定義集成的目標服務,使用 pattern 定義集成模式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 resourceArn 中配置 /topics/generateInfo-fnf-demo-jiyuan/messages 信息,就是集成了 MNS 消息隊列服務,當 generateInfo 節點觸發後會向 generateInfo-fnf-demo-jiyuanTopic 中發送一條消息。消息的正文和參數在 serviceParams 對象中 zhi'd 指定。MessageBody 是消息正文,配置 $ 表示通過輸入映射 inputMappings 產生消息正文。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"generateInfo-fnf-demo 函數:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"向 generateInfo-fnf-demo-jiyuanTopic 中發送的這條消息包含了商品信息、商家信息、地址、訂單號,表示一個下訂單流程的開始,既然有發消息,那麼必然有接受消息進行後續處理。在函數計算控制檯,創建服務,在服務下創建名爲 generateInfo-fnf-demo 的事件觸發器函數,這裏選擇 Python Runtime:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b9/b91d25e9add1c6ade82f7685393ffed0.png","alt":"17.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"創建 MNS 觸發器,選擇監聽 generateInfo-fnf-demo-jiyuanTopic:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a8/a88f38788e36d24c3679f5c3c307b59c.png","alt":"18.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打開消息服務 MNS 控制檯,創建 generateInfo-fnf-demo-jiyuanTopic:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/56/568050c54b6f7b60dd73454c157f6834.png","alt":"19.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來寫函數代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"# -*- coding: utf-8 -*-\nimport logging\nimport json\nimport time\nimport requests\nfrom aliyunsdkcore.client import AcsClient\nfrom aliyunsdkcore.acs_exception.exceptions import ServerException\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequest\ndef handler(event, context):\n # 1. 構建Serverless工作流Client\n region = \"cn-hangzhou\"\n account_id = \"XXXX\"\n ak_id = \"XXX\"\n ak_secret = \"XXX\"\n fnf_client = AcsClient(\n ak_id,\n ak_secret,\n region\n )\n logger = logging.getLogger()\n # 2. event內的信息即接受到Topic generateInfo-fnf-demo-jiyuan中的消息內容,將其轉換爲Json對象\n bodyJson = json.loads(event)\n logger.info(\"products:\" + bodyJson[\"products\"])\n logger.info(\"supplier:\" + bodyJson[\"supplier\"])\n logger.info(\"address:\" + bodyJson[\"address\"])\n logger.info(\"taskToken:\" + bodyJson[\"taskToken\"])\n supplier = bodyJson[\"supplier\"]\n taskToken = bodyJson[\"taskToken\"]\n orderNum = bodyJson[\"orderNum\"]\n # 3. 判斷什麼商家使用什麼樣的支付方式組合,這裏的示例比較簡單粗暴,正常情況下,應該使用元數據配置的方式獲取\n paymentcombination = \"\"\n if supplier == \"haidilao\":\n paymentcombination = \"zhifubao,weixin\"\n else:\n paymentcombination = \"zhifubao,weixin,unionpay\"\n # 4. 調用Java服務暴露的接口,更新訂單信息,主要是更新支付方式\n url = \"http://xx.xx.xx.xx:8080/setPaymentCombination/\" + orderNum + \"/\" + paymentcombination + \"/0\"\n x = requests.get(url)\n # 5. 給予generateInfo節點響應,並返回數據,這裏返回了訂單號和支付方式\n output = \"{\\\"orderNum\\\": \\\"%s\\\", \\\"paymentcombination\\\":\\\"%s\\\" \" \\\n \"}\" % (orderNum, paymentcombination)\n request = ReportTaskSucceededRequest.ReportTaskSucceededRequest()\n request.set_Output(output)\n request.set_TaskToken(taskToken)\n resp = fnf_client.do_action_with_exception(request)\n return 'hello world'","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼分五部分:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"構建 Serverless 工作流 Client;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"event 內的信息即接受到 TopicgenerateInfo-fnf-demo-jiyuan 中的消息內容,將其轉換爲 Json 對象;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"判斷什麼商家使用什麼樣的支付方式組合,這裏的示例比較簡單粗暴,正常情況下,應該使用元數據配置的方式獲取。比如在系統內有商家信息的配置功能,通過在界面上配置該商家支持哪些支付方式,形成元數據配置信息,提供查詢接口,在這裏進行查詢;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"調用 Java 服務暴露的接口,更新訂單信息,主要是更新支付方式;","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"給予 generateInfo 節點響應,並返回數據,這裏返回了訂單號和支付方式。因爲該節點的 pattern 是 waitForCallback,所以需要等待響應結果。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"generateInfo-fnf-demo 函數配置了 MNS 觸發器,當 TopicgenerateInfo-fnf-demo-jiyuan 有消息後就會觸發執行 generateInfo-fnf-demo 函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2. payment 節點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來是 payment 的 FDL 代碼定義:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"- type: task\n name: payment\n timeoutSeconds: 300\n resourceArn: acs:mns:::/topics/payment-fnf-demo-jiyuan/messages\n pattern: waitForCallback\n inputMappings:\n - target: taskToken\n source: $context.task.token\n - target: orderNum\n source: $local.orderNum\n - target: paymentcombination\n source: $local.paymentcombination\n - target: type\n source: $context.step.name\n outputMappings:\n - target: paymentMethod\n source: $local.paymentMethod\n - target: orderNum\n source: $local.orderNum\n - target: price\n source: $local.price\n - target: taskToken\n source: $input.taskToken\n serviceParams:\n MessageBody: $\n Priority: 1\n catch:\n - errors:\n - FnF.TaskTimeout\n goto: orderCanceled","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當流程流轉到 payment 節點後,用戶就可以進入到支付頁面。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/74/740e7077e1d3c0f64a5a468457208c69.png","alt":"20.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"payment 節點會向 MNS 的 Topicpayment-fnf-demo-jiyuan 發送消息,會觸發 payment-fnf-demo 函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"payment-fnf-demo 函數:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"payment-fnf-demo 函數的創建方式和 generateInfo-fnf-demo 函數類似。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"# -*- coding: utf-8 -*-\nimport logging\nimport json\nimport os\nimport time\nimport logging\nfrom aliyunsdkcore.client import AcsClient\nfrom aliyunsdkcore.acs_exception.exceptions import ServerException\nfrom aliyunsdkcore.client import AcsClient\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequest\nfrom mns.account import Account # pip install aliyun-mns\nfrom mns.queue import *\ndef handler(event, context):\n logger = logging.getLogger()\n region = \"xxx\"\n account_id = \"xxx\"\n ak_id = \"xxx\"\n ak_secret = \"xxx\"\n mns_endpoint = \"http://your_account_id.mns.cn-hangzhou.aliyuncs.com/\"\n queue_name = \"payment-queue-fnf-demo\"\n my_account = Account(mns_endpoint, ak_id, ak_secret)\n my_queue = my_account.get_queue(queue_name)\n # my_queue.set_encoding(False)\n fnf_client = AcsClient(\n ak_id,\n ak_secret,\n region\n )\n eventJson = json.loads(event)\n isLoop = True\n while isLoop:\n try:\n recv_msg = my_queue.receive_message(30)\n isLoop = False\n # body = json.loads(recv_msg.message_body)\n logger.info(\"recv_msg.message_body:======================\" + recv_msg.message_body)\n msgJson = json.loads(recv_msg.message_body)\n my_queue.delete_message(recv_msg.receipt_handle)\n # orderCode = int(time.time())\n task_token = eventJson[\"taskToken\"]\n orderNum = eventJson[\"orderNum\"]\n output = \"{\\\"orderNum\\\": \\\"%s\\\", \\\"paymentMethod\\\": \\\"%s\\\", \\\"price\\\": \\\"%s\\\" \" \\\n \"}\" % (orderNum, msgJson[\"paymentMethod\"], msgJson[\"price\"])\n request = ReportTaskSucceededRequest.ReportTaskSucceededRequest()\n request.set_Output(output)\n request.set_TaskToken(task_token)\n resp = fnf_client.do_action_with_exception(request)\n except Exception as e:\n logger.info(\"new loop\")\n return 'hello world'","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面代碼核心思路是等待用戶在支付頁面選擇某個支付方式確認支付。使用了 MNS 的隊列來模擬等待。循環等待接收隊列 payment-queue-fnf-demo 中的消息,當收到消息後將訂單號和用戶選擇的具體支付方式以及金額返回給 payment 節點。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前端選擇支付方式頁面:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過 generateInfo 節點後,該訂單的支付方式信息已經有了,所以對於用戶而言,當填完商品、商家、地址後,跳轉到的頁面就是該確認支付頁面,並且包含了該商家支持的支付方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"進入該頁面後,會請求 Java 服務暴露的接口,獲取訂單信息,根據支付方式在頁面上顯示不同的支付方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/58/58ae1c47ae9cb933a04f6521687bf016.png","alt":"21.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼片段如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/93/9389f554e268929d86ba27108b56c19b.png","alt":"22.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當用戶選定某個支付方式點擊提交訂單按鈕後,向 payment-queue-fnf-demo 隊列發送消息,即通知 payment-fnf-demo 函數繼續後續的邏輯。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用了一個 HTTP 觸發器類型的函數,用於實現向 MNS 發消息的邏輯,paymentMethod-fnf-demo 函數代碼:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"# -*- coding: utf-8 -*-\nimport logging\nimport urllib.parse\nimport json\nfrom mns.account import Account # pip install aliyun-mns\nfrom mns.queue import *\nHELLO_WORLD = b'Hello world!\\n'\ndef handler(environ, start_response):\n logger = logging.getLogger() \n context = environ['fc.context']\n request_uri = environ['fc.request_uri']\n for k, v in environ.items():\n if k.startswith('HTTP_'):\n # process custom request headers\n pass\n try: \n request_body_size = int(environ.get('CONTENT_LENGTH', 0)) \n except (ValueError): \n request_body_size = 0 \n request_body = environ['wsgi.input'].read(request_body_size) \n paymentMethod = urllib.parse.unquote(request_body.decode(\"GBK\"))\n logger.info(paymentMethod)\n paymentMethodJson = json.loads(paymentMethod)\n region = \"cn-xxx\"\n account_id = \"xxx\"\n ak_id = \"xxx\"\n ak_secret = \"xxx\"\n mns_endpoint = \"http://your_account_id.mns.cn-hangzhou.aliyuncs.com/\"\n queue_name = \"payment-queue-fnf-demo\"\n my_account = Account(mns_endpoint, ak_id, ak_secret)\n my_queue = my_account.get_queue(queue_name)\n output = \"{\\\"paymentMethod\\\": \\\"%s\\\", \\\"price\\\":\\\"%s\\\" \" \\\n \"}\" % (paymentMethodJson[\"paymentMethod\"], paymentMethodJson[\"price\"])\n msg = Message(output)\n my_queue.send_message(msg)\n status = '200 OK'\n response_headers = [('Content-type', 'text/plain')]\n start_response(status, response_headers)\n return [HELLO_WORLD]","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"函數的邏輯很簡單,就是向 MNS 的隊列 payment-queue-fnf-demo 發送用戶選擇的支付方式和金額。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e9/e9aacf77bd66489872e8eae0da9653ef.png","alt":"23.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3. paymentCombination 節點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"paymentCombination 節點是一個路由節點,通過判斷某個參數路由到不同的節點,以 paymentMethod 作爲判斷條件:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"- type: choice\n name: paymentCombination\n inputMappings:\n - target: orderNum\n source: $local.orderNum\n - target: paymentMethod\n source: $local.paymentMethod\n - target: price\n source: $local.price\n - target: taskToken\n source: $local.taskToken\n choices:\n - condition: $.paymentMethod == \"zhifubao\"\n steps:\n - type: task\n name: zhifubao\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan/functions/zhifubao-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n - condition: $.paymentMethod == \"weixin\"\n steps:\n - type: task\n name: weixin\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/weixin-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n - condition: $.paymentMethod == \"unionpay\"\n steps:\n - type: task\n name: unionpay\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan.LATEST/functions/union-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken\n default:\n goto: orderCanceled","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"流程是,用戶選擇支付方式後,通過消息發送給 payment-fnf-demo 函數,然後將支付方式返回,於是流轉到 paymentCombination 節點通過判斷支付方式流轉到具體處理支付邏輯的節點和函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4. zhifubao 節點","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看一個 zhifubao 節點:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"choices:\n - condition: $.paymentMethod == \"zhifubao\"\n steps:\n - type: task\n name: zhifubao\n resourceArn: acs:fc:cn-hangzhou:your_account_id:services/FNFDemo-jiyuan/functions/zhifubao-fnf-demo\n inputMappings:\n - target: price\n source: $input.price \n - target: orderNum\n source: $input.orderNum\n - target: paymentMethod\n source: $input.paymentMethod\n - target: taskToken\n source: $input.taskToken","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"節點的 resourceArn 和之前兩個節點的不同,這裏配置的是函數計算中函數的 ARN,也就是說當流程流轉到這個節點時會觸發 zhifubao-fnf-demo 函數,該函數是一個事件觸發函數,但不需要創建任何觸發器。流程將訂單金額、訂單號、支付方式傳給 zhifubao-fnf-demo 函數。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"zhifubao-fnf-demo 函數:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"# -*- coding: utf-8 -*-\nimport logging\nimport json\nimport requests\nimport urllib.parse\nfrom aliyunsdkcore.client import AcsClient\nfrom aliyunsdkcore.acs_exception.exceptions import ServerException\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskSucceededRequest\nfrom aliyunsdkfnf.request.v20190315 import ReportTaskFailedRequest\ndef handler(event, context):\n region = \"cn-xxx\"\n account_id = \"xxx\"\n ak_id = \"xxx\"\n ak_secret = \"xxx\"\n fnf_client = AcsClient(\n ak_id,\n ak_secret,\n region\n )\n logger = logging.getLogger()\n logger.info(event)\n bodyJson = json.loads(event)\n price = bodyJson[\"price\"]\n taskToken = bodyJson[\"taskToken\"]\n orderNum = bodyJson[\"orderNum\"]\n paymentMethod = bodyJson[\"paymentMethod\"]\n logger.info(\"price:\" + price)\n newPrice = int(price) * 0.8\n logger.info(\"newPrice:\" + str(newPrice))\n url = \"http://xx.xx.xx.xx:8080/setPaymentCombination/\" + orderNum + \"/\" + paymentMethod + \"/\" + str(newPrice)\n x = requests.get(url)\n return {\"Status\":\"ok\"}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼邏輯很簡單,接收到金額後,將金額打 8 折,然後將價格更新回訂單。其他支付方式的節點和函數如法炮製,變更實現邏輯就可以。在這個示例中,微信支付打了 5 折,銀聯支付打 7 折。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"完整流程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"流程中的 orderCompleted 和 orderCanceled 節點沒做什麼邏輯,流程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/21/21fde59bd975becd47831a9ed2992fe4.png","alt":"24.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從 Serverless 工作流中看到的節點流轉是這樣的:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/94/9405178009673be1262024558f88f7c7.png","alt":"25.png","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"寫在後面","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上是一個基於 Serverless 的 FC 實現的工作流,模擬構建了一個訂單模塊,規則包括:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"配置商家和支付方式的元數據規則;","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"確認支付頁面的元數據規則。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在實際項目中,需要將可定製的部分抽象爲元數據描述,需要有配置界面供運營或商家定製支付方式也就是元數據規則,然後前後端頁面基於元數據信息展示相應的內容。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果之後需要接入新的支付方式,只需要在 paymentCombination 路由節點中確定好路由規則,之後增加對應的支付方式函數即可,通過增加元數據配置項,就可以在頁面展示新加的支付方式,並路由到新的支付函數中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"經過整篇文章相信很多人對於 Serverless 的定義,以及如何基於現有的公有云系統的 Serverless 功能實現商業能力已經有了一定的瞭解,甚至基於此有實力的公司可以自研一套 Serverless 平臺。當然思想是相同的,其實文中很多邏輯與理論不止適用於 Serverless,就是我們日常基於微服務的平臺化/中臺化解決方案,都可以從中獲取設計營養在工作中應用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章