本文目錄一覽:
Go 優雅退出實現方法 amp; context原理
1.通過endless包實現
2.通過shutdown實現
在go 1.8.x後,golang在http里加入了shutdown方法,用來控制優雅退出。什麼是優雅退出? 簡單說就是不處理新請求,但是會處理正在進行的請求,把舊請求都處理完,也就是都response之後,那麼就退出。
shutdown通過context上下文實現 。
社區里不少http graceful動態重啟,平滑重啟的庫,大多是基於http.shutdown做的。平滑啟動的原理很簡單,fork子進程,繼承listen fd, 老進程優雅退出。
3.context原理
context 是 Go 並發編程中常用到一種編程模式。
在並發程序中,由於超時、取消操作或者一些異常情況,往往需要進行搶佔操作或者中斷後續操作。熟悉 channel 的朋友應該都見過使用 done channel 來處理此類問題。比如以下這個例子:
上述例子中定義了一個 buffer 為0的 channel done , 子協程運行着定時任務。如果主協程需要在某個時刻發送消息通知子協程中斷任務退出,那麼就可以讓子協程監聽這個 done channel ,一旦主協程關閉 done channel ,那麼子協程就可以推出了,這樣就實現了主協程通知子協程的需求。這很好,但是這也是有限的。
如果我們可以在簡單的通知上附加傳遞額外的信息來控制取消:為什麼取消,或者有一個它必須要完成的最終期限,更或者有多個取消選項,我們需要根據額外的信息來判斷選擇執行哪個取消選項。
考慮下面這種情況:假如主協程中有多個任務1, 2, …m,主協程對這些任務有超時控制;而其中任務1又有多個子任務1, 2, …n,任務1對這些子任務也有自己的超時控制,那麼這些子任務既要感知主協程的取消信號,也需要感知任務1的取消信號。
如果還是使用 done channel 的用法,我們需要定義兩個 done channel ,子任務們需要同時監聽這兩個 done channel 。嗯,這樣其實好像也還行哈。但是如果層級更深,如果這些子任務還有子任務,那麼使用 done channel 的方式將會變得非常繁瑣且混亂。
我們需要一種優雅的方案來實現這樣一種機制:
這個時候 context 就派上用場了。
我們首先看看 context 的結構設計和實現原理。
先看 Context 接口結構,看起來非常簡單。
Context 接口包含四個方法:
可以看到 Done 方法返回的 channel 正是用來傳遞結束信號以搶佔並中斷當前任務; Deadline 方法指示一段時間後當前 goroutine 是否會被取消;以及一個 Err 方法,來解釋 goroutine 被取消的原因;而 Value 則用於獲取特定於當前任務樹的額外信息。而 context 所包含的額外信息鍵值對是如何存儲的呢?其實可以想象一顆樹,樹的每個節點可能攜帶一組鍵值對,如果當前節點上無法找到 key 所對應的值,就會向上去父節點裡找,直到根節點,具體後面會說到。
emptyCtx 是一個 int 類型的變量,但實現了 context 的接口。 emptyCtx 沒有超時時間,不能取消,也不能存儲任何額外信息,所以 emptyCtx 用來作為 context 樹的根節點。
但我們一般不會直接使用 emptyCtx ,而是使用由 emptyCtx 實例化的兩個變量,分別可以通過調用 Background 和 TODO 方法得到,但這兩個 context 在實現上是一樣的。那麼 Background 和 TODO 方法得到的 context 有什麼區別呢?可以看一下官方的解釋:
Background 和 TODO 只是用於不同場景下:
Background 通常被用於主函數、初始化以及測試中,作為一個頂層的 context ,也就是說一般我們創建的 context 都是基於 Background ;
而 TODO 是在不確定使用什麼 context 的時候才會使用。
下面將介紹兩種不同功能的基礎 context 類型: valueCtx 和 cancelCtx 。
valueCtx 利用一個 Context 類型的變量來表示父節點 context ,所以當前 context 繼承了父 context 的所有信息; valueCtx 類型還攜帶一組鍵值對,也就是說這種 context 可以攜帶額外的信息。 valueCtx 實現了 Value 方法,用以在 context 鏈路上獲取 key 對應的值,如果當前 context 上不存在需要的 key ,會沿着 context 鏈向上尋找 key 對應的值,直到根節點。
WithValue 用以向 context 添加鍵值對:
這裡添加鍵值對不是在原 context 結構體上直接添加,而是以此 context 作為父節點,重新創建一個新的 valueCtx 子節點,將鍵值對添加在子節點上,由此形成一條 context 鏈。獲取 value 的過程就是在這條 context 鏈上由尾部上前搜尋:
跟 valueCtx 類似, cancelCtx 中也有一個 context 變量作為父節點;變量 done 表示一個 channel ,用來表示傳遞關閉信號; children 表示一個 map ,存儲了當前 context 節點下的子節點; err 用於存儲錯誤信息表示任務結束的原因。
再來看一下 cancelCtx 實現的方法:
可以發現 cancelCtx 類型變量其實也是 canceler 類型,因為 cancelCtx 實現了 canceler 接口。 Done 方法和 Err 方法沒必要說了, cancelCtx 類型的 context 在調用 cancel 方法時會設置取消原因,將 done channel 設置為一個關閉 channel 或者關閉 channel ,然後將子節點 context 依次取消,如果有需要還會將當前節點從父節點上移除。
WithCancel 函數用來創建一個可取消的 context ,即 cancelCtx 類型的 context 。 WithCancel 返回一個 context 和一個 CancelFunc ,調用 CancelFunc 即可觸發 cancel 操作。直接看源碼:
之前說到 cancelCtx 取消時,會將後代節點中所有的 cancelCtx 都取消, propagateCancel 即用來建立當前節點與祖先節點這個取消關聯邏輯。
這裡或許有個疑問,為什麼是祖先節點而不是父節點?這是因為當前 context 鏈可能是這樣的:
當前 cancelCtx 的父節點 context 並不是一個可取消的 context ,也就沒法記錄 children 。
timerCtx 是一種基於 cancelCtx 的 context 類型,從字面上就能看出,這是一種可以定時取消的 context 。
timerCtx 內部使用 cancelCtx 實現取消,另外使用定時器 timer 和過期時間 deadline 實現定時取消的功能。 timerCtx 在調用 cancel 方法,會先將內部的 cancelCtx 取消,如果需要則將自己從 cancelCtx 祖先節點上移除,最後取消計時器。
WithDeadline 返回一個基於 parent 的可取消的 context ,並且其過期時間 deadline 不晚於所設置時間 d 。
與 WithDeadline 類似, WithTimeout 也是創建一個定時取消的 context ,只不過 WithDeadline 是接收一個過期時間點,而 WithTimeout 接收一個相對當前時間的過期時長 timeout :
首先使用 context 實現文章開頭 done channel 的例子來示範一下如何更優雅實現協程間取消信號的同步:
這個例子中,只要讓子線程監聽主線程傳入的 ctx ,一旦 ctx.Done() 返回空 channel ,子線程即可取消執行任務。但這個例子還無法展現 context 的傳遞取消信息的強大優勢。
閱讀過 net/http 包源碼的朋友可能注意到在實現 http server 時就用到了 context , 下面簡單分析一下。
1、首先 Server 在開啟服務時會創建一個 valueCtx ,存儲了 server 的相關信息,之後每建立一條連接就會開啟一個協程,並攜帶此 valueCtx 。
2、建立連接之後會基於傳入的 context 創建一個 valueCtx 用於存儲本地地址信息,之後在此基礎上又創建了一個 cancelCtx ,然後開始從當前連接中讀取網絡請求,每當讀取到一個請求則會將該 cancelCtx 傳入,用以傳遞取消信號。一旦連接斷開,即可發送取消信號,取消所有進行中的網絡請求。
3、讀取到請求之後,會再次基於傳入的 context 創建新的 cancelCtx ,並設置到當前請求對象 req 上,同時生成的 response 對象中 cancelCtx 保存了當前 context 取消方法。
這樣處理的目的主要有以下幾點:
在整個 server 處理流程中,使用了一條 context 鏈貫穿 Server 、 Connection 、 Request ,不僅將上游的信息共享給下游任務,同時實現了上游可發送取消信號取消所有下游任務,而下游任務自行取消不會影響上游任務。
context 主要用於父子任務之間的同步取消信號,本質上是一種協程調度的方式 。另外在使用 context 時有兩點值得注意:上游任務僅僅使用 context 通知下游任務不再需要,但不會直接干涉和中斷下游任務的執行,由下游任務自行決定後續的處理操作,也就是說 context 的取消操作是無侵入的; context 是線程安全的,因為 context 本身是不可變的( immutable ),因此可以放心地在多個協程中傳遞使用。
golang反射框架Fx
Fx是一個golang版本的依賴注入框架,它使得golang通過可重用、可組合的模塊化來構建golang應用程序變得非常容易,可直接在項目中添加以下內容即可體驗Fx效果。
Fx是通過使用依賴注入的方式替換了全局通過手動方式來連接不同函數調用的複雜度,也不同於其他的依賴注入方式,Fx能夠像普通golang函數去使用,而不需要通過使用struct標籤或內嵌特定類型。這樣使得Fx能夠在很多go的包中很好的使用。
接下來會提供一些Fx的簡單demo,並說明其中的一些定義。
1、一般步驟
大致的使用步驟就如下。下面會給出一些完整的demo
2、簡單demo
將io.reader與具體實現類關聯起來
輸出:
3、使用struct參數
前面的使用方式一旦需要進行注入的類型過多,可以通過struct參數方式來解決
輸出
如果通過Provide提供構造函數是生成相同類型會有什麼問題?換句話也就是相同類型擁有多個值呢?
下面兩種方式就是來解決這樣的問題。
4、使用struct參數+Name標籤
在Fx未使用Name或Group標籤時不允許存在多個相同類型的構造函數,一旦存在會觸發panic。
輸出
上面通過Name標籤即可完成在Fx容器注入相同類型
5、使用struct參數+Group標籤
使用group標籤同樣也能完成上面的功能
輸出
基本上Fx簡單應用在上面的例子也做了簡單講解
1、Annotated(位於annotated.go文件) 主要用於採用annotated的方式,提供Provide注入類型
源碼中Name和Group兩個字段與前面提到的Name標籤和Group標籤是一樣的,只能選其一使用
2、App(位於app.go文件) 提供注入對象具體的容器、LiftCycle、容器的啟動及停止、類型變量及實現類注入和兩者映射等操作
至於Provide和Populate的源碼相對比較簡單易懂在這裡不在描述
具體源碼
3、Extract(位於extract.go文件)
主要用於在application啟動初始化過程通過依賴注入的方式將容器中的變量值來填充給定的struct,其中target必須是指向struct的指針,並且只能填充可導出的字段(golang只能通過反射修改可導出並且可尋址的字段),Extract將被Populate代替。 具體源碼
4、其他
諸如Populate是用來替換Extract的,而LiftCycle和inout.go涉及內容比較多後續會單獨提供專屬文件說明。
在Fx中提供的構造函數都是惰性調用,可以通過invocations在application啟動來完成一些必要的初始化工作:fx.Invoke(function); 通過也可以按需自定義實現LiftCycle的Hook對應的OnStart和OnStop用來完成手動啟動容器和關閉,來滿足一些自己實際的業務需求。
Fx框架源碼解析
主要包括app.go、lifecycle.go、annotated.go、populate.go、inout.go、shutdown.go、extract.go(可以忽略,了解populate.go)以及輔助的internal中的fxlog、fxreflect、lifecycle
Golang中國的markdown轉HTML怎麼實現
使用Markdown寫好的文章要如何轉換成HTML格式,以方便能發布在不支援Markdown的網站呢?我找到了兩個方法:Pandoc與WriteMonkey。
Pandoc是一個轉換文件格人與標籤語言的通用型工具,只要用下列指令就能把Markdown檔案轉換成HTML:
“c:\Program Files\pandoc\bin\\pandoc.exe” -f markdown -t html 輸入.txt 輸出.html
如何用golang搜索抓取淘寶商品
您好
golang搜索抓取淘寶商品需要進行數據的引入,一般是if engine.ToString(val) != “hello” { t.FailNow() } } 傳遞進去的參數的生命周期是php控制的,在request shutdown的時候內存會被釋放。 PHP 回調 Golang type greeting!
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/251053.html