當前位置:
首頁 > 最新 > Serverless 風格微服務的持續交付(上):架構案例

Serverless 風格微服務的持續交付(上):架構案例

【不要錯過文末彩蛋】

編輯 德剛

無伺服器架構 (Serverless Architectures) 簡介

Serverless 架構最早可以追溯到 Ken Fromm 發表的文章《Why The Future Of Software And Apps Is Serverless》(http://readwrite.com/2012/10/15/why-the-future-of-software-and-apps-is-serverless/)。

在這篇文章里, Ken Fromm 描述了未來雲計算基礎設施成熟的條件下應用程序是不需要伺服器端的。在無武器場景下構建應用程序的時候。

開發人員和運維人員無需擔心伺服器如何安裝配置,如何設置網路和負載均衡,無需監控狀態,甚至不再會出現伺服器相關的工作內容。

這樣可以讓原本建設機房的時間成本和貨幣成本從按年計算縮短至按秒計算。

在 Martin Fowler 的博客《Serverless Architectures》(https://martinfowler.com/articles/serverless.html)中,他將無伺服器架構分為兩種:

第一種無伺服器架構被稱為被稱為 BaaS(Backend as a Service,後端應用即服務)。即應用的架構是由一大堆第三方 API 來組織的。

一切狀態和邏輯都由這些服務提供方來管理。隨著移動應用和單頁 Web 應用這樣的富客戶端(Rich Client)應用的普及,前後端的通信漸漸以 API 調用為主。

而所需的服務不再由 服務端應用開發工程師和運維工程師來維護,只需要調用提供服務的第三方 API 就可以完成相應的功能。例如雲上的資料庫服務和用戶認證服務。

另一種無伺服器架構被稱為 FaaS(Function as a Service,函數即服務)。這一架構的興起源於 AWS Lambda 的發展。

AWS Lambda 是一種無狀態的代碼運行時服務,這項服務提供最小的代碼運行資源。

你可以使用 Java,Node.js,Python 和 C# 編寫程序處理 AWS 各種服務的事件。無需初始化一台伺服器,安裝操作系統並配置程序運行環境。

由於運行資源很少,完成的計算有限,使得這種應用無法保存狀態,因此這類程序以函數的方式存在。

本文所介紹的 Serverless 架構主要是以 AWS Lambda 以及 Amazon API Gateway 架構的應用,它同時也具備 BaaS 的特徵。

AWS Lambda 的編程模型

AWS Lambda 運行在一個假想的虛擬容器里,但你無法通過 API 配置這個容器。此外,這個虛擬的容器有一些資源限制(http://docs.aws.amazon.com/zh_cn/lambda/latest/dg/limits.html),主要限制如下:

5 分鐘(300 秒)的程序運行時間。

512 MB 的文件系統空間。(在 /tmp 目錄下)

最大1536 MB 的內存。(最小 128 MB,以 64 MB 作為增量)

最多 1024 個文件描述符。

最大 1024 個內部線程。

AWS Lambda 的編程模型如下所示:

Lambda 的執行流程:

當事件觸發 Lambda 執行的時候,Lambda 會將事件所攜帶的信息通過上下文對象(Context Object)傳給處理函數(Handler)。此外,Lambda 還可以讀取預先設置的環境變數。

執行處理函數,並將日誌通過 CloudWatch 記錄下來。

執行完畢後通過事件返回執行結果,或者拋出異常。

執行結果和對應的異常可以綁定其它資源繼續處理。

當事件請求大批量發生的時候。Lambda 會為每一個事件單獨執行一次 。這意味著每一個請求之間的執行期間,內容是不能共享的。(經本人親測,內存中存儲的是可以共享的,但內容保留的有效時間和狀態無法保證。)

Amazon API Gateway + AWS Lambda 的微服務架構

根據 Martin Fowler 對微服務的描述性定義(https://martinfowler.com/articles/microservices.html),我們可以認為微服務從技術層面包含以下特徵:

每個服務運行在自己的進程中。

服務間通信採用輕量級通信機制(通常用HTTP資源API)。

這些服務圍繞業務能力構建並且可通過全自動部署機制獨立部署。

這些服務共用一個最小型的集中式的管理。

服務可用不同的語言開發,使用不同的數據存儲技術。

在 AWS 現有的服務情況下,AWS Lambda 滿足了上面的第 1、3、5 點,這只是一個微服務的處理單元,非管理單元。

而 2 和 4 則需要另外的服務作為管理單元共同構成微服務,這個任務一般交由 API 網關實現。

Amazon API Gateway 是一種完全託管的 API 網關服務,可以幫助開發者輕鬆創建、發布、維護、監控和保護任意規模的 API。它集成了很多 API 網關的功能,諸如緩存、用戶認證等功能。

並且支持通過 HAML 和 Swagger 配置,這樣就可以用代碼管理系統配置 API 了。

Amazon API Gateway 可以根據不同的 Restful API 訪問點將請求的數據傳遞給不同的資源進行處理。一般的 AWS API 架構如下所示:

當請求通過域名訪問到應用的時候,應用會將 HTTP 請求轉發給 CDN (CloudFornt)。

CloudFront 會根據轉發規則把對應的 API 請求轉發到 API Gateway 上。

API Gateway 會根據請求的訪問點和內容交給對應的 AWS Lambda 或者 EC2 服務處理,也可以發送給其它可訪問的服務。

處理完成後將返回請求結果給客戶端。在返回的時候,API Gateway 也可以通過 Lambda 對返回內容進行處理。

相較於傳統的微服務架構,通過 API Gateway 和 Lambda 的這種集成方式可以得到更輕量級的微服務。

團隊只需要規劃好 API 訪問並完成函數的開發,就可以快速的構建出一個最簡單的微服務,使得微服務基礎設施的搭建時間從幾周縮短為幾個小時。此外,大大提升了微服務架構的開發效率和穩定性。

一次微服務架構的奇遇

2016年12月初,當時我正在以一名 DevOps 諮詢師的身份參與悉尼某一行動電話運營商的 Digital (電子渠道)部門的 DevOps 轉型項目。

這個項目是提升該部門在 AWS (Amazon Web Services)雲計算平台上的 DevOps 能力。

Digital 部門負責該電信運營商所有的互聯網和移動設備應用開發。這些應用主要是用來為用戶提供諸如 SIM 卡激活,話費查詢,話費充值,優惠套餐訂購等自助服務(Self service),從而降低營業廳和人工話務客服的成本。

自助服務的應用系統基於 Ruby on Rails 框架開發,前端部分採用 AngularJS 1.0,但是沒有採用前後端分離的設計,頁面代碼仍然是通過 ERB 組合而成。yi移動端則採用 Cordova 開發。

為了降低開發難度和工作量, 移動端的應用內容實際上是把 AngularJS 所生成的 Web 頁面通過響應式樣式的方式嵌入到移動端。但因為經常超時,所以這款 APP 體驗並不好。

整套 Rails 應用部署在 AWS 上,並且通過網關和內部業務 BOSS (Business Operating Support System) 系統隔離。BOSS 系統採用 SOAP 對外暴露服務,並由另外一個部門負責。

因此,雲上的應用所做的業務是給用戶展現一個使用友好的界面,並通過數據的轉化和內部 BOSS 系統進行交互。系統架構如下圖所示:

應用的交互流程如下

瀏覽器或者移動端通過域名(由 AWS Route 53託管)轉向 CDN(採用 AWS Cloudfront)。

CDN 根據請求的內容類別進行區分,靜態文件(圖片,JS,CSS 樣式等),會轉向 AWS S3 存儲。動態請求會直接發給負載均衡器 (AWS Elastic Load Balancer)。

負載均衡器會根據各 EC2 計算實例的負載狀態將請求轉發到不同的實例上的 Ruby On Rails 應用上。每一個應用都是一個典型的 MVC Web 應用。

EC2 上的應用會將一部分數據存儲在關係型數據服務(AWS RDS,Relational Database ServiceS)上,一部分存儲在本地文件里。

經過應用的處理,轉換成 SOAP 請求通過 網關發送給 BOSS 系統處理。BOSS 系統處理完成後會返回對應的消息。

根據業務的需要,一部分數據會採用 AWS ElasiCache 的 Redis 服務作為緩存以優化業務響應速度。

團隊痛點

這個應用經歷了多年的開發,前後已經更換過很多技術人員。但是沒有人對這個應用代碼庫有完整的的認識。因此,我們對整個團隊和產品進行了一次痛點總結:

組織結構方面

運維團隊成為瓶頸,60 個人左右的開發團隊只有 4 名 Ops 支持。運維團隊除了日常的事務以外,還要給開發團隊提供各種支持。

很多資源的使用許可權被限制在這個團隊里,就導致各種問題的解決進度進一步拖延。

隨著業務的增長,需要基礎設施代碼庫提供各種各樣的能力。然而 Ops 團隊的任何更改都會導致所有的開發團隊停下手頭的進度去修復更新所帶來的各種問題。

應用架構方面

應用架構並沒有達到前後端分離的效果,仍然需要同一個工程師編寫前後端代碼。這樣的技術棧對於對於開發人員的要求很高,然而市場上缺乏合適的 RoR 工程師,導致維護成本進一步上升。經過了三個月,仍然很難招聘到合適的工程師。

多個團隊在一個代碼庫上工作,新舊功能之間存在各種依賴點。加上 Ruby 的語言特性,使得代碼中存在很多隱含的依賴點和類/方法覆蓋,導致了開發進度緩慢。

我們一共有 4 個團隊在一個代碼庫上工作,3個團隊在開發新的功能。1 個團隊需要修復 Bug 和清理技術債,這一切都要同時進行。

技術債方面

代碼庫中有大量的重複 cucumber 自動化測試,但是缺乏正確的並行測試策略,導致自動化測試會隨機失敗,持續集成伺服器 (Jenkins)的 slave 節點本地難以創建,導致失敗原因更加難以查找。

如果走運的話,從提交代碼到新的版本發布至少需要 45 分鐘。如果不走運的話,兩三天都無法完成一次成功的構建,真是依靠人品構建。

基礎設施即代碼(Infrastructure As Code)建立在一個混合的遺留的 Ruby 代碼庫上。

這個代碼庫用來封裝一些類似於 Packer 和 AWS CLI 這樣的命令行工具,包含一些 CloudFormation 的轉化能力。

由於缺乏長期的規劃和編碼規範,加之人員變動十分頻繁,使得代碼庫難以維護。

此外,基礎設施代碼庫作為一個 gem 和應用程序代碼庫耦合在一起,運維團隊有唯一的維護許可權。因此很多基礎設施上的問題開發團隊無法解決,也不願解決。

我參與過很多 Ruby 技術棧遺留系統的維護。在經歷了這些 Ruby 項目之後,我發現 Ruby 是一個開發起來很爽但是維護起來很痛苦的技術棧。

大部分的維護更改是由於 Ruby 的版本 和 Gem 的版本更新導致的。此外,由於 Ruby 比較靈活,人們都有自己的想法和使用習慣,因此代碼庫很難維護。

雖然團隊已經有比較好的持續交付流程,但是 Ops 能力缺乏和應用架構帶來的局限阻礙了整個產品的前進。

因此,當務之急是能夠通過 DevOps 提升團隊的 Ops 能力,緩解 Ops 資源不足,削弱 DevOps 矛盾。

DevOps 組織轉型中一般有兩種方法:一種方法是提升 Dev 的 Ops 能力,另一種方法是降低 Ops 工作門檻。

在時間資源很緊張的情況下,通過技術的改進,降低 Ops 的門檻是短期內收益最大的方法。

微服務觸發點:併購帶來的業務功能合併

在我加入這個項目的時候,客戶收購了一個本地的寬頻/固定電話運營商。因此原有的系統需要需要承載固話和寬頻的新業務。

恰巧有個訂單查詢的業務需要讓當前的團隊完整這樣一個需求:通過現有的訂單查詢功能可以同時查詢移動和固網寬頻訂單。

這要求在原由的訂單查詢功能上新增添一些選項和內容,可以同時查到移動和固網寬頻的訂單。通過上述痛點可知,這在當時完成這樣一個任務的代價是十分昂貴的。

在開發的項目上進行 DevOps 轉型就像在行進的汽車上換車輪,一不留心就會讓所有團隊停止工作。因此我建議通過設立並行的新團隊來同時完成新功能的開發和 DevOps 轉型的試點。

這是一個功能拆分和新功能拆分需求,剛好訂單查詢是原系統中一個比較獨立和成熟的功能。為了避免影響原有各功能開發的進度。我們決定採用微服務架構來完成這個功能。

構建微服務的架構的策略

我們並不想重蹈之前應用架構的覆轍,我們要做到前後端分離。使得比較小的開發團隊可以並行開發,只要協商好了 介面之間的契約(Contract),未來開發完成之後會很好集成。

這讓我想起了 Chris Richardson 提出了三種微服務架構策略(http://blog.daocloud.io/microservices-7/),分別是:停止挖坑前後端分離提取微服務

停止挖坑的意思是說:如果發現自己掉坑裡,馬上停止。

原先的單體應用對我們來說就是一個焦油坑,因此我們要停止在原來的代碼庫上繼續工作。並且為新應用單獨創建一個代碼庫。所以,我們拆分策略模式如下所示:

在我們的架構里,實現新的需求就要變動老的應用。我們的想法是:

構建出新的業務頁面,生成微服務契約。

根據 API 契約構建出新的微服務。

部署 Web 前端到 S3 上,採用 S3 的 Static Web Hosting (靜態 Web 服務) 發布。

部署後端微服務上線,並採用臨時的域名和 CDN 載入點進行測試。

通過更新 CDN 把原應用的流量導向新的微服務。

刪除舊的服務代碼。

我們原本要在原有的應用上增加一個 API 用來訪問以前應用的邏輯。但想想這實際上也是一種挖坑。在評估了業務的複雜性之後。

我們發現這個功能如果全新開發只需要 2人2周(一個人月)的時間,這僅僅占我們預估工作量的20%不到。

因此我們放棄了對遺留代碼動工的念頭。最終通過微服務直接訪問後台系統,而不需要通過原有的應用。

在我們拆微服務的部分十分簡單。對於後端來說說只需要修改 CDN 覆蓋原先的訪問源(Origin)以及保存在 route.rb 里的原功能訪問點,就可以完成微服務的集成。

構建出新的業務頁面,生成微服務契約

結合上面的應用痛點和思路,在構建微服務的技術選型時我們確定了以下方向:

前端框架要具備很好的 Responsive 擴展。

採用 Swagger 來描述 API 需要具備的行為。

通過消費者驅動進行契約測試驅動微服務後端開發。

前端代碼庫和後端代碼庫分開。

前端代碼框架要對持續交付友好。

因此我們選擇了 React 作為前端技術棧並且用 yarn 管理依賴和任務。另外一個原因是我們能夠通過 React-native 為未來構建新的應用做好準備。此外,我們引入了 AWS SDK 的 nodejs 版本。

用編寫一些常見的諸如構建、部署、配置等 AWS 相關的操作。並且通過 swagger 描述後端 API 的行為。這樣,後端只需要滿足這個 API 規範,就很容易做前後端集成。

部署前端部分到 S3 上

由於 AWS S3 服務自帶 Static Web Hosting (靜態頁面服務) 功能,這就大大減少了我們構建基礎環境所花費的時間。

如果你還想著用 Nginx 和 Apache 作為靜態內容的 Web 伺服器,那麼你還不夠 CloudNative。

擁有獨立的 URL,很容易做很多 301 和 302 的重定向和改寫操作。

和 CDN (CloudFront)集成很好。

很容易和持續集成工具集成。

最大的優點:比 EC2 便宜。

根據 API 契約構建出新的微服務

在構建微服務的最初,我們當時有兩個選擇:

採用 Sinatra (一個用來構建 API 的 Ruby gem) 構建一個微服務 ,這樣可以復用原先 Rails 代碼庫的很多組件。

換句話說,只需要 copy 一些代碼,放到一個單獨的代碼庫里,就可以完成功能。但也同樣會面臨之前 Ruby 技術棧帶來的種種問題。

採用 Spring Boot 構建一個微服務,Java 作為成熟工程語言目前還是最好的選擇,社區和實踐都非常成熟。可以復用後台很多用來做 SOAP 處理的 JAR 包。另一方面是解決了 Ruby 技術棧帶來的問題。

然而,這兩個方案的都有一個共同的問題:需要通過 ruby 語言編寫的基礎設施工具構建一套運行微服務的基礎設施。

而這個基礎設施的搭建,前前後後估計得需要至少 1個月,這還是在運維團隊有人幫助的情況下的樂觀估計。

所以,要找到一種降低環境構建和運維團隊阻塞的方式避開傳統的 EC2 搭建應用的方式。

這,只有 Lambda 可以做到!

基於上面的種種考量,我們選擇了 Amazon API Gateway + Lambda 的組合。而 Amazon API Gateway + Lambda 還有額外好處:

支持用 Swagger 規範配置 API Gateway。也就是說,你只要導入前端的 Swagger 規範,就可以生成 API Gateway。

可以用數據構建 Mock API,這樣就可以很大程度上實現消費者驅動契約開發。

通過 Amazon API Gateway 的 Stage 功能,我們無需構建 QA 環境,UAT 環境和 Staging 環境。只需要指定不同的 Stage,就可以完成對應的切換。

Lambda 的發布生效時間很短,反饋很快。原先用 CloudFormation 構建的 API 基礎設施需要至少 15 分鐘,而 Lambda 的生效只需要短短几秒鐘。

Lambda 的編寫很方便,可以採用在線的方式。雖然在線 IDE 並不很好用,但是真的也寫不了幾行代碼。

Lambda 自動根據請求自擴展,無需考慮負載均衡。

雖然有這麼多優點,但不能忽略了關鍵性的問題:AWS Lambda 不一定適合你的應用場景!

根據上文對 AWS Lambda 的介紹,支持 AWS Lambda 運行的資源和時間很有限。因此很多對同步和強一致性的業務需求是無法滿足的。所以,AWS Lambda 更適合能夠非同步處理的業務場景。

此外,AWS Lambda 對消耗存儲空間和 CPU 很多的場景支持不是很好,例如 AI 和 大數據。(PS: AWS 已經有專門的 AI 和大數據服務了,所以不需要和自己過不去)

對於我們的應用場景而言,上文中的 Ruby On Rails 應用中的主要功能(至少60% 以上)實際上只是一個數據轉換適配器:把前端輸入的數據進行加工,轉換成對應的 SOAP 調用。

因此,對於這樣一個簡單的場景而言,Amazon API Gateway + Lambda 完全滿足需求!

部署後端微服務

選擇了Amazon API Gateway + Lambda 後,後端的微服務部署看起來很簡單:

更新 Lambda 函數。

更新 API 規範,並要求 API 綁定對應 Lambda 函數處理請求。

但是,這卻不是很容易的一件事。我們將在《Serverless 風格微服務的持續交付(中):持續交付的挑戰》中對這方面踩過的坑詳細介紹。

把原應用的請求導向新的微服務

這時候在 CDN 上給新的微服務配置 API Gateway 作為一個新的源(Origin),覆蓋原先寫在 route.rb 和 nginx.conf 里的 API 訪問規則就可以了。CDN 會攔截訪問請求,使得請求在 nginx 處理之前就會把對應的請求轉發到 API Gateway。

當然,如果你想做灰度發布的話,就不能按上面這種方式搞了。CloudFront 和 ELB 負載均衡 並不具備帶權轉發功能。因此你需要通過 nginx 配置,按訪問權重把 API Gateway 作為一個 upstream 里的一個 Server 就可以。

刪除舊的服務代碼

不要留著無用的遺留代碼!

不要留著無用的遺留代碼!

不要留著無用的遺留代碼!

重要且最容易被忽略的事情要說三遍。斬草要除根,雖然我們可以保持代碼不動。但是清理不再使用的遺留代碼和自動化測試可以為其它團隊減少很多不必要的工作量。

最終的架構

經過6個人兩個月的開發(原計劃8個人3個月),我們的 Serverless 微服務最終落地了。當然這中間有 60% 的時間是在探索全新的技術棧。如果熟練的話,估計 4 個人一個月就可以完成工作。

最後的架構如下圖所示:

在上圖中,請求仍然是先到 CDN (CloudFront),然後:

CDN 根據請求點的不同,把頁面請求轉發至 S3 ,把 API 請求轉發到 API Gateway。

前端的內容通過藍綠部署被放到了不同的 S3 Bucket 裡面,只需要改變 CDN 設置就可以完成對應內容的部署。

雖然對於部署來說藍綠 Bucket 乍看有一點多餘,但這是為了能夠在生產環境下做集成在線測試準備的。這樣可以使環境不一致儘可能少。

API Gateway 有自己作用的 VPC,很好的實現了網路級別的隔離。

通過 API Gateway 轉發的 API 請求分成了三類,每一類都可以根據請求狀況自擴展:

身份驗證類:第一次訪問會請求 ElastCache(Redis),如果 Token 失效或者不存在,則重新走一遍用戶驗證流程。

數據請求類:數據請求類會通過 Lambda 訪問由其他團隊開發的 Java 微服務,這類微服務是後台系統唯一的訪問點。

操作審計類:請求會記錄到 DynamoDB (一種時間序列資料庫)中,用來跟蹤非同步請求的各種日誌。

API Gateway 自己有一些緩存,可以加速 API 的訪問。

消息返回後,再有三類不同的請求的結果統一通過 API Gateway 返回給客戶端。

Serverless 風格微服務架構的優點

由於沒有 EC2 設施初始化的時間,我們減少了至少一個月的工作量,分別是:

初始化網路配置的時間。

構建 EC2 配置的時間。

構建反向代理和前端靜態內容伺服器的時間。

構建後端 API 應用基礎設施的時間。

構建負載均衡的時間。

把上述內容用 Ruby 進行基礎設施即代碼化的時間。

如果要把 API Gateway 算作是基礎設施初始化的時間來看。第一次初始化 API Gateway 用了一天,以後 API Gateway 結合持續交付流程每次修改僅僅需要幾分鐘。

無論怎麼說,Serverless 大大降低了基礎設施配置和運維門檻。

此外,對於團隊來說,Amazon API Gateway + Lambda 的微服務還帶來其它好處:

開發效率高,原先至少 45 分鐘的開發反饋周期縮短為 5 分鐘以內。

無關的代碼量少,需要維護的代碼量少。除了專註業務本身。上游和 API Gateway 的集成以及下游和後端服務的集成代碼量很少。

應用維護成本低。代碼僅僅幾十行,且都為函數式,很容易測試。避免了代碼庫內部複雜性的增加。

此外,我們做了 Java 和 NodeJs 比較。在開發同樣的功能下,NodeJS 的開發效率更高,原因是 Java 要把請求的 json 轉化為對象,也要把返回的 json 轉化為對象,而不像 nodejs 直接處理 json。

此外, Java 需要引入一些其它 JAR 包作為依賴。在 AWS 場景下開發同樣一個函數式微服務,nodejs 有 4 倍於 java 的開發效率提升。

最後

Serverless 風格的微服務雖然大大減少了開發工作量以及基礎設施的開發維護工作量。但也帶來了新的挑戰:

大量函數的管理。

SIT,UAT 環境的管理。

持續交付流水線的配置。

面對基礎設施集成帶來的測試。

這讓我們重新思考了 Serverless 架構的微服務如何更好的進行持續交付。

敬請期待下一篇《Serverless 風格微服務的持續交付(中):持續交付的挑戰 》

彩蛋

重磅 Chat

高效學習,快速變現:不走彎路的五大學習策略

分享人:

Seaborn Lee,一名會在 B 站直播寫代碼,會玩雜耍球、彈 Ukulele、極限健身、跑步、寫段子、畫畫、翻譯、寫作、演講、培訓的程序員。喜歡用編程實現自己的想法,在 Android 市場上賺過錢,有多次創業經歷。

擅長學習,習慣養成,時間管理。身體力行地影響他人做出積極的改變!目前就職於 ThoughtWorks,致力於傳播快樂高效的編程理念。業餘創立軟體匠藝社區 CodingStyle.cn,組織超過30場技術活動。

Chat簡介:

說到學習呀,真是頭大喲:

碎片化,沒有較長的連續時間來學習

難專註,捧起書,手機卻在召喚:來呀,快活呀~ 反正有,大把時光~

做不到,看了很多書,生活中卻做不到

然並卵,學了方法和工具,找不到使用場景

效率低,學習速度跟不上知識產生的速度

記不牢,學習速度趕不上遺忘速度

在這個知識泛濫、跨界競爭的年代,學習能力才是核心競爭力。你想想,過去一周,有沒有哪一件工作是不需要學習就能完成的?儘管如此重要,大部分人卻沒研究過學習這件事,以為上下班路上打開「得到」聽本書,就是碎片時間終身學習者了。

想要免費參與本場 Chat ?

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 GitChat技術雜談 的精彩文章:

關於「如何成為跨領域人工智慧工程師」「持續交付」兩場 Chat

TAG:GitChat技術雜談 |