上一節(jié)中,我們?yōu)槊總€(gè)連接都創(chuàng)建了一個(gè)goroutine來(lái)讀取其中的消息,現(xiàn)在我們將這個(gè)讀取消息的方法實(shí)現(xiàn)一下。
創(chuàng)新互聯(lián)是專(zhuān)業(yè)的安仁網(wǎng)站建設(shè)公司,安仁接單;提供成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì),網(wǎng)頁(yè)設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專(zhuān)業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行安仁網(wǎng)站開(kāi)發(fā)網(wǎng)頁(yè)制作和功能擴(kuò)展;專(zhuān)業(yè)做搜索引擎喜愛(ài)的網(wǎng)站,專(zhuān)業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來(lái)合作!
我們?cè)赼pplication目錄下新建controllers目錄,并在其中創(chuàng)建一個(gè)MessageController.go文件。
首先我們新建一個(gè)MessageController的結(jié)構(gòu)體,內(nèi)容如下
這個(gè)結(jié)構(gòu)體包括兩個(gè)內(nèi)容,一個(gè)是我們將連接放在數(shù)組之后,返回的索引,另一個(gè)是連接本身.
這個(gè)是具體的方法。
我們首先設(shè)置了一下讀消息的大小、超時(shí)時(shí)間以及超時(shí)后需要的操作。
超時(shí)時(shí)間如果設(shè)置為0,那么就是永不超時(shí)。之前在這里直接寫(xiě)0,被告知需要傳一個(gè)time.Time類(lèi)型的數(shù)據(jù)。最終谷歌后才得到了這個(gè)值time.Time{}為"0001-01-01 00:00:00 +0000 UTC"。
我們將用戶(hù)手法消息的內(nèi)容定義為一個(gè)結(jié)構(gòu)體,然后將用戶(hù)的訂閱信息的json通過(guò)json.unmarshal轉(zhuǎn)換成這個(gè)結(jié)構(gòu)體。
之后的switch操作與我們?cè)赟woole中的操作基本雷同,在查詢(xún)到login之后,調(diào)用service中 的login方法來(lái)進(jìn)行注冊(cè)。
下一節(jié)中我們?cè)俳榻B具體的注冊(cè)邏輯。
我們可以看到 gorilla/websocket中的examples中有一個(gè)聊天室的demo。
我們進(jìn)入該項(xiàng)目可以看到里面有這樣的一些內(nèi)容
按照官方的運(yùn)行方式來(lái)運(yùn)行這個(gè)項(xiàng)目
在瀏覽器中打開(kāi)8080端口,可以看到該項(xiàng)目可以被成功運(yùn)行了。
就是這樣一個(gè)簡(jiǎn)單的demo。
然后我們?nèi)タ匆幌滤木唧w實(shí)現(xiàn)。
在這個(gè)項(xiàng)目中首先定義了一個(gè)hub的結(jié)構(gòu)體:
這個(gè)結(jié)構(gòu)體中,clients代表所有已經(jīng)注冊(cè)的用戶(hù),broadcast管道會(huì)存儲(chǔ)客戶(hù)端發(fā)送來(lái)的信息。 register是一個(gè)*Client類(lèi)型的管道,用于存儲(chǔ)新注冊(cè)的用戶(hù),unregister管道反之。
我們打開(kāi)main.go,main函數(shù)的源碼為:
在這里首先會(huì)新開(kāi)一個(gè)goroutine,去跑hub的run方法,run方法中一個(gè)死循環(huán),不停地去輪詢(xún)hub中的內(nèi)容
如果取到了新用戶(hù),就加入到clients中,如果取到了信息,就循環(huán)所有的client,將信息寫(xiě)到client.send中。
我們看到在請(qǐng)求路徑為根的時(shí)候,它會(huì)請(qǐng)求一個(gè)函數(shù),而這個(gè)函數(shù)就是將home.html發(fā)送到客戶(hù)端。
而在請(qǐng)求路徑為“/ws”的時(shí)候,他會(huì)執(zhí)行一個(gè)serveWS的函數(shù)。
每當(dāng)一個(gè)新的用戶(hù)進(jìn)來(lái)之后,首先將連接升級(jí)為長(zhǎng)連接,然后將當(dāng)前的client寫(xiě)到register中,由hub.run函數(shù)去做處理。然后開(kāi)啟兩個(gè)goroutine,一個(gè)去讀client中發(fā)送來(lái)的數(shù)據(jù),一個(gè)將數(shù)據(jù)寫(xiě)入到所有的client中,去發(fā)送給用戶(hù)。
這就是整個(gè)聊天室的實(shí)現(xiàn)原理。
我們?cè)趍ian函數(shù)中,首先初始化配置文件,然后新建http連接。
這個(gè)連接創(chuàng)建之后,監(jiān)聽(tīng)服務(wù)器的9999端口。如果url的路徑后綴為 "/ws",就轉(zhuǎn)發(fā)到ws/ws.go中的IndexHandler方法中。
這個(gè)方法中首先我們創(chuàng)建一個(gè)websocket的Upgrader實(shí)例,然后我們使用Upgrader的upgrade方法來(lái)升級(jí)一下我們的連接為長(zhǎng)連接。
升級(jí)完成之后會(huì)返回一個(gè)*websocket.Conn的連接,我們之后所有的關(guān)于連接的操作,都是基于該conn的。
在該連接完成之后,我們將連接存放到一個(gè)名為Client的map中,以便之后管理更為方便。
之后,我們啟動(dòng)一個(gè)goroutine來(lái)讀取連接中發(fā)送的信息內(nèi)容,再根據(jù)內(nèi)容進(jìn)行相應(yīng)的操作。
切換到新語(yǔ)言始終是一大步,尤其是當(dāng)您的團(tuán)隊(duì)成員只有一個(gè)時(shí)有該語(yǔ)言的先前經(jīng)驗(yàn)?,F(xiàn)在,Stream 的主要編程語(yǔ)言從 Python 切換到了 Go。這篇文章將解釋stream決定放棄 Python 并轉(zhuǎn)向 Go 的一些原因。
Go 非???。性能類(lèi)似于 Java 或 C++。對(duì)于用例,Go 通常比 Python 快 40 倍。
對(duì)于許多應(yīng)用程序來(lái)說(shuō),編程語(yǔ)言只是應(yīng)用程序和數(shù)據(jù)庫(kù)之間的粘合劑。語(yǔ)言本身的性能通常并不重要。然而,Stream 是一個(gè)API 提供商,為 700 家公司和超過(guò) 5 億最終用戶(hù)提供提要和聊天平臺(tái)。多年來(lái),我們一直在優(yōu)化 Cassandra、PostgreSQL、Redis 等,但最終,您會(huì)達(dá)到所使用語(yǔ)言的極限。Python 是一門(mén)很棒的語(yǔ)言,但對(duì)于序列化/反序列化、排名和聚合等用例,它的性能相當(dāng)緩慢。我們經(jīng)常遇到性能問(wèn)題,Cassandra 需要 1 毫秒來(lái)檢索數(shù)據(jù),而 Python 會(huì)花費(fèi)接下來(lái)的 10 毫秒將其轉(zhuǎn)換為對(duì)象。
看看我如何開(kāi)始 Go 教程中的一小段 Go 代碼。(這是一個(gè)很棒的教程,也是學(xué)習(xí) Go 的一個(gè)很好的起點(diǎn)。)
如果您是 Go 新手,那么在閱讀那個(gè)小代碼片段時(shí)不會(huì)有太多讓您感到驚訝的事情。它展示了多個(gè)賦值、數(shù)據(jù)結(jié)構(gòu)、指針、格式和一個(gè)內(nèi)置的 HTTP 庫(kù)。當(dāng)我第一次開(kāi)始編程時(shí),我一直喜歡使用 Python 更高級(jí)的功能。Python 允許您在編寫(xiě)代碼時(shí)獲得相當(dāng)?shù)膭?chuàng)意。例如,您可以:
這些功能玩起來(lái)很有趣,但是,正如大多數(shù)程序員會(huì)同意的那樣,在閱讀別人的作品時(shí),它們通常會(huì)使代碼更難理解。Go 迫使你堅(jiān)持基礎(chǔ)。這使得閱讀任何人的代碼并立即了解發(fā)生了什么變得非常容易。 注意:當(dāng)然,它實(shí)際上有多“容易”取決于您的用例。如果你想創(chuàng)建一個(gè)基本的 CRUD API,我仍然推薦 Django + DRF或 Rails。
作為一門(mén)語(yǔ)言,Go 試圖讓事情變得簡(jiǎn)單。它沒(méi)有引入許多新概念。重點(diǎn)是創(chuàng)建一種非??焖偾乙子谑褂玫暮?jiǎn)單語(yǔ)言。它唯一具有創(chuàng)新性的領(lǐng)域是 goroutine 和通道。(100% 正確CSP的概念始于 1977 年,所以這項(xiàng)創(chuàng)新更多是對(duì)舊思想的一種新方法。)Goroutines 是 Go 的輕量級(jí)線程方法,通道是 goroutines 之間通信的首選方式。Goroutines 的創(chuàng)建非常便宜,并且只需要幾 KB 的額外內(nèi)存。因?yàn)?Goroutine 非常輕量,所以有可能同時(shí)運(yùn)行數(shù)百甚至數(shù)千個(gè)。您可以使用通道在 goroutine 之間進(jìn)行通信。Go 運(yùn)行時(shí)處理所有復(fù)雜性。goroutines 和基于通道的并發(fā)方法使得使用所有可用的 CPU 內(nèi)核和處理并發(fā) IO 變得非常容易——所有這些都不會(huì)使開(kāi)發(fā)復(fù)雜化。與 Python/Java 相比,在 goroutine 上運(yùn)行函數(shù)需要最少的樣板代碼。您只需在函數(shù)調(diào)用前加上關(guān)鍵字“go”:
Go 的并發(fā)方法很容易使用。與 Node 相比,這是一種有趣的方法,開(kāi)發(fā)人員必須密切關(guān)注異步代碼的處理方式。Go 中并發(fā)的另一個(gè)重要方面是競(jìng)爭(zhēng)檢測(cè)器。這樣可以很容易地確定異步代碼中是否存在任何競(jìng)爭(zhēng)條件。
我們目前用 Go 編寫(xiě)的最大的微服務(wù)編譯需要 4 秒。與以編譯速度慢而聞名的 Java 和 C++ 等語(yǔ)言相比,Go 的快速編譯時(shí)間是一項(xiàng)重大的生產(chǎn)力勝利。我喜歡在程序編譯的時(shí)候摸魚(yú),但在我還記得代碼應(yīng)該做什么的同時(shí)完成事情會(huì)更好。
首先,讓我們從顯而易見(jiàn)的開(kāi)始:與 C++ 和 Java 等舊語(yǔ)言相比,Go 開(kāi)發(fā)人員的數(shù)量并不多。根據(jù)StackOverflow的數(shù)據(jù), 38% 的開(kāi)發(fā)人員知道 Java, 19.3% 的人知道 C++,只有 4.6% 的人知道 Go。GitHub 數(shù)據(jù)顯示了類(lèi)似的趨勢(shì):Go 比 Erlang、Scala 和 Elixir 等語(yǔ)言使用更廣泛,但不如 Java 和 C++ 流行。幸運(yùn)的是,Go 是一種非常簡(jiǎn)單易學(xué)的語(yǔ)言。它提供了您需要的基本功能,僅此而已。它引入的新概念是“延遲”聲明和內(nèi)置的并發(fā)管理與“goroutines”和通道。(對(duì)于純粹主義者來(lái)說(shuō):Go 并不是第一種實(shí)現(xiàn)這些概念的語(yǔ)言,只是第一種使它們流行起來(lái)的語(yǔ)言。)任何加入團(tuán)隊(duì)的 Python、Elixir、C++、Scala 或 Java 開(kāi)發(fā)人員都可以在一個(gè)月內(nèi)在 Go 上發(fā)揮作用,因?yàn)樗暮?jiǎn)單性。與許多其他語(yǔ)言相比,我們發(fā)現(xiàn)組建 Go 開(kāi)發(fā)人員團(tuán)隊(duì)更容易。如果您在博爾德和阿姆斯特丹等競(jìng)爭(zhēng)激烈的生態(tài)系統(tǒng)中招聘人員,這是一項(xiàng)重要的優(yōu)勢(shì)。
對(duì)于我們這樣規(guī)模的團(tuán)隊(duì)(約 20 人)來(lái)說(shuō),生態(tài)系統(tǒng)很重要。如果您必須重新發(fā)明每一個(gè)小功能,您根本無(wú)法為您的客戶(hù)創(chuàng)造價(jià)值。Go 對(duì)我們使用的工具有很好的支持。實(shí)體庫(kù)已經(jīng)可用于 Redis、RabbitMQ、PostgreSQL、模板解析、任務(wù)調(diào)度、表達(dá)式解析和 RocksDB。與 Rust 或 Elixir 等其他較新的語(yǔ)言相比,Go 的生態(tài)系統(tǒng)是一個(gè)重大勝利。它當(dāng)然不如 Java、Python 或 Node 之類(lèi)的語(yǔ)言好,但它很可靠,而且對(duì)于許多基本需求,你會(huì)發(fā)現(xiàn)已經(jīng)有高質(zhì)量的包可用。
Gofmt 是一個(gè)很棒的命令行實(shí)用程序,內(nèi)置在 Go 編譯器中,用于格式化代碼。就功能而言,它與 Python 的 autopep8 非常相似。我們大多數(shù)人并不真正喜歡爭(zhēng)論制表符與空格。格式的一致性很重要,但實(shí)際的格式標(biāo)準(zhǔn)并不那么重要。Gofmt 通過(guò)使用一種正式的方式來(lái)格式化您的代碼來(lái)避免所有這些討論。
Go 對(duì)協(xié)議緩沖區(qū)和 gRPC 具有一流的支持。這兩個(gè)工具非常適合構(gòu)建需要通過(guò) RPC 通信的微服務(wù)。您只需要編寫(xiě)一個(gè)清單,在其中定義可以進(jìn)行的 RPC 調(diào)用以及它們采用的參數(shù)。然后從這個(gè)清單中自動(dòng)生成服務(wù)器和客戶(hù)端代碼。生成的代碼既快速又具有非常小的網(wǎng)絡(luò)占用空間并且易于使用。從同一個(gè)清單中,您甚至可以為許多不同的語(yǔ)言生成客戶(hù)端代碼,例如 C++、Java、Python 和 Ruby。因此,內(nèi)部流量不再有模棱兩可的 REST 端點(diǎn),您每次都必須編寫(xiě)幾乎相同的客戶(hù)端和服務(wù)器代碼。.
Go 沒(méi)有像 Rails 用于 Ruby、Django 用于 Python 或 Laravel 用于 PHP 那樣的單一主導(dǎo)框架。這是 Go 社區(qū)內(nèi)激烈爭(zhēng)論的話題,因?yàn)樵S多人主張你不應(yīng)該一開(kāi)始就使用框架。我完全同意這對(duì)于某些用例是正確的。但是,如果有人想構(gòu)建一個(gè)簡(jiǎn)單的 CRUD API,他們將更容易使用 Django/DJRF、Rails Laravel 或Phoenix。對(duì)于 Stream 的用例,我們更喜歡不使用框架。然而,對(duì)于許多希望提供簡(jiǎn)單 CRUD API 的新項(xiàng)目來(lái)說(shuō),缺乏主導(dǎo)框架將是一個(gè)嚴(yán)重的劣勢(shì)。
Go 通過(guò)簡(jiǎn)單地從函數(shù)返回錯(cuò)誤并期望調(diào)用代碼來(lái)處理錯(cuò)誤(或?qū)⑵浞祷氐秸{(diào)用堆棧)來(lái)處理錯(cuò)誤。雖然這種方法有效,但很容易失去問(wèn)題的范圍,以確保您可以向用戶(hù)提供有意義的錯(cuò)誤。錯(cuò)誤包通過(guò)允許您向錯(cuò)誤添加上下文和堆棧跟蹤來(lái)解決此問(wèn)題。另一個(gè)問(wèn)題是很容易忘記處理錯(cuò)誤。像 errcheck 和 megacheck 這樣的靜態(tài)分析工具可以方便地避免犯這些錯(cuò)誤。雖然這些變通辦法效果很好,但感覺(jué)不太對(duì)勁。您希望該語(yǔ)言支持正確的錯(cuò)誤處理。
Go 的包管理絕不是完美的。默認(rèn)情況下,它無(wú)法指定特定版本的依賴(lài)項(xiàng),也無(wú)法創(chuàng)建可重現(xiàn)的構(gòu)建。Python、Node 和 Ruby 都有更好的包管理系統(tǒng)。但是,使用正確的工具,Go 的包管理工作得很好。您可以使用Dep來(lái)管理您的依賴(lài)項(xiàng),以允許指定和固定版本。除此之外,我們還貢獻(xiàn)了一個(gè)名為的開(kāi)源工具VirtualGo,它可以更輕松地處理用 Go 編寫(xiě)的多個(gè)項(xiàng)目。
我們進(jìn)行的一個(gè)有趣的實(shí)驗(yàn)是在 Python 中使用我們的排名提要功能并在 Go 中重寫(xiě)它。看看這個(gè)排名方法的例子:
Python 和 Go 代碼都需要執(zhí)行以下操作來(lái)支持這種排名方法:
開(kāi)發(fā) Python 版本的排名代碼大約花了 3 天時(shí)間。這包括編寫(xiě)代碼、單元測(cè)試和文檔。接下來(lái),我們花了大約 2 周的時(shí)間優(yōu)化代碼。其中一項(xiàng)優(yōu)化是將分?jǐn)?shù)表達(dá)式 (simple_gauss(time)*popularity) 轉(zhuǎn)換為抽象語(yǔ)法樹(shù). 我們還實(shí)現(xiàn)了緩存邏輯,可以在未來(lái)的特定時(shí)間預(yù)先計(jì)算分?jǐn)?shù)。相比之下,開(kāi)發(fā)此代碼的 Go 版本大約需要 4 天時(shí)間。性能不需要任何進(jìn)一步的優(yōu)化。因此,雖然 Python 的最初開(kāi)發(fā)速度更快,但基于 Go 的版本最終需要我們團(tuán)隊(duì)的工作量大大減少。另外一個(gè)好處是,Go 代碼的執(zhí)行速度比我們高度優(yōu)化的 Python 代碼快大約 40 倍?,F(xiàn)在,這只是我們通過(guò)切換到 Go 體驗(yàn)到的性能提升的一個(gè)示例。
與 Python 相比,我們系統(tǒng)的其他一些組件在 Go 中構(gòu)建所需的時(shí)間要多得多。作為一個(gè)總體趨勢(shì),我們看到 開(kāi)發(fā) Go 代碼需要更多的努力。但是,我們花更少的時(shí)間 優(yōu)化 代碼以提高性能。
我們?cè)u(píng)估的另一種語(yǔ)言是Elixir.。Elixir 建立在 Erlang 虛擬機(jī)之上。這是一種迷人的語(yǔ)言,我們之所以考慮它,是因?yàn)槲覀兊囊幻麍F(tuán)隊(duì)成員在 Erlang 方面擁有豐富的經(jīng)驗(yàn)。對(duì)于我們的用例,我們注意到 Go 的原始性能要好得多。Go 和 Elixir 都可以很好地服務(wù)數(shù)千個(gè)并發(fā)請(qǐng)求。但是,如果您查看單個(gè)請(qǐng)求的性能,Go 對(duì)于我們的用例來(lái)說(shuō)要快得多。我們選擇 Go 而不是 Elixir 的另一個(gè)原因是生態(tài)系統(tǒng)。對(duì)于我們需要的組件,Go 有更成熟的庫(kù),而在許多情況下,Elixir 庫(kù)還沒(méi)有準(zhǔn)備好用于生產(chǎn)環(huán)境。培訓(xùn)/尋找開(kāi)發(fā)人員使用 Elixir 也更加困難。這些原因使天平向 Go 傾斜。Elixir 的 Phoenix 框架看起來(lái)很棒,絕對(duì)值得一看。
Go 是一種非常高性能的語(yǔ)言,對(duì)并發(fā)有很好的支持。它幾乎與 C++ 和 Java 等語(yǔ)言一樣快。雖然與 Python 或 Ruby 相比,使用 Go 構(gòu)建東西確實(shí)需要更多時(shí)間,但您將節(jié)省大量用于優(yōu)化代碼的時(shí)間。我們?cè)赟tream有一個(gè)小型開(kāi)發(fā)團(tuán)隊(duì),為超過(guò) 5 億最終用戶(hù)提供動(dòng)力和聊天。Go 結(jié)合了 強(qiáng)大的生態(tài)系統(tǒng) 、新開(kāi)發(fā)人員的 輕松入門(mén)、快速的性能 、對(duì)并發(fā)的 可靠支持和高效的編程環(huán)境 ,使其成為一個(gè)不錯(cuò)的選擇。Stream 仍然在我們的儀表板、站點(diǎn)和機(jī)器學(xué)習(xí)中利用 Python 來(lái)提供個(gè)性化的訂閱源. 我們不會(huì)很快與 Python 說(shuō)再見(jiàn),但今后所有性能密集型代碼都將使用 Go 編寫(xiě)。我們新的聊天 API也完全用 Go 編寫(xiě)。