成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go原生插件使用問題全解析

開發(fā) 前端
通常而言,在計(jì)算機(jī)編程語言領(lǐng)域,“運(yùn)行時(shí)”的概念和一些需要使用到vm的語言相關(guān)。程序的運(yùn)行由兩個(gè)部分組成:目標(biāo)代碼和“虛擬機(jī)”。比如最為典型的JAVA,即Java Class + JRE。

一、導(dǎo)言

本人在設(shè)計(jì)和落地基于Go原生插件機(jī)制的擴(kuò)展開發(fā)產(chǎn)品時(shí)踩到了很多坑,由于這方面相關(guān)資料很少,因而借此機(jī)會(huì)做一個(gè)非常粗淺的總結(jié),希望能對(duì)大家有所幫助。

本文只說問題和解決方案,不讀代碼。

二、一些背景知識(shí)

2.1 運(yùn)行時(shí)

通常而言,在計(jì)算機(jī)編程語言領(lǐng)域,“運(yùn)行時(shí)”的概念和一些需要使用到vm的語言相關(guān)。程序的運(yùn)行由兩個(gè)部分組成:目標(biāo)代碼和“虛擬機(jī)”。比如最為典型的JAVA,即Java Class + JRE。對(duì)于一些看似不需要“虛擬機(jī)”的編程語言,就不太會(huì)有“運(yùn)行時(shí)”的概念,程序的運(yùn)行只需要一個(gè)部分,即目標(biāo)代碼。但事實(shí)上,即使是C/C++,也有“運(yùn)行時(shí)”,即它所運(yùn)行平臺(tái)的OS/Lib。

Go也是一樣,因?yàn)檫\(yùn)行Go程序不需要前置部署類似于JRE的“運(yùn)行時(shí)”,所以它看起來似乎跟“虛擬機(jī)”或者“運(yùn)行時(shí)”沒啥關(guān)系。但事實(shí)上,Go語言的“運(yùn)行時(shí)”被編譯器編譯成了二進(jìn)制目標(biāo)代碼的一部分。

圖片

圖2-1. Java程序、runtime和OS關(guān)系

圖片

??圖2-2. C/C++程序、runtime和OS關(guān)系?? 

圖片

圖2-3. Go程序、runtime和OS關(guān)系?

2.2 Go原生插件機(jī)制

作為一個(gè)看起來更貼近C/C++技術(shù)棧的Go語言來說,支持類似動(dòng)態(tài)鏈接庫的擴(kuò)展一直是社區(qū)中較為強(qiáng)烈的訴求。如圖2-5,Go在標(biāo)準(zhǔn)庫中專門提供了一個(gè)plugin 包,作為插件的語言級(jí)編程界面,src/plugin 包的本質(zhì)是使用cgo機(jī)制調(diào)用unix的標(biāo)準(zhǔn)接口:dlopen() 和dlsym() 。因此,它給C/C++背景的程序員一種“這題我會(huì)”的錯(cuò)覺。

圖片

圖2-4. C/C++程序加載動(dòng)態(tài)鏈接庫

圖片

圖2-5. Go程序加載動(dòng)態(tài)鏈接庫

典型問題解決

很遺憾,與C/C++技術(shù)棧相比,Go的插件的產(chǎn)出物雖然也是一個(gè)動(dòng)態(tài)鏈接庫文件,但它對(duì)于插件的開發(fā)、使用有一系列很復(fù)雜的內(nèi)置約束。更令人頭大的是,Go語言不但沒有對(duì)這些約束進(jìn)行系統(tǒng)性的介紹,甚至寫了一些比較差的設(shè)計(jì)和實(shí)現(xiàn),導(dǎo)致插件相關(guān)問題的排錯(cuò)非常反人類。本章節(jié)重點(diǎn)跟大家一起看下,在開發(fā)、使用Go插件,主要是編譯、加載插件的時(shí)候,最常見、但必須定位到Go標(biāo)準(zhǔn)庫(主要包括編譯器、鏈接器、打包器和運(yùn)行時(shí)部分)源碼才能完全弄明白的幾個(gè)問題,及對(duì)應(yīng)的解決方法。?

簡而言之,Go的主程序在加載plugin時(shí),會(huì)在“runtime”里對(duì)兩者進(jìn)行一堆約束檢查,包括但不限于:

  • go version一致
  • go path一致
  • go dependency的交集一致

代碼一致

path一致

  • go build 某些flag一致

3.1 不一致的標(biāo)準(zhǔn)庫版本

主程序加載插件時(shí)報(bào)錯(cuò):

plugin was built with a different version of package runtime/internal/sys

從這個(gè)報(bào)錯(cuò)的文本可以得知,具體有問題的庫是runtime/internal/sys ,很顯然這是一個(gè)go的內(nèi)置標(biāo)準(zhǔn)庫。看到這里,你可能會(huì)有很大的疑惑:我明明用的是同一個(gè)本地環(huán)境編譯主程序和插件,為什么報(bào)標(biāo)準(zhǔn)庫不是一個(gè)版本?

答案是,go的error日志描述不準(zhǔn)確。而這個(gè)報(bào)錯(cuò)出現(xiàn)的根本原因可以歸結(jié)為:主程序和插件的某些關(guān)鍵編譯flag不一致,跟“版本”沒啥關(guān)系。?

比如,你使用下面的命令編譯插件:

GO111MODULE=on go build --buildmode=plugin -mod readonly -o ./codec.so ./codec.go

但是你使用goland的debug模式調(diào)試主程序,此時(shí),goland會(huì)幫你把go build命令按下面的例子組裝好:

/usr/local/go/bin/go test -c -o /private/var/folders/gy/2zv22t710sd7m0x9bcfzq23r0000gp/T/GoLand/___Test_TaskC_in_github_com_fdingiit_mpl_test.test -gcflags all=-N -l github.com/fdingiit/mpl/test #gosetup

注意,goland組裝的編譯命令里包含關(guān)鍵的-gcflags all=-N -l 參數(shù),但是插件編譯的命令里沒有。此時(shí),你在嘗試?yán)鸩寮r(shí)就會(huì)得到一個(gè)有關(guān)runtime/internal/sys的報(bào)錯(cuò)。?

圖片

圖3-1. 編譯flag不一致導(dǎo)致的加載失敗

解決這一類標(biāo)準(zhǔn)庫版本不一致問題的方案比較簡單:盡可能對(duì)齊主程序和插件編譯的flag。事實(shí)上,有一些flag是不影響插件加載的,你可以在具體的實(shí)踐中慢慢摸索。

3.2 不一致的第三方庫版本

如果你使用vendor來管理Go的依賴庫,那么當(dāng)解決3.1的問題之后,你100%會(huì)立即遇到以下這個(gè)報(bào)錯(cuò):

plugin was built with a different version of package xxxxxxxx

其中,xxxxxxxx 指的是某一個(gè)具體的三方庫,比如github.com/stretchr/testify 。這個(gè)報(bào)錯(cuò)有幾個(gè)非常典型的原因,如果沒有相關(guān)的排查經(jīng)驗(yàn),其中幾個(gè)可能會(huì)燒掉開發(fā)人員不少時(shí)間。

3.2.1 Case 1. 版本不一致

如報(bào)錯(cuò)所示,似乎原因很明確,即主程序和插件所共同依賴的某個(gè)第三方庫版本不一致,報(bào)錯(cuò)中會(huì)明確告訴你哪一個(gè)庫有問題。此時(shí),你可以對(duì)比排查主程序和插件的go.mod 文件,分別找到問題庫的版本,看看他們是否一致。如果這時(shí)候你發(fā)現(xiàn)主程和插件確實(shí)有commitid或tag的不一致問題,那解決的方法也很簡單:對(duì)齊它們。

但是在很多場景下,你只會(huì)用到三方庫的一部分:如一個(gè)package,或者只是引了一個(gè)interface。這一部分的代碼在不同的版本里根本就沒有變更;但其他沒用到的代碼的變更,同樣會(huì)導(dǎo)致整個(gè)三方庫版本號(hào)的變更,進(jìn)而導(dǎo)致你成為那個(gè)“版本不一致”的無辜受害者。

而且,此時(shí)你可能立即會(huì)遇到另一個(gè)問題:以誰為基準(zhǔn)對(duì)齊?主程序?還是插件?

從常理上來說,以主程序?yàn)榛€進(jìn)行對(duì)齊是一個(gè)比較好的策略,畢竟插件是新添加的“附屬品”,且主程序與插件通常是1對(duì)多的關(guān)系。但是,如果插件的三方庫依賴因?yàn)槿魏卧蚓褪遣荒芎椭鞒绦驅(qū)R怎么辦?在嘗試了很久以后,我暫時(shí)沒有找到一個(gè)完美解決這個(gè)問題的辦法。?

如果版本無法對(duì)齊,就只能從根本上放棄走插件這條路。

Go語言的這種對(duì)三方庫的、幾乎無腦的強(qiáng)一致性約束,從一方面來說,避免了運(yùn)行時(shí)因?yàn)榘姹静灰恢聨淼臐撛趩栴};從另一方面來說,這種刻意不給程序員靈活度的設(shè)計(jì),對(duì)插件化、定制化、擴(kuò)展化開發(fā)非常的不友好。? 

圖片

?

圖3-2. 共同依賴的三方庫版本不一致導(dǎo)致的加載失敗

3.2.2 case 2. 版本號(hào)一致,代碼不一致

當(dāng)你按照3.2.1的思路排查go.mod 文件,但是驚訝的發(fā)現(xiàn)報(bào)錯(cuò)的庫版本是一致的時(shí)候,事情就會(huì)變得復(fù)雜起來。你可能會(huì)拿出世界上最先進(jìn)的文本查驗(yàn)工具,并花掉一個(gè)上午去diff 三方庫的commitid,但它們就是一模一樣,似乎陷入了薛定諤的版本。?

出現(xiàn)這個(gè)問題可能的一個(gè)不是原因的原因是:有人直接修改了vendor目錄下的代碼,Go插件機(jī)制會(huì)對(duì)代碼內(nèi)容的一致性進(jìn)行校驗(yàn)。

這真的是一個(gè)非常令人頭大,并難以排查的原因。除了修改代碼的那個(gè)人,和已經(jīng)在其他case中被“坑”過的那些人,沒人會(huì)知道這件事情。如果修改的vendor代碼出現(xiàn)在主程序里,你就幾乎沒有任何靠譜的辦法讓它們正常工作起來。

不要直接在vendor里改代碼,回饋開源社區(qū),或者fork-replace。

好消息是,你不需要解決這個(gè)問題。因?yàn)榧词菇鉀Q了,也還會(huì)有更大的問題等著你。?

圖片

?   

圖3-2. 共同依賴的三方庫代碼被就地修改導(dǎo)致的加載失敗

3.2.3 case 3. 路徑不一致

當(dāng)按照3.2.1和3.2.2的思路都把問題排查、解決完,但它還是報(bào)different version of package的時(shí)候,可能你就會(huì)開始對(duì)Go的插件機(jī)制口吐芬芳了:版本真的一毛一樣,代碼真的一行沒動(dòng),為什么還報(bào)不同版本???

原因是:插件機(jī)制會(huì)校驗(yàn)依賴庫源碼的「路徑」,因此不能使用vendor管理依賴。

舉個(gè)例子:你的主程序源碼放在/path/to/main目錄下,因此,你的某個(gè)三方庫依賴的目錄應(yīng)該是/path/to/main/vendor/some/thrid/part/lib;同理,你的插件源碼放在/path/to/plugin目錄下,因此,同一個(gè)三方庫依賴的目錄應(yīng)該是/path/to/plugin/vendor/some/thrid/part/lib。這些「文件路徑」數(shù)據(jù)會(huì)被打包到二進(jìn)制可執(zhí)行文件里并用于校驗(yàn),當(dāng)主程序加載插件時(shí),Go的“運(yùn)行時(shí)”“聰明的”通過「文件路徑」的差異認(rèn)定它和插件用的不是同一份代碼,然后報(bào)了個(gè)different version of package。

圖片

圖3-3. 使用vendor機(jī)制管理第三方庫導(dǎo)致的加載失敗

同樣的問題也可能會(huì)出現(xiàn)在使用不同機(jī)器/用戶,分別編譯主程序、插件的場景下:用戶名不同,go代碼的路徑應(yīng)該也會(huì)不一樣。

解決這類問題的方法很暴力直接:刪掉主程序和插件的vendor目錄,或者使用-mod=readonly 編譯flag。

到這里,如果你是使用同一臺(tái)機(jī)器進(jìn)行主程序和插件的編譯,那么常見的問題應(yīng)該都基本解決了,插件機(jī)制理應(yīng)能夠正常工作。另一方面,由于不再使用vendor管理依賴,因此3.2.2的問題也會(huì)在這里被強(qiáng)制解決:要么提PR給社區(qū),要么fork-replace。?

圖片

  

圖3-4. 成功加載

3.3 不一致的Go版本

fatal error: runtime: no plugin module data

除了上面的那些問題以外,還有一個(gè)在多機(jī)器分別編譯主程/插件場景下的常見報(bào)錯(cuò)。這個(gè)報(bào)錯(cuò)的一個(gè)可能原因是Go版本不一致,對(duì)齊它們即可。(如果從機(jī)器層面就是不能對(duì)齊怎么辦?)?

圖片

圖3-5. Go版本不一致導(dǎo)致的加載失敗

統(tǒng)一解決方案

從3.1到3.3,我們看了一些很難排查,也不是很好處理的問題。除此之外,其實(shí)還有一些問題沒有被重點(diǎn)介紹進(jìn)來。作為一個(gè)編程語言官方支持的擴(kuò)展機(jī)制,做的如此用戶不友好確實(shí)出人意料。

我所在的團(tuán)隊(duì)由于重點(diǎn)依賴Go的插件機(jī)制做定開,因此必須拿出一個(gè)系統(tǒng)化的方案把這些問題統(tǒng)統(tǒng)解決掉。在嘗試直接修改Go源碼無果以后(吐槽:Go插件機(jī)制源碼寫的令人略感遺憾),我重點(diǎn)從以下幾個(gè)方面入手開展了相關(guān)工作:

  • 統(tǒng)一編譯環(huán)境:

提供一個(gè)標(biāo)準(zhǔn)的docker image用來編譯主程序和插件,規(guī)避任何go版本、gopath路徑、用戶名等不一致所帶來的問題

預(yù)制go/pkg/mod,盡可能減少因?yàn)闆]有使用vendor模式導(dǎo)致每次編譯都要重新下載依賴的問題

  • 統(tǒng)一Makefile:

提供一套主程序和插件的編譯Makefile,規(guī)避任何因?yàn)間o build命令帶來的問題

  • 統(tǒng)一插件開發(fā)腳手架:

由腳手架,而不是開發(fā)者拉齊插件與主程序的依賴版本。并由腳手架解決其他相關(guān)問題

  • ACI化:

將編譯流程aci化,進(jìn)一步避免出現(xiàn)錯(cuò)誤

圖片

?

圖4-1. 統(tǒng)一解決方案

?至此,關(guān)于Go插件的常見問題及解決方法介紹就暫告段落了,希望對(duì)你有所幫助。

Bonus

如果真的想從根本上搞清楚插件校驗(yàn)的機(jī)制,那這里為你提供一些快速進(jìn)入源碼閱讀狀態(tài)的入口。我使用的Go源碼為1.15.2版本。

相關(guān)Go源碼位置:

  • compiler

go/src/cmd/compile/*

  • linker

go/src/cmd/link/internal/ld/*

  • package loader

go/src/cmd/go/internal/load/*

  • runtime

go/src/runtime/*

5.1 go build到底在做啥

你可以在go build 命令里添加-x 參數(shù),以顯式的打印出Go程序編譯、鏈接、打包的全流程,例如:

go build -x -buildmode=plugin -o ../calc_plugin.so calc_plugin.go

5.2 目標(biāo)代碼生成

  • go/src/cmd/compile/internal/gc/obj.go:55 :注意第67和第72行,這里是兩個(gè)入口
  • go/src/cmd/compile/internal/gc/iexport.go:244 :注意280行,這里會(huì)記錄path相關(guān)數(shù)據(jù)

5.3 庫哈希生成算法

go/src/cmd/link/internal/ld/lib.go:967 :注意第995~1025行,這里計(jì)算pkg的hash

5.4 庫哈希校驗(yàn)

  • go/src/runtime/symtab.go:392 :關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
  • go/src/runtime/plugin.go:52 :鏈接期hash與運(yùn)行時(shí)hash值校驗(yàn)點(diǎn)
  • go/src/cmd/link/internal/ld/symtab.go:621 :鏈接期hash賦值點(diǎn)
  • go/src/cmd/link/internal/ld/symtab.go:521 :運(yùn)行時(shí)hash賦值點(diǎn)

責(zé)任編輯:武曉燕 來源: 阿里開發(fā)者
相關(guān)推薦

2011-08-29 14:50:08

jQuery插件

2016-12-02 19:00:13

Android FraAndroid

2010-02-02 13:32:32

Python繼承

2010-02-06 17:17:17

Android手機(jī)

2010-03-01 18:21:08

Python測試

2011-09-13 09:49:59

PhoneGap插件

2010-02-03 17:52:11

Python 2.0

2009-05-05 17:52:48

系統(tǒng)安全密碼安全Windows

2025-05-30 07:10:19

2010-03-10 13:29:01

以太網(wǎng)交換機(jī)

2022-07-26 07:47:14

架構(gòu)

2010-08-03 10:46:41

Flex代碼格式化

2024-06-12 08:54:49

Go切片參數(shù)

2025-06-12 00:21:27

2020-07-16 08:05:15

JavaGo

2025-06-04 10:08:00

Go開發(fā)云原生

2019-07-28 21:05:47

ICMPIP網(wǎng)絡(luò)協(xié)議

2022-05-08 09:11:44

WiFi樹莓派GO

2025-04-11 09:10:38

2022-04-06 08:19:13

Go語言切片
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 97精品超碰一区二区三区 | 91精品国产综合久久久久久丝袜 | 91成人午夜性a一级毛片 | 精品国产乱码久久久久久牛牛 | 青青久在线视频 | 欧洲视频一区二区 | 精品在线一区二区 | 超碰日本| 免费看a | 男女污污动态图 | 91精品国产一区二区在线观看 | 国产精品污www一区二区三区 | 一区在线视频 | 亚洲高清一区二区三区 | 久久伊人亚洲 | 成人二区三区 | 亚洲成人国产 | 精区3d动漫一品二品精区 | 激情五月综合 | 亚洲国产二区 | 成人免费淫片aa视频免费 | 亚洲成人精品国产 | 免费一看一级毛片 | 国产精品成人一区二区三区夜夜夜 | 天天操网 | 三级在线视频 | 国产精品国产成人国产三级 | 欧美国产精品一区二区 | 欧美日韩亚洲国产 | 淫片一级国产 | 精品区一区二区 | 欧美日韩视频在线第一区 | 精品视频 免费 | 久久一热| 天天干成人网 | 中文字幕蜜臀av | 精品欧美黑人一区二区三区 | 99久久久久久久 | 99精品久久 | 国产精品国产精品国产专区不片 | 波多野结衣av中文字幕 |