切換到新語言始終是一大步,尤其是當(dāng)您的團(tuán)隊成員只有一個時有該語言的先前經(jīng)驗。現(xiàn)在,Stream 的主要編程語言從 Python 切換到了 Go。這篇文章將解釋stream決定放棄 Python 并轉(zhuǎn)向 Go 的一些原因。
創(chuàng)新互聯(lián)建站自2013年起,先為西崗等服務(wù)建站,西崗等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為西崗企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
Go 非??臁P阅茴愃朴?Java 或 C++。對于用例,Go 通常比 Python 快 40 倍。
對于許多應(yīng)用程序來說,編程語言只是應(yīng)用程序和數(shù)據(jù)庫之間的粘合劑。語言本身的性能通常并不重要。然而,Stream 是一個API 提供商,為 700 家公司和超過 5 億最終用戶提供提要和聊天平臺。多年來,我們一直在優(yōu)化 Cassandra、PostgreSQL、Redis 等,但最終,您會達(dá)到所使用語言的極限。Python 是一門很棒的語言,但對于序列化/反序列化、排名和聚合等用例,它的性能相當(dāng)緩慢。我們經(jīng)常遇到性能問題,Cassandra 需要 1 毫秒來檢索數(shù)據(jù),而 Python 會花費接下來的 10 毫秒將其轉(zhuǎn)換為對象。
看看我如何開始 Go 教程中的一小段 Go 代碼。(這是一個很棒的教程,也是學(xué)習(xí) Go 的一個很好的起點。)
如果您是 Go 新手,那么在閱讀那個小代碼片段時不會有太多讓您感到驚訝的事情。它展示了多個賦值、數(shù)據(jù)結(jié)構(gòu)、指針、格式和一個內(nèi)置的 HTTP 庫。當(dāng)我第一次開始編程時,我一直喜歡使用 Python 更高級的功能。Python 允許您在編寫代碼時獲得相當(dāng)?shù)膭?chuàng)意。例如,您可以:
這些功能玩起來很有趣,但是,正如大多數(shù)程序員會同意的那樣,在閱讀別人的作品時,它們通常會使代碼更難理解。Go 迫使你堅持基礎(chǔ)。這使得閱讀任何人的代碼并立即了解發(fā)生了什么變得非常容易。 注意:當(dāng)然,它實際上有多“容易”取決于您的用例。如果你想創(chuàng)建一個基本的 CRUD API,我仍然推薦 Django + DRF或 Rails。
作為一門語言,Go 試圖讓事情變得簡單。它沒有引入許多新概念。重點是創(chuàng)建一種非??焖偾乙子谑褂玫暮唵握Z言。它唯一具有創(chuàng)新性的領(lǐng)域是 goroutine 和通道。(100% 正確CSP的概念始于 1977 年,所以這項創(chuàng)新更多是對舊思想的一種新方法。)Goroutines 是 Go 的輕量級線程方法,通道是 goroutines 之間通信的首選方式。Goroutines 的創(chuàng)建非常便宜,并且只需要幾 KB 的額外內(nèi)存。因為 Goroutine 非常輕量,所以有可能同時運行數(shù)百甚至數(shù)千個。您可以使用通道在 goroutine 之間進(jìn)行通信。Go 運行時處理所有復(fù)雜性。goroutines 和基于通道的并發(fā)方法使得使用所有可用的 CPU 內(nèi)核和處理并發(fā) IO 變得非常容易——所有這些都不會使開發(fā)復(fù)雜化。與 Python/Java 相比,在 goroutine 上運行函數(shù)需要最少的樣板代碼。您只需在函數(shù)調(diào)用前加上關(guān)鍵字“go”:
Go 的并發(fā)方法很容易使用。與 Node 相比,這是一種有趣的方法,開發(fā)人員必須密切關(guān)注異步代碼的處理方式。Go 中并發(fā)的另一個重要方面是競爭檢測器。這樣可以很容易地確定異步代碼中是否存在任何競爭條件。
我們目前用 Go 編寫的最大的微服務(wù)編譯需要 4 秒。與以編譯速度慢而聞名的 Java 和 C++ 等語言相比,Go 的快速編譯時間是一項重大的生產(chǎn)力勝利。我喜歡在程序編譯的時候摸魚,但在我還記得代碼應(yīng)該做什么的同時完成事情會更好。
首先,讓我們從顯而易見的開始:與 C++ 和 Java 等舊語言相比,Go 開發(fā)人員的數(shù)量并不多。根據(jù)StackOverflow的數(shù)據(jù), 38% 的開發(fā)人員知道 Java, 19.3% 的人知道 C++,只有 4.6% 的人知道 Go。GitHub 數(shù)據(jù)顯示了類似的趨勢:Go 比 Erlang、Scala 和 Elixir 等語言使用更廣泛,但不如 Java 和 C++ 流行。幸運的是,Go 是一種非常簡單易學(xué)的語言。它提供了您需要的基本功能,僅此而已。它引入的新概念是“延遲”聲明和內(nèi)置的并發(fā)管理與“goroutines”和通道。(對于純粹主義者來說:Go 并不是第一種實現(xiàn)這些概念的語言,只是第一種使它們流行起來的語言。)任何加入團(tuán)隊的 Python、Elixir、C++、Scala 或 Java 開發(fā)人員都可以在一個月內(nèi)在 Go 上發(fā)揮作用,因為它的簡單性。與許多其他語言相比,我們發(fā)現(xiàn)組建 Go 開發(fā)人員團(tuán)隊更容易。如果您在博爾德和阿姆斯特丹等競爭激烈的生態(tài)系統(tǒng)中招聘人員,這是一項重要的優(yōu)勢。
對于我們這樣規(guī)模的團(tuán)隊(約 20 人)來說,生態(tài)系統(tǒng)很重要。如果您必須重新發(fā)明每一個小功能,您根本無法為您的客戶創(chuàng)造價值。Go 對我們使用的工具有很好的支持。實體庫已經(jīng)可用于 Redis、RabbitMQ、PostgreSQL、模板解析、任務(wù)調(diào)度、表達(dá)式解析和 RocksDB。與 Rust 或 Elixir 等其他較新的語言相比,Go 的生態(tài)系統(tǒng)是一個重大勝利。它當(dāng)然不如 Java、Python 或 Node 之類的語言好,但它很可靠,而且對于許多基本需求,你會發(fā)現(xiàn)已經(jīng)有高質(zhì)量的包可用。
Gofmt 是一個很棒的命令行實用程序,內(nèi)置在 Go 編譯器中,用于格式化代碼。就功能而言,它與 Python 的 autopep8 非常相似。我們大多數(shù)人并不真正喜歡爭論制表符與空格。格式的一致性很重要,但實際的格式標(biāo)準(zhǔn)并不那么重要。Gofmt 通過使用一種正式的方式來格式化您的代碼來避免所有這些討論。
Go 對協(xié)議緩沖區(qū)和 gRPC 具有一流的支持。這兩個工具非常適合構(gòu)建需要通過 RPC 通信的微服務(wù)。您只需要編寫一個清單,在其中定義可以進(jìn)行的 RPC 調(diào)用以及它們采用的參數(shù)。然后從這個清單中自動生成服務(wù)器和客戶端代碼。生成的代碼既快速又具有非常小的網(wǎng)絡(luò)占用空間并且易于使用。從同一個清單中,您甚至可以為許多不同的語言生成客戶端代碼,例如 C++、Java、Python 和 Ruby。因此,內(nèi)部流量不再有模棱兩可的 REST 端點,您每次都必須編寫幾乎相同的客戶端和服務(wù)器代碼。.
Go 沒有像 Rails 用于 Ruby、Django 用于 Python 或 Laravel 用于 PHP 那樣的單一主導(dǎo)框架。這是 Go 社區(qū)內(nèi)激烈爭論的話題,因為許多人主張你不應(yīng)該一開始就使用框架。我完全同意這對于某些用例是正確的。但是,如果有人想構(gòu)建一個簡單的 CRUD API,他們將更容易使用 Django/DJRF、Rails Laravel 或Phoenix。對于 Stream 的用例,我們更喜歡不使用框架。然而,對于許多希望提供簡單 CRUD API 的新項目來說,缺乏主導(dǎo)框架將是一個嚴(yán)重的劣勢。
Go 通過簡單地從函數(shù)返回錯誤并期望調(diào)用代碼來處理錯誤(或?qū)⑵浞祷氐秸{(diào)用堆棧)來處理錯誤。雖然這種方法有效,但很容易失去問題的范圍,以確保您可以向用戶提供有意義的錯誤。錯誤包通過允許您向錯誤添加上下文和堆棧跟蹤來解決此問題。另一個問題是很容易忘記處理錯誤。像 errcheck 和 megacheck 這樣的靜態(tài)分析工具可以方便地避免犯這些錯誤。雖然這些變通辦法效果很好,但感覺不太對勁。您希望該語言支持正確的錯誤處理。
Go 的包管理絕不是完美的。默認(rèn)情況下,它無法指定特定版本的依賴項,也無法創(chuàng)建可重現(xiàn)的構(gòu)建。Python、Node 和 Ruby 都有更好的包管理系統(tǒng)。但是,使用正確的工具,Go 的包管理工作得很好。您可以使用Dep來管理您的依賴項,以允許指定和固定版本。除此之外,我們還貢獻(xiàn)了一個名為的開源工具VirtualGo,它可以更輕松地處理用 Go 編寫的多個項目。
我們進(jìn)行的一個有趣的實驗是在 Python 中使用我們的排名提要功能并在 Go 中重寫它。看看這個排名方法的例子:
Python 和 Go 代碼都需要執(zhí)行以下操作來支持這種排名方法:
開發(fā) Python 版本的排名代碼大約花了 3 天時間。這包括編寫代碼、單元測試和文檔。接下來,我們花了大約 2 周的時間優(yōu)化代碼。其中一項優(yōu)化是將分?jǐn)?shù)表達(dá)式 (simple_gauss(time)*popularity) 轉(zhuǎn)換為抽象語法樹. 我們還實現(xiàn)了緩存邏輯,可以在未來的特定時間預(yù)先計算分?jǐn)?shù)。相比之下,開發(fā)此代碼的 Go 版本大約需要 4 天時間。性能不需要任何進(jìn)一步的優(yōu)化。因此,雖然 Python 的最初開發(fā)速度更快,但基于 Go 的版本最終需要我們團(tuán)隊的工作量大大減少。另外一個好處是,Go 代碼的執(zhí)行速度比我們高度優(yōu)化的 Python 代碼快大約 40 倍?,F(xiàn)在,這只是我們通過切換到 Go 體驗到的性能提升的一個示例。
與 Python 相比,我們系統(tǒng)的其他一些組件在 Go 中構(gòu)建所需的時間要多得多。作為一個總體趨勢,我們看到 開發(fā) Go 代碼需要更多的努力。但是,我們花更少的時間 優(yōu)化 代碼以提高性能。
我們評估的另一種語言是Elixir.。Elixir 建立在 Erlang 虛擬機(jī)之上。這是一種迷人的語言,我們之所以考慮它,是因為我們的一名團(tuán)隊成員在 Erlang 方面擁有豐富的經(jīng)驗。對于我們的用例,我們注意到 Go 的原始性能要好得多。Go 和 Elixir 都可以很好地服務(wù)數(shù)千個并發(fā)請求。但是,如果您查看單個請求的性能,Go 對于我們的用例來說要快得多。我們選擇 Go 而不是 Elixir 的另一個原因是生態(tài)系統(tǒng)。對于我們需要的組件,Go 有更成熟的庫,而在許多情況下,Elixir 庫還沒有準(zhǔn)備好用于生產(chǎn)環(huán)境。培訓(xùn)/尋找開發(fā)人員使用 Elixir 也更加困難。這些原因使天平向 Go 傾斜。Elixir 的 Phoenix 框架看起來很棒,絕對值得一看。
Go 是一種非常高性能的語言,對并發(fā)有很好的支持。它幾乎與 C++ 和 Java 等語言一樣快。雖然與 Python 或 Ruby 相比,使用 Go 構(gòu)建東西確實需要更多時間,但您將節(jié)省大量用于優(yōu)化代碼的時間。我們在Stream有一個小型開發(fā)團(tuán)隊,為超過 5 億最終用戶提供動力和聊天。Go 結(jié)合了 強(qiáng)大的生態(tài)系統(tǒng) 、新開發(fā)人員的 輕松入門、快速的性能 、對并發(fā)的 可靠支持和高效的編程環(huán)境 ,使其成為一個不錯的選擇。Stream 仍然在我們的儀表板、站點和機(jī)器學(xué)習(xí)中利用 Python 來提供個性化的訂閱源. 我們不會很快與 Python 說再見,但今后所有性能密集型代碼都將使用 Go 編寫。我們新的聊天 API也完全用 Go 編寫。
很多人盲目的尋找所謂的go語言開發(fā)培訓(xùn)排行榜,但是因為沒有一個統(tǒng)一的標(biāo)準(zhǔn),這樣一個靠譜的排行榜其實不存在,請注意我這里說的是靠譜的。排行榜有,但都是一些個人總結(jié)的,有太多的個人主觀的喜好在里面,沒有權(quán)威的第三方機(jī)構(gòu)進(jìn)行評測。那么排行榜不靠譜,那么對于新手來說,怎樣才能找到靠譜的go語言開發(fā)培訓(xùn)班呢?
1、看培訓(xùn)機(jī)構(gòu)的口碑
一般來說,口碑較好的培訓(xùn)機(jī)構(gòu),說明本身的實力是很不錯的,不然實力差的,口碑也不會好的,也不可能支撐太久。所以這里推薦優(yōu)先考慮口碑好的培訓(xùn)機(jī)構(gòu),更安全更放心。比如我所在的學(xué)校,自成立以來就深受學(xué)員的信賴,今年還在火爆中。
2、教學(xué)實力
學(xué)的怎么樣,不僅看自己用不用功,還要看老師教的好不好。老師水平高,交出來的徒弟水平也不會差。所以多去了解講課的老師的經(jīng)歷,以往做過什么項目,工作了多少年,網(wǎng)上或者官網(wǎng)就能了解到。
3、課程內(nèi)容
課程學(xué)習(xí)內(nèi)容對我們來說非常重要,將決定著我們能學(xué)到什么級別的技術(shù),以后能找到什么級別的工作等等,所以在找go語言開發(fā)培訓(xùn)班的時候,看一下其培訓(xùn)課程大綱是非常有必要的。
1. 介紹
最近在研究一些消息中間件,常用的MQ如RabbitMQ,ActiveMQ,Kafka等。NSQ是一個基于Go語言的分布式實時消息平臺,它基于MIT開源協(xié)議發(fā)布,由bitly公司開源出來的一款簡單易用的消息中間件。
官方和第三方還為NSQ開發(fā)了眾多客戶端功能庫,如官方提供的基于HTTP的nsqd、Go客戶端go-nsq、Python客戶端pynsq、基于Node.js的JavaScript客戶端nsqjs、異步C客戶端libnsq、Java客戶端nsq-java以及基于各種語言的眾多第三方客戶端功能庫。
1.1 Features
1). Distributed
NSQ提供了分布式的,去中心化,且沒有單點故障的拓?fù)浣Y(jié)構(gòu),穩(wěn)定的消息傳輸發(fā)布保障,能夠具有高容錯和HA(高可用)特性。
2). Scalable易于擴(kuò)展
NSQ支持水平擴(kuò)展,沒有中心化的brokers。內(nèi)置的發(fā)現(xiàn)服務(wù)簡化了在集群中增加節(jié)點。同時支持pub-sub和load-balanced 的消息分發(fā)。
3). Ops Friendly
NSQ非常容易配置和部署,生來就綁定了一個管理界面。二進(jìn)制包沒有運行時依賴。官方有Docker image。
4.Integrated高度集成
官方的 Go 和 Python庫都有提供。而且為大多數(shù)語言提供了庫。
1.2 組件
1.3 拓?fù)浣Y(jié)構(gòu)
NSQ推薦通過他們相應(yīng)的nsqd實例使用協(xié)同定位發(fā)布者,這意味著即使面對網(wǎng)絡(luò)分區(qū),消息也會被保存在本地,直到它們被一個消費者讀取。更重要的是,發(fā)布者不必去發(fā)現(xiàn)其他的nsqd節(jié)點,他們總是可以向本地實例發(fā)布消息。
NSQ
首先,一個發(fā)布者向它的本地nsqd發(fā)送消息,要做到這點,首先要先打開一個連接,然后發(fā)送一個包含topic和消息主體的發(fā)布命令,在這種情況下,我們將消息發(fā)布到事件topic上以分散到我們不同的worker中。
事件topic會復(fù)制這些消息并且在每一個連接topic的channel上進(jìn)行排隊,在我們的案例中,有三個channel,它們其中之一作為檔案channel。消費者會獲取這些消息并且上傳到S3。
nsqd
每個channel的消息都會進(jìn)行排隊,直到一個worker把他們消費,如果此隊列超出了內(nèi)存限制,消息將會被寫入到磁盤中。Nsqd節(jié)點首先會向nsqlookup廣播他們的位置信息,一旦它們注冊成功,worker將會從nsqlookup服務(wù)器節(jié)點上發(fā)現(xiàn)所有包含事件topic的nsqd節(jié)點。
nsqlookupd
2. Internals
2.1 消息傳遞擔(dān)保
1)客戶表示已經(jīng)準(zhǔn)備好接收消息
2)NSQ 發(fā)送一條消息,并暫時將數(shù)據(jù)存儲在本地(在 re-queue 或 timeout)
3)客戶端回復(fù) FIN(結(jié)束)或 REQ(重新排隊)分別指示成功或失敗。如果客戶端沒有回復(fù), NSQ 會在設(shè)定的時間超時,自動重新排隊消息
這確保了消息丟失唯一可能的情況是不正常結(jié)束 nsqd 進(jìn)程。在這種情況下,這是在內(nèi)存中的任何信息(或任何緩沖未刷新到磁盤)都將丟失。
如何防止消息丟失是最重要的,即使是這個意外情況可以得到緩解。一種解決方案是構(gòu)成冗余 nsqd對(在不同的主機(jī)上)接收消息的相同部分的副本。因為你實現(xiàn)的消費者是冪等的,以兩倍時間處理這些消息不會對下游造成影響,并使得系統(tǒng)能夠承受任何單一節(jié)點故障而不會丟失信息。
2.2 簡化配置和管理
單個 nsqd 實例被設(shè)計成可以同時處理多個數(shù)據(jù)流。流被稱為“話題”和話題有 1 個或多個“通道”。每個通道都接收到一個話題中所有消息的拷貝。在實踐中,一個通道映射到下行服務(wù)消費一個話題。
在更底的層面,每個 nsqd 有一個與 nsqlookupd 的長期 TCP 連接,定期推動其狀態(tài)。這個數(shù)據(jù)被 nsqlookupd 用于給消費者通知 nsqd 地址。對于消費者來說,一個暴露的 HTTP /lookup 接口用于輪詢。為話題引入一個新的消費者,只需啟動一個配置了 nsqlookup 實例地址的 NSQ 客戶端。無需為添加任何新的消費者或生產(chǎn)者更改配置,大大降低了開銷和復(fù)雜性。
2.3 消除單點故障
NSQ被設(shè)計以分布的方式被使用。nsqd 客戶端(通過 TCP )連接到指定話題的所有生產(chǎn)者實例。沒有中間人,沒有消息代理,也沒有單點故障。
這種拓?fù)浣Y(jié)構(gòu)消除單鏈,聚合,反饋。相反,你的消費者直接訪問所有生產(chǎn)者。從技術(shù)上講,哪個客戶端連接到哪個 NSQ 不重要,只要有足夠的消費者連接到所有生產(chǎn)者,以滿足大量的消息,保證所有東西最終將被處理。對于 nsqlookupd,高可用性是通過運行多個實例來實現(xiàn)。他們不直接相互通信和數(shù)據(jù)被認(rèn)為是最終一致。消費者輪詢所有的配置的 nsqlookupd 實例和合并 response。失敗的,無法訪問的,或以其他方式故障的節(jié)點不會讓系統(tǒng)陷于停頓。
2.4 效率
對于數(shù)據(jù)的協(xié)議,通過推送數(shù)據(jù)到客戶端最大限度地提高性能和吞吐量的,而不是等待客戶端拉數(shù)據(jù)。這個概念,稱之為 RDY 狀態(tài),基本上是客戶端流量控制的一種形式。
efficiency
2.5 心跳和超時
組合應(yīng)用級別的心跳和 RDY 狀態(tài),避免頭阻塞現(xiàn)象,也可能使心跳無用(即,如果消費者是在后面的處理消息流的接收緩沖區(qū)中,操作系統(tǒng)將被填滿,堵心跳)為了保證進(jìn)度,所有的網(wǎng)絡(luò) IO 時間上限勢必與配置的心跳間隔相關(guān)聯(lián)。這意味著,你可以從字面上拔掉之間的網(wǎng)絡(luò)連接 nsqd 和消費者,它會檢測并正確處理錯誤。當(dāng)檢測到一個致命錯誤,客戶端連接被強(qiáng)制關(guān)閉。在傳輸中的消息會超時而重新排隊等待傳遞到另一個消費者。最后,錯誤會被記錄并累計到各種內(nèi)部指標(biāo)。
2.6 分布式
因為NSQ沒有在守護(hù)程序之間共享信息,所以它從一開始就是為了分布式操作而生。個別的機(jī)器可以隨便宕機(jī)隨便啟動而不會影響到系統(tǒng)的其余部分,消息發(fā)布者可以在本地發(fā)布,即使面對網(wǎng)絡(luò)分區(qū)。
這種“分布式優(yōu)先”的設(shè)計理念意味著NSQ基本上可以永遠(yuǎn)不斷地擴(kuò)展,需要更高的吞吐量?那就添加更多的nsqd吧。唯一的共享狀態(tài)就是保存在lookup節(jié)點上,甚至它們不需要全局視圖,配置某些nsqd注冊到某些lookup節(jié)點上這是很簡單的配置,唯一關(guān)鍵的地方就是消費者可以通過lookup節(jié)點獲取所有完整的節(jié)點集。清晰的故障事件——NSQ在組件內(nèi)建立了一套明確關(guān)于可能導(dǎo)致故障的的故障權(quán)衡機(jī)制,這對消息傳遞和恢復(fù)都有意義。雖然它們可能不像Kafka系統(tǒng)那樣提供嚴(yán)格的保證級別,但NSQ簡單的操作使故障情況非常明顯。
2.7 no replication
不像其他的隊列組件,NSQ并沒有提供任何形式的復(fù)制和集群,也正是這點讓它能夠如此簡單地運行,但它確實對于一些高保證性高可靠性的消息發(fā)布沒有足夠的保證。我們可以通過降低文件同步的時間來部分避免,只需通過一個標(biāo)志配置,通過EBS支持我們的隊列。但是這樣仍然存在一個消息被發(fā)布后馬上死亡,丟失了有效的寫入的情況。
2.8 沒有嚴(yán)格的順序
雖然Kafka由一個有序的日志構(gòu)成,但NSQ不是。消息可以在任何時間以任何順序進(jìn)入隊列。在我們使用的案例中,這通常沒有關(guān)系,因為所有的數(shù)據(jù)都被加上了時間戳,但它并不適合需要嚴(yán)格順序的情況。
2.9 無數(shù)據(jù)重復(fù)刪除功能
NSQ對于超時系統(tǒng),它使用了心跳檢測機(jī)制去測試消費者是否存活還是死亡。很多原因會導(dǎo)致我們的consumer無法完成心跳檢測,所以在consumer中必須有一個單獨的步驟確保冪等性。
3. 實踐安裝過程
本文將nsq集群具體的安裝過程略去,大家可以自行參考官網(wǎng),比較簡單。這部分介紹下筆者實驗的拓?fù)?,以及nsqadmin的相關(guān)信息。
3.1 拓?fù)浣Y(jié)構(gòu)
topology
實驗采用3臺NSQD服務(wù),2臺LOOKUPD服務(wù)。
采用官方推薦的拓?fù)?,消息發(fā)布的服務(wù)和NSQD在一臺主機(jī)。一共5臺機(jī)器。
NSQ基本沒有配置文件,配置通過命令行指定參數(shù)。
主要命令如下:
LOOKUPD命令
NSQD命令
工具類,消費后存儲到本地文件。
發(fā)布一條消息
3.2 nsqadmin
對Streams的詳細(xì)信息進(jìn)行查看,包括NSQD節(jié)點,具體的channel,隊列中的消息數(shù),連接數(shù)等信息。
nsqadmin
channel
列出所有的NSQD節(jié)點:
nodes
消息的統(tǒng)計:
msgs
lookup主機(jī)的列表:
hosts
4. 總結(jié)
NSQ基本核心就是簡單性,是一個簡單的隊列,這意味著它很容易進(jìn)行故障推理和很容易發(fā)現(xiàn)bug。消費者可以自行處理故障事件而不會影響系統(tǒng)剩下的其余部分。
事實上,簡單性是我們決定使用NSQ的首要因素,這方便與我們的許多其他軟件一起維護(hù),通過引入隊列使我們得到了堪稱完美的表現(xiàn),通過隊列甚至讓我們增加了幾個數(shù)量級的吞吐量。越來越多的consumer需要一套嚴(yán)格可靠性和順序性保障,這已經(jīng)超過了NSQ提供的簡單功能。
結(jié)合我們的業(yè)務(wù)系統(tǒng)來看,對于我們所需要傳輸?shù)陌l(fā)票消息,相對比較敏感,無法容忍某個nsqd宕機(jī),或者磁盤無法使用的情況,該節(jié)點堆積的消息無法找回。這是我們沒有選擇該消息中間件的主要原因。簡單性和可靠性似乎并不能完全滿足。相比Kafka,ops肩負(fù)起更多負(fù)責(zé)的運營。另一方面,它擁有一個可復(fù)制的、有序的日志可以提供給我們更好的服務(wù)。但對于其他適合NSQ的consumer,它為我們服務(wù)的相當(dāng)好,我們期待著繼續(xù)鞏固它的堅實的基礎(chǔ)。
一、Kafka簡述
1. 為什么需要用到消息隊列
異步:對比以前的串行同步方式來說,可以在同一時間做更多的事情,提高效率;
解耦:在耦合太高的場景,多個任務(wù)要對同一個數(shù)據(jù)進(jìn)行操作消費的時候,會導(dǎo)致一個任務(wù)的處理因為另一個任務(wù)對數(shù)據(jù)的操作變得及其復(fù)雜。
緩沖:當(dāng)遇到突發(fā)大流量的時候,消息隊列可以先把所有消息有序保存起來,避免直接作用于系統(tǒng)主體,系統(tǒng)主題始終以一個平穩(wěn)的速率去消費這些消息。
2.為什么選擇kafka呢?
這沒有絕對的好壞,看個人需求來選擇,我這里就抄了一段他人總結(jié)的的優(yōu)缺點,可見原文
kafka的優(yōu)點:
1.支持多個生產(chǎn)者和消費者2.支持broker的橫向拓展3.副本集機(jī)制,實現(xiàn)數(shù)據(jù)冗余,保證數(shù)據(jù)不丟失4.通過topic將數(shù)據(jù)進(jìn)行分類5.通過分批發(fā)送壓縮數(shù)據(jù)的方式,減少數(shù)據(jù)傳輸開銷,提高吞高量6.支持多種模式的消息7.基于磁盤實現(xiàn)數(shù)據(jù)的持久化8.高性能的處理信息,在大數(shù)據(jù)的情況下,可以保證亞秒級的消息延遲9.一個消費者可以支持多種topic的消息10.對CPU和內(nèi)存的消耗比較小11.對網(wǎng)絡(luò)開銷也比較小12.支持跨數(shù)據(jù)中心的數(shù)據(jù)復(fù)制13.支持鏡像集群
kafka的缺點:
1.由于是批量發(fā)送,所以數(shù)據(jù)達(dá)不到真正的實時2.對于mqtt協(xié)議不支持3.不支持物聯(lián)網(wǎng)傳感數(shù)據(jù)直接接入4.只能支持統(tǒng)一分區(qū)內(nèi)消息有序,無法實現(xiàn)全局消息有序5.監(jiān)控不完善,需要安裝插件6.需要配合zookeeper進(jìn)行元數(shù)據(jù)管理7.會丟失數(shù)據(jù),并且不支持事務(wù)8.可能會重復(fù)消費數(shù)據(jù),消息會亂序,可用保證一個固定的partition內(nèi)部的消息是有序的,但是一個topic有多個partition的話,就不能保證有序了,需要zookeeper的支持,topic一般需要人工創(chuàng)建,部署和維護(hù)一般都比mq高
3. Golang 操作kafka
3.1. kafka的環(huán)境
網(wǎng)上有很多搭建kafka環(huán)境教程,這里就不再搭建,就展示一下kafka的環(huán)境,在kubernetes上進(jìn)行的搭建,有需要的私我,可以發(fā)yaml文件
3.2. 第三方庫
github.com/Shopify/sarama // kafka主要的庫*github.com/bsm/sarama-cluster // kafka消費組
3.3. 消費者
單個消費者
funcconsumer(){varwg sync.WaitGroup? consumer, err := sarama.NewConsumer([]string{"172.20.3.13:30901"},nil)iferr !=nil{? ? ? fmt.Println("Failed to start consumer: %s", err)return}? partitionList, err := consumer.Partitions("test0")//獲得該topic所有的分區(qū)iferr !=nil{? ? ? fmt.Println("Failed to get the list of partition:, ", err)return}forpartition :=rangepartitionList {? ? ? pc, err := consumer.ConsumePartition("test0",int32(partition), sarama.OffsetNewest)iferr !=nil{? ? ? ? fmt.Println("Failed to start consumer for partition %d: %s\n", partition, err)return}? ? ? wg.Add(1)gofunc(sarama.PartitionConsumer){//為每個分區(qū)開一個go協(xié)程去取值formsg :=rangepc.Messages() {//阻塞直到有值發(fā)送過來,然后再繼續(xù)等待fmt.Printf("Partition:%d, Offset:%d, key:%s, value:%s\n", msg.Partition, msg.Offset,string(msg.Key),string(msg.Value))? ? ? ? }deferpc.AsyncClose()? ? ? ? wg.Done()? ? ? }(pc)? }? wg.Wait()}funcmain(){? consumer()}
消費組
funcconsumerCluster(){? groupID :="group-1"config := cluster.NewConfig()? config.Group.Return.Notifications =trueconfig.Consumer.Offsets.CommitInterval =1* time.Second? config.Consumer.Offsets.Initial = sarama.OffsetNewest//初始從最新的offset開始c, err := cluster.NewConsumer(strings.Split("172.20.3.13:30901",","),groupID, strings.Split("test0",","), config)iferr !=nil{? ? ? glog.Errorf("Failed open consumer: %v", err)return}deferc.Close()gofunc(c *cluster.Consumer){? ? ? errors := c.Errors()? ? ? noti := c.Notifications()for{select{caseerr := -errors:? ? ? ? ? ? glog.Errorln(err)case-noti:? ? ? ? }? ? ? }? }(c)formsg :=rangec.Messages() {? ? ? fmt.Printf("Partition:%d, Offset:%d, key:%s, value:%s\n", msg.Partition, msg.Offset,string(msg.Key),string(msg.Value))? ? ? c.MarkOffset(msg,"")//MarkOffset 并不是實時寫入kafka,有可能在程序crash時丟掉未提交的offset}}funcmain(){goconsumerCluster()}
3.4. 生產(chǎn)者
同步生產(chǎn)者
packagemainimport("fmt""github.com/Shopify/sarama")funcmain(){? config := sarama.NewConfig()? config.Producer.RequiredAcks = sarama.WaitForAll//賦值為-1:這意味著producer在follower副本確認(rèn)接收到數(shù)據(jù)后才算一次發(fā)送完成。config.Producer.Partitioner = sarama.NewRandomPartitioner//寫到隨機(jī)分區(qū)中,默認(rèn)設(shè)置8個分區(qū)config.Producer.Return.Successes =truemsg := sarama.ProducerMessage{}? msg.Topic =`test0`msg.Value = sarama.StringEncoder("Hello World!")? client, err := sarama.NewSyncProducer([]string{"172.20.3.13:30901"}, config)iferr !=nil{? ? ? fmt.Println("producer close err, ", err)return}deferclient.Close()? pid, offset, err := client.SendMessage(msg)iferr !=nil{? ? ? fmt.Println("send message failed, ", err)return}? fmt.Printf("分區(qū)ID:%v, offset:%v \n", pid, offset)}
異步生產(chǎn)者
funcasyncProducer(){? config := sarama.NewConfig()? config.Producer.Return.Successes =true//必須有這個選項config.Producer.Timeout =5* time.Second? p, err := sarama.NewAsyncProducer(strings.Split("172.20.3.13:30901",","), config)deferp.Close()iferr !=nil{return}//這個部分一定要寫,不然通道會被堵塞gofunc(p sarama.AsyncProducer){? ? ? errors := p.Errors()? ? ? success := p.Successes()for{select{caseerr := -errors:iferr !=nil{? ? ? ? ? ? ? glog.Errorln(err)? ? ? ? ? ? }case-success:? ? ? ? }? ? ? }? }(p)for{? ? ? v :="async: "+ strconv.Itoa(rand.New(rand.NewSource(time.Now().UnixNano())).Intn(10000))? ? ? fmt.Fprintln(os.Stdout, v)? ? ? msg := sarama.ProducerMessage{? ? ? ? Topic: topics,? ? ? ? Value: sarama.ByteEncoder(v),? ? ? }? ? ? p.Input() - msg? ? ? time.Sleep(time.Second *1)? }}funcmain(){goasyncProducer()select{? ? ? }}
3.5. 結(jié)果展示-
同步生產(chǎn)打?。?/p>
分區(qū)ID:0,offset:90
消費打印:
Partition:0,Offset:90,key:,value:Hello World!
異步生產(chǎn)打?。?/p>
async:7272async:7616async:998
消費打?。?/p>
Partition:0,Offset:91,key:,value:async:7272Partition:0,Offset:92,key:,value:async:7616Partition:0,Offset:93,key:,value:async:998
Go語言是谷歌推出的一種全新的編程語言,可以在不損失應(yīng)用程序性能的情況下降低代碼的復(fù)雜性。谷歌首席軟件工程師羅布派克(Rob Pike)說:我們之所以開發(fā)Go,是因為過去10多年間軟件開發(fā)的難度令人沮喪。
Go是谷歌2009發(fā)布的第二款編程語言。2009年7月份,谷歌曾發(fā)布了Simple語言,它是用來開發(fā)Android應(yīng)用的一種BASIC語言.
Go Logo
北京時間2010年1月10日,Go語言摘得了TIOBE公布的2009年年度大獎。該獎項授予在2009年市場份額增長最多的編程語言。
谷歌資深軟件工程師羅布·派克(Rob Pike)表示,"Go讓我體驗到了從未有過的開發(fā)效率。"派克表示,和今天的C++或C一樣,Go是一種系統(tǒng)語言。他解釋道,"使用它可以進(jìn)行快速開發(fā),同時它還是一個真正的編譯語言,我們之所以現(xiàn)在將其開源,原因是我們認(rèn)為它已經(jīng)非常有用和強(qiáng)大。"
2007年,谷歌把Go作為一個20%項目開始研發(fā),即讓員工抽出本職工作之外時間的20%, 投入在該項目上。除了派克外,該項目的成員還有其他谷歌工程師也參與研發(fā)。
派克表示,編譯后Go代碼的運行速度與C語言非常接近,而且編譯速度非???,就像在使用一個交互式語言?,F(xiàn)有編程語言均未專門對多核處理器進(jìn)行優(yōu)化。Go就是谷歌工程師為這類程序編寫的一種語言。它不是針對編程初學(xué)者設(shè)計的,但學(xué)習(xí)使用它也不是非常困難。Go支持面向?qū)ο?,而且具有真正的閉包(closures)和反射 (reflection)等功能。
在學(xué)習(xí)曲線方面,派克認(rèn)為Go與Java類似,對于Java開發(fā)者來說,應(yīng)該能夠輕松學(xué)會 Go。之所以將Go作為一個開源項目發(fā)布,目的是讓開源社區(qū)有機(jī)會創(chuàng)建更好的工具來使用該語言,例如 Eclipse IDE中的插件。
在谷歌公開發(fā)布的所有網(wǎng)絡(luò)應(yīng)用中,均沒有使用Go,但是谷歌已經(jīng)使用該語言開發(fā)了幾個內(nèi)部項目。派克表示,Go是否會對谷歌即將推出的Chrome OS產(chǎn)生影響,還言之尚早,不過Go的確可以和Native Client配合使用。他表示"Go可以讓應(yīng)用完美的運行在瀏覽器內(nèi)。"例如,使用Go可以更高效的實現(xiàn)Wave,無論是在前端還是后臺。
Go 同時具有兩種編譯器,一種是建立在GCC基礎(chǔ)上的Gccgo,另外一種是分別針對64位x64和32位x86計算機(jī)的一套編譯器(6g和8g)。谷歌目前正在研發(fā)其對ARM芯片和Android設(shè)備的支持。派克表示,"Android手機(jī)存在的問題是,我們一直沒有一個數(shù)學(xué)協(xié)處理器。"
不可以,完全沒有可比性。
Golang的優(yōu)勢是開發(fā)速度,C可以自由、精準(zhǔn)的操控內(nèi)存。
拿string類型舉個栗子:
1、修改字符串:
golang:需要分配新內(nèi)存,然后進(jìn)行內(nèi)存copy。
c:可直接修改,可realloc。
2、存一段data:
golang:使用[]byte類型,[]byte轉(zhuǎn)成string需要進(jìn)行內(nèi)存拷貝(排除掉利用指針進(jìn)行類型轉(zhuǎn)換的情況)。
c:直接用char[],可讀可寫。
golang中為了語言的安全性,類似的這種限制有很多,犧牲了一部分性能。但golang的優(yōu)勢也是顯而易見的,goroutine、chan都很好用,而c則需要自己進(jìn)行進(jìn)程、線程的管控。