<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                > 作者 張磊 2015年4月,傳聞許久的Borg論文總算出現在了Google Research的頁面上。雖然傳言Borg作為G家的“老”項目一直是槽點滿滿,而且本身的知名度和影響力也應該比不上當年的“三大論文”,但是同很多好奇的小伙伴一樣,筆者還是饒有興趣地把這篇“非典型”論文拜讀了一番。 注:本文作者張磊將在8月28日~29日的CNUT全球容器技術峰會上分享題為《[從0到1:Kubernetes實戰](http://cnutcon.com/)》的演講,演講中他將重點剖析Kubernetes的核心原理和實踐經驗,并分享大規模容器集群管理所面臨的問題和解決思路。 ### 1. Borg在講什么故事 其實,如果這篇論文發表在兩三年前,Borg的關注度恐怕真沒有今天這么高。作為一篇本質上是關于數據中心利用率的半工程、半研究性的成果,這個領域的關注人群來自大廠的運維部以及系統部的技術同僚可能要占很大的比例。而對于絕大多數研究人員,普通開發者,甚至包括平臺開發者而言,Borg論文本身的吸引力應該說都是比較有限的。 不過,一旦我們把Borg放到當前這個時間點上來重新審視,這篇本該平淡的論文就擁有了眾多深層意義。當然,這一切絕非偶然,從2013年末以Docker為代表的容器技術的迅速興起,2014年Google容器管理平臺Kubernetes和Container Engine的強勢擴張,再到如今由Mesos一手打造的DCOS(數據中心操作系統)概念的炙手可熱。容器技術令人咋舌的進化速度很快就將一個曾經并不需要被大多數開發人員關注的問題擺到了臺面: 我們應該如何高效地抽象和管理一個頗具規模的服務集群? 這,正是Borg全力闡述的核心問題。 說得更確切一點,當我們逐步接納了以容器為單位部署和運行應用之后,運維人員終于可以從無休止的包管理,莫名其妙的環境差異,繁雜重復的批處理和任務作業的中稍微回過一點神來,開始重新審視自己手中的物理資源的組織和調度方式:即我們能不能將容器看作傳統操作系統的進程,把所有的服務器集群抽象成為統一的CPU、內存、磁盤和網絡資源,然后按需分配給任務使用呢? 所以,作為《Docker背后的技術解析》系列文章的特別篇,筆者將和讀者一起從Borg出發,結合它的同源項目Kubernetes中嘗試探索一下這個問題的答案。 ### 2. Borg的核心概念 同大多數PaaS、云平臺類項目宣稱的口號一樣,Borg最基本的出發點還是“希望能讓開發者最大可能地把精力集中在業務開發上”,而不需要關心這些代碼制品的部署細節。不過,另一方面,Borg非常強調如何對一個大規模的服務器集群做出更合理的抽象,使得開發者可以像對待一臺PC一樣方便地管理自己的所有任務。這與Mesos現在主推的觀點是一致的,同時也是Borg同PaaS類項目比如Flynn、Deis、Cloud Foundry等區別開來的一個主要特征:即Borg,以及Kubernetes和Mesos等,**都不是一個面向應用的產物**。 什么叫面向應用? 就是以應用為中心。系統原生為用戶提交的制品提供一系列的上傳、構建、打包、運行、綁定訪問域名等接管運維過程的功能。這類系統一般會區分”應用“和”服務“,并且以平臺自己定義的方式為”應用“(比如Java程序)提供具體的”服務“(比如MySQL服務)。面向應用是PaaS的一個很重要的特點。 另一方面,Borg強調的是**規模**二字。文章通篇多次強調了Google內部跑在Borg上的作業數量、以及被Borg托管的機器數量之龐大。比如我們傳統認知上的“生產級別集群”在文章中基本上屬于Tiny的范疇,而Borg隨便一個Medium的計算單元拿出來都是一家中大型企業數據中心的規模(10K個機器)。這也應證了淘寶畢玄老大曾經說過的:“規模絕對是推動技術發展的最關鍵因素”。 Borg里服務器的劃分如下: Site = 一組數據中心(Cluster), Cluster = 一組計算單元(Cell), Cell = 一組機器。 其中計算單元(Cell)是最常用的集群類別。 ### 2.1 Job,Task 既然Borg不關心“應用”和“服務”的區別,也不以應用為中心,那么它需要接管和運行的作業是什么? 是**Job**。 Borg文章里對Job的定義很簡單,就是多個任務(Task)的集合,而所謂Task就是跑在Linux容器里的應用進程了。這樣看起來Job是不是就等同于Kubernetes里的Pod(容器組)呢? 其實不然。Job映射到Kubernetes中的話,其實等同于用戶提交的“應用”,至于這個應用運行了幾個副本Pod,每個Pod里又運行著哪些容器,用戶并不需要關心。用戶只知道,我們訪問這個服務,應該返回某個結果,就夠了。 舉個例子,因為高可用等原因,用戶常常會在Kubernetes里創建并啟動若干個一模一樣的Pod(這個功能是通過Kubernetes的Replication Controller實現的)。這些一模一樣的Pod“副本”的各項配置和容器內容等都完全相同,他們抽象成一個邏輯上的概念就是Job。 由于Job是一個邏輯上的概念,Borg實際上負責管理和調度的實體就是Task。用戶的submit、kill、update操作能夠觸發Task狀態機從Pending到Running再到Dead的的轉移,這一點論文里有詳細的圖解。值得一提的是,作者還強調了Task是通過先SIGTERM,一定時間后后再SIGKILL的方式來被殺死的,所以Task在被殺死前有一定時間來進行“清理,保存狀態,結束正在處理的請求并且拒絕新的請求”的工作。 ### 2.2 Alloc Borg中,真正與Pod對應的概念是Alloc。 Alloc的主要功能,就是在一臺機器上“劃”一塊資源出來,然后一組Task就可以運行在這部分資源上。這樣,“超親密”關系的Task就可以被分在同一個Alloc里,比如一個“Tomcat應用”和它的“logstash服務”。 Kubernetes中Pod的設計與Alloc如出一轍:屬于同一個Pod的Docker容器共享Network Namepace和volume,這些容器使用localhost來進行通信,可以共享文件,任何時候都會被當作一個整體來進行調度。 所以,Alloc和Pod的設計其實都是在遵循“一個容器一個進程”的模型。經常有人問,我該如何在Docker容器里跑多個進程?其實,這種需求最好是通過類似Pod這種方法來解決:每個進程都跑在一個單獨的容器里,然后這些容器又同屬于一個Pod,共享網絡和指定的volume。這樣既能滿足這些進程之間的緊密協作(比如通過localhost互相訪問,直接進行文件交換),又能保證每個進程不會擠占其他進程的資源,它們還能作為一個整體進行管理和調度。如果沒有Kubernetes的話,Pod可以使用“Docker in Docker”的辦法來模擬,即使用一個Docker容器作為Pod,真正需要運行的進程作為Docker容器嵌套運行在這個Pod容器中,這樣它們之間互不干涉,又能作為整體進調度。 另外,Kubernetes實際上沒有Job這個說法,而是直接以Pod和Task來抽象用戶的任務,然后使用相同的**Label**來標記同質的Pod副本。這很大程度是因為在Borg中Job Task Alloc的做法里,會出現“交叉”的情況,比如屬于不同Job的Task可能會因為“超親密”關系被劃分到同一個Alloc中,盡管此時Job只是個邏輯概念,這還是會給系統的管理帶來很多不方便。 ### 2.3 Job的分類 Borg中的Job按照其運行特性劃分為兩類:LRS(Long Running Service)和batch jobs。 上述兩種劃分在傳統的PaaS中也很常見。LRS類服務就像一個“死循環”,比如一個Web服務。它往往需要服務于用戶或者其它組件,故對延時敏感。當然論文里Google舉的LRS例子就要高大上不少,比如Gmail、Google Docs。 而batch jobs類任務最典型的就是Map-Reduce的job,或者其它類似的計算任務。它們的執行往往需要持續一段時間,但是最終都會停止,用戶需要搜集并匯總這些job計算得到的結果或者是job出錯的原因。所以Borg在Google內部起到了YARN和Mesos的角色,很多項目通過在Borg之上構建framework來提交并執行任務。Borg里面還指出,batch job對服務器瞬時的性能波動是不敏感的,因為它不會像LRS一樣需要立刻響應用戶的請求,這一點可以理解。 比較有意思的是,Borg中大多數LRS都會被賦予高優先級并劃分為生產環境級別的任務(prod),而batch job則會被賦予低優先級(non-prod)。在實際環境中,prod任務會被分配和占用大部分的CPU和內存資源。正是由于有了這樣的劃分,Borg的“資源搶占”模型才得以實現,即prod任務可以占用non-prod任務的資源,這一點我們后面會專門說明。 對比Kubernetes,我們可以發現在LRS上定義上是與Borg類似的,但是目前Kubernetes卻不能支持batch job:因為對應的Job Controller還沒有實現。這意味著當前Kubernetes上一個容器中的任務執行完成退出后,會被Replication Controller無條件重啟。Kubernetes尚不能按照用戶的需求去搜集和匯總這些任務執行的結果。 ### 2.4 優先級和配額 前面已經提到了Borg任務**優先級**的存在,這里詳細介紹一下優先級的劃分。 Borg中把優先級分類為監控級、生產級、批任務級、盡力級(也叫測試級)。其中監控級和生產級的任務就是前面所說的prod任務。為了避免在搶占資源的過程中出現級聯的情況觸發連鎖反應(A搶占B,B搶占C,C再搶占D),Borg規定**prod任務不能互相搶占**。 如果說優先級決定了當前集群里的任務的重要性,**配額**則決定了任務是否被允許運行在這個集群上。 盡管我們都知道,對于容器來說,CGroup中的配額只是一個限制而并非真正割據的資源量,但是我們必須為集群設定一個標準來保證提交來任務不會向集群索要過分多的資源。Borg中配額的描述方法是:該用戶的任務在一段時間內在某一個計算單元上允許請求的最大資源量。需要再次重申,配額一定是任務提交時就需要驗證的,它是任務合法性的一部分。 既然是配額,就存在超賣的情況。在Borg中,允許被超賣的是non-prod的任務,即它們在某個計算單元上請求的資源可能超出了允許的額度,但是在允許超賣的情況下它們仍然有可能被系統接受(雖然很可能由于資源不足而暫時進入Pending狀態)。而優先級最高的任務則被Borg認為是享有無限配額的。 與Kubernetes類似的是,Borg的配額也是管理員靜態分配的。Kubernetes通過用戶空間(namespace)來實現了一個簡單的多租戶模型,然后為每一個用戶空間指定一定的配額,比如: ~~~ apiVersion: v1beta3 kind: ResourceQuota metadata: name: quota spec: hard: cpu: "20" memory: 10Gi pods: "10" replicationcontrollers: "20" resourcequotas: "1" services: "5" ~~~ 到這里,我們有必要多說一句。像Borg、Kubernetes以及Mesos這類項目,它們把系統中所有需要對象都抽象成了一種“資源”保存在各自的分布式鍵值存儲中,而管理員則使用如上所示的“資源描述文件”來進行這些對象的創建和更新。這樣,整個系統的運行都是圍繞著“資源”的增刪改查來完成的,各組件的主循環遵循著“檢查對象”、“對象變化”、“觸發事件”、“處理事件”這樣的周期來完成用戶的請求。這樣的系統有著一個明顯的特點就是它們一般都沒有引入一個消息系統來進行事件流的協作,而是使用“ectd”或者“Zookeeper”作為事件系統的核心部分。 2.5 名字服務和監控 與Mesos等不同,Borg中使用的是自家的一致性存儲項目Chubby來作為分布式協調組件。這其中存儲的一個重要內容就是為每一個Task保存了一個DNS名字,這樣當Task的信息發生變化時,變更能夠通過Chubby及時更新到Task的負載均衡器。這同Kubernetes通過Watch監視etcd中Pod的信息變化來更新服務代理的原理是一樣的,但是由于使用了名為“Service”的服務代理機制(Service可以理解為能夠自動更新的負載均衡組件),Kubernetes中默認并沒有內置名字服務來進行容器間通信(但是提供了插件式的DNS服務供管理員選用)。 在監控方面,Borg中的所有任務都設置了一個健康檢查URL,一旦Borg定期訪問某個Task的URL時發現返回不符合預期,這個Task就會被重啟。這個過程同Kubernetes在Pod中設置health_check是一樣的,比如下面這個例子: ~~~ apiVersion: v1beta3 kind: Pod metadata: name: pod-with-healthcheck spec: containers: - name: nginx image: nginx # defines the health checking livenessProbe: # an http probe httpGet: path: /_status/healthz port: 80 # length of time to wait for a pod to initialize # after pod startup, before applying health checking initialDelaySeconds: 30 timeoutSeconds: 1 ports: - containerPort: 80 ~~~ 這種做法的一個小缺點是Task中服務的開發者需要自己定義好這些/healthzURL和對應的響應邏輯。當然,另一種做法是可以在容器里內置一些“探針”來完成很多健康檢查工作而做到對用戶的開發過程透明。 除了健康檢查,Borg對日志的處理也很值得借鑒。Borg中Task的日志會在Task退出后保留一段時間,方便用戶進行調試。相比之下目前大多數PaaS或者類似項目的容器退出后日志都會立即被刪除(除非用戶專門做了日志存儲服務)。 最后,Borg輕描淡寫地帶過了保存event做審計的功能。這其實與Kubernetes的event功能也很類似,比如Kube的一條event的格式類似于: ~~~ 發生時間 結束時間 重復次數 資源名稱 資源類型 子事件 發起原因 發起者 事件日志 ~~~ 3. Borg的架構與設計 Borg的架構與Kubernetes的相似度很高,在每一個Cell(工作單元)里,運行著少量Master節點和大量Worker節點。其中,Borgmaster負責響應用戶請求以及所有資源對象的調度管理;而每個工作節點上運行著一個稱為Borglet的Agent,用來處理來自Master的指令。這樣的設計與Kubernetes是一致的,Kubernetes這兩種節點上的工作進程分別是: ~~~ Master: apiserver, controller-manager, scheduler Minion: kube-proxy, kubelet ~~~ 雖然我們不清楚Borg運行著的工作進程有哪些,但單從功能描述里面我們不難推測到至少在Master節點上兩者的工作進程應該是類似的。不過,如果深入到論文中的細節的話,我們會發現Borg在Master節點上的工作要比Kubernetes完善很多。 ### 3.1 Borgmaster 首先,Borgmaster由一個獨立的scheduler和主Borgmaster進程組成。其中,主進程負責響應來自客戶端的RPC請求,并且將這些請求分為“變更類”和“只讀”類。 在這一點上Kubernetes的apiserver處理方法類似,kuber的API服務被分為“讀寫”(GET,POST,PUT,DELETE)和“只讀”(GET)兩種,分別由6443和7080兩個不同的端口負責響應,并且要求“讀寫”端口6443只能以HTTPS方式進行訪問。同樣,Kubernetes的scheduler也是一個單獨的進程。 但是,相比Kubernetes的單點Master,Borgmaster是一個由五個副本組成的集群。每一個副本都在內存中都保存了整個Cell的工作狀態,并且使用基于Paxos的Chubby項目來保存這些信息和保證信息的一致性。Borgmaster中的Leader是也是集群創建的時候由Paxos選舉出來的,一旦這個Leader失敗,Chubby將開始新一輪的選舉。論文中指出,這個重選舉到恢復正常的過程一般耗時10s,但是在比較大的Cell里的集群會由于數據量龐大而延長到一分鐘。 更有意思的是,Borgmaster還將某一時刻的狀態通過定時做快照的方式保存成了checkpoint文件,以便管理員回滾Borgmaster的狀態,從而進行調試或者其他的分析工作。基于上述機制,Borg還設計了一個稱為Fauxmaster的組件來加載checkpoint文件,從而直接進入某時刻Borgmaster的歷史狀態。再加上Fauxmaster本身為kubelet的接口實現了“樁”,所以管理員就可以向這個Fauxmaster發送請求來模擬該歷史狀態數據下Borgmaster的工作情況,重現當時線上的系統狀況。這個對于系統調試來說真的是非常有用。此外,上述Fauxmaster還可以用來做容量規劃,測試Borg系統本身的變更等等。這個Fauxmaster也是論文中第一處另我們眼前一亮的地方。 上述些特性使得Borg在Master節點的企業級特性上明顯比Kubernetes要成熟得多。當然,值得期待的是Kube的高可用版本的Master也已經進入了最后階段,應該很快就能發布了。 ### 3.2 Borg的調度機制 用戶給Borg新提交的任務會被保存在基于Paxos的一致性存儲中并加入到等待隊列。Borg的scheduler會異步地掃描這個隊列中的任務,并檢查當前正在被掃描的這個任務是否可以運行在某臺機器上。上述掃描的順序按照任務優先級從高到低來Round-Robin,這樣能夠保證高優先級任務的可滿足性,避免“線頭阻塞”的發生(某個任務一直不能完成調度導致它后面的所有任務都必須進行等待)。每掃描到一個任務,Borg即使用調度算法來考察當前Cell中的所有機器,最終選擇一個合適的節點來運行這個任務。 此算法分兩階段: 第一,可行性檢查。這個檢查每個機器是所有符合任務資源需求和其它約束(比如指定的磁盤類型),所以得到的結果一般是個機器列表。需要注意的是在可行性檢查中,一臺機器“資源是否夠用”會考慮到搶占的情況,這一點我們后面會詳細介紹。 第二,打分。這個過程從上述可行的機器列表中通過打分選擇出分數最高的一個。 這里重點看**打分過程**。Borg設計的打分標準有如下幾種: 1. 盡量避免發生低優先級任務的資源被搶占;如果避免不了,則讓被搶占的任務數量最少、優先級最低; 1. 挑選已經安裝了任務運行所需依賴的機器; 1. 使任務盡量分布在不同的高可用域當中; 1. 混合部署高優先級和低優先級任務,這樣在流量峰值突然出現后,高優先級可以搶占低優先級的資源(這一點很有意思)。 Borg其實曾經使用過E-PVM模型(簡單的說就是把所有打分規則按照一定算法綜合成一種規則)來進行打分的。但是這種調度的結果是任務最終被平均的分散到了所有機器上,并且每臺機器上留出了一定的空閑空間來應對壓力峰值。這直接造成了整個集群資源的碎片化。 與上述做法的相反的是另一個極端,即盡量讓所有的機器都填滿。但是這將導致任務不能很好的應對突發峰值。而且Borg或者用戶對于任務所需的資源配額的估計往往不是很準確,尤其是對于batch job來說,它們所請求的資源量默認是很少的(特別是CPU資源)。所以在這種調度策略下batch job會很容易被填充在狹小的資源縫隙中,這時一旦遇到壓力峰值,不僅batch job會出問題,與它運行在同一臺機器上的LRS也會遭殃。 而Borg采用的是“混部加搶占”的模式,這種做法集成了上述兩種模型的優點:兼顧公平性和利用率。這其中,LRS和batch job的混部以及優先級體系的存在為資源搶占提供了基礎。這樣,Borg在“可行性檢查”階段就可以考慮已經在此機器上運行的任務的資源能被搶占多少。如果算上可以搶占的這部分資源后此機器可以滿足待調度任務的需求的話,任務就會被認為“可行”。接下,Borg會按優先級低到高“kill”這臺機器上的任務直到滿足待運行任務的需求,這就是搶占的具體實施過程。當然,被“kill”的任務會重新進入了調度隊列,等待重新調度。 另一方面Borg也指出在任務調度并啟動的過程中,安裝依賴包的過程會構成80%的啟動延時,所以調度器會優先選擇已經安裝好了這些依賴的機器。這讓我想起來以前使用VMware開發的編排系統BOSH時,它的每一個Job都會通過spec描述自己依賴哪些包,比如GCC。所以當時為了節省時間,我們會在部署開始前使用腳本并發地在所有目標機器上安裝好通用的依賴,比如Ruby、GCC這些,然后才開始真正的部署過程。 事實上,Borg也有一個類似的包分發的過程,而且使用的是類似BitTorrent的協議。 這時我們回到Kubernetes上來,不難發現它與Borg的調度機制還比較很類似的。這當然也就意味著Kubernetes中沒有借鑒傳說中的Omega共享狀態調度(反倒是Mesos的Roadmap里出現了類似”樂觀并發控制“的概念)。 Kubernetes的調度算法也分為兩個階段: - “Predicates過程”:篩選出合格的Minion,類似Borg的“可行性檢查”。這一階段Kubernetes主要需要考察一個Minion的條件包括: - 容器申請的主機端口是否可用 - 其資源是否滿足Pod里所有容器的需求(僅考慮CPU和Memory,且沒有搶占機制) - volume是否沖突 - 是否匹配用戶指定的Label - 是不是指定的hostname “Priorities過程”:對通過上述篩選的Minon打分,這個打分的標準目前很簡單: - 選擇資源空閑更多的機器 - 屬于同一個任務的副本Pod盡量分布在不同機器上 從調度算法實現上差異中,我們可以看到Kubernetes與Borg的定位有著明顯的不同。Borg的調度算法中資源搶占和任務混部是兩個關鍵點,這應是考慮到了這些策略在Google龐大的機器規模上所能帶來的巨大的成本削減。所以Borg在算法的設計上強調了混部狀態下對資源分配和任務分布的優化。而Kubernetes明顯想把調度過程盡量簡化,其兩個階段的調度依據都采用了簡單粗暴的硬性資源標準,而沒有支持任何搶占策略,也沒有優先級的說法。當然,有一部分原因是開源項目的用戶一般都喜歡定制自己的調度算法,從這一點上來說確實是“less is more”。總之,最終的結果是盡管保留了Borg的影子(畢竟作者很多都是一伙人),Kubernetes調度器的實現上卻完全是另外一條道路,確切的說更像Swarm這種偏向開發者的編排項目。 此外,還有一個非常重要的因素不得不提,那就是Docker的鏡像機制。Borg在Google服役期間所使用的Linux容器雖然應用極廣且規模龐大,但核心功能還是LXC的變體或者強化版,強調的是隔離功能。這一點從它的開源版項目lmctfy的實現,以及論文里提到需要考慮任務依賴包等細節上我們都可以推斷出來。可是Docker的厲害之處就在于直接封裝了整個Job的運行環境,這使得Kubernetes在調度時可以不必考慮依賴包的分布情況,并且可以使用Pod這樣的“原子容器組”而不是單個容器作為調度單位。當然,這也提示了我們將來進行Docker容器調度時,其實也可以把鏡像的分布考慮在內:比如事先在所有工作節點上傳基礎鏡像;在打分階段優先選擇任務所需基礎鏡像更完備的節點。 如果讀者想感受一下沒有鏡像的Docker容器是什么手感,不妨去試用一下DockerCon上剛剛官宣的runc項目(https://github.com/opencontainers/runc)。runc完全是一個libcontainer的直接封裝,提供所有的Docker容器必備功能,但是沒有鏡像的概念(即用戶需要自己指定rootfs環境),這十分貼近lmctfy等僅專注于隔離環境的容器項目。 ### 3.3 Borglet 離開了Borgmaster節點,我們接下來看一下工作節點上的Borglet組件,它的主要工作包括: 啟停容器,進行容器失敗恢復,通過kernel參數操作和管理OS資源,清理系統日志,收集機器狀態供Borgmaster及其他監控方使用。 這個過程中,Borgmaster會通過定期輪詢來檢查機器的狀態。這種主動poll的做法好處是能夠大量Borglet主動匯報狀態造成流量擁塞,并且能防止“恢復風暴”(比如大量失敗后恢復過來的機器會在同段一時間不停地向Borgmaster發送大量的恢復數據和請求,如果沒有合理的擁塞控制手段,者很可能會阻塞整個網絡或者直接把master拖垮掉)。一旦收到匯報信息后,充當leader的Borgmaster會根據這些信息更新自己持有的Cell狀態數據。 這個過程里,集群Borgmaster的“優越性”再次得到了體現。Borgmaster的每個節點維護了一份無狀態的“鏈接分片(link shard)”。每個分片只負責一部分Borglet機器的狀態檢查,而不是整個Cell。而且這些分片還能夠匯集并diif這些狀態信息,最后只讓leader獲知并更新那些發生了變化的數據。這種做法有效地降低了Borgmaster的工作負載。 當然,如果一個Borglet在幾個poll周期內都沒有回應,他就會被認為宕機了。原本運行在整個節點上的任務容器會進入重調度周期。如果此期間Borglet與master的通信恢復了,那么master會請求殺死那些被重調度的任務容器,以防重復。Borglet的運行并不需要依賴于Borgmaster,及時master全部宕機,任務依然可以正常運行。 與Borg相比,Kubernetes則選擇了方向相反的狀態匯報策略。當一個kubelet進程啟動后,它會主動將自己注冊給master節點上的apiserver。接下來,kubelet會定期向apiserver更新自己對應的node的信息,如果一段時間內沒有更新,則master就會認為此工作節點已經發生故障。上述匯報信息的收集主要依賴于每個節點上運行的CAdvisor進程,而并非直接與操作系統進行交互。 事實上,不止kubelet進程會這么做。Kubernetes里的所有組件協作,都會采用主動去跟apiServer建立聯系,進而通過apiserver來監視、操作etcd的資源來完成相應的功能。 舉個例子,用戶向apiserver發起請求表示要創建一個Pod,在調度器選擇好了某個可用的minion后apiserver并不會直接告訴kubelet說我要在這個機器上創建容器,而是會間接在etcd中創建一個“boundPod”對象(這個對象的意思是我要在某個kubelet機器上綁定并運行某個Pod)。與此同時,kubelet則定時地主動檢查有沒有跟自己有關的“boundPod”,一旦發現有,它就會按照這個對象保存的信息向Docker Daemon發起創建容器的請求。 這正是Kubernetes設計中“一切皆資源”的體現,即所有實體對象,消息等都是作為etcd里保存起來的一種資源來對待,其他所有協作者要么通過監視這些資源的變化來采取動作,要么就是通過apiserver來對這些資源進行增刪改查。 所以,我們可以把Kubernetes的實現方法描述為“面向etcd的編程模式”。這也是Kubernetes與Borg設計上的又一個不同點,說到底還是規模存在的差異:即Kubernetes認為它管理的集群中不會存在那么多機器同時向apiserver發起大量的請求。這也從另一個方面表現出了作者們對etcd響應能力還是比較有信心的。 ### 3.4 可擴展性 這一節里與其說在Borg的可擴展性,倒不如說在講它如何通過各種優化實現了更高的可擴展性。 首先是對Borgmaster的改進。最初的Borgmaster就是一個同步循環,在循環過程中順序進行用戶請求響應、調度、同Borglet交互等動作。所以Borg的第一個改進就是將調度器獨立出來,從而能夠同其他動作并行執行。改進后的調度器使用Cell集群狀態的緩存數據來不斷重復以下操作: - 從Borgmaster接受集群的狀態變化 - 更新本地的集群狀態緩存數據 - 對指定的Task執行調度工作 - 將調度結果告訴Borgmaster 這些操作組成了調度器的完整工作周期。 其次,Borgmaster上負責響應只讀請求和同Borglet進行交互的進程也被獨立出來,通過職責的單一性來保證各自的執行效率。這些進程會被分配在Borgmaster的不同副本節點上來進一步提高效率(只負責同本副本節點所管理的那部分Worker節點進行交互)。 最后是專門針對調度器的優化。 緩存機器的打分結果。畢竟每次調度都給所有機器重新打一次分確實很無聊。只有當機器信息或者Task發生了變化(比如任務被從這個機器上調度走了)時,調度器緩存的機器分數才會發生更新。而且,Borg會忽略那些不太明顯的資源變化,減少緩存的更新次數。 劃分Task等價類。Borg的調度算法針對的是一組需求和約束都一樣的Task(等價類)而不是單個Task來執行的。 隨機選擇一組機器來做調度。這是很有意思的一種做法,即Borg調度器并不會把Cell里的所有機器拿過來挨個進行可行性檢查,而是不斷地隨機挑選一個機器來檢查可行性,判斷是否通過,再挑選下一個,直到通過篩選的機器達到一定的數目。然后再在這些通過篩選的機器集合里進行打分過程。這個策略與著名的Sparrow調度器的做法很類似。 這些優化方法大大提高了Borg的工作效率,作者在論文中指出在上述功能被禁掉,有些原來幾百秒完成的調度工作需要幾天才能完全完成。 ### 4. 可用性 Borg在提高可用性方面所做的努力與大多數分布式系統的做法相同。比如: - 自動重調度失敗的任務 - 將同一Job的不同任務分布在不同的高可用域 - 在機器或者操作系統升級的過程中限制允許的任務中斷的次數和同時中斷的任務數量 - 保證操作的冪等性,這樣當客戶端失敗時它可以放心的發起重試操作 - 當一臺機器失聯后,任務重調度的速度會被加以限制,因為Borg不能確定失聯的原因是大規模的機器失敗(比如斷電),還是部分網絡錯誤。 - 任務失敗后,在一段時間內在本地磁盤保留日志及其他關鍵數據,哪怕對應的任務已經被殺死或者調度到其他地方了 最后也是最重要的,Borglet的運行不依賴于master,所以哪怕控制節點全部宕機,用戶提交的任務依然正常運行。 在這一部分,Kubernetes也沒有特別的設計。畢竟,在任務都已經容器化的情況下,只要正確地處理好容器的調度和管理工作,任務級別高可用的達成并不算十分困難。 至此,論文的前四章我們就介紹完了。通過與Kubernetes的實現作比較,我們似乎能得到一個“貌合神離”的結論。即Kubernetes與Borg從表面上看非常相似:相同的架構,相似的調度算法,當然還有同一伙開發人員。但是一旦我們去深入一些細節就會發現,在某些重要的設計和實現上,Borg似乎有著和Kubernetes截然不同的認識:比如完全相反的資源匯報方向,復雜度根本不在一個水平上的Master實現(集群VS單點),對batch job的支持(Kubernetes目前不支持batch job),對于任務優先級和資源搶占的看法等等。 這些本來可以照搬的東西,為什么在Kubernetes又被重新設計了一遍呢?在本文的第二部分,我們將一步步帶領讀者領悟造成這些差異的原因,即:資源回收和利用率優化。敬請關注。 ### 作者簡介 **張磊**,浙江大學博士,科研人員, VLIS lab云計算團隊技術負責人、策劃人 ### 參考文獻 - http://research.google.com/pubs/pub43438.html - https://github.com/googlecloudplatform/kubernetes 查看原文:[Docker背后的容器集群管理——從Borg到Kubernetes(一)](http://www.infoq.com/cn/articles/docker-container-cluster-management-part-01) # 解析微服務架構(二)微服務架構綜述 作者 王磊 在[解析微服務架構(一) 單塊架構系統以及其面臨的挑戰](http://www.infoq.com/cn/articles/analysis-the-architecture-of-microservice-part-01)中,我們談到了隨著市場的快速發展,業務的不斷擴大,單塊架構應用面臨著越來越多的挑戰,其改造與重構勢在必行。 ### 微服務的誕生 微服務架構(Microservice Architect)是一種架構模式,它提倡將單塊架構的應用劃分成一組小的服務,服務之間互相協調、互相配合,為用戶提供最終價值。每個服務運行在其獨立的進程中,服務與服務間采用輕量級的通信機制互相溝通。每個服務都圍繞著具體業務進行構建,并且能夠被獨立的部署到生產環境、類生產環境等。 微服務架構雖然誕生的時間并不長,但其在各種演講、文章、書籍上所出現的頻率已經讓很多人意識到它對軟件架構領域所帶來的影響。 ### 背景 其實,微服務的誕生并非偶然。它是互聯網高速發展,敏捷、精益、持續交付方法論的深入人心,虛擬化技術與DevOps文化的快速發展以及傳統單塊架構無法適應快速變化等多重因素的推動下所誕生的產物: ![](https://box.kancloud.cn/2015-08-19_55d41b1d787dd.png) #### 1. 互聯網行業的快速發展 過去的十年中,互聯網對我們的生活產生了翻天覆地的變化。購物、打車、訂餐、支付,甚至美甲、洗車等,想到的,想不到的活動都可以通過互聯網完成,越來越多的傳統行業公司也開始依賴互聯網技術打造其核心競爭優勢。互聯網時代的產品通常有兩類特點:需求變化快和用戶群體龐大。在這種情況下,如何從系統架構的角度出發,構建靈活、易擴展的系統,快速應對需求的變化;同時,隨著用戶量的增加,如何保證系統的可伸縮性、高可用性,成為系統架構面臨的挑戰。 #### 2. 敏捷、精益方法論的深入人心 縱觀IT行業過去的十年,敏捷、精益、持續交付等價值觀、方法論的提出以及實踐,讓很多組織意識到應變市場變化、提高響應力的重要性。精益創業(Lean Startup)幫助組織分析并建立最小可實行產品(Minimum Viable Product),通過迭代持續改進;敏捷方法幫助組織消除浪費,通過反饋不斷找到正確的方向;持續交付幫助組織構建更快、更可靠、可頻繁發布的交付機制。經過這些方法論以及實踐的推行和嘗試后,從宏觀上而言,大部分組織已經基本上形成了一套可遵循、可參考、可實施的交付體系。這時候,逐漸完善并改進各個細節的需求就會更加強烈。所謂細節,就是類似如何找到靈活性高、擴展性好的架構方式、如何用更有效的技術、工具解決業務問題等。 #### 3. 虛擬化技術與DevOps文化的快速發展 虛擬化技術和基礎設施自動化(Infrastructure As Code)的快速發展極大的簡化了基礎設施的創建、配置以及系統的安裝和部署。譬如云平臺的成熟以及像[Chef](https://www.chef.io/)、[Puppet](https://puppetlabs.com/)、[Ansible](http://www.ansible.com/)等工具的使用,讓更多的基礎設施能夠通過自動化的方式動態創建。同時,容器化技術的發展以及[Docker](https://www.docker.com/)的出現,更是將虛擬化技術推向了一個史無前例的高潮。另外,DevOps文化的推行打破了傳統開發與運維之間的壁壘,幫助組織形成更高效的、開發與運維高度協作的交付團隊。這些技術與文化的快速發展,極大程度上解決了傳統環境創建難、配置難以及‘最后一公里’的部署難、交付難等問題,成為推動微服務誕生、發展的重要因素之一。 #### 4. 單塊架構系統面臨的挑戰 幾年前我們熟悉的傳統IT系統,也可以稱之為單塊架構系統,是以技術分層,譬如邏輯層、數據層等。但隨著用戶需求個性化、產品生命周期變短、市場需求不穩定等因素的出現,單塊架構系統面臨著越來越多的挑戰。因此,如何找到一種更有效的、更靈活、更適應當前互聯網時代需求的系統架構方式,成為大家關注的焦點。 所以說,微服務的誕生決不是偶然,是多重因素推動下的必然產物。 ### 微服務與SOA ### SOA簡述 早在1996年,Gartner就提出面向服務架構(SOA)。SOA闡述了“對于復雜的企業IT系統,應按照不同的、可重用的粒度劃分,將功能相關的一組功能提供者組織在一起為消費者提供服務”,其目的是為了解決企業內部不同IT資源之間無法互聯而導致的信息孤島問題。 2002年,SOA被稱作"現代應用開發領域最重要的課題之一",其正在幫助企業從資源利用的角度出發,將IT資源整合成可操作的、基于標準的服務,使其能被重新組合和應用。 但是,由于SOA本身的廣義性以及抽象性,在其誕生的相當長一段時間內,人們對SOA存在著不同的認知和理解。 ![](https://box.kancloud.cn/2015-08-19_55d41b1d96e62.png) 直到2000年左右,[ESB(Enterprise Service Bus)](https://en.wikipedia.org/wiki/Enterprise_service_bus)、[WebService](https://en.wikipedia.org/wiki/Web_service)、[SOAP](https://en.wikipedia.org/wiki/Soap)等這類技術的出現,才使得SOA漸漸落地。同時,更多的廠商像IBM、Oracle等也分別提出基于SOA的解決方案或者產品。 ### 微服務與SOA 實際上,微服務架構并不是一個全新的概念。仔細分析SOA的概念,就會發現,其和我們今天所談到的微服務思想幾乎一致。那在SOA誕生這么多年后,為什么又提出了微服務架構呢? 鑒于過去十幾年互聯網行業的高速發展,以及敏捷、持續集成、持續交付、DevOps,云技術等的深入人心,服務架構的開發、測試、部署以及監控等,相比我們提到的傳統的SOA實現,已經大相徑庭,主要區別如下表所示: | SOA實現 | 微服務架構實現 | |-----|-----| | 企業級,自頂向下開展實施 | 團隊級,自底向上開展實施 | | 服務由多個子系統組成,粒度大 | 一個系統被拆分成多個服務,粒度細 | | 企業服務總線,集中式的服務架構 | 無集中式總線,松散的服務架構 | | 集成方式復雜(ESB/WS/SOAP) | 集成方式簡單(HTTP/REST/JSON) | | 單塊架構系統,相互依賴,部署復雜 | 服務都能獨立部署 | 相比傳統SOA的服務實現方式,微服務更具有靈活性、可實施性以及可擴展性,其強調的是一種獨立測試、獨立部署、獨立運行的軟件架構模式。 ![](https://box.kancloud.cn/2015-08-19_55d41b1db6161.png) ### 微服務架構的定義 其實,即便了解了上面的介紹,也很難對微服務下一個準確的定義。就像NoSQL,我們談論了好幾年的NoSQL,知道NoSQL代表著什么樣的含義,也可以根據不同的應用場景選擇不同的NoSQL數據庫,但是我們還是很難對它下一個準確的定義。類似的,關于什么是‘函數式編程’,也或多或少存在同樣的窘境。我們可以輕松的選擇不同的函數式編程語言,可以輕松的寫出函數式編程風格的代碼,但很難對什么是函數式編程下一個準確的定義。 實際上,從業界的討論來看,微服務本身并沒有一個嚴格的定義。不過,ThoughtWorks的首席科學家,馬丁 -福勒先生對微服務的這段描述,似乎更加具體、貼切,通俗易懂: > Microservice > The microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies. > 微服務架構 > 微服務架構是一種架構模式,它提倡將單一應用程序劃分成一組小的服務,服務之間互相協調、互相配合,為用戶提供最終價值。每個服務運行在其獨立的進程中,服務與服務間采用輕量級的通信機制互相溝通(通常是基于HTTP協議的RESTful API)。每個服務都圍繞著具體業務進行構建,并且能夠被獨立的部署到生產環境、類生產環境等。另外,應當盡量避免統一的、集中式的服務管理機制,對具體的一個服務而言,應根據業務上下文,選擇合適的語言、工具對其進行構建。 總結下來,微服務架構中的核心部分包括以下幾點: - 小, 且專注于做?件事情 - 獨立的進程中 - 輕量級的通信機制 - 松耦合、獨立部署 ### 總結 隨著市場的快速發展,業務的不斷擴大,單塊架構應用面臨著越來越多的挑戰,其改造與重構勢在必行。而微服務架構的誕生,是互聯網高速發展,虛擬化技術應用以及持續交付、DevOps深入人心的綜合產物。隨著用戶需求個性化、產品生命周期變短,微服務架構是未來軟件軟件架構朝著靈活性、擴展性、伸縮性以及高可用性發展的必然方向。同時,以Docker為代表的容器虛擬化技術的盛行,將大大降低微服務實施的成本,為微服務落地以及大規模使用提供了堅實的基礎和保障。 ### 作者簡介 王磊,ThoughtWorks公司首席咨詢師。開源軟件的愛好者和貢獻者,社區活動的參與者,Practical RubyGems的譯者, [GDCR](http://gdcr.coderetreat.org/)西安的組織者。于2012年加入ThoughtWorks,為國內外諸多客戶提供項目交付和咨詢服務;在加入ThoughtWorks之前,曾就職過多家知名外企,具有豐富的敏捷項目實戰經驗。目前致力于微服務架構、虛擬化容器、持續交付、以及DevOps的研究與實踐。 ### 參考文獻 http://microservices.io/ htto://martinfowler.com/articles/microservices.html 查看原文:[解析微服務架構(二)微服務架構綜述](http://www.infoq.com/cn/articles/analysis-the-architecture-of-microservice-part-02) # 多范式編程語言-以Swift為例 作者 郭麟 ### Swift的編程范式 編程范式是程序語言背后的思想。代表了程序語言的設計者認為程序應該如何被構建和執行。常見的編程范式有:過程式、面向對象、函數式、泛型編程等。 一些編程語言是專門為某種特定范式設計的,例如,C語言是過程式編程語言;Smalltalk和Java是較純粹的面向對象編程語言;Haskell、Scheme、Clojure是函數式編程語言。 另外一些編程語言和編程范式的關系并不一一對應,如Python、Scala、Groovy同時支持面向對象和一定程度上的函數式編程。Swift 也是支持多種編程范式的編程語言。 由于代表了語言背后的思想,編程范式很大程度上決定了語言會呈現為何種面貌。用不著深入學習,僅僅瀏覽代碼,就能發現Scala和Swift很類似,這是因為它們支持的編程范式是類似的;Scheme和Swift看起來就相差很遠,這是因為它們支持的編程范式很不一樣。對于理解一門編程語言而言,相對于語言的語法和編寫經驗,理解語言的編程范式更重要。因為,就像看一本書,琢磨作者如何用詞,如何構建章節是很重要,但更重要的是理解書所要表達的思想。 Swift即支持面向對象編程范式,也支持函數式編程范式,同時還支持泛型編程。Swift支持多種編程范式是由它的目標決定的。Swift創造的初衷就是提供一門實用的工業語言。不同于Haskell這類出自大學和研究機構的具有學術性質的編程語言。蘋果推出Swift時就帶著著明確的商業目的:Mac OS和iOS系統的主要編程語言 Objective-C已顯老態,Swift將使得蘋果系統的開發者擁有一門更現代的編程語言,從而促進蘋果整個生態圈的良性發展。 Swift的設計和開發無不體現著“實用的工業語言”這一目標。這決定了 Swift無法做極端的語言實驗,它需要在理智地面對現實的基礎上,謹慎地尋求突破。這就決定了Swift需要繼承歷史遺產,在照顧現在大多數程序員的現實需求基礎上,面向未來有所發展。 ### 面向對象 面向對象編程的核心概念是繼承,多態,和封裝。以對象構建程序的基本單元的面向對象編程語言中,繼承提供了一種復用代碼的方法;多態提供了更高的抽象能力,使得我們可以設計出更通用的程序;封裝提供一種使用代碼更為便捷安全的機制。Swift擁有以上所有的面向對象特性。所以,Swift是一門完備的面向對象編程語言。 Swift繼承了Objective-C面向對象方面的主要特性,提供以類為主的封裝和繼承機制。但給予了結構體(Struct)和枚舉(Enum)更豐富的面向對象特征,使它們可以用于封裝更為復雜的對象。另外,相對于 Objective-C,Swift是一門更為安全的語言。 ### 單繼承,多協議 在繼承上,Swift不同于C++可以繼承一個或者若干個類,而類似于 Objective-C和Java,只能單繼承。但Swift可以實現多個協議(Java 中對應的是接口Interface)。這在一定程度上彌補了沒有多繼承的局限,同時又避免了多繼承難以控制的缺陷。 除了實現協議,Swift還可以實現多個擴展(Extension)。擴展是一種向已有的類,枚舉或者結構體添加新功能的方法。擴展和Objective-C中的分類(Category)類似,但與Objective-C中的分類不同的是,Swift中的擴展沒有名字。 ### 更強大的結構體,枚舉 C++和Java等大部分面向對象編程語言主要以類(Class)作為實現面向對象的基本結構。Swift則賦予了結構體(Struct)和枚舉(Enum)更多的面向對象特征,使結構體和枚舉也能承擔部分數據封裝工作。在其他一些語言需要用類來解決的場景中,Swift可以使用結構體和枚舉類型,而且更為合適。例如,Swift的Array和Dictionary是用結構體實現,而不是用類實現的,這不同于大多數編程語言。 Swift的結構體和枚舉可以像類一樣,完成下列事情: - 定義屬性 - 定義方法 - 擁有構造器 - 可以被擴展(Extension) - 可以遵守協議 (Protocol) 在封裝這一點上,結構體和枚舉幾乎和類完全一致。不同的地方是,結構體和枚舉是不能繼承或者被繼承的。所以,這兩種數據類型也就沒有多態性。 總結一下,Swift中的類和其他面向對象編程語言的類一樣是面向對象語言的核心概念,具有面向對象的基本特征。Swift的結構體和枚舉擁有比其他面向對象編程語言更多的面向對象特性,可以封裝更復雜的對象。但不可繼承,也就沒有了多態性。 ### 更多的值類型,而不是引用類型 結構體,枚舉與類的另外一個區別是:結構體和枚舉是值類型,而類是引用類型。 值類型在賦值和作為函數參數被傳遞時,實際上是在進行復制,操作的是對象的拷貝。Swift中有大量值類型,包括Number,String,Array,Dictionary,Tuple,Struct和Enum等。 引用類型在賦值和作為函數參數被傳遞時,傳遞的是對象的引用,而并不是對象的拷貝。這些引用都指向同一個實例。對這些引用的操作,都將影響同一個實例。 在Swift中區分值類型和引用類型是為了將可變的對象和不可變的數據區分開來。可變的對象,使用引用類型;不可變的數據,使用值類型。值類型的數據,可以保證不會被意外修改。值類型的數據傳遞給函數,函數內部可以自由拷貝,改變值,而不用擔心產生副作用。在多線程環境下,多個線程同時運行,可能會意外錯誤地修改數據,這常常會是一種難以調試的bug。而使用值類型,你可以安全地在線程間傳遞數據,因為值類型傳遞是拷貝,所以無需在線程間同步數據變化。這就可以保證代碼線程環境下的安全性。 結構體是值類型,暗示了結構體應該主要用于封裝數據。例如,三維坐標系中的點Point,代表幾何形狀的大小的Size等。而類是引用類型,意味著類應該用于封裝具有狀態的,可以繼承的對象。例如,人,動物等。 Swift中,Array、Dictionary、String都是值類型,它們的行為就像C語言中的Int一樣。你可以像使用Int一樣簡單安全地使用Array,而不用考慮深度拷貝之類煩人問題。Swift增強了對值類型的支持,鼓勵我們使用值類型。因為值類型更安全。更多地使用值類型,將有助于我們寫出行為更可預測,更安全的代碼。 ### 更安全的語言 #### 類型安全語言 Swift是強類型語言,這意味著Swift禁止錯誤類型的參數繼續運算。例如,你不能讓String和Float相加。這與C#和Java一致;而與C和Javascript這類弱類型語言不一樣。 Swift是靜態類型語言,這意味著Swift中變量是在編譯期進行類型檢查的。編譯時,編譯器會盡力找出包括類型錯誤在內的相關錯誤。例如,String和Int相加這種類型運算錯誤,編譯器在編譯時就能告訴你,而不會在運行時才報錯。這與C#和Java一致;而與Python和Ruby這類動態類型語言不一樣。 Swift不允許不正確的類型運算或類型轉換發生,所以Swift是類型安全的。 Swift支持類型推導,并且有一個相當不錯的類型推導器。大部分情況下,你都不用聲明類型,編譯器可以根據上下文為你推導出變量的類型。 #### 安全的初始化過程 Swift中類(包括結構體和枚舉)的初始化過程類似于Java的設計。Swift 有一類特別的方法,被作為初始化方法,它們沒有func前綴,而是以init為方法名。這不同于Objective-C中的初始化方法只是一個普通的方法。對于初始化方法的特殊處理可以在語言機制上保證初始化方法只被調用一次。這種機制在Objective-C中是不存在的,在 Objective-C中,初始化方法就像其它的普通方法一樣,可以被多次調用。 Swift中初始化方法必須保證所有實例變量都被初始化。Swift初始化方法要求特殊的初始化順序。先保證當前類的實例變量被初始化,再調用父類的初始化方法完成父類實例變量的初始化。 Swift保證了初始化方法只會被調用一次,同時所有的實例變量都會被初始化。這使得Swift初始化過程很安全。 #### 安全的重寫 Swift提供了重寫(Overriding)保護機制。如果要重寫基類的方法,就必須在子類的重寫方法前加上overriding關鍵字。這么做是向編譯器聲明你想提供一個重寫版本。編譯器會確認,基類里是否存在具有相同方法定義的方法。如果,基類中沒有相同的方法定義,編譯器就會報錯。另一方面,如果沒有加上overriding關鍵字的方法和基類的某個方法的定義相同,編譯器也會報錯,以防止意外的重寫行為。這樣就能從兩方面保證重寫行為的正確性。 #### Optionals Swift中的Optionals讓我們能夠更安全地應對有可能存在,也有可能不存在的值。在Objective-C里我們主要依靠文檔來了解一個API是否會返回nil。Optionals則讓我們將這份責任交給了類型系統。如果API的返回值聲明為Optional,就表示它可以是nil。如果它不是Optional,就表示它不可能是nil。 在Swift中,類型后面加問號聲明Optional類型,以及感嘆號!對 Optional類型拆包都只是語法糖。Optionals其實是由枚舉實現的: ~~~ enum Optional<T> : Reflectable, NilLiteralConvertible { case None case Some(T) //... } ~~~ 也就是說,Optional其實是一種枚舉類型。我們通過語言的類型系統來明確可能為nil的情況。這比Objective-C中使用文檔來說明要安全得多。 ### 面向對象編程總結 現在絕大部分程序員的工作語言仍然是面向對象編程語言。大部分流行的現代編程語言都會允許你創建對象。面向對象編程語言易于建模。因為,對象和類似乎很容易和現實世界中的事物和概念對應。但編程實踐表明,任何東西都成為對象并不是一件好事情。舉一個Java中的蹩腳例子:Java中只有對象才能作為參數傳入函數(當然還有原始類型Primitive Type),所以為了將函數作為參數傳遞給另一個函數,需要將函數包裹在一個對象中,通常會使用一個匿名類(這也是Java中,監聽器 Listener通常的實現方法),而這個類不會有其他作用,只是為了滿足 Java一切皆為對象的設計,從而通過編譯。 Java擁有純粹的面向對象概念。它從設計之初,就希望以一切皆為對象的純對象模型來為世界建模。但發展到現在,Java中加入了越來越多非對象的東西。引入了閉包,從而獲得了函數式編程中的一級函數;引入泛型,從而獲得了參數化的類型。這可能暗示了,這個世界是如此豐富多彩,使用單一模型為世界建模并不會成功。 Swift在追求統一純粹的編程范式這一點上并不固執。Swift完整地支持面向對象編程,擁有完備的面向對象基礎概念。這使得熟悉面向對象編程的程序員學習和使用Swift的成本降低了。Java或者Objective-C程序員對Swift的很多概念會覺得很熟悉。對他們而言,學習Swift并不困難,很快就能將Swift投入到實際生產之中。 同時,Swift還一定程度上支持函數式編程風格。在適合函數式編程的場景下,同時程序員又擁有函數式編程的思維和能力時,可以使用Swift 以函數式的編程方法改善生產力。這將在下一章詳細介紹。 ### 函數式編程 函數式編程是一種以數學函數為程序語言建模的核心的編程范式。它將計算機運算視為數學函數計算,并且避免使用程序狀態以及可變對象。函數式編程思想主要有兩點: - 以函數為程序語言建模的核心 - 避免狀態和可變性 函數是函數式編程的基石。函數式編程語言的代碼就是由一個個函數組合而成的。編寫函數式語言的過程就是設計函數的過程。大規模程序由成千上萬的函數組成,為了有效的組合這些函數。函數式編程語言,會盡量避免狀態,避免可變對象。沒有可變的狀態,就使得函數式語言中的函數變為了純函數。純函數更容易模塊化,更容易理解,對于復用是友好的。 ### 函數 函數式編程的核心是函數,函數是“頭等公民”。這就像面向對象語言的主要抽象方法是類,函數式編程語言中的主要抽象方法是函數。Swift中的函數具有函數式語言中的函數的所有特點。你可以很容易地使用Swift 寫出函數式風格的代碼。 #### 高階函數,一級函數 高階函數,指可以將其他函數作為參數或者返回結果的函數。 一級函數,進一步擴展了函數的使用范圍,使得函數成為語言中的“頭等公民”。這意味函數可在任何其他語言構件(比如變量)出現的地方出現。可以說,一級函數是更嚴格的高階函數。 Swift中的函數都是一級函數,當然也都是高階函數。 前文中舉過Java中為了將函數作為參數傳遞給另外一個函數,需要將函數包裹在一個多余的匿名類中的蹩腳例子。Swift函數都是一級函數,可以直接將函數作為參數傳遞給另外一個函數。這就避免了Java里出現的這種多余的匿名類。 #### 閉包 閉包是一個會對它內部引用的所有變量進行隱式綁定的函數。也可以說,閉包是由函數和與其相關的引用環境組合而成的實體。?函數實際上是一種特殊的閉包。 Objective-C在后期加入了對閉包支持。閉包是一種一級函數。通過支持閉包,Objective-C拓展其語言表達能力。但是如果與Swift的閉包語法相比,Objective-C的閉包會顯得有些繁重復雜。 以下示例顯示了Swift閉包語言的簡潔和優雅: ~~~ let r = 1...3 let t = r.map { (i: Int) -> Int in return i * 2 } ~~~ 該例中,map函數遍歷了數組,用作為函數參數被傳入的閉包處理了數組里的所有元素,并返回了一個處理過的新數組。例子中可以看到,Swift 中使用`{}`來創建一個匿名閉包。使用`in`來分割參數和返回類型。在很多情況下,由于存在類型推導,可以省略類型聲明。 ### 不變性 在介紹Swift的不變性之前,先討論一下Haskell這門純函數式語言。這將有助于我們對于不變性有更深刻的理解。 簡單而言,Haskell 沒有變量。這是因為,Haskell追求更高級別的抽象,而變量其實是對一類低級計算機硬件:存儲器空間(寄存器,內存)的抽象。變量存在的原因,可以視為計算機語言進化的遺跡,比如在初期直接操作硬件的匯編語言中,需要變量來操作存儲過程。而在計算機出現之前,解決數學計算問題都是圍繞構建數學函數。數學中,不存在計算機語言中這種需要重復賦值的變量。 Haskell基于更抽象的數學模型。使用Haskell編程只需專注于設計數據之間的映射關系。而在數學上,表示兩個數據之間映射關系的實體就是函數。這使得編寫Haskell代碼和設計數學函數的過程是一致的,Haskell 程序員的思路也更接近數學的本質。Haskell摒棄了變量的同時,也拋棄了循環控制。這是因為沒有變量,也就沒有了控制循環位置的循環變量。這也很好理解。回憶一下我們在學習計算機之前的數學課程中,也無需使用到for這類概念。我們還是使用函數處理一個序列到另外一個序列的轉換。 不變性導致另外一個結果,就是純函數。沒有可變的狀態,沒有可變對象,就使得函數式語言中的函數變為了純函數。純函數即沒有副作用的函數,無論多少次執行,相同的輸入就意味著相同的輸出。一個純函數的行為并不取決于全局變量、數據庫的內容或者網絡連接狀態。純代碼天然就是模塊化的:每個函數都是自包容的,并且都帶有定義良好的接口。純函數具有非常好的特性。它意味著理解起來更簡單,更容易組合,測試起來更方便,線程安全性。 Swift提供了一定程度的不變性支持。在Swift中,可以使用`var`聲明普通的變量,也可以使用`let`快捷方便地聲明不變量。 ~~~ //變量 var mutable //不變量 let immutable = 1 ~~~ Swift 區分`var`和`let`是為了使用編譯器來強制這種區分。Swift 中聲明了不變量,就必須在聲明時同時初始化,或者在構造器中初始化。除這兩個地方之外,都無法再改變不變量。Swift中鼓勵使用不變量。因為,使用不變量更容易寫出容易理解,容易測試,松耦合的代碼。 不變性有諸多好處。 - 更高層次的抽象。程序員可以以更接近數學的方式思考問題。 - 更容易理解的代碼。由于不存在副作用,無論多少次執行,相同的輸入就意味著相同的輸出。純函數比有可變狀態的函數和對象理解起來要容易簡單得多。你無需再擔心對象的某個狀態的改變,會對它的某個行為(函數)產生影響。 - 線程安全的代碼。這意味著多線程環境下,運行代碼沒有同步問題。它們也不可能因為異常的發生而處于無法預測的狀態中。 不像Haskell這種純函數式編程語言只能申明不可變量,Swift提供變量和不可變量兩種申明方式。程序員可以自由選擇:在使用面向對象編程范式時,可以使用變量。在需要的情況下,Swift也提供不變性的支持。 ### 惰性求值 惰性計算是函數式編程語言的一個特性。惰性計算的表達式不在它被綁定到變量之后就立即求值,而是在該值被取用的時候求值。惰性計算有如下優點。 - 首先,你可以用它們來創建無限序列這樣一種數據類型。因為直到需要時才會計算值,這樣就可以使用惰性集合模擬無限序列。 - 第二,減少了存儲空間。因為在真正需要時才會發生計算。所以,節約了不必要的存儲空間。 - 第三,減少計算量,產生更高效的代碼。因為在真正需要時才會發生計算。所以,節約那部分沒有使用到的值的計算時間。例如,尋找數組中第一個符合某個條件的值。找到了之后,數組里該值之后的值都可以不必計算了。 純函數式編程語言,如Haskell中是默認進行惰性求值的。所以,Haskell被稱為惰性語言。而大多數編程語言如Java、C++求值都是嚴格的,或者說是及早求值。Swift默認是嚴格求值,也就是每一個表達式都需要求值,而不論這個表達式在實際中是否確實需要求值。但是,Swift 也提供了支持惰性求值的語法。在需要惰性時,需要顯式聲明。這為開發者在Swift中使用惰性提供了條件。 下面的例子展示了將默認是嚴格求值的數組變為惰性序列: ~~~ let r = 1...3 let seq = lazy(r).map { (i: Int) -> Int in println("mapping \(i)") return i * 2 } for i in seq { println(i) } ~~~ 將獲得如下結果: ~~~ mapping 1 2 mapping 2 4 mapping 3 6 ~~~ 結果顯示seq是一個惰性序列。它的值只有在需要時才會真正發生計算。 ### 函數式編程總結 函數式編程語言并不年輕,它的歷史和面向對象編程一樣悠久。1958年被創造出來的Lisp是最古老的函數式編程語言。它比C語言年代更為久遠。但直到最近,函數式編程思想才逐漸被重視。幾乎所有新發明的編程語言都或多或少受到了函數式編程思想的影響。Python、Scala、Groovy、Swift都有一級函數,閉包。使得你可以將函數直接傳給另外一個函數,函數也能夠以返回值形式被另一個函數返回。消除狀態,提供不變性的好處越來越多被接受,Scala、Groovy、Swift都提供了聲明不可變對象的方法,以支持編寫更趨近于函數式風格的代碼。 函數編程語言有其優秀的地方,也許將來會成為一個重要的編程范式。但是,函數式編程的重要性可能更多會間接地體現在影響其他編程語言的發展上。未來,可能很難出現一門主要以函數式編程范式設計的主流編程語言。如同Java這樣的以單一編程范式(面向對象)構建,而成為主流的編程語言的機會應該不會太多了。如同Haskell這樣追求純粹的函數式編程語言,更多的可能只是一個偏學術的語言實驗。 容我再重復一次上一節提到的理由:這個世界是如此豐富多彩,使用單一模式為世界建模可能并不會成功。當然,這類預測常常會被打破。如果,將來計算機領域出現了能解決所有問題的統一范式,我將很樂意再次學習和討論它。但如果僅僅討論現狀的話,我們仍然不得不面對一個分裂和折衷的世界。 Swift并不是一門主要以函數式編程范式構建的語言,它更多的是借鑒融合了函數式編程一些優秀思想(更靈活強大的函數,不變性的優點)。Swift在大多數的場景下,仍然主要會以面向對象編程語言的面目出現。因為,作為另一門面向對象編程語言Objective-C的繼任者,Swift 需要繼承Objective-C的遺產:Cocoa。我們現在寫Swift代碼,大部分時候還是在Cocoa框架之上,可以說 Cocoa就是Swift的標準庫。在一個主要以面向對象語言編寫的框架中寫代碼,最合適的思維方式仍然會是面向對象的。Cocoa可以說是Swift得以在高起點出發的基礎,也可以說其發生胎換骨變化的阻礙。 Swift對函數式編程的支持,使得程序員多了一種選擇。Swift并不強迫程序員一定要以面向對象的方法思維。在場景合適的情況下,程序員可以選擇使用函數式風格編寫代碼。如果確實是合適的場景,就能夠改善生產力。 ### 面向對象與函數式編程 如果,我們按語言范式給現在流行的語言分類,支持面向對象的編程語言應該會是最長的隊伍。現在大部分流行的現代編程語言都是面向對象的,它們都會允許你創建對象。但同時,你會發現比較流行的幾個編程語言,Python、Scala甚至Java都或多或少都受到了函數式編程語言的影響。它們都引入一些函數式編程的概念,可以在一定程度上編寫出具有函數式風格的代碼。 在熟悉了類面向對象編程語言之后,再接觸函數式編程語言,常常會覺得耳目一新,甚至隱約覺得函數式語言會是救世良方。那我們是否應該就此徹底轉向函數式編程語言呢?使用Haskell來拯救世界? 面向對象編程語言在大規模實踐之后,我們確實更深刻地了解了它們的缺點(例如,難以編寫多線程環境下的軟件應用;繼承并不是代碼復用的好方法)。函數式語言也確實有不少優點,有些優點恰恰就能解決面向對象語言的問題(純函數十分適應多線程環境,純函數天生就是模塊化的,對于代碼復用十分友好)。但是,函數式編程也許也存在某些問題。而這些問題,可能要在更大規模的業界實踐之后才會暴露出來。現在我們已經認識到,單純以對象為世界建模是有困難的。那么以數學模型來為世界建模可能也并不會好到哪里去。而可以確信的是,它們都有自己各自擅長的領域和環境。我們仍然還無法使用某種單一的編程范式來解決所有問題。 更大的現實是無數企業已經在面向對象編程語言上做了巨大的投資,即使現在面向對象編程已經暴露出一些問題,而函數式編程又呈現出不少能解決這些問題的優點,任何一個謹慎的人都不會,也不可能馬上拋棄面向對象編程,徹底全面地轉向函數式編程語言。 現實的選擇是支持面向對象編程的同時,提供函數式的支持。這樣,在大部分面向對象游刃有余的地方,仍然可以使用面向對象的方法。而在適合函數式編程的地方,而你又擁有函數式編程的思維和能力時,還可以采用函數式的編程方法改善生產力。 Swift就是這樣一個現實的選擇。完善的面向對象支持,使Swift繼承了 Objective-C遺留下來的豐厚遺產。在Swift中使用Objective-C對象并不復雜。如果,你遇到一個對多線程安全性有要求的場景,需要使用函數式風格編寫這部分代碼,這在Swift中也是很輕松的。 ### 泛型編程 泛型編程是另外一個有趣的話題。泛型為程語言提供了更高層級的抽象,即參數化類型。換句話說,就是把一個原本特定于某個類型的算法或類當中的類型信息抽象出來。這個抽象出來的概念在C++的 STL(Standard Template Library)中就是模版(Template)。STL 展示了泛型編程的強大之處,一出現就成為了C++的強大武器。除C++之外,C#、Java、Haskell等編程語言也都引入了泛型概念。 泛型編程是一個稍微局部一些的概念,它僅僅涉及如何更抽象地處理類型。這并不足以支撐起一門語言的核心概念。我們不會聽到一個編程語言是純泛型編程的,而沒有其他編程范式。但正因為泛型并不會改變程序語言的核心,所以在大多數時候,它可以很好地融入到其他編程范式中。C++、Scala、Haskell這些風格迥異的編程語言都支持泛型。泛型編程提供了更高的抽象層次,這意味著更強的表達能力。這對大部分編程語言來說都是一道美味佐餐美酒。 在Swift中,泛型得到廣泛使用,許多Swift標準庫是通過泛型代碼構建出來的。例如Swift的數組和字典類型都是泛型集合。這樣的例子在 Swift中隨處可見。 ### 泛型函數 Swift函數支持泛型。泛型函數通過將函數參數和返回值定義為泛型類型,使得函數可以作用于任何適合的類型。下面展示了一個簡單的泛型函數: ~~~ func swapTwoValues<T>(inout a: T, inout b: T) { let temporaryA = a a = b b = temporaryA } ~~~ ### 泛型類型 除了泛型函數之外,Swift還可以自定義泛型類,泛型結構體和泛型枚舉。這樣的泛型類型可以作用于任何類型,其用法和Swift提供的 Array和Dictionary相同。 用一個棧(Stack)的例子展示泛型結構體的定義和使用。泛型枚舉和泛型類的定義和使用方法是相同的。 ~~~ // 定義一個泛型結構體 struct Stack<T> { var items = [T]() mutating func push(item: T) { items.append(item) } mutating func pop() -> T { return items.removeLast() } } // 使用一個泛型結構體 var stackOfStrings = Stack<String>() stackOfStrings.push("uno") ~~~ 泛型類型參數T被用在了三個地方: - 創建數組items時,指定了items中可以存儲的數據類型; - 指定了函數push的參數類型; - 指定了函數pop的返回值類型。 ### 泛型協議 而對于協議,Swift中沒有提供類似結構體或類那樣的方法來定義泛型協議。但我們可以使用typealias關鍵字定義該協議的關聯類型,這樣一定程度上可以模擬泛型協議的效果,例子如下: ~~~ protocol GeneratorType { typealias Element mutating func next() -> Element? } ~~~ 實現該協議的類必須定義一個別名為Element的關聯類型。這和泛型的概念異曲同工,一定程度上實現了泛型協議。 ### 泛型約束 在泛型的編程實踐中,我們會遇到一些需要對泛型類型做進一步約束的場景。類型約束為泛型參數指定了一個類型,或者要求其實現某個特定的協議。比如,`意味著泛型參數指代的對象需要遵守 Equatable協議。 類型約束對泛型參數的類型做了一定約束,可以強制要求泛型參數代表的類型遵守某個協議。而where語句可以更進一步對類型約束中聲明的泛型參數所需要遵守的協議作出更詳細的要求。where語句也可以對協議的關聯類型作進一步約束。比如,你可以要求兩個泛型參數所遵守的協議的關聯類型是相同的。 ### 泛型編程總結 總體而言,Swift提供了全面的泛型編程語法,讓程序員可以寫出抽象層次更高,更為靈活的代碼,在避免了重復代碼的同時,又能擁有良好的類型安全性。 ### 總結 最后總結一下,Swift是一門典型的多范式編程語言,支持面向對象是為了繼承面向對象編程豐厚的成果;支持函數式編程,是為了探索新的可能;支持泛型編程,則是一道美味的佐餐美酒。 Swift允許程序員在大部分使用面向對象就游刃有余的時候,輕松地繼續使用面向對象編程;而在適合函數式編程的場景下,同時程序員又擁有函數式編程的思維和能力時,還可以使用Swift以函數式的編程方法改善生產力;以及任何時候程序員都可以在Swift中使用泛型,提高抽象層次。 ### 參考文檔 - [Blog: airspeedvelocity](http://airspeedvelocity.net/) - [Apple’s Swift blog](https://developer.apple.com/swift/blog/) - [Blog: objc.io](http://www.objc.io/issue-16/) - [Apple’s Document “Swift Programming Language”](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/) 查看原文:[多范式編程語言-以Swift為例](http://www.infoq.com/cn/articles/multi-paradigm-programming-language-swift) ![](https://box.kancloud.cn/2015-08-19_55d41b1dcee66.jpg) # Oracle專家談MySQL Cluster如何支持200M的QPS 作者 謝麗 [Andrew Morgan](https://twitter.com/andrewmorgan)是Oracle MySQL首席產品經理。 近日,他[撰文](http://highscalability.com/blog/2015/5/18/how-mysql-is-able-to-scale-to-200-million-qps-mysql-cluster.html)介紹了MySQL Cluster如何支持200M的QPS。 ### MySQL Cluster簡介 [MySQL Cluster](http://www.clusterdb.com/)是一個實時可擴展且符合ACID的事務型內存數據庫。該數據庫有高達99.999%的可用性和低廉的開源軟件總擁有成本。在設計方面,它采用了一種分布式、多主節點的架構,消除了單點故障,能夠在商用硬件上橫向擴展,并借助“自動分片(auto-sharding)”功能為通過SQL和NoSQL接口訪問數據的讀/寫密集型工作負載提供服務。 最初,MySQL Cluster被設計成一個嵌入式的電信數據庫,用于網內應用程序,需要具備運營商級的可用性和實時性能。之后,其功能隨著新功能集的增加迅速增強,其應用領域隨之也擴展到了本地或云上的Web、移動和企業應用程序,包括:大規模OLTP、實時分析、電子商務(庫存管理、購物車、支付處理、訂單追蹤)、在線游戲、金融交易(欺詐檢測)、移動與微支付、會話管理&緩存、流式推送、分析及推薦、內容管理與交付、通信與在線感知服務、訂閱者/用戶信息管理與權益等。 ### MySQL Cluster體系結構 在MySQL Cluster內部,總共有三種類型的節點為應用程序提供服務。下面是一張MySQL Cluster體系結構簡圖,其中包含6個節點組,共12個“數據節點(Data Node)”: ![](https://box.kancloud.cn/2015-08-19_55d41b1e09163.png) **數據節點**是MySQL Cluster的主要節點。它們提供如下功能:內存內及基于磁盤的數據存儲與管理、表的自動“分片(sharding)”及按用戶定義分區、數據節點間數據同步復制、事務與數據檢索、自動故障恢復、自我修復(故障解決后自動重新同步)。 表會自動跨數據節點分區,每個數據節點都是一個可以接受寫操作的主節點。這使得寫密集型工作負載很容易在節點之間分配,而且對于應用程序而言,這個過程是透明的。 MySQL Cluster采用了一種無資源共享的體系結構(比如不使用共享磁盤)存儲和分發數據,并同步生成至少一個數據副本,如果某個數據節點出現故障,則總是有另一個數據節點存儲了同樣的信息, 使得請求和事務可以繼續而不被中斷。任何在數據節點故障期間短暫中斷(亞秒級)的事務都可以回滾并重新執行。 MySQL Cluster允許用戶選擇如何存儲數據:全部在內存中或者部分在磁盤上(僅限于未索引數據)。內存內存儲對于經常變化的數據(活動工作集)而言尤其有用。存儲在內存中的數據會定期地(本地檢查點)寫入本地磁盤,并在所有數據節點之間協調,這樣,MySQL Cluster可以從系統完全失效(比如停電)的情況下恢復過來。基于磁盤的存儲可以用于存儲性能要求不那么嚴格的數據,其數據集大于可用內存。與其它大多數數據庫服務器一樣,為了提高性能,MySQL Cluster使用頁緩存將經常使用的、基于磁盤存儲的數據緩存在數據節點的內存中。 **應用節點**提供從應用邏輯到數據節點的連接。應用程序可以使用SQL訪問數據庫,通過一臺或多個MySQL服務器對存儲在MySQL Cluster中的數據執行SQL接口的功能。當訪問MySQL服務器時,可以使用任何一種標準的[MySQL連接器](http://www.mysql.com/downloads/connector/),這使用戶有許多種訪問技術可選擇。NDB API是其中一個可選的方案。這是一個基于C++的高性能接口,可以提供額外控制、更好的實時行為及更高的吞吐能力。NDB API還提供了一個層,使NoSQL接口可以繞過SQL層直接訪問MySQL Cluster,降低了延遲,提高了開發靈活性。現有接口包括Java、JPA、Memcached、JavaScript與Node.js、HTTP/REST(借助Apache Module)。所有應用節點都可以訪問所有數據節點的數據,所以,它們即使出現故障也不會導致服務中斷,因為應用程序只要使用剩下的節點就可以了。 **管理節點**負責向MySQL Cluster中的所有節點發布集群配置信息以及節點管理。管理節點在啟動、向集群加入節點及系統重新配置時使用。管理節點關閉和重啟不會影響數據節點和應用節點的運行。在默認情況下,在遇到導致“集群分裂(split-brain)”或[網絡分區](http://www.clusterdb.com/mysql-cluster/mysql-cluster-fault-tolerance-impact-of-deployment-decisions/)的網絡故障時,管理節點還提供仲裁服務。 ### 通過透明分片實現可擴展性 ![](https://box.kancloud.cn/2015-08-19_55d41b1e28830.png) 任何表的行都可以透明地分成多個分區/片段。對于每一個片段,都會有一個單獨的數據節點保存它所有的數據,并處理所有針對那些數據的讀寫操作。每個數據節點還有一個伙伴節點,它們共同組成了一個節點組;伙伴節點存儲了那個片段的第二個副本以及一個它自己原有的片段。MySQL Cluster使用同步兩段提交協議確保事務提交的變化同時存儲到兩個數據節點。 MySQL Cluster默認使用表的主鍵作為“分片鍵(shard key)”,并對分片鍵執行MD5散列,從而選擇數據應該存儲的片段/分區。如果一個事務或查詢需要訪問多個數據節點的數據,那么其中一個數據節點將承擔事務協調器的角色,并將工作委派給其它所需的數據節點;結果會在提供給應用程序前合并。需要注意的是,事務或查詢可以連接來自多個分片和多個表的數據,這與傳統的、實現了分片機制的NoSQL數據存儲相比是一個巨大的優勢。 ![](https://box.kancloud.cn/2015-08-19_55d41b1e3bccd.png) 當單個節點就可以滿足高強度查詢/事務的數據操作需求時,就實現了最理想的(線性)擴展(因為這減少了數據節點間消息傳遞的網絡延遲)。要做到這一點,應用程序應該清楚地知道數據分布——這實際上就是說定義模式的人可以指定用作分片鍵的列。比如,上圖中的表使用了由user-id和服務名組合而成的主鍵;如果只使用user-id作為分片鍵,那么表中特定用戶的所有行將會總是存儲在同一個片段中。更為強大之處在于,如果其它表中也使用了同樣的user-id列,并將其設定為分片鍵,那么所有表中特定用戶的數據都會存儲在同一個片段中,那個用戶的查詢/事務就可以由單個數據節點處理。 ### 利用NoSQL API最大限度地提高數據訪問速度 MySQL Cluster提供了許多種數據訪問方式;最常用的方法是SQL,但從下圖可以看出,還有許多原生API可供應用程序從數據庫直接讀/寫數據,避免了向SQL轉換并傳遞給MySQL服務器的低效和開發復雜度。目前,MySQL Cluster提供了面向C++、Java、JPA、JavaScript/Node.js、HTTP及Memcached協議的API。 [![](https://box.kancloud.cn/2015-08-19_55d41b1e5079c.png)](#) **基準測試:每秒2億次查詢** 根據設計,MySQL Cluster用于處理以下兩種工作負載: - **OLTP(在線事務處理)**:內存優化型表可以提供次毫秒級的低延遲以及極高水平的OLTP工作負載并發能力,并且仍然可以提供良好的穩定性;此外,它們也能夠用于基于磁盤存儲的表。 - **即時搜索**:MySQL Cluster提高了執行表掃描時可以使用的并發數,極大地提高了未索引列的搜索速度。 話雖如此,MySQL Cluster旨在處理OLTP工作負載方面達到最佳,特別是在以并發方式發送大量查詢/事務請求的情況下。為此,他們使用[flexAsynch](http://dev.mysql.com/downloads/benchmarks.html)基準測試,測量更多數據節點加入集群后NoSQL訪問性能的提升。 ![](https://box.kancloud.cn/2015-08-19_55d41b1e61f34.png) 在該基準測試中,每個數據節點運行在一個專用的56線程Intel E5-2697 v3(Haswell)機器上。上圖顯示了在數據節點從2增加到32(注意:MySQL Cluster目前最多支持48個數據節點)的過程中吞吐量的變化。從中可以看出,吞吐量呈線性增長,在32個數據節點時,達到了**每秒2億次NoSQL查詢**。 讀者可以登錄[MySQL Cluster基準測試頁面](http://www.mysql.com/why-mysql/benchmarks/mysql-cluster/),查看關于這次測試的最新結果及更詳細的描述。 每秒2億次查詢的基準測試是在MySQL Cluster 7.4(最新的正式版本)上得出的,關于該版本的更多信息請查看[這里](http://www.clusterdb.com/mysql-cluster/mysql-cluster-7-4-is-ga-200-million-qps-and-active-active-geor)。 查看原文:[Oracle專家談MySQL Cluster如何支持200M的QPS](http://www.infoq.com/cn/articles/oracle-expert-talk-how-mysql-cluster-support-200m-qps) ![](https://box.kancloud.cn/2015-08-19_55d41b1e75584.jpg) # 雜談:創業公司的產品開發與團隊管理 作者 賈彥民 一般來說,創業公司規模小,人員少,沒有大公司的官僚作風。而官僚作風是很害人的東西,記得在大公司時,本來一言而決的一點小事,常常因為害怕承擔失敗的責任,或參與者(如管項目或管人的經理們)有意的要突出自己的影響力或存在感,在各色人等中往來穿梭,仿佛煞有介事,經過無數次的會議討論卻議而不決。而軟件工程師們為配合這樣的戲碼常常被搞得焦頭爛額,無可適從。創業公司由于管理層次簡單,很少受到官僚作風的困擾,但也不能想當然地認為萬事大吉,高枕無憂了。其實創業公司也有創業公司的局限性,這些局限性常常被忽略,從而影響到產品的開發,茲總結如下。 創業不一定志高。人們往往認為創業公司旨在IPO,對自己的產品計劃往往雄心勃勃。其實并不盡然,有人就認為,創業公司的資源配備不能和大公司相提并論,因此,做出的產品比大公司差些也是理所當然的。而創業公司產品的優勢主要在于低廉的價格。聽起來似乎有道理,其實是在為自身的懶惰或能力上的缺陷尋找借口。就如同國家足球隊那樣沒有出息,踢輸了,或怪草皮太硬,或怪裁判不公,或怪狀態不佳,或怪對手太強。在競爭如此激烈的市場,一個創業公司如果做不出最好的產品,為用戶提供實實在在的價值,既沒有生存的可能,也沒有生存的必要。因此,三軍可奪其帥,匹夫不可奪其志也,唯有如此,創業公司才有成功的可能性。 簡單不一定高效。有些創業公司不太注重項目制定計劃,或者不太善于制定項目計劃。沒有完善可行的項目計劃,就更談不到項目狀態和進度的檢查和管控,常常是做到哪里算哪里。當然看起來很簡單,高效卻是未必。大家不清楚什么時間做什么事,也不知道事情的輕重緩急。每個人似乎都在忙忙叨叨,而究竟忙些什么,卻是糊里糊涂。臨近項目結束時,發現還差很多,于是加班加點趕進度,哪里還顧得上產品質量。 人少不一定溝通流暢。軟件開發當然需要團隊合作,無論團隊大小,項目經理、產品經理、UX設計師、開發工程師、測試工程師需要準確有效地溝通協調。對于新的功能:產品經理一定要講明白它的作用和價值,開發工程師才有信心做好;開發工程師要和測試工程師一起檢查主要的功能測試點,制定測試計劃;項目經理要確保開發計劃讓所有人都清楚地知道,并協調任務之間的相互依賴。創業公司人少事多,有時候身兼數職,若沒有有意識的溝通,大家都埋頭于自己的工作,個人自掃門前雪,莫管他人瓦上霜,就可能出現這樣的情況,事情做完了,才突然很驚訝地發現完全搞錯了。 年輕不一定朝氣蓬勃。曾國藩認為:“軍事是喜朝氣,最忌暮氣,惰則皆暮氣也。”創業階段的公司本應該是披荊斬棘,銳意進取,表現出勃勃的生機。但創業的艱辛往往超出創業者的想象,不斷的挫敗侵蝕著創業者的意志,從而直接影響民心士氣。如果面對困難既沒有愿景,也沒有解決之道,由失敗引起的沮喪就可能變成不可逆轉的暮氣,最終壓垮整個公司。 正如有什么樣的人民就有什么樣的政府,產品的品質歸根結底取決于做產品的人,有什么樣的工程師(開發和測試)、設計師、架構師、產品經理,就有什么樣的產品。因此,保證產品品質最有效的辦法莫過于打造一支高水平的團隊,而團隊建設很大程度上取決于領導的藝術。 讓我們來看看從歷史中可以學到什么。秦失其鹿,天下人共逐之。最后的競爭在最有實力的兩個對手劉邦和項羽之間展開,當然最終的結果是屢敗屢戰的劉邦完勝屢戰屢勝的項羽。歷史學家們從個人性格、政治智慧、軍事才能、戰略戰術等各個角度對勝負之數做了全方位的分析,我認為決定最終勝負的最重要的原因當屬創業**團隊的建設**。劉邦的團隊網羅了當時頂尖的一流人才,項羽當初的創業團隊也毫不遜色。項羽擁有戰國貴族的血統,接受過良好的教育,“見人恭敬慈愛,言語嘔嘔,人有疾病,涕泣分食飲”,更尊范增為亞父。而平民出身流氓習性難改的劉邦卻慢而侮人,甚至于向儒生的帽子里面撒尿。但是在項羽實力鼎盛的時候,一些了不起的大牛如陳平、韓信之輩卻選擇去項王而從沛公游,何也?原因其實很簡單,劉邦長期在黑社會中混跡的經歷以及在革命斗爭的實踐中鍛煉出了高度的智慧和不凡的見識。使他有能力明白團隊中大牛們的高明見解,聞弦歌而知雅意,并且不斷激發團隊的想象力和創造力。這其實就是所謂的**領導力**,也是劉邦“不能將兵,而善將將”的秘訣。而項羽雖然個人素質過硬,具有很高的軍事天分,巨鹿一戰,大破秦軍,“召見諸侯將,入轅門,無不膝行而前,莫敢仰視”。但他畢竟“too young, too simple, sometimes na?ve”,不是不尊重別人的建議,而是缺乏君臨天下而應有的領導力,稍微高深一點的意見便不能理解。讓我們來看一個具體的例子,當有人勸霸王定都關中的時候,他卻說:“富貴不歸故鄉,如衣錦夜行,誰知之者?”如此幼稚可笑的話,真叫人瞠目結舌,怪不得人家罵他沐猴衣冠,豎子不足與謀。而面對同樣的建議,盡管“左右大臣皆山東人,多勸上都雒陽”,但是劉邦卻知道這個意見的重要價值,排除眾議,定都關中,為后世弭平七國之亂起到了至關重要的作用。所以,一流的人才都離開項羽,跳槽到劉邦那里,把好的**主意**講給聽得懂的人了。 關于這一點,Steve Jobs講得更明白: > “For most things in life, the range between best and average is 30% or so. The best airplane flight, the best meal, they may be 30% better than your average one. What I saw with Woz was somebody who was fifty times better than the average engineer. He could have meetings in his head. The Mac team was an attempt to build a whole team like that, A players. People said they wouldn’t get along, they’d hate working with each other. But I realized that A players like to work with A players, they just didn’t like working with C players.” 一流的人才愿意和一流的人才一起工作,因為他們之間有共同語言,相互理解;而一流的人才卻不愿意和三流的人才一起工作,因為三流的人才根本不能明白一流的人才的想法和意圖,討論問題時總是纏夾不清,糊糊涂涂,無可無不可。另外,劉邦比之項羽還有另一個重要的優點,那就是毫不吝嗇的激勵措施,劉邦“使人攻城略地,因以與之,與天下同其利”,而項羽“至使人有功當封爵者,印刓敝,忍不能予。”所以,百戰百勝的項羽不能保證最后決戰的勝利。垓下一戰,只落得霸王別姬,英雄氣短,天數耶?人事耶?歷史事件常常會驚人的相似,相似的劇本在三國時期再次上演。這次的主角換成了雄才大略的曹操和剛愎自用的袁紹。袁紹榆木疙瘩一樣簡單的大腦也同樣不能理解高明的見解,一而再,再而三地失去良機。在官渡之戰中被實力遠不如自己的曹操擊敗,從此一蹶不振。 歷史的經驗更加驗證了那句俗話,兵熊熊一個,將熊熊一窩。做領導的不一定十八般兵器,樣樣精通,但一定要加強修養,提高領導力,這是打造一支優秀的開發團隊的必要條件。產品的開發是百分之百的智力游戲和創造性活動,每個工程師的最重要的貢獻是TA的創造力和想象力。而領導要有足夠的智慧和見識用來領悟他們高明的建議,并想方設法利用各種激勵措施把他們的想象力和創造力最大限度地激發出來。相反,一個比較“熊”的領導形式上搞再多的頭腦風暴,對所謂的創新也一樣的于事無補。我曾經歷過這樣一個產品開發團隊,幾乎所有的人對產品的目標和價值都不甚了了,甚至說不明白產品是什么,也不知道如何與同類型的產品做區分。很多人建議要把這些問題好好討論清楚,但是團隊的領導認為先做起來看,在開發中逐步加深認識,也許一下子所有的問題都清楚了,最不濟大家也可以在開發中練練手。最終的結果可想而知,產品不可避免地失敗了,剛剛招聘成立的部門也全部解散了。這不僅是公司的損失,而且影響到每一位員工的職業生涯,同時也浪費了寶貴的生產力。這說明如果領導把握不住產品的正確方向,即使開發團隊努力再多也不過是徒勞無益,與成功南轅北轍,漸行漸遠。 大公司設計的那些官僚而笨拙的開發管理流程不是創業公司可以承受之重。其實,最好的管理就是沒有管理。無為而治,所有人都在生產第一線,寫代碼,做測試。仿佛是一條由機器人操控的自動生產線,所有的開發任務都按部就班進行,順利得像流水一樣。沒有任何人力、物力浪費在管理工作上,當然生產效率是最高的。我想,這是一種可望而不可即的理想狀況。因為,產品開發本身不是流水線式的作業,存在許多不確定因素,比如對于每一項開發任務,幾乎不可能正確地估計出需要的時間,產品的需求隨時可能發生變化,任務之間存在各種依賴關系。所以,我們需要把開發工作劃分為具體的可操作的開發任務,估計每項任務所需的時間,根據需求的變化確定任務的優先級,協調任務之間的進度等,所有這些都屬于管理工作的范疇。因此,我們只能盡量減少管理,而做不到完全沒有管理的理想狀態。為了下文敘述的方便,我們把管理產品開發的人統稱為開發經理,可能的角色包括產品經理,項目經理,技術主管等。開發經理應努力克制自己,盡量減少因為管理對工程師的工作帶來的干擾以及時間與精力的開銷,讓工程師專注于創造性的工作。開發經理要多做功課,精簡管理的流程,沒有必要開的會就不要去開,能夠開短會的就不要開長會,小會能夠解決問題的就不要開大會。記得在某大公司時,開發經理們為了同步項目的進度和狀態,早上工程師開始工作之前,每個開發小組都要花一刻鐘到半個小時的時間開一個例會,報告自己昨天的工作和今天的計劃。另外,還有數不清的各種各樣的項目狀況匯報會。這當然方便了開發經理了解項目的狀況,但卻浪費了工程師的時間和精力,去一遍又一遍地應付開發經理們重復且無聊問題。 對于創業公司而言,管理要做到盡量簡單,只要每個人都有事做,清楚地知道什么時間做什么事,并保證每件事的質量,就基本達到管理的目的了。開發經理要像一個在場邊指揮的足球教練,能夠及時發現團隊或個人的問題和缺陷,并采取有效措施加以補救,不能等到形勢嚴重了還渾然不覺,而當團隊運行順利時,開發經理就應該像空氣一樣自動消失;開發經理要像一個高明的調酒師,調酒師知道各種調料的風格味道,并能按照恰當比例制成美酒佳釀,開發經理也要清楚團隊中每個工程師的性情,優點和缺點,取長補短,合理搭配組合,高效率、高質量地完成任務;開發經理要像一個拉拉隊長,激發工程師的創造力和想象力,鼓舞士氣,傳播正能量;開發經理要像一個后勤部長,凡是和產品開發不直接相關的雜事,都能搞定。 為了追求簡單高效,照搬大公司的產品開發流程固然不可取。但對于行之有效的方法也斷不能一概拒之門外。比如,每個人都要清楚地知道產品開發計劃,規范測試的方法和流程等。當然有些方法要經過改造才能適用于創業公司。比如代碼審核,很多程序員認為是在給自己的工作挑刺,仿佛被批斗一樣,感覺很不舒服,甚至有些抗拒。若從另外一個角度來看,代碼審核就是另外不同的情景。當程序員對應用的需求不太明確時,代碼審核可以用來幫助理清需求;當程序員對自己的代碼沒有信心時,代碼審核可以用來幫助發現問題,修正錯誤;當程序員寫出優雅的算法時,代碼審核可以用來展示TA的奇思妙想。所以,代碼審核絕不應該是挑戰程序員,而是幫助程序員。這一點同程序員解釋清楚了,那么,程序員就會主動要求審核自己的代碼了。 查看原文:[雜談:創業公司的產品開發與團隊管理](http://www.infoq.com/cn/articles/product-development-and-team-management-of-venture-company) ![](https://box.kancloud.cn/2015-08-19_55d41b1e9051e.jpg) ![](https://box.kancloud.cn/2015-08-19_55d41b1ec3b6e.jpg)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看