為什么各個語言都會有這么多的依賴問題?
軟件包的分發規模產生了巨大的變化
大部分主流編程語言都誕生於上個世紀,代碼包的分發范圍在當時僅限於小規模的團體,例如公司內部或者單個軟件項目內部,這種分發規模 只要內部有良好的代碼約定就不會導致模塊依賴沖突,但今天我們已經廣泛運用github社區來分發軟件代碼包,分發的規模已經跨越國界跟種族以及不同的語言,這在當時是無法想象的。
-
例如現如今依然非常流行的C語言,就存在經典的函數命名沖突的問題,在小規模團體內部可以通過約定函數命名前綴來避免編譯時的沖突。C++在后續引入了命名空間解決了這一問題
-
C++編譯器版本眾多,又有很多公司或者團體采用二進制閉源發布代碼,又導致了二進制ABI依賴問題, 在今天你依舊可以看到golang swift這些新興的編譯型的語言都是依賴源碼編譯的,因為它們大多各自語言的各個版本之間的並不是二進制兼容的,例如X86 就有fastcall stdcall 等等函數調用約定,之前我翻了一下golang的內部實現,發現go語言在內部又搞了自己一套匯編調用約定,在go使用標准的C函數庫的時候還需要做一些轉換。
動態語言在運行時鏈接函數
動態語言天生就有的毛病,所有的調用都是在運行時才能確認被調用的模塊的位置以及代碼,因為在動態語言中,並不像C/C++那樣在編譯期有大量的靜態檢查,當然你要是喜歡用C/C++的void * ,上帝也沒法拯救你。
- Java通過虛擬機字節碼兼容性約定以及命名空間,在字節碼層面使用操作數棧屏蔽了編譯型語言的二進制調用約定問題, 但其動態鏈接,依舊沒有避免運行時依賴缺失的問題,例如包A依賴了包B的v1版本,但是在項目中引入的包C的依賴了包B的v2版本,在maven的仲裁機制下,編譯后只導入了包B的v1版本,當程序跑進包C里面的代碼就會因為包B的v1版本缺失了一些v2版本的特性而報錯,而且這些因為maven仲裁機制導致的依賴缺失的問題並不會在編譯期被發現,大多只能等到線上運行的時候才會被發現,如果包C的代碼並不是熱點代碼,大部分時候程序並不會跑進包C,很有可能你會在深夜因為報錯日志而被領導催促起床來解決版本依賴問題。解決這個問題的辦法只能依靠版本語義管理,在每次發布前都要檢測maven版本仲裁是否存在版本號不兼容的情況。
例如 aa.bb.cc 這種版本號,cc代表bugfix的版本,
大多時候出現這種依賴沖突,人工仲裁選擇使用高版本即可,而bb代表大的改動,可能存在接口不兼容的情況,
這個時候就要對依賴進行代碼檢查,確保Java動態鏈接調用沒有問題才能上線使用。
當然很多公司內部的包並不存在版本語義化管理的規范,這個時候你只能祈求上帝,編寫你依賴模塊的那個老哥,沒有修改外部接口
- 在Python里面同樣存在類似Java這樣的版本沖突導致的依賴缺失問題,而且這些問題大多時候很難被發現,只能等到運行時,例如你在代碼中使用模塊A的高版本才有的python方法,但是你運行時import了一個低版本的模塊A,這個低版本的模塊A里面剛好缺失了模塊A高版本才有的python方法,而恰好這個代碼並不是熱點代碼(在服務器上老半年不一定會跑到的地方),那就等着這顆雷在線上隨時爆炸吧。
微服務下完全失控的依賴管理
在微服務下,以我個人的觀察,大部分公司的微服務接口,可能只采用了簡單的人肉管理,而且微服務大多都是跨部門維護接口的情況,這個時候如果溝通不暢,更容易出現問題。當然跨服務的RPC調用大多都是熱點代碼,如果出現問題,大多很容易被暴露出來。
- Java中使用了feign 通過靜態編譯約束大概率上解決了 接口參數的問題, 但如果你的feign模塊是依賴方的低版本,而對方在生產發布流程中部署了高版本的服務,照舊會出現問題,當然這個時候需要強有力的跨部門跨技術組的協調方案,不然依舊會暴露依賴沖突這個問題。