1. golang配製高性能sql.DB
有很多教程是關於Go的sql.DB類型和如何使用它來執行SQL資料庫查詢的。但大多數內容都沒有講述 SetMaxOpenConns() , SetMaxIdleConns() 和 SetConnMaxLifetime()方法, 您可以使用它們來配置sql.DB的行為並改變其性能。
轉自:https://www.jianshu.com/p/13542f6e15fa
整理:go語言中文文檔:www.topgoer.cn
在本文我將詳細解釋這些設置的作用,並說明它們所能產生的(積極和消極)影響。
一個sql.DB對象就是一個資料庫連接池,它包含「正在用」和「空閑的」連接。一個正在用的連接指的是,你正用它來執行資料庫任務,例如執行SQL語句或行查詢。當任務完成連接就是空閑的。
當您創建sql.DB執行資料庫任務時,它將首先檢查連接池中是否有可用的空閑連接。如果有可用的連接,那麼Go將重用現有連接,並在執行任務期間將其標記為正在使用。如果池中沒有空閑連接,而您需要一個空閑連接,那麼Go將創建一個新的連接。
默認情況下,在同一時間打開連接的數量是沒有限制(包含使用中+空閑)。但你可以通過SetMaxOpenConns()方法實現自定義限制,如下所示:
在這個示例代碼中,連接池現在有5個並發打開的連接數。如果所有5個連接都已經被標記為正在使用,並且需要另一個新的連接,那麼應用程序將被迫等待,直到5個連接中的一個被釋放並變為空閑。
為了說明更改MaxOpenConns的影響,我運行了一個基準測試,將最大打開連接數設置為1、2、5、10和無限。基準測試在PostgreSQL資料庫上執行並行的INSERT語句,您可以在這里找到代碼。測試結果:
對於這個基準測試,我們可以看到,允許打開的連接越多,在資料庫上執行INSERT操作所花費的時間就越少(打開的連接數為1時,執行速度3129633ns/op,而無限連接:531030ns/op——大約快了6倍)。這是因為允許打開的連接越多,可以並發執行的資料庫查詢就越多。
默認情況下,sql.DB允許連接池中最多保留2個空閑連接。你可以通過SetMaxIdleConns()方法改變它,如下所示:
從理論上講,允許池中有更多的空閑連接將提高性能,因為這樣就不太可能從頭開始建立新連接——因此有助於提升資料庫性能。
讓我們來看看相同的基準測試,最大空閑連接設置為none, 1,2,5和10:
當MaxIdleConns設置為none時,必須為每個INSERT從頭創建一個新的連接,我們可以從基準測試中看到,平均運行時和內存使用量相對較高。
只允許保留和重用一個空閑連接對基準測試影響特別明顯——它將平均運行時間減少了大約8倍,內存使用量減少了大約20倍。繼續增加空閑連接池的大小會使性能變得更好,盡管改進並不明顯。
那麼,您應該維護一個大的空閑連接池嗎?答案取決於應用程序。重要的是要意識到保持空閑連接是有代價的—它佔用了可以用於應用程序和資料庫的內存。
還有一種可能是,如果一個連接空閑時間太長,那麼它可能會變得不可用。例如,MySQL的wait_timeout設置將自動關閉任何8小時(默認)內未使用的連接。
當發生這種情況時,sql.DB會優雅地處理它。壞連接將自動重試兩次,然後放棄,此時Go將該連接從連接池中刪除,並創建一個新的連接。因此,將MaxIdleConns設置得太大可能會導致連接變得不可用,與空閑連接池更小(使用更頻繁的連接更少)相比,會佔有更多的資源。所以,如果你很可能很快就會再次使用,你只需保持一個空閑的連接。
最後要指出的是,MaxIdleConns應該總是小於或等於MaxOpenConns。Go強制執行此操作,並在必要時自動減少MaxIdleConns。
現在讓我們看看SetConnMaxLifetime()方法,它設置連接可重用的最大時間長度。如果您的SQL資料庫也實現了最大連接生命周期,或者—例如—您希望方便地在負載均衡器後交換資料庫,那麼這將非常有用。
你可以這樣使用它:
在這個例子中,所有的連接都將在創建後1小時「過期」,並且在過期後無法重用。但注意:
從理論上講,ConnMaxLifetime越短,連接過期的頻率就越高——因此,需要從頭創建連接的頻率就越高。為了說明這一點,我運行了將ConnMaxLifetime設置為100ms、200ms、500ms、1000ms和無限(永遠重用)的基準測試,默認設置為無限打開連接和2個空閑連接。這些時間段顯然比您在大多數應用程序中使用的時間要短得多,但它們有助於很好地說明行為。
在這些特定的基準測試中,我們可以看到,與無限生存期相比,在100ms生存期時內存使用量增加了3倍以上,而且每個INSERT的平均運行時也稍微長一些。
如果您在代碼中設置了ConnMaxLifetime,那麼一定要記住連接將過期(隨後重新創建)的頻率。例如,如果您總共有100個連接,而ConnMaxLifetime為1分鍾,那麼您的應用程序可能每秒鍾殺死和重新創建1.67個連接(平均值)。您不希望這個頻率太大,最終會阻礙性能,而不是提高性能。
最後,如果不說明超過資料庫連接數量的硬限制將會發生什麼,那麼本文就不完整了。 為了說明這一點,我將修改postgresql.conf文件,這樣總共只允許5個連接(默認是100個)…
然後在無限連接的情況下重新運行基準測試……
一旦達到5個連接的硬限制,資料庫驅動程序(pq)立即返回一個太多客戶端連接的錯誤消息,而無法完成INSERT。為了防止這個錯誤,我們需要將sql.DB中打開連接的最大總數(正在使用的+空閑的)設置為低於5。像這樣:
現在,sql.DB在任何時候最多隻能創建3個連接,基準測試運行時應該不會出現任何錯誤。但是這樣做需要注意:當達到開放連接數限制,並且所有連接都在使用時,應用程序需要執行的任何新的資料庫任務都將被迫等待,直到連接標記為空閑。例如,在web應用程序的上下文中,用戶的HTTP請求看起來會「掛起」,甚至在等待資料庫任務運行時可能會超時。
為了減輕這種情況,你應該始終在一個上下文中傳遞。在調用資料庫時,啟用上下文的方法(如ExecContext()),使用固定的、快速的超時上下文對象。
總結
1、根據經驗,應該顯式設置MaxOpenConns值。這應該小於資料庫和基礎設施對連接數量的硬性限制。
2、一般來說,更高的MaxOpenConns和MaxIdleConns值將帶來更好的性能。但你應該注意到效果是遞減的,連接池空閑連接太多(連接沒有被重用,最終會變壞)實際上會導致性能下降。
3、為了降低上面第2點帶來的風險,您可能需要設置一個相對較短的ConnMaxLifetime。但你也不希望它太短,導致連接被殺死或不必要地頻繁重建。
4、MaxIdleConns應該總是小於或等於MaxOpenConns。
對於中小型web應用程序,我通常使用以下設置作為起點,然後根據實際吞吐量水平的負載測試結果進行優化。
2. golang chan 緩沖設置多大
看你的需求,如果並發性不是太強的話make10足夠了,完全不並發的話不用緩沖都可以,大數據的話可能要多一些
3. golang 可以監聽多個埠嗎
由於linux的socket監聽機制和TCP協議,多個進程無法監聽同一個埠,但是具體到nginx,可以多個nginx進程監聽到不同埠,通過一個主進程埠做upstream來實現負載均衡,這個有點類似於網路的匯聚,可以設置不同的策略,比如iphash,urlhash或者RR。
4. 【golang詳解】go語言GMP(GPM)原理和調度
Goroutine調度是一個很復雜的機制,下面嘗試用簡單的語言描述一下Goroutine調度機制,想要對其有更深入的了解可以去研讀一下源碼。
首先介紹一下GMP什麼意思:
G ----------- goroutine: 即Go協程,每個go關鍵字都會創建一個協程。
M ---------- thread內核級線程,所有的G都要放在M上才能運行。
P ----------- processor處理器,調度G到M上,其維護了一個隊列,存儲了所有需要它來調度的G。
Goroutine 調度器P和 OS 調度器是通過 M 結合起來的,每個 M 都代表了 1 個內核線程,OS 調度器負責把內核線程分配到 CPU 的核上執行
模型圖:
避免頻繁的創建、銷毀線程,而是對線程的復用。
1)work stealing機制
當本線程無可運行的G時,嘗試從其他線程綁定的P偷取G,而不是銷毀線程。
2)hand off機制
當本線程M0因為G0進行系統調用阻塞時,線程釋放綁定的P,把P轉移給其他空閑的線程執行。進而某個空閑的M1獲取P,繼續執行P隊列中剩下的G。而M0由於陷入系統調用而進被阻塞,M1接替M0的工作,只要P不空閑,就可以保證充分利用CPU。M1的來源有可能是M的緩存池,也可能是新建的。當G0系統調用結束後,根據M0是否能獲取到P,將會將G0做不同的處理:
如果有空閑的P,則獲取一個P,繼續執行G0。
如果沒有空閑的P,則將G0放入全局隊列,等待被其他的P調度。然後M0將進入緩存池睡眠。
如下圖
GOMAXPROCS設置P的數量,最多有GOMAXPROCS個線程分布在多個CPU上同時運行
在Go中一個goroutine最多佔用CPU 10ms,防止其他goroutine被餓死。
具體可以去看另一篇文章
【Golang詳解】go語言調度機制 搶占式調度
當創建一個新的G之後優先加入本地隊列,如果本地隊列滿了,會將本地隊列的G移動到全局隊列裡面,當M執行work stealing從其他P偷不到G時,它可以從全局G隊列獲取G。
協程經歷過程
我們創建一個協程 go func()經歷過程如下圖:
說明:
這里有兩個存儲G的隊列,一個是局部調度器P的本地隊列、一個是全局G隊列。新創建的G會先保存在P的本地隊列中,如果P的本地隊列已經滿了就會保存在全局的隊列中;處理器本地隊列是一個使用數組構成的環形鏈表,它最多可以存儲 256 個待執行任務。
G只能運行在M中,一個M必須持有一個P,M與P是1:1的關系。M會從P的本地隊列彈出一個可執行狀態的G來執行,如果P的本地隊列為空,就會想其他的MP組合偷取一個可執行的G來執行;
一個M調度G執行的過程是一個循環機制;會一直從本地隊列或全局隊列中獲取G
上面說到P的個數默認等於CPU核數,每個M必須持有一個P才可以執行G,一般情況下M的個數會略大於P的個數,這多出來的M將會在G產生系統調用時發揮作用。類似線程池,Go也提供一個M的池子,需要時從池子中獲取,用完放回池子,不夠用時就再創建一個。
work-stealing調度演算法:當M執行完了當前P的本地隊列隊列里的所有G後,P也不會就這么在那躺屍啥都不幹,它會先嘗試從全局隊列隊列尋找G來執行,如果全局隊列為空,它會隨機挑選另外一個P,從它的隊列里中拿走一半的G到自己的隊列中執行。
如果一切正常,調度器會以上述的那種方式順暢地運行,但這個世界沒這么美好,總有意外發生,以下分析goroutine在兩種例外情況下的行為。
Go runtime會在下面的goroutine被阻塞的情況下運行另外一個goroutine:
用戶態阻塞/喚醒
當goroutine因為channel操作或者network I/O而阻塞時(實際上golang已經用netpoller實現了goroutine網路I/O阻塞不會導致M被阻塞,僅阻塞G,這里僅僅是舉個栗子),對應的G會被放置到某個wait隊列(如channel的waitq),該G的狀態由_Gruning變為_Gwaitting,而M會跳過該G嘗試獲取並執行下一個G,如果此時沒有可運行的G供M運行,那麼M將解綁P,並進入sleep狀態;當阻塞的G被另一端的G2喚醒時(比如channel的可讀/寫通知),G被標記為,嘗試加入G2所在P的runnext(runnext是線程下一個需要執行的 Goroutine。), 然後再是P的本地隊列和全局隊列。
系統調用阻塞
當M執行某一個G時候如果發生了阻塞操作,M會阻塞,如果當前有一些G在執行,調度器會把這個線程M從P中摘除,然後再創建一個新的操作系統的線程(如果有空閑的線程可用就復用空閑線程)來服務於這個P。當M系統調用結束時候,這個G會嘗試獲取一個空閑的P執行,並放入到這個P的本地隊列。如果獲取不到P,那麼這個線程M變成休眠狀態, 加入到空閑線程中,然後這個G會被放入全局隊列中。
隊列輪轉
可見每個P維護著一個包含G的隊列,不考慮G進入系統調用或IO操作的情況下,P周期性的將G調度到M中執行,執行一小段時間,將上下文保存下來,然後將G放到隊列尾部,然後從隊列中重新取出一個G進行調度。
除了每個P維護的G隊列以外,還有一個全局的隊列,每個P會周期性地查看全局隊列中是否有G待運行並將其調度到M中執行,全局隊列中G的來源,主要有從系統調用中恢復的G。之所以P會周期性地查看全局隊列,也是為了防止全局隊列中的G被餓死。
除了每個P維護的G隊列以外,還有一個全局的隊列,每個P會周期性地查看全局隊列中是否有G待運行並將其調度到M中執行,全局隊列中G的來源,主要有從系統調用中恢復的G。之所以P會周期性地查看全局隊列,也是為了防止全局隊列中的G被餓死。
M0
M0是啟動程序後的編號為0的主線程,這個M對應的實例會在全局變數rutime.m0中,不需要在heap上分配,M0負責執行初始化操作和啟動第一個G,在之後M0就和其他的M一樣了
G0
G0是每次啟動一個M都會第一個創建的goroutine,G0僅用於負責調度G,G0不指向任何可執行的函數,每個M都會有一個自己的G0,在調度或系統調用時會使用G0的棧空間,全局變數的G0是M0的G0
一個G由於調度被中斷,此後如何恢復?
中斷的時候將寄存器里的棧信息,保存到自己的G對象裡面。當再次輪到自己執行時,將自己保存的棧信息復制到寄存器裡面,這樣就接著上次之後運行了。
我這里只是根據自己的理解進行了簡單的介紹,想要詳細了解有關GMP的底層原理可以去看Go調度器 G-P-M 模型的設計者的文檔或直接看源碼
參考: (https://www.cnblogs.com/X-knight/p/11365929.html)
(https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-goroutine/)
5. golang中怎麼處理socket長連接
Socket通信的原理還是比較簡單的, 它大致分為以下幾個步驟。 伺服器端的步驟如下。 (1)建立伺服器端的Socket,開始偵聽整個網路中的連接請求。 (2)當檢測到來自客戶端的連接請求時,向客戶端發送收到連接請求的信息,並建立與客戶端之間的...
6. golang中怎麼處理socket長連接
為每個client fd開兩個goroutine,一個recv,一個send。同時還有加2個channel,一個用於recv routine向邏輯主線程傳送收到的數據,一個用於邏輯主線程向send goroutine傳送待發送的數據,是這樣的么?
實際上需要 3 個 goroutine,一個 read,一個 send,還有一個 handle。
read goroutine 讀,然後寫入 recevice chan。
write goroutine 把 send chan 的東西寫。
handle goroutine 是 conn 的主要處理邏輯,負責把 recevice chan 的東西讀出來 call 業務邏輯。
業務邏輯中要寫數據就直接寫入 send chan。
這樣就可以保證,業務邏輯的讀寫都是在 handle goroutine 上處理,而避免 race 產生。
如果需要定時任務(比如心跳),就在 handle goroutine 上加上一個 timer.C;
如果需要 goroutine 下發任務,在 handle goroutine 增加一個 task chan,hanlde 收到 task 後處理業務;
如果需要輸出結果,那就增加 result chan,業務邏輯把數據輸出即可。
----------------------------
還有,如果我開2個goroutine的話,client斷開連接了,假設recv goroutine先發生err並且close(fd),那在send goroutine中該如何處理呢?有可能不應該這樣處理,那應該怎麼處理呢?
如果 net.Conn Close() 了,不論 Read() 阻塞還是 Write() 阻塞都會立即收到 err 返回。
一般來說,Write() 是不可能主動知道連接斷開的,除非是 SetDeadline() 猜測對方斷掉了,指定時間內沒有寫成功就認為是斷開。Read() 是可以主動收到對方發來的斷開(TCP FIN),但也沒辦法知道異常的斷開(當然也可以設置超時)。
無論是誰,是確實收到 FIN 還是 Deadline 猜測斷開,只要 Close() 大家就知道連接斷開了。
handle goroutine 還有一個用處就是:你的程序主動結束的時候,能正確的 close conn,讓對方知道你是真的斷開了,而不用去猜。
7. 如何部署Golang應用
安裝supervisord
# 通過引導程序 ez_setup.py 來安裝。這個引導程序會聯網下載最新版本setuptools來安裝,同時也可以更新本地的setuptools。
wget http://peak.telecommunity.com/dist/ez_setup.py
sudo python ez_setup.py
# 更新setuptools:
sudo python ez_setup.py -U setuptools
# 安裝supervisor
easy_install supervisor
# 生成配置文件
echo_supervisord_conf >/etc/supervisord.conf
# 編輯配置文件
vim /etc/supervisord.conf
# 進入vim後找到最後兩行,打開注釋(取消前面的分號),
# [include]
# files = supervisor.d/*.ini
# 將所有的supervisor配置都放到 /etc/supervisor.d目錄
mkdir /etc/supervisor.d
創建 supervisor 對應程序的配置文件
其中的一些路徑需要換成自己對應的,這里將 zankbo 這個web 應用放在了對應的用戶目錄下
通過在生產伺服器上設置environment可以在程序里判斷是線上還是開發模式,如 zankbo 的 debug判斷
當然也可已在啟動命令處加入參數,如 command = /home/zankbo/gopath/src/zankbo/zankbo -d 來關閉Debug模式。
if os.Getenv("APP_NAME") == "ZANKBO_PRODUCT" {
beego.RunMode = "prod"
}
vim /etc/supervisor.d/zankbo.ini
# 寫入
[program:zankbo]
directory = /home/zankbo/gopath/src/zankbo
environment=APP_NAME="ZANKBO_PRODUCT"
command = /home/zankbo/gopath/src/zankbo/zankbo
autostart = true
startsecs = 5
user = zankbo
redirect_stderr = true
stdout_logfile = /home/zankbo/log/zankbo.log
建立對應的用戶
useradd zankbo
# 將www用戶加入到zankbo用戶組,Nginx以www用戶運行
usermod -a -G zankbo www
# 更改用戶家目錄用戶組的許可權,使Nginx可以訪問
chmod g+rx /home/zankbo
部署Go環境
其中的目錄為,go:Go安裝目錄 gopath:Go工作目錄,下面有src、pkg、bin三個目錄 log:日誌文件夾
[zankbo@MyCloudServer ~]$ pwd
/home/zankbo
[zankbo@MyCloudServer ~]$ vim .bashrc
# 設置Go環境變數,在.bashrc文件末尾寫下如下內容
export GOROOT=$HOME/go
export GOPATH=$HOME/gopath
export PATH=$PATH:$GOROOT/bin:$GOPATH/bi
# 切換到用戶家目錄
[root@MyCloudServer ~]# su - zankbo
[zankbo@MyCloudServer ~]$ ls
go gopath log
將項目代碼放到gopath/src下面,如我的播客項目:
[zankbo@MyCloudServer ~]$ tree -L 2 gopath/src/
gopath/src/
├── github.com
│ ├── astaxie
│ ├── beego
│ ├── go-sql-driver
│ ├── howeyc
│ ├── jacobsa
│ ├── smartystreets
│ └── wendal
└── zankbo
├── admin
├── blog
├── build_pkg.sh
├── common
├── conf
├── controllers
├── dbstruct.mwb
├── main.go
├── models
├── static
├── views
└── zankbo
導入項目sql文件到資料庫
在項目文件夾執行build
[zankbo@MyCloudServer zankbo]$ pwd
/home/zankbo/gopath/src/zankbo
[zankbo@MyCloudServer zankbo]$ go build
會在項目下生成與包名對應的可執行文件,這里為:zankbo,build的時候可能會遇到錯誤,比如mysql的密碼之類的,可根據提示排錯。
通過supervisor 來啟動服務
# supervisorctl start zankbo
配置Nginx
server {
listen 80;
server_name zankbo.com www.zankbo.com;
root /home/zankbo/gopath/src/zankbo;
error_log logs/zankbo.com.error.log warn ;
location /static/ {
root /home/zankbo/gopath/src/zankbo;
location ~ .*\.(js|css)$ {
access_log off;
expires 1d;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
gzip off;
access_log off;
expires 3d;
}
}
location / {
proxy_pass http://127.0.0.1:8080;
}
}
8. golang中有什麼方法可以在控制器中設置http超時時間
設置時間間隔為5秒 如果get不到就斷開連接,post方法類似 都在client里設置http的超時時間
timeout := time.Duration(5 * time.Second)
client := http.Client{
Timeout: timeout,}
client.Get(url)
9. golang 如何確定在使用哪個網卡
進入你的項目,conf目錄,app.conf。裡面有一項 httpport = 就是了
10. golang中怎麼處理socket長連接
實際上需要 3 個 goroutine,一個 read,一個 send,還有一個 handle。
read goroutine 讀,然後寫入 recevice chan。
write goroutine 把 send chan 的東西寫。
handle goroutine 是 conn 的主要處理邏輯,負責把 recevice chan 的東西讀出來 call 業務邏輯。
業務邏輯中要寫數據就直接寫入 send chan。
這樣就可以保證,業務邏輯的讀寫都是在 handle goroutine 上處理,而避免 race 產生。
如果需要定時任務(比如心跳),就在 handle goroutine 上加上一個 timer.C;
如果需要 goroutine 下發任務,在 handle goroutine 增加一個 task chan,hanlde 收到 task 後處理業務;
如果需要輸出結果,那就增加 result chan,業務邏輯把數據輸出即可。
----------------------------
還有,如果我開2個goroutine的話,client斷開連接了,假設recv goroutine先發生err並且close(fd),那在send goroutine中該如何處理呢?有可能不應該這樣處理,那應該怎麼處理呢?
如果 net.Conn Close() 了,不論 Read() 阻塞還是 Write() 阻塞都會立即收到 err 返回。
一般來說,Write() 是不可能主動知道連接斷開的,除非是 SetDeadline() 猜測對方斷掉了,指定時間內沒有寫成功就認為是斷開。Read() 是可以主動收到對方發來的斷開(TCP FIN),但也沒辦法知道異常的斷開(當然也可以設置超時)。
無論是誰,是確實收到 FIN 還是 Deadline 猜測斷開,只要 Close() 大家就知道連接斷開了。
handle goroutine 還有一個用處就是:你的程序主動結束的時候,能正確的 close conn,讓對方知道你是真的斷開了,而不用去猜。