Go語言在掃碼支付系統(tǒng)中的成功實(shí)踐
今天的內(nèi)容主要分四個方面。***,金融支付系統(tǒng)的一些特點(diǎn);第二,我們的掃碼支付系統(tǒng)技術(shù)選型;第三,系統(tǒng)迭代過程中的架構(gòu)演進(jìn);第四,與Go相關(guān)的一些坑。
金融支付系統(tǒng)的一些特點(diǎn)
首先從業(yè)務(wù)流程入手,其實(shí)非常簡單。一位消費(fèi)者結(jié)賬時,假如選擇掃碼支付的方式付款 100 元,產(chǎn)生一筆交易信息。如圖 1 所示,我們看上面藍(lán)色的線條,通過商家的收款產(chǎn)品,把這 100 元的交易信息送到我們的掃碼支付系統(tǒng),然后傳遞到后面的微信、支付寶或者其他支持掃碼支付的相應(yīng)錢包,完成這筆交易信息的傳遞,完成這筆交易處理。
在行業(yè)內(nèi),通常稱藍(lán)色的線條為信息流。信息流是什么意思?就是傳遞這筆交易的信息。接下來, 完成 信息傳遞還沒有結(jié)束,看圖中灰色的線條,通常在第二天的時候,即 T+1 時,我們會通過商家的清算銀行把這 100 元清到商戶的清算賬戶,這樣才算完成了這筆資金的清算,因此我們稱下面的灰色線條為資金流。很簡單,我們今天更關(guān)注的是上面信息流相關(guān)的處理。
接下來,我們看一下信息流相關(guān)處理。除了我剛剛提到的,實(shí)時交易處理 100 元的信息傳遞外,還會涉及到哪些方面呢?一個是實(shí)時交易的服務(wù),另外一個是商戶對賬的服務(wù)。剛剛提到資金流的傳遞是商家收到 100 元,那么收到的金額對不對,和前一天交易行為是不是匹配呢?商家需要一些對賬報(bào)表,來核對相應(yīng)交易行為、到賬資金,這就是對賬服務(wù)。
另外,其他的一些商戶服務(wù),包括一些商戶信息的維護(hù),商戶交易行為的查詢,交易記錄的查詢方面,最基本來說是這三類的服務(wù)。另外,可能會有更多附加的服務(wù),包括風(fēng)控,以及其他的一些增值營銷服務(wù)等。
今天,我們聚焦在這三類基本服務(wù),看看三類基本服務(wù)對應(yīng)的后臺系統(tǒng)的類型是什么樣子。
首先是實(shí)時交易服務(wù):API Gateway?;氐絼倓?100 元的處理,從商家收款產(chǎn)品,到我們掃碼交易的處理系統(tǒng),接口過來以后,在系統(tǒng)中進(jìn)行一些相應(yīng)的業(yè)務(wù)邏輯處理,交易信息落地,之后把交易信息分發(fā)到后面相應(yīng)的接口。整個流程,實(shí)際上無非就是接口的轉(zhuǎn)換,中間攙雜一些相應(yīng)的業(yè)務(wù)處理。跟我們微服務(wù)架構(gòu)上的 API Gateway 的定位其實(shí)非常類似,可以理解為它就是一個加入了一些業(yè)務(wù)邏輯的 API Gateway 系統(tǒng)。從商家對賬服務(wù)來說,通常是在交易發(fā)生的第二天進(jìn)行資金流的流轉(zhuǎn),與之同步的會有相應(yīng)的對賬報(bào)表要給到商家,為商家提供對賬服務(wù)。通常來說,對賬服務(wù)會涉及到批處理相應(yīng)系統(tǒng)。商戶服務(wù)剛才提到的查詢、信息維護(hù)等,這些可能是相應(yīng)的 web portal 提供相應(yīng)的商戶服務(wù)。
從業(yè)務(wù)角度出發(fā) , 對我們的系統(tǒng)有什么基本的要求?
***,既然是支付系統(tǒng),安全性肯定是再怎么強(qiáng)調(diào)也不為過的;
第二,穩(wěn)定性也是一個必不可少要考慮的關(guān)鍵點(diǎn)。包括我們的商家可能會有各種類型的客戶,包括餐館可能白天營業(yè),夜店可能半夜也會有交易發(fā)生。整個系統(tǒng) 7*24 小時的穩(wěn)定性,也是需要考慮的重點(diǎn);
第三,我們系統(tǒng)的吞吐量,這個就很好理解了,當(dāng)前面的客戶、商家交易量上來的時候,整個系統(tǒng)包括并發(fā)處理能力,請求的響應(yīng)時間等,都是業(yè)務(wù)上非常關(guān)注的重點(diǎn)。
回到最開始提到的問題,我們用 Golang 來搭建支付處理系統(tǒng),靠不靠譜。 在我理解就是這三方面的考量:安全性有沒有保證,穩(wěn)定性是不是足夠穩(wěn)定,吞吐量是不是能夠達(dá)到業(yè)務(wù)的要求。接下來看看到底能還是不能,進(jìn)入技術(shù)選型話題。
技術(shù)選型
從技術(shù)選型來說,剛才提到的三個方面,主要是業(yè)務(wù)需求方面的考量。業(yè)務(wù)需求方面,在 2015 年, 我們 搭建這套系統(tǒng)的時候,還有另一點(diǎn)非常重要的考量。因?yàn)? 2015 年正好是掃碼支付方式快速發(fā)展的階段,當(dāng)時,業(yè)務(wù)方面對于系統(tǒng)的快速迭代要求是非常高的。技術(shù)選型方面,除了業(yè)務(wù)需求,還有技術(shù)需求和團(tuán)隊(duì)需求。技術(shù)需求是什么意思?我們知道在軟件開發(fā)領(lǐng)域,有一句話 叫 “沒有銀彈” , 不能拿起錘子當(dāng)什么都是釘子,每一門技術(shù)都有它適用的場景和范圍。剛才幾個系統(tǒng),幾個服務(wù)類型顯然都是 Golang 所擅長的范圍,這是技術(shù)需求的角度。
團(tuán)隊(duì)需求是什么含義呢?想跟大家分享一個故事。前兩年,我朋友的一家公司有一個網(wǎng)站,網(wǎng)站***個版本是請外包團(tuán)隊(duì)實(shí)現(xiàn)的,拿到驗(yàn)收發(fā)現(xiàn)很不錯,使用的是"世界上***的語言"。然而問題來了,當(dāng)這家公司拿到***期交付成果以后,想要自己接過來,在上面疊加一些功能進(jìn)行版本的迭代。結(jié)果發(fā)現(xiàn)團(tuán)隊(duì)里沒有會"***語言"的成員,出于種種原因既沒有從外部招到,也沒有從內(nèi)部培養(yǎng)起來。結(jié)果是他們又花了幾個月,用另一門語言把這個網(wǎng)站重寫了一遍,這就是我們在團(tuán)隊(duì)需求里面想提到的一點(diǎn),一門技術(shù)不管是編程語言還是系統(tǒng)組件,要引入這門技術(shù),首先團(tuán)隊(duì)里要有人會這門技術(shù)。另一點(diǎn)也是通常會比較容易忽略的,就是對于一門技術(shù)的引入,除了團(tuán)隊(duì)中有相應(yīng)的人能夠?qū)戇@個代碼,很重要的是,有沒有人能夠 hold 這門技術(shù)。在遇到技術(shù)難題的時候,有人能夠處理。這門技術(shù)在團(tuán)隊(duì)內(nèi)的培訓(xùn)時,能夠有帶頭人也是一個非常重要的方面。
從技術(shù)選型的角度主要是這三方面的考量,結(jié)合我們 2015 年搭建這套系統(tǒng)的現(xiàn)狀來看一下。當(dāng)時我們團(tuán)隊(duì)的技術(shù)棧編程語言方面是 C、Java、Golang。選擇掃碼支付系統(tǒng)實(shí)現(xiàn)的語言的時候,C 是***個被我們否決掉的,開發(fā)效率就滿足不了我們快速迭代的要求。我們更多的可能是從 Java、Golang 進(jìn)行權(quán)衡。Golang 團(tuán)隊(duì)當(dāng)時人數(shù)并不多,只有三位小伙伴,但是三位小伙伴都是非常不錯的,對 Golang 的理解也很棒。到現(xiàn)在我們回頭來看,從三個人到現(xiàn)在大概有接近兩年時間的發(fā)展,我們團(tuán)隊(duì)中現(xiàn)在有 20 多位,超過整個研發(fā)團(tuán)隊(duì)一半以上的人數(shù),已經(jīng)可以熟練的掌握 Golang,用 Golang 來實(shí)現(xiàn)我們的業(yè)務(wù)系統(tǒng)、業(yè)務(wù)功能, Golang 在我們團(tuán)隊(duì)中的普及發(fā)展,也是非常迅速的。團(tuán)隊(duì)背景大概是這樣。
技術(shù)需求方面,我們來看 Golang 的技術(shù)特點(diǎn)。我這里簡單列舉一些對我們感受比較深刻的方面:
***,快速上手,學(xué)習(xí)曲線平滑,開發(fā)效率非常高。我們團(tuán)隊(duì)發(fā)展歷程可以充分證明這一點(diǎn),從三位 Golang 小伙伴到二十多位,一年多不到兩年的時間,大多數(shù)是從內(nèi)部轉(zhuǎn)型過來的,不管是 C 還是 Java 轉(zhuǎn)型過來小伙伴,大家轉(zhuǎn)型和上手過程的普遍感受都是學(xué)習(xí)起來沒有什么困難,上手非??欤_發(fā)效率方面也非常不錯。在 2015 年最初搭建這套系統(tǒng)的時候,行業(yè)市場瞬息萬變,甚至不夸張的說, 當(dāng)時這個系統(tǒng) 一天一個版本,開發(fā)效率非常高;
第二,天生支持并發(fā)編程,對于我們后端普遍需要并發(fā)處理的場景也是非常適合的;
第三,簡潔的錯誤處理: panic、recover、defer 。有人可能會喜歡,有人可能會不太習(xí)慣,我個人很習(xí)慣這樣的處理方式。這塊我們也遇到一些問題,踩過一些坑,這個在***會跟大家分享。
剛剛是團(tuán)隊(duì)需求、技術(shù)需求方面。接下來我們更關(guān)注業(yè)務(wù)需求方面。我們剛才提到三點(diǎn):
***,安全性。從安全性角度出發(fā)為什么選擇 Golang?選擇 Golang 靠不靠譜?一個支付系統(tǒng),它的整個處理流程的安全性,實(shí)際上涉及到方方面面,包括數(shù)據(jù)傳輸?shù)陌踩?,是否有?shù)據(jù)泄露的風(fēng)險(xiǎn),是否有防篡改的措施;數(shù)據(jù)落地存儲的時候,存儲是不是關(guān)鍵信息有做加密;網(wǎng)絡(luò)方面,網(wǎng)絡(luò)是不是有接入層、防火墻等等。整個系統(tǒng)的安全性,從接入層到應(yīng)用層、系統(tǒng)組件,再到 Database,每一層可能都會有相應(yīng)安全性方面的考量。落地到編程語言選擇,Golang 的安全性靠不靠譜呢?編程語言的安全性更關(guān)注什么呢?很自然的想到一點(diǎn)就是語言的漏洞。
關(guān)于漏洞,我們將收集到一個數(shù)據(jù)和大家分享一下,如圖 2 所示是一個漏洞搜集的網(wǎng)站,我用關(guān)鍵詞 Golang 搜索漏洞,可以看到五個,然后用關(guān)鍵詞 Java 搜索,有 1660 個。沒有黑 Java 的意思,解釋一下這 1000 多個是什么意思:畢竟 Java 發(fā)展這么多年已經(jīng)非常成熟,JDK 的漏洞其實(shí)非常少了,1000 多個絕大多數(shù)都是各種框架的漏洞。例如我們一些存量系統(tǒng),有的 web 平臺用的 ssh 框架,眾所周知的"萬年漏洞王"迫使我們每年都要對這套框架進(jìn)行一次升級。反過來看 Golang,一方面是因?yàn)?Golang 比較新,曝出來的漏洞沒有那么多;另一方面,Golang 的安全性也確實(shí)沒有遇到過什么問題。而且 Golang 背靠 Google,有龐大的社區(qū)。所以從編程語言的安全性方面考慮的話,在我們看來選擇 Golang 沒有必要顧慮太多,這是安全性方面。
第二,穩(wěn)定性。其實(shí)和安全性類似的一點(diǎn)是,同樣需要考量在系統(tǒng)整體架構(gòu),系統(tǒng)各個層級等方面的穩(wěn)定性。昨天下午 B 站的老師也跟大家分享了微服務(wù)演進(jìn)過程中,系統(tǒng)穩(wěn)定性方面的考量,限流、容錯、故障隔離等。系統(tǒng)的接入層限流是不是足夠好,應(yīng)用層是不是高可用,緩存、數(shù)據(jù)庫等組件也都需要有穩(wěn)定性方面相應(yīng)的考量。同樣,落地到我們的編程語言應(yīng)用實(shí)現(xiàn)方面,在穩(wěn)定性上更關(guān)注的是什么內(nèi)容呢?在我們看來,應(yīng)用高可用架構(gòu),即應(yīng)用的實(shí)現(xiàn)要做到無狀態(tài),支持橫向擴(kuò)展。其實(shí)這一點(diǎn),不管是 Golang 還是 Java 還是其他的語言,只要結(jié)構(gòu)、代碼設(shè)計(jì)的好, 做到這一點(diǎn) 沒有什么壓力。
第三,吞吐量。隨著業(yè)務(wù)發(fā)展,交易量逐步增加,客戶越來越多,吞吐量是否得到很好的支撐?在這方面,準(zhǔn)備了兩個并發(fā)處理能力方面的例子。這兩個也是我們系統(tǒng)中會經(jīng)常用到的功能。一個是 http 接口的并發(fā)處理能力, 另外一個是 RSA 加解密的例子。
如圖 3 所示是我的實(shí)驗(yàn)環(huán)境,是用自己的 MacBook,雙核 8G 的環(huán)境,Golang 是 1.7 的版本。同時為了有一個對標(biāo)的效果,單獨(dú)拿 Golang 看它的效率可能看不出什么,因此拿 Java 做了一個對標(biāo),再強(qiáng)調(diào)一遍, 只是一個對標(biāo)的目的, 沒有黑 Java 的意思。
如圖 4 所示,我們來看***個http 接口,這是 Golang 版本非常簡單的 http 接口,這個不用多說,大家看一眼也知道,十幾行的代碼,啟動一個 http 服務(wù),收到一個請求以后,應(yīng)答,回十個字節(jié),這是 Golang 的版本。Java 版本做的事情是一樣,但是 Java 代碼比較多,沒有截全,因?yàn)? Java 的 http 本身沒有帶多線程的方式,寫了一個簡單的線程池,用多線程的方式去處理 http 的請求。
接下來是測試的結(jié)果,十個用戶一萬個請求進(jìn)行測試。上面是一個 Golang 的結(jié)果,吞吐量 12000 多,請求 響應(yīng) 時間是 0.815 毫秒。下面是 Java 版本,吞吐量是 11000 多,響應(yīng)時間是0.891 毫秒,這兩個版本差別不大,非常相近。從 http 接口方面來說,Java 和 Golang 的處理結(jié)果相差不是很大。
下一個例子是 RSA 加解密處理。上面是 Golang 的版本,也是采用并發(fā)處理的方式,循環(huán) 1000 次,每次進(jìn)行一次加密、一次解密,加密的密鑰用 2048 byte 長 度的密鑰,待加密的數(shù)據(jù)是 245 byte。下面是 Java 的版本,也是截了一部分出來,實(shí)現(xiàn)方式和 Golang 實(shí)現(xiàn)方式一樣。
我們直接看結(jié)果,如圖 5 所示,上面是 Golang 的結(jié)果,下面是 Java 的結(jié)果, 用 的是 Mac 自帶的 time 命令來統(tǒng)計(jì)耗時。Golang 的結(jié)果,程序?qū)嶋H執(zhí)行時間(real time)是 2.78 秒 ,J ava 執(zhí)行時間是 7.74 秒,這里會看到三倍左右的差距。這個只能說明在 RSA 加解密處理的簡單場景上面,使用各自的標(biāo)準(zhǔn)庫,可能 Golang 的效率會比 Java 實(shí)現(xiàn) 效率 更高一些。當(dāng)然大家也可以很容易的找出一些反例,在某些場景,Java 比 Golang 的實(shí)現(xiàn)效率更高一些。什么意思呢?從不同的場景,不同語言的實(shí)現(xiàn)效率或者好一些或者壞一些。對于我們選擇 Golang 來說,只要我們證明它在 我們常用的一些場景 效率沒有問題,我們就可以用 Golang 來做這套系統(tǒng) 。
實(shí)際上我們對于 Golang 的吞吐量方面的信心,一方面源于我們的測試結(jié)果,另一方面其實(shí)在我們搭建這套掃碼支付系統(tǒng)之前,我們還使用 Golang 做過另一個秒殺系統(tǒng)。這個秒殺系統(tǒng)***個版本是使用 Java 來做擋板服務(wù)器,但是,可能是我們參數(shù)調(diào)優(yōu)沒有做好,在壓力測試的時候,單臺壓到 500 tps 就上不去了。但由于時間緊任務(wù)重,沒有來得及做仔細(xì)的參數(shù)調(diào)優(yōu),我們換成用 Golang 試了試。結(jié)果一晚上的開發(fā)時間,輕松幾萬上去沒有什么問題。這也使得我們在 Golang 吞吐量方面建立了很強(qiáng)的信心。
從業(yè)務(wù)需求角度考慮,不管是安全性、穩(wěn)定性、吞吐量,選擇 Golang 都沒有什么壓力。
***總結(jié)一下,我們最終選擇 Golang 的出發(fā)點(diǎn):作為需要快速原型、快速迭代的項(xiàng)目,需要的開發(fā)效率非常高,在滿足當(dāng)前和未來可預(yù)期的高可用、吞吐量等業(yè)務(wù)需求的前提下, Golang 的高效開發(fā)效率,簡單部署和運(yùn)維,是我們擁抱 Golang 的主要原因。
以上是整個技術(shù)選型方面的考量 關(guān)鍵點(diǎn), 多說一句,剛才多次提到系統(tǒng)的吞吐量、高可用的關(guān)鍵點(diǎn),其實(shí)除了編程語言這一層,更多的可能跟整體的系統(tǒng)架構(gòu)有很大關(guān)系,當(dāng)然這是另一個話題了。
架構(gòu)演進(jìn)
我們來一起看一下系統(tǒng)架構(gòu)演進(jìn)的過程。 如圖 6 所示, 是 2015 年上線的***個掃碼交易處理系統(tǒng)。當(dāng)時整個后臺系統(tǒng)非常簡單。因?yàn)楫?dāng)時需要版本快速迭代,而且我們更多的主要力量在為商家提供的收款產(chǎn)品。我們可以看到各種各樣的收款產(chǎn)品,包括云收銀的產(chǎn)品系列,iOS、安卓的 APP,SDK,包括 PC 端的商業(yè)軟件,右上角的智能 POS 系列產(chǎn)品等。最初上線的時候,需要利用這些產(chǎn)品快速鋪開前端市場,所以在后臺系統(tǒng)上,是一個非常簡單的架構(gòu)。系統(tǒng)在上線之后的一段時間,其實(shí)還是相對穩(wěn)定的,畢竟越簡單越穩(wěn)定。但是隨著業(yè)務(wù)量的增加,以及業(yè)務(wù)上需要疊加的功能越來越多,這樣的一個單體應(yīng)用結(jié)構(gòu)很明顯是撐不住的。所以,接下來我們進(jìn)行了一系列的架構(gòu)調(diào)整、演進(jìn)。
如圖 7 所示的架構(gòu)是我們幾個月之前的系統(tǒng)結(jié)構(gòu),比***個版本復(fù)雜很多。主要的掃碼處理系統(tǒng)是在圖左。我們看應(yīng)用層,從一個單體應(yīng)用延伸出了多個,包括我們剛剛提到的不同類型的服務(wù):掃碼網(wǎng)關(guān)的實(shí)時交易處理服務(wù),為商戶提供對賬報(bào)表的 批處理的服務(wù) ,為商戶提供信息查詢的 平臺服務(wù)。 另外還搭建了我們的風(fēng)控系統(tǒng),因?yàn)轱L(fēng)控在整個支付交易處理中也是非常重要的方面。APP 后臺,是我們 iOS、安卓的 App 提供的單獨(dú)的后臺服務(wù)。整個這一套系統(tǒng),都是 使用 Golang 實(shí)現(xiàn)的。Golang 在統(tǒng)一系統(tǒng)實(shí)現(xiàn)的技術(shù)棧方面,為我們提供了很大的幫助 。 中間件方面 ,為了 解耦和提高穩(wěn)定性, 我們引入了kafka、redis等系統(tǒng)組件。 為了實(shí)現(xiàn)跨機(jī)房災(zāi)備系統(tǒng)搭建,我們自研了一套數(shù)據(jù)庫的同步工具,可以從 MongoDB 文檔型的數(shù)據(jù)庫,將數(shù)據(jù)實(shí)時同步到 MongoDB 或者 MySQL 等其他關(guān)系型數(shù)據(jù)庫,這套工具也是使用 Golang 來做的。
在整個架構(gòu)的完善方面,根據(jù)業(yè)務(wù)職能拆出更多的系統(tǒng)。目前我們的系統(tǒng)在此基礎(chǔ)之上,進(jìn)行了更多的一些往服務(wù)化方面的逐步演進(jìn),這是整個系統(tǒng)架構(gòu)演進(jìn)的過程。
一些坑
如圖 8 所示,很簡單的 十幾行代碼,它提供的是 TCP 長連接服務(wù)端的服務(wù)。每 accept 一條鏈接,收到客戶端的請求之后,開一個 goroutine,通過 handleConnection 這個函數(shù)進(jìn)行客戶端連接的處理。針對 handleConnection 這個函數(shù)進(jìn)行了一層包裝,稱之為 TcpRecoverWrap,包裝里做了什么,看名字也知道,是一個 recover 的包裝,具體的實(shí)現(xiàn)在圖右,非常簡單,也是 Golang 里面比較常用的處理方式,給一個函數(shù)加一個包裝,包裝里面加一個 defer 進(jìn)行 recover 處理。
這段代碼看起來很簡單,那么問題來 了 ,是不是在 handleConnection 函數(shù)內(nèi)發(fā)生的任何 panic 都能通過 TcpRecoverWrap 這樣的實(shí)現(xiàn)方式來解決?既然提出這個問題,答案肯定是否定的。來看一下 handleConnection 的具體 實(shí)現(xiàn),如圖 9 右邊所示,是一個 TCP 的服務(wù)端,已經(jīng) accept 一個鏈接,開始 read,在一個死循環(huán)里面進(jìn)行 read 操作,每 read 一條客戶端發(fā)來的請求信息,開一個 goroutine 進(jìn)行實(shí)際的業(yè)務(wù)處理,把讀到的消息給到業(yè)務(wù)處理的函數(shù),當(dāng)業(yè)務(wù)處理完成,拿到業(yè)務(wù)處理結(jié)果以后,把結(jié)果寫到一個 channel 里面,這個結(jié)果由另一個 goroutine 來接收,接收到以后把應(yīng)答的消息回復(fù)給 TCP 客戶端,也是很簡單的一段代碼。
我們剛才列出來的兩個問題,全都在這一段代碼里面了。***個變量作用域,第二個 chan 操作。這幾十行代碼里,哪一個變量作用域存在問題?右上角的 reqBytes 的變量,我們看到前面在 for 循環(huán)外面定義了一個 reqBytes,接下來進(jìn)入到循環(huán)中,每一次讀到的東西都放到 reqBytes,我們可以看到,其實(shí)每次循環(huán),使用的 reqBytes 是一個變量,它的內(nèi)存空間是一個。那么問題來了, goroutine 開啟方式是一個必包的方式, 外面的變量對于每個子 goroutine 來說是可見的,也就是每讀到一條消息,放到同樣一個變量里面,有可能的結(jié)果是,***個消息開了一個 goro u tine,但這個 goroutine 還沒來得及調(diào)度的時候,又收到到第二個消息,那么第二個消息會把***個 goroutine 里面的消息覆蓋,不同請求之間會產(chǎn)生關(guān)聯(lián)影響。
第二個 chan 操作問題。我們知道在寫 chan 的時候,如果 chan 已經(jīng)被關(guān)了,那么對它進(jìn)行寫入操作會 panic。我們看圖9中的 writeMsgQueue 這個 chan ,在 defer 里面進(jìn)行了關(guān)閉。當(dāng)這個 TCP 連接不可用的時候, handleConnection 函數(shù) return 之前關(guān)閉這個 chan,目的是 chan 關(guān)閉后,通過檢查 chan 狀態(tài),可以促使 goroutine 退出,避免 goroutine 泄露,但是這個 defer 的 close 引入了另一個問題,如果服務(wù)端收到消息、開 goroutine 進(jìn)行業(yè)務(wù)處理的過程中,客戶端已經(jīng)斷開了連接,那么此時 chan 已經(jīng)關(guān)閉了,當(dāng)服務(wù)端業(yè)務(wù)處理完成的時候,寫入一個已關(guān)閉的 chan ,就會panic。更可怕的是,這個 panic 是不能被 TcpRecoverWrap 包裝的,因?yàn)樵趫?zhí)行 defer 結(jié)束、關(guān)閉了chan 之后,handleConnection 函數(shù)就已經(jīng) return,也就是這個 panic 是發(fā)生在另一個 goroutine 的,和 handleConnection 函數(shù)沒有關(guān)系,很顯然剛才的包裝方式是沒有辦法捕捉這樣的 panic 進(jìn)行 recover 的。
今天分享的內(nèi)容大概就是這些,那么怎么修這些問題呢?很簡單,變量作用域拿到 for 循環(huán)里面,panic 的問題可以加另外一個 tcp 關(guān)閉信號,通過這樣的方式,來修復(fù)剛才說的兩個問題。當(dāng)然修復(fù)方式可能有很多種,僅供大家參考。