《微服務:從設計到部署》總結筆記

英文原文出自:https://www.nginx.com/blog/introduction-to-microservices/
筆記首發於個人語雀,有空來幫我點個稻穀呀。html

port_by_arsenixc-d90p0xb.jpg

1 簡介

monolithic app(都寫在一塊兒)的劣勢

image.png

  • 代碼依賴極其複雜
  • 難以理解
  • 啓動時間變長
  • CPU密集型邏輯和內存消耗型邏輯沒法拆開優化,浪費雲計算資源
  • 可靠性變差:一處內存泄漏,全家昇天
  • 難以作架構升級,例如更換語言或框架

微服務

image.png

  • 把你的巨型應用拆解成小型的獨立的互聯的服務們
  • 服務之間互相之間提供服務和消費服務
  • 客戶端經過API網關來調用服務們
  • 運行時的一組服務能夠是多臺物理機上的多個container
  • 服務擁有本身的獨立的數據庫存儲,這意味着服務能夠選擇本身最適合的數據庫類型。

微服務的優點

  • 拆解了複雜的應用,單個服務更好迭代和維護。
  • 每一個服務能夠自由地選擇最合適的技術棧。
  • 服務能夠分開部署,能夠更加自由地進行持續集成。

微服務的劣勢

  • 必需要開發服務間通訊機制
  • 須要常常處理「部分失敗」的狀況,由於一個請求如今是是一組服務的調用。
  • 數據庫結構設計和操做,例如跨表操做,數據一致性(事務)問題等。
  • 測試更加困難,由於要測試你的服務,你必須先啓動其餘服務。
  • 在維護或更新時,你一般須要更新多個服務。
  • 服務數量衆多時,部署也是一個難題。

2 構建微服務:使用API網關

  • 【背景】例如在作一個電商寶貝詳情時,你客戶端須要調:購物車服務、訂單服務、類目服務、評價服務、庫存服務、發貨服務、推薦服務....

不使用API網關,挨個調過去

  • 客戶端經過移動網絡發送N個網絡請求,是很是不靠譜的
  • 服務可能用的並不是是web友好的協議,例如thrift,沒法提供服務。
  • 讓重構微服務們變得困難,由於它們被客戶端直接依賴了

使用API網關

  • API網關能夠調用多個微服務, 而後提供一個「item/detail?id=xxxx」的統一接口
  • API能夠順便作負載均衡、監控、受權和認證等等多種統一功能
  • 【優】封裝了應用的內部結構設計,客戶端只須要和API網關通訊
  • 【優】能夠經過服務,自由組合出最適合客戶端的API
  • 【劣】你須要單獨開發、部署、管理API網關
  • 【劣】API網關可能成爲開發的瓶頸,例如開發者暴露新的服務時要更新到API網關

實現API網關

  • 爲了API網關的性能和可擴展性,應該支持異步NIO。
  • 在JVM上你可使用Netty、Vertx、Spring Reactor等NIO框架
  • 在非JVM上你可使用node.js來實現NIO
  • 你還可使用Nginx Plus(要錢的)

使用Reactive Programming

  • 對於互不相關的服務,應該同時一塊兒調用。有前後順序的服務可能要定義好前置和後置。
  • 使用傳統的異步+回調的方式來書寫組合服務的代碼會讓你很快陷入回調地獄。
  • Java、Scala、JS中都有響應式的方案,你還可使用ReactiveX來書寫這類代碼。

服務調用:

一般有兩種調用方式java

  • 異步——消息隊列
  • 同步——HTTP或者Thrift等

服務發現

  • 過去你一般手動控制服務的ip地址,可是在微服務體系中,你沒法作到。
  • 因爲各個服務的隨時擴容和升級,它們的地址都是在動態變化的。
  • 因此API網關須要有服務註冊,服務發現的能力。不管是server-side發現,仍是client-side發現。

處理部分失敗

  • API網關不能夠由於下游的失敗而block住,它要根據場景來決定如何處理錯誤。node

    • 例如只是商品推薦服務掛了,商品詳情頁接口應該仍然返回其餘全部數據
    • 若是是商品基礎信息掛了,商品詳情頁接口應該反饋錯誤給到客戶端。
  • 能夠返回硬編碼打底數據,或者緩存數據。(API網關作, 服務無感知)
  • 安利了一下Netflix Hystrix, 可是彷佛已經處於維護狀態再也不更新了。

3 構建微服務:微服務架構中的跨進程通訊

  • 【背景】在monolithic應用中,組件之間互相經過語言級別的方法就能夠進行調用,可是在微服務應用中,這些組件都被分佈式地部署在不一樣的機器上的不一樣容器裏。基本每一個服務都是一個進程,因此服務們不得不使用跨進程通訊(IPC)機制。

交互方式

  • 一對一通訊 vs 一對多通訊
  • 同步 vs 異步    

            Screen Shot 2019-12-19 at 2.32.43 PM.png

    • request/response: client發起請求而後同步等待響應結果。
    • notifications:client發出一個請求,但並不須要返回,也不等待。
    • request/async response: client發出一個請求,但響應是異步的,在等待響應的過程當中,client並不阻塞。
    • publish/subscribe: client發出一條消息,被一個或多個感興趣的服務消費。
    • public/async responses: client發出一個請求消息,而後等待一段時間來接收感興趣的服務的返回。
    • 每一個服務均可能用上述多種交互方式,例以下圖image.png

    定義API

    • 服務的API是一種服務和它的客戶端們之間的約定。
    • 使用某種接口定義語言(IDL)很是重要,例如Protobuf。
    • 你甚至可使用API-first這種方式,也就是先定義API再實現它,來進行開發。
    • API定義依賴於你使用的IPC機制,例如使用消息那麼API就須要消息通道和消息類型。

    更新API

    • 在monolithic的app裏,你通常改了API後,去代碼裏直接改全部的調用處.
    • 在微服務體系裏, 更新API會困可貴多,你沒辦法讓你服務的消費者挨個更新。你可能要增量地添加新版本的服務,線上可能同時存在兩個版本的API,這是一種很是重要的更新策略。
    • 爲了實現上述能力,一種可行的方式是添加版本號,讓多個版本的API同時存在。

    處理部分失敗

    • 在微服務體系中,部分失敗是很是常見的,由於全部服務們都在不一樣的進程裏。
    • 假設有一個場景,你的產品詳情頁裏須要使用推薦服務,而此時推薦服務掛掉了:image.png

    若是你按照一直等待去設計,那麼頗有可能會消耗掉你全部的線程,致使產品詳情服務完全掛掉linux

    • 下面有4條由Netflix推薦的部分失敗錯誤處理策略nginx

      • 網絡超時:永遠不要一直等待,必定要有超時重試/超時失敗機制。
      • 限制等待中請求數量:等待中的請求應該有一個上限值,一旦上限達到了,就不該該再處理請求,這些超出限額的請求應該當即失敗。
      • 短路模式:當失敗率達到必定程度時,後續的請求應該當即失敗,再也不請求。短路後,應該每隔一段時間重試,若是重試成功,那麼就再也不短路。
      • 提供降級:提供失敗時的降級邏輯,例如得到緩存數據。

    IPC機制

    • 異步、基於消息的IPC機制git

      • client向某服務發送一條消息,但並不等待它。若是此次調用須要返回,被調用服務會也異步經過消息返回給client。client徹底基於異步,也就是不等待的方式來編寫。
      • 消息通常由header和body構成,而且經過channel來交換。
      • 兩種消息channel:1. 點對點 2. pub/sub
      • 案例:image.png
      • 出行服務發一條消息「一個新出行建立了!」到名叫「新的出行」的pub/sub型channel中,通知全部感興趣的服務(好比派單服務)。派單服務找到了合適的司機之後,發送一條消息「司機接單了!」到一個名叫「派單」的pub/sub型channel中,通知全部感興趣的服務。
      • 消息隊列實現有很是多種:RabbitMQ、Kafka、ActiveMQ.....
      • 用消息機制的優勢:github

        • 把服務的consumer和provider經過消息解耦了。
        • 消息可堆積,比起實時調用有更高的容錯率。
        • 調用方式也解耦了,consumer和provider之間只須要遵照消息約定便可。
      • 用消息機制的缺點:web

        • 更高的運維複雜性,消息系統也是一個要維護的系統啊!
        • 要實現請求-響應這種同步請求會更加麻煩
        • 【做者補充】消息隊列的高可用、不被重複消費、可靠性、順序性都是很是複雜的課題。
    • 同步、請求/響應型的IPCdocker

      • 一般來講,一個線程會在等待請求時阻塞。有些可能已經經過Future或者Rx Observables這種Ractive Pattern的東西把它變成了異步的方式。
      • 可是在這種類型的IPC裏,client一般但願請求可以儘快及時地返回。
      • 常見的主要由兩種協議:Rest、Thrift
      • 基於HTTP方式的協議(Rest)的好處:數據庫

        • 簡單熟悉
        • HTTPAPI能夠直接在瀏覽器、Postman、curl等多種環境下測試。
        • 直接支持 請求/響應 模式
        • 沒有任何中間代理,系統架構簡單
      • 基於HTTP方式的協議(Rest)的壞處:

        • 只支持 請求/響應 模式
        • provider和consumer都必須活着不能掛
      • 基於HTTP方式的一些IDL或平臺:RAML、Swagger
      • Thrift

        • 使用一個C風格的IDL來定義API,而且使用Thrift編譯器來生成客戶端和服務端代碼模板。
        • 支持C++、Java、Pyhton、Ruby、Erlang、Node.js
        • Thrift方法能夠返回空值,也就是能夠實現單向的通知,並不必定要返回。
        • 支持JSON/二進制等格式
    • 消息格式

      • 無論用什麼方式和語言,最好選擇語言無關的消息格式,你沒法保證未來你不換語言。
      • 在文本和二進制中,tradeoff大概就是包大小or人類可閱讀性。

    4 微服務架構中的服務發現

    爲何使用服務發現?

    • 你之前,你可能把要調用的IP放在配置裏,而後直接調用
    • 可是在雲時代的微服務應用中,你應該不太能這麼作,看圖:

    image.png

    • 你會發現,服務實例們都有着動態分配的網絡位置,並且這些位置因爲擴容、更新等等還在變化。
    • 目前主要由兩種服務發現:client側服務發現、server側服務發現

    client側服務發現

    image.png

    • 這種模式下,consumer決定了所調用服務的網絡地址。全部的服務provider,都把本身的網絡地址,註冊到服務註冊中心,consumer拿到了一坨ip地址以後,本身選擇一個load-balancing策略,而後調用其中一個。
    • 服務Provider在啓動時向註冊中心註冊服務,在結束進程時給註冊中心註銷服務。服務的健康經過心跳機制來進行保障。
    • Netflix OSS是一個client側服務發現的好例子,Netflix Eureka是一個REST型服務註冊中心。
    • Netflix Ribbon是一個IPC客戶端,可以和Eureka合做,提供load-balancing。
    • 優勢:1. 簡單直接  2. 因爲consumer決定了路由,能夠作靈活的、應用特定的負載均衡策略
    • 缺點:耦合了consumer和服務註冊中心,你必須給每一個編程語言實現consumer側的服務發現邏輯。

    server側服務發現

    image.png

    • consumer經過一個load balancer來請求provider,load balancer要請求服務註冊中心來得到全部可提供服務的實例以及它們的網絡地址。也就是load-balancer決定了到底要請求誰。
    • 亞馬遜的ELB就是一個server側服務發現路由。ELB把服務註冊直接作到了本身裏面。
    • Nginx也能夠充當server側服務發現中的load balancer,好比這篇文章裏,你可使用Consul Template來動態更新nginx的反向代理。
    • 一些部署環境例如K8S或者Marathon,在集羣裏的每一個宿主上都運行Proxy,這個Proxy就扮演了server側服務發現中的load-balancer。若是你想給一個服務發請求,一個consumer必須經過proxy來調用。
    • 優勢:

      • consumer和服務發現是徹底解耦的,無腦請求load-balancer就行了。
      • 不用再給每一個語言實現一套load-balancing機制了。
      • 有些部署環境甚至直接提供了這種load-balancing服務。
    • 缺點:若是沒有提供好的load-balancing服務,你就要本身實現。

    服務註冊中心

    • 概念詳解

      • 它就是一個服務實例們網絡位置的數據庫。
      • 它必須高可用,並實時更新。
      • 它實際上是一坨實例們,這些實例們要經過某種副本協議來保持一致性
    • 好比Netflix Eureka,它是基於REST的。它經過POST方法來註冊服務,每30秒經過PUT方法刷新一次,經過DELETE方法刪除服務,經過GET方法來得到一個可用的服務實例。
    • Netflix爲了實現服務註冊的高可用性,經過運行N個Eureka實例,使用DNS的TEXT記錄來存儲Eureka集羣的配置,這個配置也就是一個可用區->一組網絡地址的map。當一個新的Eureka啓動,它就請求DNS來得到Eureka集羣配置,而且給它本身一個新的IP。Eureka的clients,services就請求DNS來發現Eureka實例們的地址,而且會盡量選相同可用區的Eureka實例。
    • 其餘的服務註冊中心們還有

      • etcd:K8S和Cloud Foundry用的它
      • consul
      • Zookeeper:你們最熟悉了
    • 在K8S、Marathon、AWS中並無顯式的、單獨的服務註冊中心,由於它們是內置的基礎設置。

    服務註冊的方式

    • 目前主要有兩種方式來處理服務的註冊和註銷:自注冊模式和三方註冊模式。
    • 自注冊模式image.png

      • 這種狀況下,服務實例本身負責註冊和註銷它提供的服務,同時服務本身還須要不停地發送心跳包來阻止本身的註冊過時。
      • Netflix OSS Eureka client是一個很好的自注冊例子。Spring Cloud中也能夠直接使用註解來實現註冊方式。
      • 優勢:簡單直接、不須要其餘系統組件。
      • 缺點:把服務實例和註冊中心耦合起來了,你要實現各個編程語言的註冊代碼。
    • 三方註冊模式image.png

      • 顧名思義,服務實例們要向一個註冊管理者(registrar)來進行註冊,而註冊管理者也經過健康檢查機制來跟蹤服務實例們的狀況,隨時註銷掛掉的服務實例。
      • 有一個開源的註冊管理者叫作Registrator ,它能自動註冊和註銷以docker container方式部署的服務實例。Registrator支持etcd和Consul。
      • 還有一個有名的註冊管理者是NetflixOSS Prana,它主要是爲了非JVM語言設計的,它是一個sidecar應用,也就是說它跟着每個服務實例運行。Prana和Eureka配合,向Eureka進行註冊和註銷服務。
      • 註冊管理者,也是不少部署環境的內置基礎設施,例如在AWS EC2和K8S中都是。
      • 優勢:服務和服務註冊中心解耦、你也再也不須要去實現特定語言的註冊邏輯。
      • 缺點:若是部署環境沒提供註冊管理者,那你就要本身實現。

    5 微服務中事件驅動的數據管理

    背景

    • 在一個monolithic的應用中,使用關係型數據庫的一個好處是,你可使用符合ACID原則的事務。
    • ACID原則

      • Atomicity 原子性: 變動要麼都成功,要麼都失敗。
      • Consistency 一致性:數據庫的狀態永遠是一致的。
      • Isolation 獨立性:事務之間不會有交錯執行的狀態(由於可能會致使數據不一致)。
      • Durability 持久性:事務成功後,修改是持久的。
    • 另外一個好處是你可使用SQL,你能夠輕鬆地進行多表操做,數據庫幫你解決大部分性能問題。
    • 遺憾的是當切換到微服務架構之後,這些好處你就再也不可以享受,由於全部的數據都被各自的微服務所擁有,也就是說他們擁有各自獨立的數據庫,數據的訪問只能經過API層面來進行。
    • 來吧,更糟糕的是:不一樣的微服務可能還用了不一樣類型的數據庫。例如NoSQL系列的,graph型的(例如Neo4j)。因此微服務架構中一般混用各類類型的數據庫,咱們稱之爲polyglot persistence 。

    挑戰1:如何在多個微服務中實現事務

    • 假設有一個在線的B2B商店,「客戶服務」維護了客戶信息,「訂單服務」管理訂單們,而且保障一個新訂單不會用完客戶的餘額。
    • 在過去的monolithic版的應用中,咱們用一個事務,就能搞定「檢查餘額夠不夠+建立訂單」這件事。
    • 可是在微服務體系中,咱們的ORDER表和CUSTOMER表如今是私有的:image.png
    • 訂單服務並沒有法直接訪問CUSTOMER表,它只能經過客戶服務提供的API來間接修改CUSTOMER表。也許你可使用分佈式事務,也叫two-phase-commit(2PC)來解決。可是在現代化的應用中,你很難使用2PC。著名的CAP theorem告訴咱們,在ACID中你要抉擇要C仍是A,而通常更好的選擇都是A。並且,在不少NoSQL的數據庫中根本不支持2PC。

    挑戰2:如何進行多表查詢

    • 假設你的客戶端須要展現客戶的信息以及他全部的訂單。
    • 若是訂單服務提供了根據客戶id查它訂單的服務,那麼你能夠直接調這個服務。
    • 可是若是訂單服務,沒有提供這個服務,好比只支持按照訂單id查詢時,你該怎麼辦呢?

    事件驅動架構

    • 在大部分應用中,解決方案就是使用數據驅動架構。當一個值得注意的事情發生了之後,這個微服務發出一個事件,好比「它更新了某個entity」這件事。其餘對此感興趣的微服務訂閱這些事件,當它們收到這件事情時,就作它們本身的業務邏輯,好比把本身對於這個entity的冗餘字段也更新一下。
    • 你可使用事件來實現跨越多個服務的事務,這樣的一個事務,包括的許多步驟,每一個步驟包括一個微服務更新本身的業務邏輯entity,而且發出一個事件通知下一些微服務。來看下面這個流程:

      • 圖1:訂單服務建立了一個新訂單,發出一個「一個訂單建立辣!!」的消息

            image.png

    • 圖2:客戶服務消費這個「一個訂單建立辣!!」消息,而且給這個訂單預留了餘額,而後發送一個「已預留餘額」事件

              image.png

    • 圖3:訂單服務消費「餘額已預留」事件,而且把訂單狀態設爲「建立成功」

    image.png

    - 這裏面須要假設
    - 每一個服務自動地更新數據庫,而且發佈事件
    - 消息代理必須保證事件至少都被交付了一次
    • 儘管如此你也只是實現了事務,沒有實現ACID的事務,這僅僅是提供了弱保障,例如eventual consistency. 這樣的事務模型被稱爲BASE model.
    • 【解決查詢問題】你還可使用事件來維護一個額外"物料化視圖",這個視圖包含的預先join好的,來自多個微服務的數據。例如能夠專門有一個「客戶訂單視圖」服務,來專門訂閱相關事件(客戶服務產生的事件、訂單服務產生的事件等),而後更新這個視圖。image.png

      • 你可使用文檔化數據庫例如MongoDB來實現這個額外視圖,而且給每個客戶都存一個document。這樣當有一個客戶來的時候,你能夠光速地反饋這個客戶的相關訂單,由於你已經事先存好了。
    • 事件驅動架構的優勢

      • 它使得跨多個服務的普通事務得以實現。
      • 它讓應用能夠經過「物料化視圖」實現快速查詢。
    • 事件驅動架構的缺點

      • 編程將更加更加複雜
      • 你必須得實現補償事務(回滾等)來從應用級錯誤中恢復。例如:當你檢查餘額失敗時,你應該取消訂單。
      • 應用將要面對數據不一致的狀況,例如「物料化視圖」中的數據並不是最新的。並且還要處理事件相關的各類問題,好比重複消費(冪等性),漏消費等問題(其實也就是消息隊列常見問題系列)。

    實現原子性

    • 【背景】在事件驅動架構裏,你還會遇到一個原子性問題

      • 假設訂單服務要插入訂單表,同時發出一個「訂單建立辣!」事件,這兩步操做必需要原子地完成。
      • 假設服務在插入了訂單表,發出事件以前掛掉了,那麼系統就會出現一致性問題。
      • 一般標準的作法是使用分佈式事務,可是如上面所說(好比CAP theorem),這並非咱們想作的。
    • 使用本地事務完成發送事件

      • 這個操做有個術語叫作multi‑step process involving only local transactions
      • 藉助本地事務,其實就是你要一個本服務的事件表,它的做用相似消息隊列:image.png

        • 事務1:訂單服務插入一個新訂單,而且在事件表裏插入一個新的訂單建立事件。
        • 事務2:事件發送線程讀取事件表找到未發送的事件,更新時間並標記事件已發送。
        • 【筆記】這樣就保障了「插表+創未發事件」和「事件發送並更新事件狀態」的原子性,首先這兩個由於是事務因此必定全成功或者全失敗,其次是萬一在二者之間的時候掛掉了,重啓之後事件發送線程還會繼續事務性地讀取未發事件並從新發送。
      • 優點:不依賴2PC(分佈式事務)也能保障每次更新的原子性,發送的也是仍然是業務邏輯層面的事件。
      • 劣勢:寫代碼容易漏寫(??我反正沒看懂原文啥意思,由於複雜就特麼能漏寫??)。當使用NoSQL型數據庫時會很困難,由於它們的查詢能力和事務性都比較差。
    • 挖掘數據庫事務日誌

      • 另外一種不用2PC實現原子性的方法就是,有一個單獨的線程去挖掘數據庫事務或者commit日誌。也就是數據庫被更新了之後,就有日誌,而這個挖日誌線程就讀日誌,而後發消息給消息代理。image.png
      • 一種實現方式就是使用開源的LinkedIn Databus ,它能夠挖掘Oracle事務日誌併發送消息。LinkdeIn用它來保障多個數據存儲的一致性。
      • 另外一個例子是streams mechanism in AWS DynamoDB,是一種託管的NoSQL數據庫,它有一種DynamoDB流,按照時間順序記錄了24小時內的增刪改查。應用能夠讀這些變化來發送事件。
      • 優點:保障了每次更新均可以發送事件。事務日誌也能夠簡化應用邏輯,由於把事件發送和應用業務邏輯拆分開來了。
      • 劣勢:每一個數據庫的事務日誌都不太同樣,而且隨着數據庫版本變化,並且你還須要作高層業務邏輯到底層數據庫日誌的轉換。
    • 使用數據溯源

      • 數據溯源是:不直接存儲實體如今的狀態,它則是存儲數據變化的事件(是否是想到了Redux的Action和時間旅行?)。應用會回放全部的數據變化事件來更新實體的狀態。至關於先事件再業務了,因此保障了原子性。
      • 舉個🌰,按照傳統的作法,一個ORDER實體對應數據庫裏ORDER表,可是在數據溯源方式裏,表裏存的都是狀態變化:訂單建立、確認、發貨、取消等等。image.png
      • 事件都存儲在了Event Store,這個Event Store其實就像架構裏的事件代理。它提供了其餘服務訂閱這些事件的API。
      • 優點:

        • 順便就實現了事件代理,一箭雙鵰。
        • 它用一種微服務的方式解決了數據一致性問題。
        • 因爲是持久化事件而非實體,他基本避免了 object‑relational impedance mismatch problem
        • 業務邏輯和業務實體耦合程度低,這讓它具有更好的遷移性。
      • 劣勢:它是一種徹底不同的編程範式,有陡峭的學習曲線。Event Store只直接支持按主鍵查詢業務實體。你必須使用Command Query Responsibility Segregation來實現查詢。

    6 選擇一種微服務部署策略

    動機

    • 部署monolithic應用時,你一般是在N臺物理機(或虛擬機)上部署M個相同的服務實例。這種部署要比微服務要更直接、簡單。
    • 微服務一般包括上百的服務,而且它們用了不用的語言和框架。爲了讓某個功能運行,你可能要起一坨服務才能work。每一個服務都有本身單獨的部署、資源消耗、擴容、監控的方式。因此微服務的部署雖然很困難,可是也必需要保障快速、可靠、低耗。

    每一個宿主機多個服務實例模式

    • 這種模式就是,你在多臺機器上部署多個不一樣的服務實例,不一樣的服務實例在不一樣的端口上。

    image.png

    • 這種模式還有不少變種,例如

      • 每個服務實例是一個或一組進程

        • 部署一個Java服務實例做爲一個web應用放在Apache Tomcat 上面。
        • 一個Node.js服務實例可能包含一個父進程和多個子進程。
      • 在同一個進程或進程組運行多個服務實例

        • 你能夠在同一個Tomcat上運行多個java web應用
        • 在OSGI容器上運行多個OSGI的bundle
    • 優點

      • 資源利用相對合理有效,它們共享操做系統和服務器。例如:多web應用共享tomcat和JVM。
      • 部署很是快速,你只要把代碼或者構建產物copy到機器上而後啓動就好了。
      • 啓動服務很是迅速,由於服務進程就是它本身,它就啓動本身就行了。
    • 劣勢

      • 服務實例之間沒有太多隔離,就算你能夠監控每一個服務用了多少資源,你沒辦法限制每一個服務實例能用多少。有一些服務也許能夠消耗掉全部的內存和CPU。若是是同一個進程下的多個服務實例,就更加沒有隔離了,他們可能共享好比JVM堆。
      • 運維團隊必須知道部署你這個服務的細節,由於不一樣的應用實現方式都不同,語言、框架、依賴、環境均可能不太同樣。這會增長運維的風險。

    每一個宿主機一個服務實例模式

    • 另外一種方式就是,每一個宿主機只有一個服務實例。這種模式下主要由兩種子模式:每一個虛擬機一個服務實例和每一個容器一個服務實例
    • 每一個虛擬機一個服務實例

      • 你把每個服務都打成一個虛擬機鏡像,例如 Amazon EC2 AMI。每一個服務實例都是一個運行這個鏡像的虛擬機image.png
      • Netflix就用這種方式來部署它的視頻串流服務,它使用 Aminator來把每一個服務打成AWS EC2 AMI,每一個服務實例運行在一個EC2身上。
      • 還有不少其餘工具你能夠用來構建你本身的VM,你能夠用一些持續集成工具(好比Jenkins)來調用Animator來打包成虛擬機鏡像。Packer.io 也是一個不錯的選擇,它支持各類虛擬化技術,不只只有EC2。
      • CloudNative有一個叫Bakery的服務,它是一個用來建立EC2 AMI的SaaS。你能夠配置你的CI服務器來調用Bakery(固然前提是你過了你的測試),Bakery會幫你把你的服務打包成成一個AMI。
      • 優點

        • VM之間相互獨立,擁有固定的CPU和內存,不會互相攫取資源。
        • 能夠藉助成熟的雲計算基礎設施,例如AWS,來快速擴張和部署。
        • 把你的服務封裝成了VM之後,就是黑盒了,部署不須要關心細節。
      • 劣勢

        • 資源利用更加低效,由於有時候服務並不能榨乾虛擬機的性能,而虛擬機就會有性能剩餘。
        • 基於上一點,不少雲服務按照虛擬機數量收費,並無論你的虛擬機忙仍是閒。
        • 當部署新版本的虛擬機時一般很慢,由於虛擬機大小一般都是比較大的,而且初始化,啓動操做系統等等也須要時間。
        • 維護虛擬機自己也是一個很重的擔子。
    • 每一個容器一個服務實例模式

      • 每一個服務實例運行在它本身的容器裏。容器是一種 virtualization mechanism at the operating system level,你能夠簡單理解爲更輕量級更厲害的虛擬機。一個容器包含了一個或多個運行在沙箱裏的進程。你能夠限制每一個容器的CPU和內存等資源。常見的容器技術有DockerSolaris Zones.image.png
      • 爲了使用這種模式,你要把你的服務打包成一個容器鏡像,容器鏡像包含了一個完整的linux文件系統,服務自己的代碼,相關的依賴等,一切的目的都是爲了讓這單個服務跑起來。好比你要打包一個java應用的容器鏡像,你可能就須要一個java運行時,一個Tomcat,以及你編譯後的jar包。
      • 當你須要在一個機器上運行多個容器的時候,你可能就須要服務編排工具了,例如Kubernetes 或者 Marathon。它基於容器對資源的需求以及如今剩下的資源,來決定容器到底放哪裏。
      • 優點

        • 和VM同樣,隔離了你的每一個服務實例。
        • 和VM同樣,封裝了你使用的技術,容器能夠不關心細節就部署。
        • 不一樣於VM,容器更輕,構建也更快,啓動也更快.
      • 劣勢

        • 容器常常部署在按VM數量收費的IaaS上,也就是說,你可能要花額外的錢。
    • 容器和VM的邊界正在慢慢消失,二者正在互相靠攏。

    Serveless部署

    • AWS Lambda就是一個很是典型serveless部署,他支持你用各類語言寫代碼,這些代碼做爲一個函數,能夠直接響應請求或事件。AWS會幫你解決下面的機器、內存等物理需求,你只須要關心業務邏輯就行了。
    • 一個Lambda函數是一個無狀態服務,它能夠直接和AWS其餘的服務交互,好比當S3插進來一個新東西的時候,可讓一個函數響應並作後處理。函數還能夠調用其餘三方服務
    • 你有這麼些方法能夠調用一個函數

      • 直接經過你的服務來請求。
      • 經過AWS其餘服務產生的事件觸發。
      • 提供給AWS的Api gateway來提供HTTP服務。
      • 週期性的運行,基於一種cron的時間表。
    • 你能看到,AWS Lambda是一種很是方便的部署微服務的方式,而且它還基於請求收費,你只須要爲你的使用量付費。你也不用關心底層的IT基礎設施.
    • 劣勢

      • 它不適合用來作長時間運行的服務,好比要從某個消息代理持續消費消息.
      • 請求必須在300秒之內完成.(AWS的限制吧)
      • 服務必須無狀態,並且上一次函數調用和下一次函數調用極可能不在一臺機器上。

    7 從一個單應用重構爲微服務

    • 你最好不要使用「Big Bang」策略,也就是徹底重寫你的服務。既危險又耗時。
    • 你能夠增量性地重構你的應用,你能夠逐漸地構建一個基於微服務的新應用,和你的原來應用同時運行。隨着時間的推移,全部的功能都被慢慢的從原應用遷移到微服務。

    策略1 中止挖坑

    • 不要繼續把這個monolithic的天坑繼續挖了,若是要加新功能,不要加到這個大應用裏。你把新需求用微服務的模式來作。image.png
    • 如圖,你新加了一個request router,把新功能的請求路由到你的新微服務裏,老功能路由到老monolithic應用裏。有點像API網關。
    • 你還加了一坨膠水代碼,其實就是爲了新老服務之間互相調用,由於他們之間也可能有交互。你能夠用RPC、直接訪問數據庫、訪問老數據庫同步過來的數據等方式來實現這種調用。
    • 策略優點:它阻止了原應用變得更加難以維護。新開發的服務能夠獨立地開發和部署。你能夠當即開始享受微服務帶給你的好處。
    • 但這個策略沒有對原應用作任何優化,你須要看策略2來如何改造原應用。

    策略2 分離先後端

    • 這個策略主要思路是,幫你分離展現層和業務邏輯層以及數據訪問(DAO)層,從而作到讓原來的monolithic應用縮小一些。一般一個典型的企業級應用有這些組件層:

      • 展現層:處理HTTP請求和展現web界面的組件們。
      • 業務邏輯層:應用實現業務邏輯的核心組件們。
      • 數據訪問層:應用訪問數據和消息代理的基礎組件們。
    • 有一種常見的作法,你能夠把你的應用按照下圖,拆分紅兩個子應用:image.png

      • 一層子應用包括了展現層。
      • 另外一層子應用包含了業務邏輯和數據訪問。
    • 策略優點

      • 它讓你可以單獨地部署和擴容兩個應用,它們各自均可以快速迭代
      • 因爲業務邏輯層和數據訪問層單獨抽離了一個應用,你的新微服務如今能夠調用這坨UI無關的服務了。
    • 然而這個策略仍然只是一個部分解決方案,有可能拆分後兩個應用仍是會變成難以維護的monolithic應用,因此你還須要看策略3。

    策略3 抽取服務

    • 這個策略就是要把現有的在你原應用裏的模塊轉換成單獨的微服務。每次你抽出來一個新的微服務,原應用就變小了。只要你抽得足夠多,原來的這個大應用就會消失或者乾脆也變成一個微服務。
    • 轉換成微服務的優先級

      • 首先你最好先抽象容易抽象的模塊,這樣你就能先積累抽象微服務的經驗
      • 而後你應該優先抽取能給你帶來最大收益的模塊,因此你須要給你的模塊排一個優先級。
      • 你還能夠先抽象要特別的物理資源的模塊,好比某個模塊特別須要內存數據庫,你能夠先抽這個模塊,而後把它放到內存比較大的環境裏。
      • 抽象模塊的粒度,能夠按照這樣一個簡單原則:好比某個模塊和其餘的模塊的交流均可以經過異步消息完成,那麼這個模塊就能夠抽出來。
    • 如何抽取一個模塊

      • 首先要定義抽出來的模塊如何和系統進行交互,一般是一組雙向的API。但一般都比較難,由於這種API會和系統耦合得比較多。用了Domain Model pattern 的就更難重構了。
      • 一旦你實現了粗粒度的接口,你就要把模塊抽出來作一個單獨的服務了。這時候爲了同心,你還須要使用IPC機制。image.png
      • 在上面的例子中,模塊Z是要被抽象出來的模塊。它的組件被模塊X和模塊Y使用了,因此

        • 第一步就是要定義出一組粗粒的API來讓X和Y經過API和Z交互。
        • 第二步就是把模塊弄成單獨的服務。這時候你須要IPC通訊來完成他們之間的跨服務調用。
      • 你甚至能夠按照新的API來從新寫這個抽象的服務。你每抽出來一個服務,你就朝着微服務方向前進了一步,隨着時間推移,你的原應用終將消散,而你就把它演進成了一套微服務。
    相關文章
    相關標籤/搜索