Go 插件系統(tǒng),一個(gè)涼了快半截的特性?
本文轉(zhuǎn)載自微信公眾號(hào)「腦子進(jìn)煎魚了」,作者陳煎魚。轉(zhuǎn)載本文請(qǐng)聯(lián)系腦子進(jìn)煎魚了公眾號(hào)。
大家好,我是煎魚。
在 Go 語言中,有一個(gè)好像很好用,但卻比較少人提及的功能,那就是 Go Plugin。
目前在 Go 工程中普遍還沒廣泛的使用起來,覆蓋率不高。在 Go issues 上吐槽挺多,甚至感覺有點(diǎn)像涼了半截的樣子。
前段時(shí)間小咸魚的同事問了他這功能怎么用,他正想甩出一個(gè)鏈接,但發(fā)現(xiàn)...煎魚竟然沒寫過,這不,Go 知識(shí)板塊的文章地圖得補(bǔ)全。
今天煎魚就大家一起學(xué)習(xí) Go Plugin,看看為什么會(huì)說感覺 “有點(diǎn)像涼了半截” 的樣子,打開來看看這個(gè)問題在哪。
是什么
Go Team 最早在 Go1.7 實(shí)驗(yàn),在 Go1.8 正式引入了 Go Plugin 的機(jī)制。于 2016 年發(fā)布,一開始僅支持 Linux 實(shí)現(xiàn):
Go Plugin 機(jī)制實(shí)現(xiàn)了 Go 插件的加載和符號(hào)解析,能夠支持將我們所編寫的 Go 包編譯為共享庫(.so)。
這樣 Go 工程就可以加載所編譯好的 Go Plugin(已經(jīng)變成了共享庫文件),在程序中調(diào)用共享庫中的函數(shù)、常量、變量等使用。也稱其為 Go 語言中的熱插拔的插件系統(tǒng)。
截止 Go1.17 為止,Go Plugin 僅支持在 Linux、FreeBSD 和 MacOS 上運(yùn)行,還不支持 Windows。
為什么需要
Go 語言是靜態(tài)語言,正常我們寫一個(gè)程序,分如下兩個(gè)角度來看:
- 從代碼編寫的角度來看:我們?cè)诔绦蚓帉懙臅r(shí)候就已經(jīng)把所有的功能實(shí)現(xiàn)給確定了,不會(huì)發(fā)生什么根本性的變化。
- 從程序的角度來看:在 Go 進(jìn)行編譯時(shí),就已經(jīng)把所有引用的標(biāo)準(zhǔn)庫、第三方庫等都編譯打包好進(jìn)二進(jìn)制文件了,因此也就無法在運(yùn)行時(shí)去動(dòng)態(tài)加載,所以沒法有其它的可能性。
那么為什么需要 Go Plugin 呢,原因如下:
- 可插拔的插件:程序能夠隨時(shí)的安裝插件,也能夠卸載他,獲得更多運(yùn)行時(shí)的自定義能力。
- 可動(dòng)態(tài)加載運(yùn)行時(shí)模塊:隨時(shí)安裝了插件,自然也就需要可自行決定運(yùn)行哪個(gè)插件的模塊了。
- 可獨(dú)立開發(fā)插件、模塊:主系統(tǒng)和子插件,可能由不同的團(tuán)隊(duì)開發(fā)和提供,也更有價(jià)值。
其實(shí)本質(zhì)上還是希望程序能夠在運(yùn)行時(shí)實(shí)現(xiàn)動(dòng)態(tài)的外部加載,根據(jù)不同的條件、場(chǎng)景加載不同的插件功能。
使用方法
通用概念
Go 官方給出的例子非常簡(jiǎn)單,只需要在 Go 編譯時(shí)指定為插件就可以了。
編譯的命令例子如下:
- go build -buildmode=plugin
當(dāng)一個(gè)插件初次被打開時(shí),所有尚未成為程序一部分的包的init函數(shù)被調(diào)用。不過主函數(shù)不被運(yùn)行。需要注意一個(gè)插件只會(huì)被初始化一次,插件不能被關(guān)閉。
其共有如下幾個(gè) API:
- type Plugin
- func Open(path string) (*Plugin, error)
- func (p *Plugin) Lookup(symName string) (Symbol, error)
- type Symbol
- Plugin.Open:開啟一個(gè) Go 插件。如果一個(gè)路徑已經(jīng)被打開,那么將返回現(xiàn)有的 *Plugin。
- Plugin.Lookup:在插件中搜索名所傳入的符號(hào),符號(hào)是任何導(dǎo)出的變量或函數(shù)。如果沒有找到該符號(hào),它會(huì)報(bào)告一個(gè)錯(cuò)誤。
主要就是細(xì)分為插件和符號(hào),符號(hào)(Symbol)本身是一個(gè) interface,在調(diào)用 Plugin 相關(guān)方法后還是需要進(jìn)一步斷言才能使用。
實(shí)際編寫
了解基本定義后,我們定義一個(gè)插件,一般我們會(huì)有個(gè) plugins/ 的目錄,作為主程序的附屬插件集。
插件的代碼如下:
- package main
- import "fmt"
- var V int
- func F() {
- fmt.Printf("腦子進(jìn)了 %d 次煎魚 \n", V)
- }
包名必須為 main,在該插件根目錄運(yùn)行:
- go build -buildmode=plugin -o plugin.so main.go
就可以看到在編譯的目錄下多出了 plugin.so 文件,這就是這個(gè)插件經(jīng)過編譯后的動(dòng)態(tài)庫 .so 文件。
隨后只需在主程序加載這個(gè)插件就可以了,如下:
- import (
- "plugin"
- )
- func main() {
- p, err := plugin.Open("plugin.so")
- if err != nil {
- panic(err)
- }
- v, err := p.Lookup("V")
- if err != nil {
- panic(err)
- }
- f, err := p.Lookup("F")
- if err != nil {
- panic(err)
- }
- *v.(*int) = 999
- f.(func())()
- }
輸出結(jié)果:
- 腦子進(jìn)了 999 次煎魚
在程序中,我們先調(diào)用了 plugin.Open 方法打開了前面所編譯的 plugin.so 動(dòng)態(tài)庫。
緊接著調(diào)用 plugin.Lookup 方法,定位到了變量 V 和 方法 F,但由于其返回值都是 Symbol(interface),因此我們需要對(duì)其進(jìn)行類型斷言,隨時(shí)才可以調(diào)用和使用。
至此完成了一個(gè)插件的基本使用。
為什么不被需要
在前面我們提到了大量 Go Plugin 的優(yōu)點(diǎn),也演示了其 Plugin 代碼編寫起來有多么的簡(jiǎn)單和方便。
但,為什么 Go Plugin 已經(jīng)發(fā)布了 4 年依然沒有被大規(guī)模應(yīng)用,甚至對(duì)于不少業(yè)務(wù)開發(fā)來講是不被需要的呢,或是壓根不知道有這東西?
究其原因,我個(gè)人認(rèn)為一個(gè)東西的廣泛應(yīng)用要至少符合以下三大點(diǎn):
- 基數(shù):需要的場(chǎng)景多。
- 上手:方便且易用。
- 質(zhì)量:沒有大問題。
比較折騰的人的是,Go Plugin 這三大點(diǎn)都欠一些火候,綜合導(dǎo)致了該功能的沒有大規(guī)模應(yīng)用。
像是要應(yīng)用 Go Plugin 有諸如下約束:
- 環(huán)境問題:不支持 Windows 等(暫無計(jì)劃,#19282),MacOS 有些問題,一開始只支持 Linux,其他的也是后面慢慢增加的支持。
- Go 版本問題:Plugin 構(gòu)建環(huán)境和第三方包的依賴版本需要保持一致。
- 特性問題:Plugin 特性的缺失,例如不支持插件的關(guān)閉,暫時(shí)無新計(jì)劃支持(#20461)。
總結(jié)
在 Go issues 中暢游時(shí),能看到許多小伙伴在以往 4 年踩過的坑和無奈。甚至有一個(gè)高贊回答(#19282)表示:插件功能主要是一個(gè)技術(shù)演示,由于一些不道德的原因,被作為語言的穩(wěn)定功能發(fā)布(The plugin feature is mostly a tech demo that for some unholy reason got released as a stable feature of the language.)。
目前 Go Plugin 并不是 Go Team 的優(yōu)先事項(xiàng),在 Windows/Mac 的支持存在問題。GOPATH 有問題,不同 GO 版本也有問題。更是建議如果您想要插件,請(qǐng)走較慢的 grpc 路線,因?yàn)樗鼈兪怯行У牟寮?/p>
也可以參考為數(shù)不多的一些 Go Plugin 用戶的方案,例如:tidb,甚至寫了個(gè)指導(dǎo)文檔。
但如果要在生產(chǎn)正式使用,勸你還是需要慎重考慮,又或是再等等...等更完善的那一天?
參考
Go Package plugin
Why is there no windows support for plugins?
plugin: add Windows support
plugin: Add support for closing plugins
如何評(píng)價(jià) Go 標(biāo)準(zhǔn)庫中新增的 plugin 包?
一文搞懂Go語言的plugin