承載量是分布式系統(tǒng)存在的原因 當一個互聯(lián)網(wǎng)業(yè)務獲得大眾歡迎的時候,最顯著碰到的技術(shù)問題,就是服務器非常繁忙。當每天有1000萬個用戶訪問你的網(wǎng)站時,無論你使用什么樣的服務器硬件,都不可能只用一臺機器就承載的了。因此,在互聯(lián)網(wǎng)程序員解決服務器端問題的時候,必須要考慮如何使用多臺服務器,為同一種互聯(lián)網(wǎng)應用提供服務,這就是所謂“分布式系統(tǒng)”的來源。 然而,大量用戶訪問同一個互聯(lián)網(wǎng)業(yè)務,所造成的問題并不簡單。從表面上看,要能滿足很多用戶來自互聯(lián)網(wǎng)的請求,最基本的需求就是所謂性能需求:用戶反應網(wǎng)頁打開很慢,或者網(wǎng)游中的動作很卡等等。而這些對于“服務速度”的要求,實際上包含的部分卻是以下幾個:高吞吐、高并發(fā)、低延遲和負載均衡。 高吞吐,意味著你的系統(tǒng),可以同時承載大量的用戶使用。這里關(guān)注的整個系統(tǒng)能同時服務的用戶數(shù)。這個吞吐量肯定是不可能用單臺服務器解決的,因此需要多臺服務器協(xié)作,才能達到所需要的吞吐量。而在多臺服務器的協(xié)作中,如何才能有效的利用這些服務器,不致于其中某一部分服務器成為瓶頸,從而影響整個系統(tǒng)的處理能力,這就是一個分布式系統(tǒng),在架構(gòu)上需要仔細權(quán)衡的問題。 高并發(fā)是高吞吐的一個延伸需求。當我們在承載海量用戶的時候,我們當然希望每個服務器都能盡其所能的工作,而不要出現(xiàn)無謂的消耗和等待的情況。然而,軟件系統(tǒng)并不是簡單的設(shè)計,就能對同時處理多個任務,做到“盡量多”的處理。很多時候,我們的程序會因為要選擇處理哪個任務,而導致額外的消耗。這也是分布式系統(tǒng)解決的問題。 低延遲對于人數(shù)稀少的服務來說不算什么問題。然而,如果我們需要在大量用戶訪問的時候,也能很快的返回計算結(jié)果,這就要困難的多。因為除了大量用戶訪問可能造成請求在排隊外,還有可能因為排隊的長度太長,導致內(nèi)存耗盡、帶寬占滿等空間性的問題。如果因為排隊失敗而采取重試的策略,則整個延遲會變的更高。所以分布式系統(tǒng)會采用很多請求分揀和分發(fā)的做法,盡快的讓更多的服務器來出來用戶的請求。但是,由于一個數(shù)量龐大的分布式系統(tǒng),必然需要把用戶的請求經(jīng)過多次的分發(fā),整個延遲可能會因為這些分發(fā)和轉(zhuǎn)交的操作,變得更高,所以分布式系統(tǒng)除了分發(fā)請求外,還要盡量想辦法減少分發(fā)的層次數(shù),以便讓請求能盡快的得到處理。 由于互聯(lián)網(wǎng)業(yè)務的用戶來自全世界,因此在物理空間上可能來自各種不同延遲的網(wǎng)絡(luò)和線路,在時間上也可能來自不同的時區(qū),所以要有效的應對這種用戶來源的復雜性,就需要把多個服務器部署在不同的空間來提供服務。同時,我們也需要讓同時發(fā)生的請求,有效的讓多個不同服務器承載。所謂的負載均衡,就是分布式系統(tǒng)與生俱來需要完成的功課。 由于分布式系統(tǒng),幾乎是解決互聯(lián)網(wǎng)業(yè)務承載量問題,的最基本方法,所以作為一個服務器端程序員,掌握分布式系統(tǒng)技術(shù)就變得異常重要了。然而,分布式系統(tǒng)的問題,并非是學會用幾個框架和使用幾個庫,就能輕易解決的,因為當一個程序在一個電腦上運行,變成了又無數(shù)個電腦上同時協(xié)同運行,在開發(fā)、運維上都會帶來很大的差別。 分布式系統(tǒng)提高承載量的基本手段 分層模型(路由、代理) 使用多態(tài)服務器來協(xié)同完成計算任務,最簡單的思路就是,讓每個服務器都能完成全部的請求,然后把請求隨機的發(fā)給任何一個服務器處理。最早期的互聯(lián)網(wǎng)應用中,DNS輪詢就是這樣的做法:當用戶輸入一個域名試圖訪問某個網(wǎng)站,這個域名會被解釋成多個IP地址中的一個,隨后這個網(wǎng)站的訪問請求,就被發(fā)往對應IP的服務器了,這樣多個服務器(多個IP地址)就能一起解決處理大量的用戶請求。 然而,單純的請求隨機轉(zhuǎn)發(fā),并不能解決一切問題。比如我們很多互聯(lián)網(wǎng)業(yè)務,都是需要用戶登錄的。在登錄某一個服務器后,用戶會發(fā)起多個請求,如果我們把這些請求隨機的轉(zhuǎn)發(fā)到不同的服務器上,那么用戶登錄的狀態(tài)就會丟失,造成一些請求處理失敗。簡單的依靠一層服務轉(zhuǎn)發(fā)是不夠的,所以我們會增加一批服務器,這些服務器會根據(jù)用戶的Cookie,或者用戶的登錄憑據(jù),來再次轉(zhuǎn)發(fā)給后面具體處理業(yè)務的服務器。 除了登錄的需求外,我們還發(fā)現(xiàn),很多數(shù)據(jù)是需要數(shù)據(jù)庫來處理的,而我們的這些數(shù)據(jù)往往都只能集中到一個數(shù)據(jù)庫中,否則在查詢的時候就會丟失其他服務器上存放的數(shù)據(jù)結(jié)果。所以往往我們還會把數(shù)據(jù)庫單獨出來成為一批專用的服務器。 至此,我們就會發(fā)現(xiàn),一個典型的三層結(jié)構(gòu)出現(xiàn)了:接入、邏輯、存儲。然而,這種三層結(jié)果,并不就能包醫(yī)百病。例如,當我們需要讓用戶在線互動(網(wǎng)游就是典型) ,那么分割在不同邏輯服務器上的在線狀態(tài)數(shù)據(jù),是無法知道對方的,這樣我們就需要專門做一個類似互動服務器的專門系統(tǒng),讓用戶登錄的時候,也同時記錄一份數(shù)據(jù)到它那里,表明某個用戶登錄在某個服務器上,而所有的互動操作,要先經(jīng)過這個互動服務器,才能正確的把消息轉(zhuǎn)發(fā)到目標用戶的服務器上。 又例如,當我們在使用網(wǎng)上論壇(BBS)系統(tǒng)的時候,我們發(fā)的文章,不可能只寫入一個數(shù)據(jù)庫里,因為太多人的閱讀請求會拖死這個數(shù)據(jù)庫。我們常常會按論壇板塊來寫入不同的數(shù)據(jù)庫,又或者是同時寫入多個數(shù)據(jù)庫。這樣把文章數(shù)據(jù)分別存放到不同的服務器上,才能應對大量的操作請求。然而,用戶在讀取文章的時候,就需要有一個專門的程序,去查找具體文章在哪一個服務器上,這時候我們就要架設(shè)一個專門的代理層,把所有的文章請求先轉(zhuǎn)交給它,由它按照我們預設(shè)的存儲計劃,去找對應的數(shù)據(jù)庫獲取數(shù)據(jù)。 根據(jù)上面的例子來看,分布式系統(tǒng)雖然具有三層典型的結(jié)構(gòu),但是實際上往往不止有三層,而是根據(jù)業(yè)務需求,會設(shè)計成多個層次的。為了把請求轉(zhuǎn)交給正確的進程處理,我們而設(shè)計很多專門用于轉(zhuǎn)發(fā)請求的進程和服務器。這些進程我們常常以Proxy或者Router來命名,一個多層結(jié)構(gòu)常常會具備各種各樣的Proxy進程。這些代理進程,很多時候都是通過TCP來連接前后兩端。然而,TCP雖然簡單,但是卻會有故障后不容易恢復的問題。而且TCP的網(wǎng)絡(luò)編程,也是有點復雜的?!?,人們設(shè)計出更好進程間通訊機制:消息隊列。 盡管通過各種Proxy或者Router進程能組建出強大的分布式系統(tǒng),但是其管理的復雜性也是非常高的。所以人們在分層模式的基礎(chǔ)上,想出了更多的方法,來讓這種分層模式的程序變得更簡單高效的方法。 并發(fā)模型(多線程、異步) 當我們在編寫服務器端程序是,我們會明確的知道,大部分的程序,都是會處理同時到達的多個請求的。因此我們不能好像HelloWorld那么簡單的,從一個簡單的輸入計算出輸出來。因為我們會同時獲得很多個輸入,需要返回很多個輸出。在這些處理的過程中,往往我們還會碰到需要“等待”或“阻塞”的情況,比如我們的程序要等待數(shù)據(jù)庫處理結(jié)果,等待向另外一個進程請求結(jié)果等等……如果我們把請求一個挨著一個的處理,那么這些空閑的等待時間將白白浪費,造成用戶的響應延時增加,以及整體系統(tǒng)的吞吐量極度下降。 所以在如何同時處理多個請求的問題上,業(yè)界有2個典型的方案。一種是多線程,一種是異步。在早期的系統(tǒng)中,多線程或多進程是最常用的技術(shù)。這種技術(shù)的代碼編寫起來比較簡單,因為每個線程中的代碼都肯定是按先后順序執(zhí)行的。但是由于同時運行著多個線程,所以你無法保障多個線程之間的代碼的先后順序。這對于需要處理同一個數(shù)據(jù)的邏輯來說,是一個非常嚴重的問題,最簡單的例子就是顯示某個新聞的閱讀量。兩個++操作同時運行,有可能結(jié)果只加了1,而不是2。所以多線程下,我們常常要加很多數(shù)據(jù)的鎖,而這些鎖又反過來可能導致線程的死鎖。 因此異步回調(diào)模型在隨后比多線程更加流行,除了多線程的死鎖問題外,異步還能解決多線程下,線程反復切換導致不必要的開銷的問題:每個線程都需要一個獨立的??臻g,在多線程并行運行的時候,這些棧的數(shù)據(jù)可能需要來回的拷貝,這額外消耗了CPU。同時由于每個線程都需要占用??臻g,所以在大量線程存在的時候,內(nèi)存的消耗也是巨大的。而異步回調(diào)模型則能很好的解決這些問題,不過異步回調(diào)更像是“手工版”的并行處理,需要開發(fā)者自己去實現(xiàn)如何“并行”的問題。 異步回調(diào)基于非阻塞的I/O操作(網(wǎng)絡(luò)和文件),這樣我們就不用在調(diào)用讀寫函數(shù)的時候“卡”在那一句函數(shù)調(diào)用,而是立刻返回“有無數(shù)據(jù)”的結(jié)果。而Linux的epoll技術(shù),則利用底層內(nèi)核的機制,讓我們可以快速的“查找”到有數(shù)據(jù)可以讀寫的連接\文件。由于每個操作都是非阻塞的,所以我們的程序可以只用一個進程,就處理大量并發(fā)的請求。因為只有一個進程,所以所有的數(shù)據(jù)處理,其順序都是固定的,不可能出現(xiàn)多線程中,兩個函數(shù)的語句交錯執(zhí)行的情況,因此也不需要各種“鎖”。從這個角度看,異步非阻塞的技術(shù),是大大簡化了開發(fā)的過程。由于只有一個線程,也不需要有線程切換之類的開銷,所以異步非阻塞成為很多對吞吐量、并發(fā)有較高要求的系統(tǒng)首選。 int epoll_create(int size);//創(chuàng)建一個epoll的句柄,size用來告訴內(nèi)核這個監(jiān)聽的數(shù)目一共有多大 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 緩沖技術(shù) 在互聯(lián)網(wǎng)服務中,大部分的用戶交互,都是需要立刻返回結(jié)果的,所以對于延遲有一定的要求。而類似網(wǎng)絡(luò)游戲之類服務,延遲更是要求縮短到幾十毫秒以內(nèi)。所以為了降低延遲,緩沖是互聯(lián)網(wǎng)服務中最常見的技術(shù)之一。 早期的WEB系統(tǒng)中,如果每個HTTP請求的處理,都去數(shù)據(jù)庫(MySQL)讀寫一次,那么數(shù)據(jù)庫很快就會因為連接數(shù)占滿而停止響應。因為一般的數(shù)據(jù)庫,支持的連接數(shù)都只有幾百,而WEB的應用的并發(fā)請求,輕松能到幾千。這也是很多設(shè)計不良的網(wǎng)站人一多就卡死的最直接原因。為了盡量減少對數(shù)據(jù)庫的連接和訪問,人們設(shè)計了很多緩沖系統(tǒng)——把從數(shù)據(jù)庫中查詢的結(jié)果存放到更快的設(shè)施上,如果沒有相關(guān)聯(lián)的修改,就直接從這里讀。 最典型的WEB應用緩沖系統(tǒng)是Memcache。由于PHP本身的線程結(jié)構(gòu),是不帶狀態(tài)的。早期PHP本身甚至連操作“堆”內(nèi)存的方法都沒有,所以那些持久的狀態(tài),就一定要存放到另外一個進程里。而Memcache就是一個簡單可靠的存放臨時狀態(tài)的開源軟件。很多PHP應用現(xiàn)在的處理邏輯,都是先從數(shù)據(jù)庫讀取數(shù)據(jù),然后寫入Memcache;當下次請求來的時候,先嘗試從Memcache里面讀取數(shù)據(jù),這樣就有可能大大減少對數(shù)據(jù)庫的訪問。 然而Memcache本身是一個獨立的服務器進程,這個進程自身并不帶特別的集群功能。也就是說這些Memcache進程,并不能直接組建成一個統(tǒng)一的集群。如果一個Memcache不夠用,我們就要手工用代碼去分配,哪些數(shù)據(jù)應該去哪個Memcache進程。——這對于真正的大型分布式網(wǎng)站來說,管理一個這樣的緩沖系統(tǒng),是一個很繁瑣的工作。 因此人們開始考慮設(shè)計一些更高效的緩沖系統(tǒng):從性能上來說,Memcache的每筆請求,都要經(jīng)過網(wǎng)絡(luò)傳輸,才能去拉取內(nèi)存中的數(shù)據(jù)。這無疑是有一點浪費的,因為請求者本身的內(nèi)存,也是可以存放數(shù)據(jù)的?!@就是促成了很多利用請求方內(nèi)存的緩沖算法和技術(shù),其中最簡單的就是使用LRU算法,把數(shù)據(jù)放在一個哈希表結(jié)構(gòu)的堆內(nèi)存中。 而Memcache的不具備集群功能,也是一個用戶的痛點。于是很多人開始設(shè)計,如何讓數(shù)據(jù)緩存分不到不同的機器上。最簡單的思路是所謂讀寫分離,也就是緩存每次寫,都寫到多個緩沖進程上記錄,而讀則可以隨機讀任何一個進程。在業(yè)務數(shù)據(jù)有明顯的讀寫不平衡差距上,效果是非常好的。 然而,并不是所有的業(yè)務都能簡單的用讀寫分離來解決問題,比如一些在線互動的互聯(lián)網(wǎng)業(yè)務,比如社區(qū)、游戲。這些業(yè)務的數(shù)據(jù)讀寫頻率并沒很大的差異,而且也要求很高的延遲。因此人們又再想辦法,把本地內(nèi)存和遠端進程的內(nèi)存緩存結(jié)合起來使用,讓數(shù)據(jù)具備兩級緩存。同時,一個數(shù)據(jù)不在同時的復制存在所有的緩存進程上,而是按一定規(guī)律分布在多個進程上?!@種分布規(guī)律使用的算法,最流行的就是所謂“一致性哈希”。這種算法的好處是,當某一個進程失效掛掉,不需要把整個集群中所有的緩存數(shù)據(jù),都重新修改一次位置。你可以想象一下,如果我們的數(shù)據(jù)緩存分布,是用簡單的以數(shù)據(jù)的ID對進程數(shù)取模,那么一旦進程數(shù)變化,每個數(shù)據(jù)存放的進程位置都可能變化,這對于服務器的故障容忍是不利的。 Orcale公司旗下有一款叫Coherence的產(chǎn)品,是在緩存系統(tǒng)上設(shè)計比較好的。這個產(chǎn)品是一個商業(yè)產(chǎn)品,支持利用本地內(nèi)存緩存和遠程進程緩存協(xié)作。集群進程是完全自管理的,還支持在數(shù)據(jù)緩存所在進程,進行用戶定義的計算(處理器功能),這就不僅僅是緩存了,還是一個分布式的計算系統(tǒng)。 存儲技術(shù)(NoSQL) 相信CAP理論大家已經(jīng)耳熟能詳,然而在互聯(lián)發(fā)展的早期,大家都還在使用MySQL的時候,如何讓數(shù)據(jù)庫存放更多的數(shù)據(jù),承載更多的連接,很多團隊都是絞盡腦汁。甚至于有很多業(yè)務,主要的數(shù)據(jù)存儲方式是文件,數(shù)據(jù)庫反而變成是輔助的設(shè)施了。 然而,當NoSQL興起,大家突然發(fā)現(xiàn),其實很多互聯(lián)網(wǎng)業(yè)務,其數(shù)據(jù)格式是如此的簡單,很多時候根部不需要關(guān)系型數(shù)據(jù)庫那種復雜的表格。對于索引的要求往往也只是根據(jù)主索引搜索。而更復雜的全文搜索,本身數(shù)據(jù)庫也做不到。所以現(xiàn)在相當多的高并發(fā)的互聯(lián)網(wǎng)業(yè)務,首選NoSQL來做存儲設(shè)施。最早的NoSQL數(shù)據(jù)庫有MangoDB等,現(xiàn)在最流行的似乎就是Redis了。甚至有些團隊,把Redis也當成緩沖系統(tǒng)的一部分,實際上也是認可Redis的性能優(yōu)勢。 NoSQL除了更快、承載量更大以外,更重要的特點是,這種數(shù)據(jù)存儲方式,只能按照一條索引來檢索和寫入。這樣的需求約束,帶來了分布上的好處,我們可以按這條主索引,來定義數(shù)據(jù)存放的進程(服務器)。這樣一個數(shù)據(jù)庫的數(shù)據(jù),就能很方便的存放在不同的服務器上。在分布式系統(tǒng)的必然趨勢下,數(shù)據(jù)存儲層終于也找到了分布的方法。 分布式系統(tǒng)在可管理性上造成的問題分布式系統(tǒng)并不是簡單的把一堆服務器一起運行起來就能滿足需求的。對比單機或少量服務器的集群,有一些特別需要解決的問題等待著我們。 硬件故障率 所謂分布式系統(tǒng),肯定就不是只有一臺服務器。假設(shè)一臺服務器的平均故障時間是1%,那么當你有100臺服務器的時候,那就幾乎總有一臺是在故障的。雖然這個比方不一定很準確,但是,當你的系統(tǒng)所涉及的硬件越來越多,硬件的故障也會從偶然事件變成一個必然事件。一般我們在寫功能代碼的時候,是不會考慮到硬件故障的時候應該怎么辦的。而如果在編寫分布式系統(tǒng)的時候,就一定需要面對這個問題了。否則,很可能只有一臺服務器出故障,整個數(shù)百臺服務器的集群都工作不正常了。 除了服務器自己的內(nèi)存、硬盤等故障,服務器之間的網(wǎng)絡(luò)線路故障更加常見。而且這種故障還有可能是偶發(fā)的,或者是會自動恢復的。面對這種問題,如果只是簡單的把“出現(xiàn)故障”的機器剔除出去,那還是不夠的。因為網(wǎng)絡(luò)可能過一會兒就又恢復了,而你的集群可能因為這一下的臨時故障,丟失了過半的處理能力。 如何讓分布式系統(tǒng),在各種可能隨時出現(xiàn)故障的情況下,盡量的自動維護和維持對外服務,成為了編寫程序就要考慮的問題。由于要考慮到這種故障的情況,所以我們在設(shè)計架構(gòu)的時候,也要有意識的預設(shè)一些冗余、自我維護的功能。這些都不是產(chǎn)品上的業(yè)務需求,完全就是技術(shù)上的功能需求。能否在這方面提出對的需求,然后正確的實現(xiàn),是服務器端程序員最重要的職責之一。 資源利用率優(yōu)化 在分布式系統(tǒng)的集群,包含了很多個服務器,當這樣一個集群的硬件承載能力到達極限的時候,最自然的想法就是增加更多的硬件。然而,一個軟件系統(tǒng)不是那么容易就可以通過“增加”硬件來提高承載性能的。因為軟件在多個服務器上的工作,是需要有復雜細致的協(xié)調(diào)工作。在對一個集群擴容的時候,我們往往會要停掉整個集群的服務,然后修改各種配置,最后才能重新啟動一個加入了新的服務器的集群。 由于在每個服務器的內(nèi)存里,都可能會有一些用戶使用的數(shù)據(jù),所以如果冒然在運行的時候,就試圖修改集群中提供服務的配置,很可能會造成內(nèi)存數(shù)據(jù)的丟失和錯誤。因此,運行時擴容在對無狀態(tài)的服務上,是比較容易的,比如增加一些Web服務器。但如果是在有狀態(tài)的服務上,比如網(wǎng)絡(luò)游戲,幾乎是不可能進行簡單的運行時擴容的。 分布式集群除了擴容,還有縮容的需求。當用戶人數(shù)下降,服務器硬件資源出現(xiàn)空閑的時候,我們往往需要這些空閑的資源能利用起來,放到另外一些新的服務集群里去??s容和集群中有故障需要容災有一定類似之處,區(qū)別是縮容的時間點和目標是可預期的。 由于分布式集群中的擴容、縮容,以及希望盡量能在線操作,這導致了非常復雜的技術(shù)問題需要處理,比如集群中互相關(guān)聯(lián)的配置如何正確高效的修改、如何對有狀態(tài)的進程進行操作、如何在擴容縮容的過程中保證集群中節(jié)點之間通信的正常。作為服務器端程序員,會需要花費大量的經(jīng)歷,來對多個進程的集群狀態(tài)變化,造成的一系列問題進行專門的開發(fā)。 軟件服務內(nèi)容更新 現(xiàn)在都流行用敏捷開發(fā)模式中的“迭代”,來表示一個服務不斷的更新程序,滿足新的需求,修正BUG。如果我們僅僅管理一臺服務器,那么更新這一臺服務器上的程序,是非常簡單的:只要把軟件包拷貝過去,然后修改下配置就好。但是如果你要對成百上千的服務器去做同樣的操作,就不可能每臺服務器登錄上去處理。 服務器端的程序批量安裝部署工具,是每個分布式系統(tǒng)開發(fā)者都需要的。然而,我們的安裝工作除了拷貝二進制文件和配置文件外,還會有很多其他的操作。比如打開防火墻、建立共享內(nèi)存文件、修改數(shù)據(jù)庫表結(jié)構(gòu)、改寫一些數(shù)據(jù)文件等等……甚至有一些還要在服務器上安裝新的軟件。 如果我們在開發(fā)服務器端程序的時候,就考慮到軟件更新、版本升級的問題,那么我們對于配置文件、命令行參數(shù)、系統(tǒng)變量的使用,就會預先做一定的規(guī)劃,這能讓安裝部署的工具運行更快,可靠性更高。 除了安裝部署的過程,還有一個重要的問題,就是不同版本間數(shù)據(jù)的問題。我們在升級版本的時候,舊版本程序生成的一些持久化數(shù)據(jù),一般都是舊的數(shù)據(jù)格式的;而我們升級版本中如果涉及修改了數(shù)據(jù)格式,比如數(shù)據(jù)表結(jié)果,那么這些舊格式的數(shù)據(jù),都要轉(zhuǎn)換改寫成新版本的數(shù)據(jù)格式才行。這導致了我們在設(shè)計數(shù)據(jù)結(jié)構(gòu)的時候,就要考慮清楚這些表格的結(jié)構(gòu),是用最簡單直接的表達方式,來讓將來的修改更簡單;還是一早就預計到修改的范圍,專門預設(shè)一些字段,或者使用其他形式存放數(shù)據(jù)。 除了持久化數(shù)據(jù)以外,如果存在客戶端程序(如受擊APP),這些客戶端程序的升級往往不能和服務器同步,如果升級的內(nèi)容包含了通信協(xié)議的修改,這就造成了我們必須為不同的版本部署不同的服務器端系統(tǒng)的問題。為了避免同時維護多套服務器,我們在軟件開發(fā)的時候,往往傾向于所謂“版本兼容”的協(xié)議定義方式。而怎樣設(shè)計的協(xié)議才能有很好的兼容性,又是服務器端程序需要仔細考慮的問題。 數(shù)據(jù)統(tǒng)計和決策 一般來說,分布式系統(tǒng)的日志數(shù)據(jù),都是被集中到一起,然后統(tǒng)一進行統(tǒng)計的。然而,當集群的規(guī)模到一定程度的時候,這些日志的數(shù)據(jù)量會變得非??植?。很多時候,統(tǒng)計一天的日志量,要消耗計算機運行一天以上的時間。所以,日志統(tǒng)計這項工作,也變成一門非常專業(yè)的活動。 經(jīng)典的分布式統(tǒng)計模型,有Google的Map Reduce模型。這種模型既有靈活性,也能利用大量服務器進行統(tǒng)計工作。但是缺點是易用性往往不夠好,因為這些數(shù)據(jù)的統(tǒng)計和我們常見的SQL數(shù)據(jù)表統(tǒng)計有非常大的差異,所以我們最后還是常常把數(shù)據(jù)丟到MySQL里面去做更細層面的統(tǒng)計。 由于分布式系統(tǒng)日志數(shù)量的龐大,以及日志復雜程度的提高。我們變得必須要掌握類似Map Reduce技術(shù),才能真正的對分布式系統(tǒng)進行數(shù)據(jù)統(tǒng)計。而且我們還需要想辦法提高統(tǒng)計工作的工作效率。 解決分布式系統(tǒng)可管理性的基本手段 目錄服務(ZooKeeper) 分布式系統(tǒng)是一個由很多進程組成的整體,這個整體中每個成員部分,都會具備一些狀態(tài),比如自己的負責模塊,自己的負載情況,對某些數(shù)據(jù)的掌握等等。而這些和其他進程相關(guān)的數(shù)據(jù),在故障恢復、擴容縮容的時候變得非常重要。 簡單的分布式系統(tǒng),可以通過靜態(tài)的配置文件,來記錄這些數(shù)據(jù):進程之間的連接對應關(guān)系,他們的IP地址和端口,等等。然而一個自動化程度高的分布式系統(tǒng),必然要求這些狀態(tài)數(shù)據(jù)都是動態(tài)保存的。這樣才能讓程序自己去做容災和負載均衡的工作。 一些程序員會專門自己編寫一個DIR服務(目錄服務),來記錄集群中進程的運行狀態(tài)。集群中進程會和這個DIR服務產(chǎn)生自動關(guān)聯(lián),這樣在容災、擴容、負載均衡的時候,就可以自動根據(jù)這些DIR服務里的數(shù)據(jù),來調(diào)整請求的發(fā)送目地,從而達到繞開故障機器、或連接到新的服務器的操作。 然而,如果我們只是用一個進程來充當這個工作。那么這個進程就成為了這個集群的“單點”——意思就是,如果這個進程故障了,那么整個集群可能都無法運行的。所以存放集群狀態(tài)的目錄服務,也需要是分布式的。幸好我們有ZooKeeper這個優(yōu)秀的開源軟件,它正是一個分布式的目錄服務區(qū)。 ZooKeeper可以簡單啟動奇數(shù)個進程,來形成一個小的目錄服務集群。這個集群會提供給所有其他進程,進行讀寫其巨大的“配置樹”的能力。這些數(shù)據(jù)不僅僅會存放在一個ZooKeeper進程中,而是會根據(jù)一套非常安全的算法,讓多個進程來承載。這讓ZooKeeper成為一個優(yōu)秀的分布式數(shù)據(jù)保存系統(tǒng)。 由于ZooKeeper的數(shù)據(jù)存儲結(jié)構(gòu),是一個類似文件目錄的樹狀系統(tǒng),所以我們常常會利用它的功能,把每個進程都綁定到其中一個“分枝”上,然后通過檢查這些“分支”,來進行服務器請求的轉(zhuǎn)發(fā),就能簡單的解決請求路由(由誰去做)的問題。另外還可以在這些“分支”上標記進程的負載的狀態(tài),這樣負載均衡也很容易做了。 目錄服務是分布式系統(tǒng)中最關(guān)鍵的組件之一。而ZooKeeper是一個很好的開源軟件,正好是用來完成這個任務。 消息隊列服務(ActiveMQ、ZeroMQ、Jgroups) 兩個進程間如果要跨機器通訊,我們幾乎都會用TCP/UDP這些協(xié)議。但是直接使用網(wǎng)絡(luò)API去編寫跨進程通訊,是一件非常麻煩的事情。除了要編寫大量的底層socket代碼外,我們還要處理諸如:如何找到要交互數(shù)據(jù)的進程,如何保障數(shù)據(jù)包的完整性不至于丟失,如果通訊的對方進程掛掉了,或者進程需要重啟應該怎樣等等這一系列問題。這些問題包含了容災擴容、負載均衡等一系列的需求。 為了解決分布式系統(tǒng)進程間通訊的問題,人們總結(jié)出了一個有效的模型,就是“消息隊列”模型。消息隊列模型,就是把進程間的交互,抽象成對一個個消息的處理,而對于這些消息,我們都有一些“隊列”,也就是管道,來對消息進行暫存。每個進程都可以訪問一個或者多個隊列,從里面讀取消息(消費)或?qū)懭胂ⅲㄉa(chǎn))。由于有一個緩存的管道,我們可以放心的對進程狀態(tài)進行變化。當進程起來的時候,它會自動去消費消息就可以了。而消息本身的路由,也是由存放的隊列決定的,這樣就把復雜的路由問題,變成了如何管理靜態(tài)的隊列的問題。 一般的消息隊列服務,都是提供簡單的“投遞”和“收取”兩個接口,但是消息隊列本身的管理方式卻比較復雜,一般來說有兩種。一部分的消息隊列服務,提倡點對點的隊列管理方式:每對通信節(jié)點之間,都有一個單獨的消息隊列。這種做法的好處是不同來源的消息,可以互不影響,不會因為某個隊列的消息過多,擠占了其他隊列的消息緩存空間。而且處理消息的程序也可以自己來定義處理的優(yōu)先級——先收取、多處理某個隊列,而少處理另外一些隊列。 但是這種點對點的消息隊列,會隨著集群的增長而增加大量的隊列,這對于內(nèi)存占用和運維管理都是一個復雜的事情。因此更高級的消息隊列服務,開始可以讓不同的隊列共享內(nèi)存空間,而消息隊列的地址信息、建立和刪除,都采用自動化的手段?!@些自動化往往需要依賴上文所述的“目錄服務”,來登記隊列的ID對應的物理IP和端口等信息。比如很多開發(fā)者使用ZooKeeper來充當消息隊列服務的中央節(jié)點;而類似Jgropus這類軟件,則自己維護一個集群狀態(tài)來存放各節(jié)點今昔。 另外一種消息隊列,則類似一個公共的郵箱。一個消息隊列服務就是一個進程,任何使用者都可以投遞或收取這個進程中的消息。這樣對于消息隊列的使用更簡便,運維管理也比較方便。不過這種用法下,任何一個消息從發(fā)出到處理,最少進過兩次進程間通信,其延遲是相對比較高的。并且由于沒有預定的投遞、收取約束,所以也比較容易出BUG。 不管使用那種消息隊列服務,在一個分布式服務器端系統(tǒng)中,進程間通訊都是必須要解決的問題,所以作為服務器端程序員,在編寫分布式系統(tǒng)代碼的時候,使用的最多的就是基于消息隊列驅(qū)動的代碼,這也直接導致了EJB3.0把“消息驅(qū)動的Bean”加入到規(guī)范之中。 事務系統(tǒng) 在分布式的系統(tǒng)中,事務是最難解決的技術(shù)問題之一。由于一個處理可能分布在不同的處理進程上,任何一個進程都可能出現(xiàn)故障,而這個故障問題則需要導致一次回滾。這種回滾大部分又涉及多個其他的進程。這是一個擴散性的多進程通訊問題。要在分布式系統(tǒng)上解決事務問題,必須具備兩個核心工具:一個是穩(wěn)定的狀態(tài)存儲系統(tǒng);另外一個是方便可靠的廣播系統(tǒng)。 事務中任何一步的狀態(tài),都必須在整個集群中可見,并且還要有容災的能力。這個需求,一般還是由集群的“目錄服務”來承擔。如果我們的目錄服務足夠健壯,那么我們可以把每步事務的處理狀態(tài),都同步寫到目錄服務上去。ZooKeeper再次在這個地方能發(fā)揮重要的作用。 如果事務發(fā)生了中斷,需要回滾,那么這個過程會涉及到多個已經(jīng)執(zhí)行過的步驟。也許這個回滾只需要在入口處回滾即可(加入那里有保存回滾所需的數(shù)據(jù)),也可能需要在各個處理節(jié)點上回滾。如果是后者,那么就需要集群中出現(xiàn)異常的節(jié)點,向其他所有相關(guān)的節(jié)點廣播一個“回滾!事務ID是XXXX”這樣的消息。這個廣播的底層一般會由消息隊列服務來承載,而類似Jgroups這樣的軟件,直接提供了廣播服務。 雖然現(xiàn)在我們在討論事務系統(tǒng),但實際上分布式系統(tǒng)經(jīng)常所需的“分布式鎖”功能,也是這個系統(tǒng)可以同時完成的。所謂的“分布式鎖”,也就是一種能讓各個節(jié)點先檢查后執(zhí)行的限制條件。如果我們有高效而單子操作的目錄服務,那么這個鎖狀態(tài)實際上就是一種“單步事務”的狀態(tài)記錄,而回滾操作則默認是“暫停操作,稍后再試”。這種“鎖”的方式,比事務的處理更簡單,因此可靠性更高,所以現(xiàn)在越來越多的開發(fā)人員,愿意使用這種“鎖”服務,而不是去實現(xiàn)一個“事務系統(tǒng)”。 自動部署工具(Docker) 由于分布式系統(tǒng)最大的需求,是在運行時(有可能需要中斷服務)來進行服務容量的變更:擴容或者縮容。而在分布式系統(tǒng)中某些節(jié)點故障的時候,也需要新的節(jié)點來恢復工作。這些如果還是像老式的服務器管理方式,通過填表、申報、進機房、裝服務器、部署軟件……這一套做法,那效率肯定是不行。 在分布式系統(tǒng)的環(huán)境下,我們一般都是采用“池”的方式來管理服務。我們預先會申請一批機器,然后在某些機器上運行服務軟件,另外一些則作為備份。顯然我們這一批服務器不可能只為某一個業(yè)務服務,而是會提供多個不同的業(yè)務承載。那些備份的服務器,則會成為多個業(yè)務的通用備份“池”。隨著業(yè)務需求的變化,一些服務器可能“退出”A服務而“加入”B服務。 這種頻繁的服務變化,依賴高度自動的軟件部署工具。我們的運維人員,應該掌握這開發(fā)人員提供的部署工具,而不是厚厚的手冊,來進行這類運維操作。一些比較有經(jīng)驗的開發(fā)團隊,會統(tǒng)一所有的業(yè)務底層框架,以期大部分的部署、配置工具,都能用一套通用的系統(tǒng)來進行管理。而開源界,也有類似的嘗試,最廣為人知的莫過于RPM安裝包格式,然而RPM的打包方式還是太復雜,不太符合服務器端程序的部署需求。所以后來又出現(xiàn)了Chef為代表的,可編程的通用部署系統(tǒng)。 然而,當NoSQL興起,大家突然發(fā)現(xiàn),其實很多互聯(lián)網(wǎng)業(yè)務,其數(shù)據(jù)格式是如此的簡單,很多時候根部不需要關(guān)系型數(shù)據(jù)庫那種復雜的表格。對于索引的要求往往也只是根據(jù)主索引搜索。而更復雜的全文搜索,本身數(shù)據(jù)庫也做不到。所以現(xiàn)在相當多的高并發(fā)的互聯(lián)網(wǎng)業(yè)務,首選NoSQL來做存儲設(shè)施。最早的NoSQL數(shù)據(jù)庫有MangoDB等,現(xiàn)在最流行的似乎就是Redis了。甚至有些團隊,把Redis也當成緩沖系統(tǒng)的一部分,實際上也是認可Redis的性能優(yōu)勢。 NoSQL除了更快、承載量更大以外,更重要的特點是,這種數(shù)據(jù)存儲方式,只能按照一條索引來檢索和寫入。這樣的需求約束,帶來了分布上的好處,我們可以按這條主索引,來定義數(shù)據(jù)存放的進程(服務器)。這樣一個數(shù)據(jù)庫的數(shù)據(jù),就能很方便的存放在不同的服務器上。在分布式系統(tǒng)的必然趨勢下,數(shù)據(jù)存儲層終于也找到了分布的方法。 為了管理大量的分布式服務器端進程,我們確實需要花很多功夫,其優(yōu)化其部署管理的工作。統(tǒng)一服務器端進程的運行規(guī)范,是實現(xiàn)自動化部署管理的基本條件。我們可以根據(jù)“操作系統(tǒng)”作為規(guī)范,采用Docker技術(shù);也可以根據(jù)“Web應用”作為規(guī)范,采用某些PaaS平臺技術(shù);或者自己定義一些更具體的規(guī)范,自己開發(fā)完整的分布式計算平臺。 日志服務(log4j) 服務器端的日志,一直是一個既重要又容易被忽視的問題。很多團隊在剛開始的時候,僅僅把日志視為開發(fā)調(diào)試、排除BUG的輔助工具。但是很快會發(fā)現(xiàn),在服務運營起來之后,日志幾乎是服務器端系統(tǒng),在運行時可以用來了解程序情況的唯一有效手段。 盡管我們有各種profile工具,但是這些工具大部分都不適合在正式運營的服務上開啟,因為會嚴重降低其運行性能。所以我們更多的時候需要根據(jù)日志來分析。盡管日志從本質(zhì)上,就是一行行的文本信息,但是由于其具有很大的靈活性,所以會很受開發(fā)和運維人員的重視。 日志本身從概念上,是一個很模糊的東西。你可以隨便打開一個文件,然后寫入一些信息。但是現(xiàn)代的服務器系統(tǒng),一般都會對日志做一些標準化的需求規(guī)范:日志必須是一行一行的,這樣比較方便日后的統(tǒng)計分析;每行日志文本,都應該有一些統(tǒng)一的頭部,比如日期時間就是基本的需求;日志的輸出應該是分等級的,比如fatal/error/warning/info/debug/trace等等,程序可以在運行時調(diào)整輸出的等級,以便可以節(jié)省日志打印的消耗;日志的頭部一般還需要一些類似用戶ID或者IP地址之類的頭信息,用于快速查找定位過濾某一批日志記錄,或者有一些其他的用于過濾縮小日志查看范圍的字段,這叫做染色功能;日志文件還需要有“回滾”功能,也就是保持固定大小的多個文件,避免長期運行后,把硬盤寫滿。 由于有上述的各種需求,所以開源界提供了很多游戲的日志組件庫,比如大名鼎鼎的log4j,以及成員眾多的log4X家族庫,這些都是應用廣泛而飽受好評的工具。 不過對比日志的打印功能,日志的搜集和統(tǒng)計功能卻往往比較容易被忽視。作為分布式系統(tǒng)的程序員,肯定是希望能從一個集中節(jié)點,能搜集統(tǒng)計到整個集群日志情況。而有一些日志的統(tǒng)計結(jié)果,甚至希望能在很短時間內(nèi)反復獲取,用來監(jiān)控整個集群的健康情況。要做到這一點,就必須有一個分布式的文件系統(tǒng),用來存放源源不斷到達的日志(這些日志往往通過UDP協(xié)議發(fā)送過來)。而在這個文件系統(tǒng)上,則需要有一個類似Map Reduce架構(gòu)的統(tǒng)計系統(tǒng),這樣才能對海量的日志信息,進行快速的統(tǒng)計以及報警。有一些開發(fā)者會直接使用Hadoop系統(tǒng),有一些則用Kafka來作為日志存儲系統(tǒng),上面再搭建自己的統(tǒng)計程序。 日志服務是分布式運維的儀表盤、潛望鏡。如果沒有一個可靠的日志服務,整個系統(tǒng)的運行狀況可能會是失控的。所以無論你的分布式系統(tǒng)節(jié)點是多還是少,必須花費重要的精力和專門的開發(fā)時間,去建立一個對日志進行自動化統(tǒng)計分析的系統(tǒng)。 分布式系統(tǒng)在開發(fā)效率上造成的問題和解決思路根據(jù)上文所述,分布式系統(tǒng)在業(yè)務需求的功能以為,還需要增加額外很多非功能的需求。這些非功能需求,往往都是為了一個多進程系統(tǒng)能穩(wěn)定可靠運行而去設(shè)計和實現(xiàn)的。這些“額外”的工作,一般都會讓你的代碼更加復雜,如果沒有很好的工具,就會讓你的開發(fā)效率嚴重下降。 微服務框架:EJB、WebService 當我們在討論服務器端軟件分布的時候,服務進程之間的通信就難免了。然而服務進程間的通訊,并不是簡單的收發(fā)消息就能完成的。這里還涉及了消息的路由、編碼解碼、服務狀態(tài)的讀寫等等。如果整個流程都由自己開發(fā),那就太累人了。 所以業(yè)界很早就推出了各種分布式的服務器端開發(fā)框架,最著名的就是“EJB”——企業(yè)JavaBean。但凡冠以“企業(yè)”的技術(shù),往往都是分布式下所需的部分,而EJB這種技術(shù),也是一種分布式對象調(diào)用的技術(shù)。我們?nèi)绻枰尪鄠€進程合作完成任務,則需要把任務分解到多個“類”上,然后這些“類”的對象就會在各個進程容器中存活,從而協(xié)作提供服務。這個過程很“面向?qū)ο蟆?。每個對象都是一個“微服務”,可以提供某些分布式的功能。 而另外一些系統(tǒng),則走向?qū)W習互聯(lián)網(wǎng)的基本模型:HTTP。所以就有了各種的WebService框架,從開源的到商業(yè)軟件,都有各自的WebService實現(xiàn)。這種模型,把復雜的路由、編解碼等操作,簡化成常見的一次HTTP操作,是一種非常有效的抽象。開發(fā)人員開發(fā)和部署多個WebService到Web服務器上,就完成了分布式系統(tǒng)的搭建。 不管我們是學習EJB還是WebService,實際上我們都需要簡化分布式調(diào)用的復雜程度。而分布式調(diào)用的復雜之處,就是因為需要把容災、擴容、負載均衡等功能,融合到跨進程調(diào)用里。所以使用一套通用的代碼,來為所有的跨進程通訊(調(diào)用),統(tǒng)一的實現(xiàn)容災、擴容、負載均衡、過載保護、狀態(tài)緩存命中等等非功能性需求,能大大簡化整個分布式系統(tǒng)的復雜性。 一般我們的微服務框架,都會在路由階段,對整個集群所有節(jié)點的狀態(tài)進行觀察,如哪些地址上運行了哪些服務的進程,這些服務進程的負載狀況如何,是否可用,然后對于有狀態(tài)的服務,還會使用類似一致性哈希的算法,去盡量試圖提高緩存的命中率。當集群中的節(jié)點狀態(tài)發(fā)生變化的時候,微服務框架下的所有節(jié)點,都能盡快的獲得這個變化的情況,從新根據(jù)當前狀態(tài),重新規(guī)劃以后的服務路由方向,從而實現(xiàn)自動化的路由選擇,避開那些負載過高或者失效的節(jié)點。 有一些微服務框架,還提供了類似IDL轉(zhuǎn)換成“骨架”、“樁”代碼的工具,這樣在編寫遠程調(diào)用程序的時候,完全無需編寫那些復雜的網(wǎng)絡(luò)相關(guān)的代碼,所有的傳輸層、編碼層代碼都自動的編寫好了。這方面EJB、Facebook的Thrift,Google gRPC都具備這種能力。在具備代碼生成能力的框架下,我們編寫一個分布式下可用的功能模塊(可能是一個函數(shù)或者是一個類),就好像編寫一個本地的函數(shù)那樣簡單。這絕對是分布式系統(tǒng)下非常重要的效率提升。 異步編程工具:協(xié)程、Futrue、Lamda 在分布式系統(tǒng)中編程,你不可避免的會碰到大量的“回調(diào)”型API。因為分布式系統(tǒng)涉及非常多的網(wǎng)絡(luò)通信。任何一個業(yè)務命令,都可能被分解到多個進程,通過多次網(wǎng)絡(luò)通信來組合完成。由于異步非阻塞的編程模型大行其道,所以我們的代碼也往往動不動就要碰到“回調(diào)函數(shù)”。然而,回調(diào)這種異步編程模型,是一種非常不利于代碼閱讀的編程方法。因為你無法從頭到尾的閱讀代碼,去了解一個業(yè)務任務,是怎樣被逐步的完成的。屬于一個業(yè)務任務的代碼,由于多次的非阻塞回調(diào),從而被分割成很多個回調(diào)函數(shù),在代碼的各處被串接起來。 更有甚者,我們有時候會選擇使用“觀察者模式”,我們會在一個地方注冊大量的“事件-響應函數(shù)”,然后在所有需要回調(diào)的地方,都發(fā)出一個事件。——這樣的代碼,比單純的注冊回調(diào)函數(shù)更難理解。因為事件對應的響應函數(shù),通常在發(fā)出事件處是無法找到的。這些函數(shù)永遠都會放在另外的一些文件里,而且有時候這些函數(shù)還會在運行時改變。而事件名字本身,也往往是匪夷所思難以理解的,因為當你的程序需要成千上百的事件的時候,起一個容易理解名符其實的名字,幾乎是不可能的。 為了解決回調(diào)函數(shù)這種對于代碼可讀性的破壞作用,人們發(fā)明了很多不同的改進方法。其中最著名的是“協(xié)程”。我們以前常常習慣于用多線程來解決問題,所以非常熟悉以同步的方式去寫代碼。協(xié)程正是延續(xù)了我們的這一習慣,但不同于多線程的是,協(xié)程并不會“同時”運行,它只是在需要阻塞的地方,用Yield()切換出去執(zhí)行其他協(xié)程,然后當阻塞結(jié)束后,用Resume()回到剛剛切換的位置繼續(xù)往下執(zhí)行。這相當于我們可以把回調(diào)函數(shù)的內(nèi)容,接到Y(jié)ield()調(diào)用的后面。這種編寫代碼的方法,非常類似于同步的寫法,讓代碼變得非常易讀。但是唯一的缺點是,Resume()的代碼還是需要在所謂“主線程”中運行。用戶必須自己從阻塞恢復的時候,去調(diào)用Resume()。協(xié)程另外一個缺點,是需要做棧保存,在切換到其他協(xié)程之后,棧上的臨時變量,也都需要額外占用空間,這限制了協(xié)程代碼的寫法,讓開發(fā)者不能用太大的臨時變量。 而另外一種改善回調(diào)函數(shù)的寫法,往往叫做Future/Promise模型。這種寫法的基本思路,就是“一次性把所有回調(diào)寫到一起”。這是一個非常實用的編程模型,它沒有讓你去徹底干掉回調(diào),而是讓你可以把回調(diào)從分散各處,集中到一個地方。在同一段代碼中,你可以清晰的看到各個異步的步驟是如何串接、或者并行執(zhí)行的。 最后說一下lamda模型,這種寫法流行于js語言的廣泛應用。由于在其他語言中,定一個回調(diào)函數(shù)是非常費事的:Java語言要設(shè)計一個接口然后做一個實現(xiàn),簡直是五星級的費事程度;C/C++支持函數(shù)指針,算是比較簡單,但是也很容易導致代碼看不懂;腳本語言相對好一些,也要定義個函數(shù)。而直接在調(diào)用回調(diào)的地方,寫回調(diào)函數(shù)的內(nèi)容,是最方便開發(fā),也比較利于閱讀的。更重要的,lamda一般意味著閉包,也就是說,這種回調(diào)函數(shù)的調(diào)用棧,是被分別保存的,很多需要在異步操作中,需要建立一個類似“會話池”的狀態(tài)保存變量,在這里都是不需要的,而是可以自然生效的。這一點和協(xié)程有異曲同工之妙。 不管使用哪一種異步編程方式,其編碼的復雜度,都是一定比同步調(diào)用的代碼高的。所以我們在編寫分布式服務器代碼的時候,一定要仔細規(guī)劃代碼結(jié)構(gòu),避免出現(xiàn)隨意添加功能代碼,導致代碼的可讀性被破壞的情況。不可讀的代碼,就是不可維護的代碼,而大量異步回調(diào)的服務器端代碼,是更容易出現(xiàn)這種情況的。 云服務模型:IaaS/PaaS/SaaS 在復雜的分布式系統(tǒng)開發(fā)和使用過程中,如何對大量服務器和進程的運維,一直是一個貫穿其中的問題。不管是使用微服務框架、還是統(tǒng)一的部署工具、日志監(jiān)控服務,都是因為大量的服務器,要集中的管理,是非常不容易的。這里背后的原因,主要是大量的硬件和網(wǎng)絡(luò),把邏輯上的計算能力,切割成很多小塊。 隨著計算機運算能力的提升,出現(xiàn)的虛擬化技術(shù),卻能把被分割的計算單元,更智能的統(tǒng)一起來。其中最常見的就是IaaS技術(shù):當我們可以用一個服務器硬件,運行多個虛擬的服務器操作系統(tǒng)的時候,我們需要維護的硬件數(shù)量就會成倍的下降。而PaaS技術(shù)的流行,讓我們可以為某一種特定的編程模型,統(tǒng)一的進行系統(tǒng)運行環(huán)境的部署維護。而不需要再一臺臺服務器的去裝操作系統(tǒng)、配置運行容器、上傳運行代碼和數(shù)據(jù)。在沒有統(tǒng)一的PaaS之前,安裝大量的MySQL數(shù)據(jù)庫,曾經(jīng)是消耗大量時間和精力的工作。 當我們的業(yè)務模型,成熟到可以抽象為一些固定的軟件時,我們的分布式系統(tǒng)就會變得更加易用。我們的計算能力不再是代碼和庫,而是一個個通過網(wǎng)絡(luò)提供服務的云——SaaS,這樣使用者根本來維護、部署的工作都不需要,只要申請一個接口,填上預期的容量額度,就能直接使用了。這不僅節(jié)省了大量開發(fā)對應功能的事件,還等于把大量的運維工作,都交出去給SaaS的維護者——而他們做這樣的維護會更加專業(yè)。 在運維模型的進化上,從IaaS到PaaS到SaaS,其應用范圍也許是越來越窄,但使用的便利性卻成倍的提高。這也證明了,軟件勞動的工作,也是可以通過分工,向更專業(yè)化、更細分的方向去提高效率。 總結(jié)分布式系統(tǒng)問題的解決路徑 針對服務器承載能力的問題,騰訊WeTest運用了沉淀十多年的內(nèi)部實踐經(jīng)驗總結(jié),通過基于真實業(yè)務場景和用戶行為進行壓力測試,幫助游戲開發(fā)者發(fā)現(xiàn)服務器端的性能瓶頸,進行針對性的性能調(diào)優(yōu),降低服務器采購和維護成本,提高用戶留存和轉(zhuǎn)化率。 |
|