也談如何構建高性能服務端程序
當前位置:點晴教程→知識管理交流
→『 技術文檔交流 』
引子 我接觸過很多編程語言,接觸過各種各樣的服務器端開發,Java,Go,Ruby,Javascript等語言,Spring,Node.js,Rails等等常見服務器端框架和編程模型都有接觸。這里談一下我個人對高性能服務器端程序的一些看法,希望給各位讀者一些認識。這片文章提到的內容也是 Coding(https://coding.net) 代碼托管乃至整站都在使用的一些概念和技術。 此外,閱讀這篇文章,有如下幾個前提:不談硬件,不評論編程語言以及框架的好壞,不談高級算法,可拍磚,拒絕噴子 三個關鍵詞 Cache,Asynchronous,Concurrent CacheCache 翻譯成中文就是緩存,臺灣的叫法叫做快取,其本質是將獲取緩慢或者計算緩慢的數據結果暫時存儲起來,以便以后再次獲取或者計算同樣的數據可以直接從存儲中取得結果,從而可能提升性能的一種手段。Cache 最早是應用在計算機的 CPU 中,這篇文章不談硬件,所以有需要了解 CPU 的緩存的同學可自行搜索。 可以想象,如果讓一個人一遍一遍的從 1+2+3+4+…+99+100=? 這樣去算,他加到最后發現等于5050,而這個過程耗費了他大量的時間,耗費了大量的腦力,在此期間,他可能把所有精力都放在這個計算上面而無暇顧及其他事情。等到他累得滿頭大汗,加完了結果,他告訴你是 5050。沒過多久,你又讓他做同樣的事情,我相信這家伙會不加思索的再次告訴你 5050。為什么?你會笑我說,人又不是傻子,這為同學肯定記得這個結果是5050啊。 可是,計算機不一樣,計算機就是你上面要嘲笑的那個傻子,他傻到,完全不會記得剛在做了什么事情,他會傻乎乎的再重新算一遍告訴你結果。沒錯如果你問他一萬遍,這頭沒有腦子的機器會算一萬遍的。雖然上面這個從1加到100這個例子對于一款現代化的計算機來講簡直是小菜一碟,但是計算機往往面臨的計算難題是我們人類所無法企及的。 Cache 就是為了來解決這個事情的,因為事情往往是這樣的:你會發現一些非常復雜的過程的計算結果是可重用的,而且把這個結果暫時存儲在某些地方,查找起來也是極為方便的。 所以,現在你理解了緩存,那可以來思考一些緩存的設計策略了。這里做一點說明,不同的緩存策略跟具體的業務系統關系非常大,制定緩存策略需要根據具體的情況來分析。常用的策略:
不知不覺中,你有沒有發現,1+2+3+4+…+99+100=5050 是個永遠都成立的事實,這也就意味著,它永遠不用被清除。可事實是往往是,緩存是有有效期的,例如需要緩存今天的天氣情況,今天是 2014年11月16日,到了明天就是 11月17日,天氣就不一樣了。再例如需要緩存 Coding 的最新冒泡列表,當有人發布了新的冒泡,那么這個列表就得被更新。從這個角度來看,緩存的策略又有如下常見的幾種:
嗯,既然提到了緩存的更新或者清除,那么就牽扯到緩存的更新策略。例子永遠好過大段的理論:假如我們要緩存 Coding 的冒泡列表。有這么一種策略:當用戶請求時我們檢查下是否已存在這樣的緩存,如果有直接返回緩存數據,否則我們生成這個列表(計算機的計算過程),返回給用戶并且把冒泡列表(計算結果)存儲起來,以便以后的用戶訪問時直接獲取。當用戶發布了一個新的冒泡的時候,我們清除這個緩存,再有用戶請求時將重復以上過程。這是其中一種完整的緩存清除策略。另外一種是,每當我們收到一個用戶發布的冒泡時,都重新構建這個緩存,用戶每次查看冒泡列表都是取的緩存數據。這兩種緩存分別稱之為:
關于 Cache 還有很多很多需要注意和設計上的思路和策略,這里不再一一贅述。這些緩存在不同的維度有不同的策略,我們需要根據具體的業務情況來選擇合適的策略。Coding 的很多業務中使用了上述很多種策略,例如我們常見的分支列表和標簽列表就是使用觸發式失效緩存,我們的廣場項目列表就是使用主動式緩存構建。 Asynchronous Asynchronous 的意思是異步。什么是異步呢?就是不在第一時間告知調用者結果,告訴他我已經收到這個任務了,我會處理,處理完畢后通知你結果,如果你不是等不到結果就無法進行下去的話,你完全可以先干別的事情。 服務端程序設計往往也是這樣,在你等待一個很緩慢的過程的時候,如果你不是必須要得到這個過程的結果才能繼續下去,你完全可以先進行別的過程,等到那個緩慢的過程執行完畢后,它會通知你結果的。 異步已經在現在的各種編程領域有了很廣泛的應用,例如 Ajax 技術,就是一種異步的手段,在瀏覽器和服務器交互的時候,完全不影響你在網頁上的其他操作。 異步在各種編程語言和框架中都有相應的支持,這里簡單介紹一下 Javascript 的異步支持。熟悉它的人的人請無視這段。它使用回調的方式支持異步,大致意思是,A 交代給 B 一個任務,并且告知 B 任務完成后繼續執行哪段程序(往往包裝成一個匿名function),B執行完任務后,執行這個匿名的 function,這樣來完成異步過程。在 Javascript 中大量的使用這種回調的異步方案,已經不再局限于對一個緩慢的過程了,可以對幾乎所有的過程都采用異步處理。 在服務端程序中,除了使用線程,協程,回調之外,另外一種常見的異步的支持方式就是消息隊列。其原理是,生產者發送消息到消息隊列中,消費者從中取出消息,做出相應處理,并把結果存儲起來或者通過某種方式告知生產者。 異步在很多時候可以運用現代化計算機 CPU 的多核特性和分布式計算特性,能顯著的提升應用的性能,但是一個前提就是,異步的任務的結果必須是主進程進行下一步操作所不依賴的,否則主進程必須等待,直到這個任務執行結束,拿到結果再進行下一步,這時就變成了傳統的同步計算了。 異步操作在 Coding 中也有非常廣泛的應用。例如當用戶執行完一次 Push,Coding 需要生成一條 Push 的動態,需要清理掉相應的緩存,需要觸發相關的 WebHook 等等,這些操作都是通過消息隊列來異步完成的。因為這些操作非常的耗時,而且完全不需要即時完成,所以用戶在 Push 的時候等待著這些操作完成是很不合理的。異步操作在這里即展示出了其應用多核和多臺服務器的優勢,在某種程度上還能提升用戶體驗。 Golang 是 Google 2009 年發布的一門現代化語言,其語言特性對異步提供了良好的支持。這里舉個例子體現一下異步的魅力: //一個結構體 這一段程序涉及到了 Golang 的 goroutine 和 channel,不了解的可以去查一下相關資料。 ConcurrentConcurrent 的意思是并行。現代化的 CPU 往往具有多個核心,而且有些 CPU 也具有超線程能力。如果我們可以將單個過程拆分成小的任務,交給 CPU 的多個核心,或者是分布式計算系統的多個計算節點,就可以充分利用并行計算來提升性能。前提是這些任務相互之間不要有相互依賴的關系。依然是例子:需要計算網站上某一批用戶的活躍度積分,傳統的,我們會查出這一批用戶,然后寫一個循環,然后輪流計算他們的積分,最后得到結果。其實每個用戶的積分的計算都是獨立的,相互不依賴,那么我們就可以利用這一點來并行化這個計算。 下面給出一段 Coding 代碼托管中的程序,這段程序是指定條件獲取一個提交列表,使用了并行計算的一種 并發循環: public List<Commit> getCommits(String objectId, String path, int offset, int maxCount) { 這段程序是一個并發循環的例子,例子中需要根據一些參數查詢到 Commit 的列表,而 repo.getCommit 這個過程完全不需要一個一個輪流查詢,因為他們是完全獨立的,所以可以使用 Java 的 Cocurrent 包來做并發循環,充分利用多核來盡快得到執行結果。 總結 關于高性能服務器程序需要關注的點還有很多,這里只是簡單的介紹了下三個利器(Cache,Asynchronous,Concurrent)。而即便是這三個利器,我的介紹也只是冰山一角,但是請相信你看懂了我介紹的這些東西,重新去思考服務端編程會獲得不少收獲的。 最后再給一些小提示:
該文章在 2014/12/2 23:51:37 編輯過 |
關鍵字查詢
相關文章
正在查詢... |