操作系統(tǒng)原理

第二個層面我們談?wù)劜僮飨到y(tǒng)原理。這塊最核心的就是線程和進程之間的通訊,這個通訊包括互斥、同步、消息。大家經(jīng)常會接觸到互斥。只要有共享變量就一定會有鎖。在Go語言的服務(wù)器開發(fā),很難避開鎖。為什么呢?因為服務(wù)器本身是其實是有很多請求同時在響應的,服務(wù)器本身就是共享資源,既然是共享資源,那么必然是有鎖的。

這里我話外要提一提的是 Erlang。Erlang里面很多人會說它沒有鎖。我一直有個看法,不是因為Erlang是函數(shù)式程序設(shè)計語言,它沒有變量,所以沒有鎖。只要是服務(wù)器,有很多并發(fā)的請求,那么服務(wù)器就一定是共享資源,這個是物理事實,是不可改變的。為什么Erlang可以沒有鎖,原因是因為Erlang強制讓所有的請求排隊了。排隊其實就是單線程化,那當然沒有鎖的,在C里面,在Go里面都可以這么做,所以這并不奇怪。因此,本質(zhì)上來講,并不是因為它是函數(shù)式程序設(shè)計語言,而是因為它把請求串行化,也就是說不并發(fā)。那怎么并發(fā)呢?Erlang里面想要并發(fā),其實是用異步消息,也就是將消息發(fā)出去,讓別人做,自己繼續(xù)往下執(zhí)行。這樣就涉及到的異步編程,這些我今天不展開講。但是我認為,本質(zhì)上來講,服務(wù)器編程其實互斥是難以避免的,因此,Golang服務(wù)器runtim.GOMAXPROCS(1)將程序設(shè)為單線程后,仍然需要鎖,單線程!=所有請求串行化處理。而鎖主要存在以下幾個問題。

1. 鎖最大的問題:不易控制

很多人會因為慢而避開鎖,其實這樣做是錯誤的。大部分框架想避開鎖并不是因為鎖慢,而是不易控制,主要表現(xiàn)為如果鎖忘記了Unlock,結(jié)果將是災難性的,因為不止是該請求掛掉,而是整個服務(wù)器掛掉了,所有的請求都會被這個鎖擋在外面。如果Lock和Unlock不匹配,將因為一個請求而導致所有人均受影響。

2. 鎖的次要問題:性能殺手

鎖雖然會導致代碼串行化執(zhí)行,但鎖并不是特別慢。因為線程之間的通訊,它有其他原語,如同步、收發(fā)消息,這些都是比鎖慢很多的原語。網(wǎng)絡(luò)上有部分人用Golang的Channel實現(xiàn)鎖,這很不正確。因為Channel就是線程之間發(fā)消息,成本比鎖高很多。比鎖快的東西,一是沒有鎖,二是原子操作。其中,原子操作并未比鎖快很多,因為如果在沖突不多的情況下,一個鎖基本上就是一個原子操作,發(fā)現(xiàn)沒有沖突,直接繼續(xù)執(zhí)行。所以鎖的成本并沒有像大家想象的那么高,尤其是在服務(wù)端,因為服務(wù)端絕大部分應用的程序其實是IO比較多,更多的時間是花在IO上面的。

在鎖的最佳實踐里面,核心是控制鎖的粒度。如果鎖的粒度太大,例如把某一個IO操作給包進去了,那這個鎖就比較災難了。比如這個IO操作是操作數(shù)據(jù)庫,那么這個鎖把數(shù)據(jù)庫的操作,請求和返回結(jié)果這樣一個操作包進去了,那這個鎖的粒度就很大,就會導致很多人都會被擋在外面。這個是鎖粒度的問題。這也是鎖里面比較難控制的一個點。

在鎖的最佳實踐里面,第一點是要懂得善用defer。在Go里面有一點是比較好的,Go語言里面有defer,容易讓你避免鎖的Lock和Unlock不匹配的問題,可以大大降低用鎖的心智負擔。但濫用defer可能會導致鎖的粒度變得很大,因為你可能在函數(shù)的開始就Lock,然后defer Unlock,這樣整個函數(shù)的執(zhí)行全都被鎖,函數(shù)里面只要有時間較長的IO操作,服務(wù)器的性能就會下降。這是鎖需要注意的地方。

另外,鎖的最佳實踐中,第二點是要善用讀寫鎖。絕大部分服務(wù)器里面,尤其是一些請求量比較大的請求,大部分請求的讀操作居多而寫操作較少,這種情況下用讀寫鎖是非常好的方法,可以大大降低鎖的成本。另外一個降低鎖粒度的方法是鎖數(shù)組。鎖數(shù)組是用于什么場景呢?如果服務(wù)器共享資源本身有很強的分區(qū)特征,那么用鎖數(shù)組比較好。例如你要做一個網(wǎng)盤服務(wù),不同用戶之間的數(shù)據(jù)沒有關(guān)系,網(wǎng)盤就是一個文件系統(tǒng),它是樹型結(jié)構(gòu),這個樹型結(jié)構(gòu)的操作往往需要較高的一致性的要求,不能出現(xiàn)操作到一半被另外一個操作給中斷,導致文件系統(tǒng)的樹結(jié)構(gòu)被破壞。所以在網(wǎng)盤里面更有可能出現(xiàn)包含了IO操作的大鎖,這種情況下,如果某個用戶的一次網(wǎng)盤同步操作會影響其他用戶就會很難受。因此,在網(wǎng)盤服務(wù)的一個系統(tǒng)里,用鎖數(shù)組會比較自然,你可以直接用用戶的ID除以鎖數(shù)組的數(shù)組大小然后取模,數(shù)組的大小決定于服務(wù)的并發(fā)量有多大,選一個合適的值就好。這樣可以讓不同的用戶相互不干擾,同一個用戶只影響他自己。

我認為,掌握好與鎖相關(guān)的技術(shù),基本上是將服務(wù)器里面很可能最大的一個坑給解決了。線程間其他的通訊,比如說同步、消息相關(guān)的坑相對少。例如,Go語言的channel實際上非常好用,既可以作為同步原語,也可以作為收發(fā)消息的原語。channel唯一一個需要注意的,channel是有緩沖區(qū)大小的,所以如果不設(shè)緩沖區(qū)的話,有一個goroutine發(fā)消息,另一個goroutine如果沒有及時接收的話,發(fā)消息的那個goroutine就阻塞了。但是這個其實也很容易就能找到問題,所以這個問題不是很大。但是要注意,channel不是唯一的同步原語。Go語言里面其實同步原語還是蠻多的。比如說Group,這是一個很好用的同步原語,它是用來干嗎的呢?它是讓很多人一起干做某件事情,然后最后在某一個總控的地方等所有的人干完,然后繼續(xù)往下走的一個原語。另外一個就是Cond原語,Cond其實用得不多,原因是channel把大部分Cond適用的場景給滿足了。但是作為操作系統(tǒng)原理中經(jīng)常提的生產(chǎn)者消費者模型里面最重要的一個原語,了解它是很重要的。因為channel這樣一個通訊設(shè)施,它背后其實是可以認為就是用Cond實現(xiàn)的。而Cond它要比channel原始很多,應用范疇也要廣得多。我今天不展開講Cond了,大家要感興趣,可以翻一翻操作系統(tǒng)原理相關(guān)的書。

存儲系統(tǒng)原理

七牛就是做存儲的。我覺得存儲這個東西對服務(wù)端開發(fā)來說很重要。為什么呢?因為實際上服務(wù)器端開發(fā)的難度原理上比大家想象得要大,之所以今天大家不會覺得特別特別累,就是因為有存儲中間件。存儲是什么東西呢?存儲其實是狀態(tài)的維持者,存儲它本身不是問題,但是有了服務(wù)器之后,它就是問題。因為大家在桌面端,大家知道存儲的要求不高的,文件系統(tǒng)就是一個存儲,那它放圖片或者放什么,丟了就丟了,也沒有多少操作系統(tǒng)關(guān)心它丟了會怎么樣。但是在服務(wù)器端大家都知道,服務(wù)必須邏輯上是不宕機的。也就意味著狀態(tài)維持的人是不能掛掉的。物理的服務(wù)器肯定是會掛掉的,但是哪怕物理服務(wù)器掛掉了,你的邏輯的服務(wù)或者說服務(wù)器本身不應該被掛掉的。因此,它的狀態(tài)繼續(xù)要維持,那誰維持呢?就是存儲。如果這個世界上沒有存儲中間件的話,大家可以想象,寫服務(wù)器是非常非常累的,你每做一件事情,做這件事情的每一步,都要想一想,中間需要把狀態(tài)存下來,以便萬一掛掉之后我該怎么辦這樣一個問題。

因此,存儲中間件是大家最重要的生存基礎(chǔ)。對于服務(wù)器程序員來講,它是真正革命性的,它是讓你能夠今天這么輕松的寫代碼的基礎(chǔ)。這也是我們需要理解存儲系統(tǒng)為什么重要,它是大家賴以生存的最重要的一個外部條件。存儲我蠻早的時候提過一個觀點,存儲就是數(shù)據(jù)結(jié)構(gòu)。這個世界上存儲中間件是寫不完的,很多很多,消息隊列這些是存儲,文件系統(tǒng)、數(shù)據(jù)庫、搜索引擎的倒排檔等等,這些其實都是存儲。為什么說存儲就是數(shù)據(jù)結(jié)構(gòu)呢?因為在桌面端開發(fā)的時候,大家都知道數(shù)據(jù)結(jié)構(gòu)通常都是自己寫的,或者說某個語言的標準庫寫的。但是在服務(wù)端里面,因為狀態(tài)通常是持久化的,所以數(shù)據(jù)結(jié)構(gòu)很難寫。而存儲其實就是一個中間件服務(wù),是讓你把狀態(tài)維持這樣一件事情,從業(yè)務(wù)里面剝離出來??梢韵胂螅鎯κ欠浅6鄻踊?,并且會和大家熟知的各種各樣的數(shù)據(jù)結(jié)構(gòu)對應起來(參考文檔)。

靠譜的服務(wù)器是怎么構(gòu)建的呢?很核心的一個原理,叫Fail Fast,也就是速錯。我認為,速錯思想對于服務(wù)端開發(fā)來說非常非常重要。但是速錯理念的基礎(chǔ)是靠譜的存儲。因為速錯的意思是說,系統(tǒng)萬一有問題,就掛掉了,掛要之后要重啟重新做。但是重新做,你得知道它剛才在干什么,它的基礎(chǔ)就是要有人維持狀態(tài),也就是存儲。速錯的思想最早是在硬件領(lǐng)域,后來Erlang語言中首先提出將速錯這樣一個思想運用在軟件開發(fā)里面,以構(gòu)建高可靠的軟件系統(tǒng)。這是一篇Erlang作者的博士論文。這篇文章對于我的影響是非常大的,是我個人在服務(wù)端開發(fā)里面的啟蒙的一個著作。大家知道軟件是偏實踐的科學,比較少有體系化的理念出現(xiàn),這個是我見過的很棒的一個服務(wù)端開發(fā)或者分布式系統(tǒng)相關(guān)的理論,個人受益匪淺。

然而存儲為什么難呢?是因為別人都可以Fail Fast,但是存儲系統(tǒng)不行。存儲系統(tǒng)必須遵守頂層設(shè)計理念,其實是和Fail Fast相反的,它需要達到的結(jié)果是,無論怎么錯都應該有正確的結(jié)果。當然如果說存儲系統(tǒng)完全和Fail Fast相反倒也不至于,因為存儲系統(tǒng)的內(nèi)部實現(xiàn)細節(jié)本身,還是會用到很多速錯相關(guān)的原理。但是存儲系統(tǒng)對外表現(xiàn)出來的、所呈現(xiàn)的使用界面,和速錯原理會有反過來的感覺。因為無論發(fā)生什么樣的錯誤,包括軟件、網(wǎng)絡(luò)、磁盤、服務(wù)器斷電、內(nèi)存,甚至是IDC故障等等,對于一個存儲系統(tǒng)來講,它都認為,這必須是能承受的,必須有合理的結(jié)果。當然這個能承受的范圍,不同的存儲系統(tǒng)是不一樣的,代價也不一樣。比如說MemCache這樣的存儲系統(tǒng),它就不考慮斷電這樣的問題。對于MySQL這樣的東西,如果說在最早的時候,它是不考慮宕機這樣的故障的,后來引入了主從之后,你就可以想象,它就能夠解決服務(wù)器掛掉、硬盤掛掉等問題。不同的存儲系統(tǒng),因為對可靠性要求不一樣,它的實現(xiàn)難度也有非常大的差別(參考文檔)。

那么現(xiàn)實中的存儲,好吧,第一個我提了七牛云存儲,我這是打廣告了。第二像MongoDB、MySQL等這些都是存儲。大家經(jīng)常接觸的也主要是這一些。

模塊的設(shè)計

我一般講模塊設(shè)計的時候,都會先講架構(gòu)相關(guān)的一些東西。當然架構(gòu)這個話題,要完整的講,可以講很長很長時間。因為架構(gòu)的話題真的很復雜。如果只是用一兩頁描述架構(gòu)的話,我會談這么一些點。首先架構(gòu)師必須重視的第一件事情是需求,因為架構(gòu)的目的是為了滿足需求,這一點千萬不能搞錯。談到架構(gòu),很多人都會喜歡說,我設(shè)計了一個牛逼的框架。但是我長期以來在強調(diào)的一個觀點是說,框架這種事情其實在架構(gòu)哲學里面一點都不重要,框架其實是實踐層面的事情,架構(gòu)真正需要關(guān)心的其實是需求的正交分解,怎么樣把需求分解得足夠的正交。所謂的正交就是兩個模塊之間沒有什么太復雜的關(guān)系。當然正交是數(shù)學里面的詞,我不知道其他人有沒有會把它用到這個領(lǐng)域。但是我覺得正交這個詞很符合需求分解的這個概念。

隨著大需求(比如說一個應用程序,或者一個服務(wù)器)逐漸被切成很多個小需求,小的需求繼續(xù)分解變成一個個類和函數(shù)。這一層層的需求分解的單元,本質(zhì)上來講,都是同樣的東西,都是模塊,只是粒度問題。所有這些app、service、package、class、func等,統(tǒng)一都可以稱之為模塊。那所有的模塊,第一重要的是什么呢?是它的規(guī)格。模塊里面最核心的,任何一個模塊的規(guī)格是要體現(xiàn)需求。為什么我會說我是反框架的,因為框架其實就是模塊的連接方式,不同的模塊如何連接這個框架。那這種連接方式通常是易變的、不穩(wěn)定的,因為框架是需要演進的。隨著需求的增加、修改,它會不斷演進,肯定后面會發(fā)現(xiàn),之前搭的框架不太好了,需要重構(gòu)。框架需要變的時候,通常很痛苦,所以也是很多人為什么重視框架的原因。但是不應該因為這一點兒把框架看得太重。因為不穩(wěn)定的東西,通常是最不重要的東西。你要抓住的是穩(wěn)定的東西。因此,框架只是實踐程度可依賴的東西,但是從架構(gòu)來講不要太強調(diào)。

模塊,剛才我講了,模塊其實最重要的是規(guī)格,也就是使用界面,或者叫interface(接口)。對于一個應用程序來說,interface就是用戶交互。對于一個service來說,interface就是api。對于一個package來說,就是包的導出的函數(shù)或者類。對于一個class來說,就是公開的方法。對于函數(shù)來說就是函數(shù)的原型。這些都是interface。模塊的interface必須體現(xiàn)需求,否則這個interface就不是一個好interface。

總結(jié)一下,如果要提煉模塊的最佳實踐的話,我會提煉這樣三點。

第一,模塊的需求一定要是單一職責。就是這個模塊不能做太多的事情,如果有太多的事情,它就要進一步的分解。

第二,模塊的使用界面要體現(xiàn)需求。大家一看這個模塊的界面,就知道這個模塊是干什么的。比如一個軟件,你下載下來玩的時候,一看就應該知道這個軟件目的是什么,而不是看了好幾眼都分不清楚這個軟件到底是財務(wù)軟件還是什么軟件,那這個interface就太糟糕了。所以其實所有的interface都是一樣的,都要體現(xiàn)需求。

第三是模塊的可測試性。任何一個模塊,如果提煉得好的話,它應該很容易測試。為什么這一點很重要呢?因為測試在軟件系統(tǒng)里面其實非常重要,尤其是在服務(wù)端開發(fā),尤其是像七牛這樣一個做基礎(chǔ)服務(wù)的,一個bug或者一個故障會導致成千上萬甚至上百萬的公司受影響,那么這個測試非常非常重要??蓽y試性包括什么呢?它包括把模塊跑起來的最小的環(huán)境。如果一個模塊耦合小,意味著外部環(huán)境依賴少,這個模塊就很容易測試。反過來,很容易測試意味著這個模塊的耦合很低。因此,模塊的可測試性,其實能夠反向來推導這個模塊設(shè)計得好與不好。

展開來講,第一,模塊的使用界面,它應該符合需求,而不應該符合某種框架的需要。這一點,我為什么強調(diào)呢?而且是反復強調(diào)呢?是因為我認為很多剛剛踏入這個行業(yè)的人會違背這一點,包括我自己。最早做office軟件的時候,我很清楚自己犯了無數(shù)次這樣的錯誤,所以我后來把這一條作為非常重要的告誡自己的點。因為不體現(xiàn)需求的話,意味著這個模塊的使用界面是不穩(wěn)定的。最自然體現(xiàn)需求的使用界面是最穩(wěn)定的。第二,我認為模塊應該是可完成的。也就是說它的需求是穩(wěn)定的可預期的,或者是說模塊的目標是單一的,只做一件事情。只有這樣才能做到模塊可完成。但是反例很多很多。比如C++里面有Boost、MFC、QT這些庫。其實你知道,它們都是大而統(tǒng),包含很多的東西,你不知道這個庫是干嘛的。這種我個人是非常反對。我早期也是這樣的,早期自己寫了一些通用庫,都是很含糊,想到一個很好的東西,就把它扔到通用庫里面,最后這個通用庫就變成垃圾筒,什么東西都有。任何一個模塊,都有一個你對它的邊界的界定,邊界界定好之后,這個模塊總歸有一天,它逐步趨于穩(wěn)定,最終幾乎不必去修改(就算修改也只是實現(xiàn)上的優(yōu)化)。

剛才我也講了模塊應該是可測試的??蓽y試性可以表征一個模塊的耦合度。耦合越低越容易測試。所謂的耦合就是環(huán)境依賴,我依賴外部的東西越少越容易測試。一個模塊要測試的話,必須要模擬整個環(huán)境,讓它跑起來。

服務(wù)器的設(shè)計

服務(wù)器的設(shè)計首先要遵循模塊的設(shè)計,其次是服務(wù)器有服務(wù)器特有的一些東西。第一是服務(wù)器的測試。七牛對于測試非??粗兀瑓⒓舆^上次Gopher China大會的都知道我講的內(nèi)容,就是HTTP服務(wù)器如何測試。七牛為此自己發(fā)明了一個DSL語言,就是領(lǐng)域?qū)S谜Z言,專門用于測試?,F(xiàn)在這個DSL在我們團隊用得非常廣泛,基本上所有新增的模塊都會用這個方法進行單元測試。第二個是服務(wù)器的可維護性。我沒有講服務(wù)器本身應該怎么設(shè)計,因為這個其實跟領(lǐng)域是有關(guān)系的,也就是你做什么事情,本身是很具化的,我沒辦法告訴你應該怎么樣設(shè)計。服務(wù)器的設(shè)計,無非遵循我剛剛講的模塊設(shè)計的一些準則,但是服務(wù)器有它自己的特征,因為它作為一個互聯(lián)網(wǎng),或者作為一個C/S結(jié)構(gòu)的東西,它有一些通用的需求。剛才我們講模塊需要做需求的正交分解,那作為一個Web服務(wù)器的話,除了業(yè)務(wù)相關(guān)的東西,會不會有一些通用的需求?其實通用的需求是非常多的,我這里列了很多,但是肯定不完整,當然列這一些,已經(jīng)有更多細節(jié)的話題可以展開來講。

第一個比如路由,這個就不用說了。大家看到大部分的Web框架都會解決路由相關(guān)的問題。第二個是協(xié)議,通常大家看到比較多的協(xié)議,如果用HTTP的話,會比較多的見到form、json或者xml。第三個是授權(quán),授權(quán)我就不展開了。會話,其實跟授權(quán)類似。第四個是問題的跟蹤和定位。這個我等一下再講。第五個是審計。審計有兩個用處,一個是計費,像七牛這樣的服務(wù)需要審計,因為每一次API請求,會有計費相關(guān)的東西;另一個做對賬,你說有我說沒有,那最終是有還是沒有,看服務(wù)器的日志來說了算。第六個是性能的調(diào)優(yōu),然后是測試和監(jiān)控。為什么會有這么多的需求呢?原因是因為服務(wù)器開發(fā),我覺得大家可能關(guān)注了開發(fā)兩個字,但是可能忘了服務(wù)器開發(fā)完了是干什么的。服務(wù)器開發(fā)完了,它后面還要在線上跑很長時間,而且絕大部分的時間是在線上。所以服務(wù)器的開發(fā)其實和在線上運維的過程是不能脫節(jié)的。正因為不能脫節(jié),所以我們才會關(guān)注像監(jiān)控、性能調(diào)優(yōu)、問題跟蹤定位、審計相關(guān)的一些需求。

這一些其實都是和具體的業(yè)務(wù)無關(guān)的,所有的服務(wù)器都需要。那么其實這些需求,可以被分解出來,由一些基礎(chǔ)組件去實現(xiàn)。當然因為這些東西,很多都是和七牛內(nèi)部的組件相關(guān),所以我沒有展開。

1. 服務(wù)器的測試

我大概的講一下服務(wù)器的測試方法,在七牛這邊怎么用的,還有服務(wù)器可維護性相關(guān)的東西。第一個是七牛用的兩個東西,一個是叫mockhttp,當然這個不一定要用,因為我知道Go其實有標準的httptest模塊,它能夠監(jiān)聽隨機端口的服務(wù)。七牛也用這種方式起測試服務(wù)器,但是我自己個人更喜歡用mockhttp。因為它不監(jiān)聽物理的端口,所以它沒有端口沖突問題,心智相對負擔比較低,而且相比那種監(jiān)聽真正物理端口的程序跑得會更快一些。這個mockhttp已經(jīng)開源了,在github.com/qiniu上可以找到。

第二個是基于七牛的httptest,暫時還沒有開源。今天我沒有辦法完整地講,因為我之前有個完整的講座,在網(wǎng)上可以搜索得到。它最核心的思想是什么呢?你不用寫client端sdk,直接就可以基于http協(xié)議寫測試案例。如果沒有這樣的工具,寫一個服務(wù)器的測試,顯然第一件事情就寫一個sdk,把你的服務(wù)器的interface,也就是網(wǎng)絡(luò)api包裝一下,包裝成一個類,里面有很多函數(shù)。然后通過這個類去測你的服務(wù)。這種模式有什么不好的地方呢?最大的問題是這個sdk,其實很多時候是不穩(wěn)定的。不穩(wěn)定會帶來一個問題,就是這個sdk你改改改,有可能改sdk的人忘了服務(wù)器的測試案例在用它,改了后會導致編不過,從而導致測試案例失敗。當然也有另外一種做法,就是我為服務(wù)器的測試專門寫一個sdk,但是我覺得這個成本是比較高昂的,因為你相當于只為某一個具體的場景專門做一個事情,而這個事情,可能工作量不一定非常巨大,但是很繁雜。因此,七牛的httptest最本質(zhì)的點是,可以直接寫一個看起來像直接發(fā)網(wǎng)絡(luò)包的方式去做測試。然后盡可能把網(wǎng)絡(luò)協(xié)議的文本描述讓它看起來更人性化一些,讓人一看就知道發(fā)過去的是什么。這樣也可以認為是寫了一個sdk,但是這個sdk非常通用,所有的HTTP服務(wù)器都能去用它。這樣所有的HTTP服務(wù)測試,包括單元測試和集成測試,都可以用這種方式測。

2. 服務(wù)器的可維護性

我剛才在講服務(wù)器的需求時提過這一點,也是我覺得非常非常需要去強調(diào)的一點,就是服務(wù)器的可維護性。這一點是極其極其重要的,因為服務(wù)器的開發(fā)和運維并不能分割的,服務(wù)器本身的設(shè)計需要為運維做好準備。這個系統(tǒng)跑到線上會發(fā)生很多問題,發(fā)生這些問題之后,如何快速地解決,需要在開發(fā)階段就去思考。正因為如此,所以才會有很多出于可維護性上的一些基礎(chǔ)的需求,包括日志。日志其實是最基礎(chǔ)的。沒有日志怎么排除這種故障呢?但是對于經(jīng)常要發(fā)生的情況,服務(wù)器設(shè)計本身就需要避免,最最基本的不能有單點,因為有單點,一個服務(wù)器掛掉了,線上就完蛋了,運維就要立刻跟上。但是這種事情必然會發(fā)生。對于必然會發(fā)生的事情,必然是需要在開發(fā)階段就去避免。所以某種意義上來說,高可用是為了可維護性,如果不是為了可維護性,就不用考慮高可用的問題。

服務(wù)器的可維護性,我覺得大概分為這樣幾個類別的需求。第一個是性能瓶頸。性能瓶頸,比如說你發(fā)現(xiàn)業(yè)務(wù)支撐的并發(fā)量不夠,經(jīng)常需要加機器,這時候要發(fā)現(xiàn)慢到底慢在哪里。也許你認為網(wǎng)站剛剛上線的時候,不用考慮這個問題,但是如果這個網(wǎng)站能做大的話,總有一天會碰到瓶頸問題。因此,最早的時候,就要為瓶頸問題考慮好,如果萬一發(fā)生瓶頸,如何能盡快發(fā)現(xiàn)瓶頸在哪里。此外,我認為非常非常關(guān)鍵的是異常情況的預警。很多時候如果存在瓶頸,那么等它發(fā)生的時候就已經(jīng)是災難了。最好的情況下,在達到災難的臨界點之前,最好有個預警線,在那個預警線上開放排除問題就比較好一點。

第二個是故障發(fā)現(xiàn)和處理。當線上真的發(fā)現(xiàn)故障了,雖然我們極力去避免,但是肯定避免不了了,一定會發(fā)生故障,沒有一個公司不會發(fā)生故障。發(fā)生故障的時候,如何去快速地響應,這個就是快速地定位故障源。這個其實也是服務(wù)器開發(fā)里面,我覺得需要深度去考慮的一個問題。對于經(jīng)常發(fā)生的故障,必須要實現(xiàn)自我恢復。也就是我剛剛第一個講的。一旦發(fā)生這個事情,不是偶然,是經(jīng)常的。那么你必須要在開發(fā)階段解決,而不是到線上運維階段解決這個問題。

第三個是用戶問題排查,一個用戶,提供了一個非常個例化的問題,不是服務(wù)總體的一個問題,可能就是一個客服的個例問題,那么就需要有所謂的reqid。每一個用戶請求都有一個唯一的reqid,一旦是個例的問題需要跟進,客戶要告訴我你的reqid是多少,輸入這個ID,就能把所有和該請求相關(guān)的東西都能找到。整個服務(wù)端的請求鏈都能找到之后,這個問題的排查就更容易。

分享到

崔歡歡

相關(guān)推薦