七牛CEO許式偉:服務端開發那些事兒

服務端開發對於任何互聯網公司來講,都並非易事,它所涉及的技術知識面非常廣泛,如果開發人員的經驗不足,將直接影響產品用戶的體驗。作爲七牛雲存儲創始人,許式偉有着超過15年的編程經驗,對於服務端開發那些事甚是瞭解。因此,在本文中,他將對服務端開發所涉及的各方面原理知識進行詳細闡述,內容涵蓋網絡協議、操作系統原理、存儲系統原理、模塊設計、服務器設計等多方面。

以下爲演講實錄,略有刪減。

大家好,我今天的演講題目是《服務端開發那些事兒》,主要涵蓋的內容爲網絡協議、操作系統原理、存儲系統原理、模塊設計和服務器設計。這些是我覺得服務端開發人員比較直接相關的東西。第一個是網絡協議,因爲畢竟服務端是基於C/S模型,一上來涉及的就是協議。第二個是操作系統原理,因爲服務端開發和客戶端不太一樣,服務端涉及到大量的鎖、通訊相關的東西,所以操作系統原理對服務端程序員比對客戶端程序員來說是要重要很多的。我最早的時候在做office軟件,那時候基本上不涉及到太多多線程的東西,只是應用的邏輯很複雜,這是桌面端開發的特點。第三個東西,我覺得是存儲系統原理。存儲系統會有哪一些基本的道理,我覺得也是做服務端開發非常重視的。第四個是模塊設計本身,這個是和服務端開發沒有關係,是所有的開發人員應該掌握的一些基礎的東西。再後一點是服務器開發本身設計的相關的要點。

網絡協議

首先從網絡協議開始。在七牛有一個特點,就是我們所有的服務端都是直接基於HTTP協議的,很少有定義私有網絡協議的行爲。我們認爲HTTP協議的周邊支撐是非常完善的,而且因爲它是文本協議,所以大家去調試的時候非常容易理解。如果是私有二進制協議的話,還需要要專門爲它寫一個包的解析和查看的工具。而HTTP有很多天然的好處在裏面,所以我們會基於HTTP協議。HTTP協議最直接的就是GET、POST等,我就不詳細講了,更復雜一點就是這是帶授權的。以下4張圖將涵蓋最基本的HTTP協議。

操作系統原理

第二個層面我們談談操作系統原理。這塊最核心的就是線程和進程之間的通訊,這個通訊包括互斥、同步、消息。大家經常會接觸到互斥。只要有共享變量就一定會有鎖。在Go語言的服務器開發,很難避開鎖。爲什麼呢?因爲服務器本身是其實是有很多請求同時在響應的,服務器本身就是共享資源,既然是共享資源,那麼必然是有鎖的。

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

鎖最大的問題:不易控制

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

鎖的次要問題:性能殺手

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

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

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

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

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

存儲系統原理

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

因此,存儲中間件是大家最重要的生存基礎。對於服務器程序員來講,它是真正革命性的,它是讓你能夠今天這麼輕鬆的寫代碼的基礎。這也是我們需要理解存儲系統爲什麼重要,它是大家賴以生存的最重要的一個外部條件。存儲我蠻早的時候提過一個觀點,存儲就是數據結構。這個世界上存儲中間件是寫不完的,很多很多,消息隊列這些是存儲,文件系統、數據庫、搜索引擎的倒排檔等等,這些其實都是存儲。爲什麼說存儲就是數據結構呢?因爲在桌面端開發的時候,大家都知道數據結構通常都是自己寫的,或者說某個語言的標準庫寫的。但是在服務端裏面,因爲狀態通常是持久化的,所以數據結構很難寫。而存儲其實就是一箇中間件服務,是讓你把狀態維持這樣一件事情,從業務裏面剝離出來。可以想象,存儲是非常多樣化的,並且會和大家熟知的各種各樣的數據結構對應起來(參考:http://open.qiniudn.com/golang-and-cloud-storage.pdf)。

靠譜的服務器是怎麼構建的呢?很核心的一個原理,叫Fail Fast,也就是速錯。我認爲,速錯思想對於服務端開發來說非常非常重要。但是速錯理念的基礎是靠譜的存儲。因爲速錯的意思是說,系統萬一有問題,就掛掉了,掛要之後要重啓重新做。但是重新做,你得知道它剛纔在幹什麼,它的基礎就是要有人維持狀態,也就是存儲。速錯的思想最早是在硬件領域,後來Erlang語言中首先提出將速錯這樣一個思想運用在軟件開發裏面,以構建高可靠的軟件系統。這是一篇Erlang作者的博士論文(http://open.qiniudn.com/[Joe-Armstrong][CN]Making-reliable-distributed-systems-in-the-presence-of-software-errors.pdf)。這篇文章對於我的影響是非常大的,是我個人在服務端開發裏面的啓蒙的一個著作。大家知道軟件是偏實踐的科學,比較少有體系化的理念出現,這個是我見過的很棒的一個服務端開發或者分佈式系統相關的理論,個人受益匪淺。

然而存儲爲什麼難呢?是因爲別人都可以Fail Fast,但是存儲系統不行。存儲系統必須遵守頂層設計理念,其實是和FailFast相反的,它需要達到的結果是,無論怎麼錯都應該有正確的結果。當然如果說存儲系統完全和Fail Fast相反倒也不至於,因爲存儲系統的內部實現細節本身,還是會用到很多速錯相關的原理。但是存儲系統對外表現出來的、所呈現的使用界面,和速錯原理會有反過來的感覺。因爲無論發生什麼樣的錯誤,包括軟件、網絡、磁盤、服務器斷電、內存,甚至是IDC故障等等,對於一個存儲系統來講,它都認爲,這必須是能承受的,必須有合理的結果。當然這個能承受的範圍,不同的存儲系統是不一樣的,代價也不一樣。比如說MemCache這樣的存儲系統,它就不考慮斷電這樣的問題。對於MySQL這樣的東西,如果說在最早的時候,它是不考慮宕機這樣的故障的,後來引入了主從之後,你就可以想象,它就能夠解決服務器掛掉、硬盤掛掉等問題。不同的存儲系統,因爲對可靠性要求不一樣,它的實現難度也有非常大的差別(參考:http://open.qiniudn.com/cloud-storage-tech.pdf)。

那麼現實中的存儲,好吧,第一個我提了七牛雲存儲,我這是打廣告了。第二像MongoDB、MySQL等這些都是存儲。大家經常接觸的也主要是這一些。

模塊的設計

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

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

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

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

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

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

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

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

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

服務器的設計

服務器的設計首先要遵循模塊的設計,其次是服務器有服務器特有的一些東西。第一是服務器的測試。七牛對於測試非常看重,參加過上次Gopher China大會的都知道我講的內容,就是HTTP服務器如何測試。七牛爲此自己發明了一個DSL語言,就是領域專用語言,專門用於測試。現在這個DSL在我們團隊用得非常廣泛,基本上所有新增的模塊都會用這個方法進行單元測試。第二個是服務器的可維護性。我沒有講服務器本身應該怎麼設計,因爲這個其實跟領域是有關係的,也就是你做什麼事情,本身是很具化的,我沒辦法告訴你應該怎麼樣設計。服務器的設計,無非遵循我剛剛講的模塊設計的一些準則,但是服務器有它自己的特徵,因爲它作爲一個互聯網,或者作爲一個C/S結構的東西,它有一些通用的需求。剛纔我們講模塊需要做需求的正交分解,那作爲一個Web服務器的話,除了業務相關的東西,會不會有一些通用的需求?其實通用的需求是非常多的,我這裏列了很多,但是肯定不完整,當然列這一些,已經有更多細節的話題可以展開來講。

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

這一些其實都是和具體的業務無關的,所有的服務器都需要。那麼其實這些需求,可以被分解出來,由一些基礎組件去實現。當然因爲這些東西,很多都是和七牛內部的組件相關,所以我沒有展開。

服務器的測試

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

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

服務器的可維護性

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

服務器的可維護性,我覺得大概分爲這樣幾個類別的需求。

第一個是性能瓶頸。性能瓶頸,比如說你發現業務支撐的併發量不夠,經常需要加機器,這時候要發現慢到底慢在哪裏。也許你認爲網站剛剛上線的時候,不用考慮這個問題,但是如果這個網站能做大的話,總有一天會碰到瓶頸問題。因此,最早的時候,就要爲瓶頸問題考慮好,如果萬一發生瓶頸,如何能儘快發現瓶頸在哪裏。此外,我認爲非常非常關鍵的是異常情況的預警。很多時候如果存在瓶頸,那麼等它發生的時候就已經是災難了。最好的情況下,在達到災難的臨界點之前,最好有個預警線,在那個預警線上開放排除問題就比較好一點。

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章