文|丁飞(花名:路德 )
蚂蚁集团高级工程师
深耕于 SOFAMesh 产品的商业化落地 主要方向为基于服务网格技术的系统架构升级方案设计与落地
本文 4394 字 阅读 10 分钟
|前言|
MOSN 作为蚂蚁集团在 ServiceMesh 解决方案中的数据面组件,从设计之初就考虑到了第三方的扩展开发需求。目前,MOSN 支持通过 gRPC、WASM、以及 Go 原生插件三种机制对其进行扩展。
我在主导设计和落地基于 Go 原生插件机制的扩展能力时遇到了很多问题,鉴于这方面的相关资料很少,因而就有了这个想法来做一个非常粗浅的总结,希望能对大家有所帮助。
注:本文只说问题和解决方案,不读代码,文章最后会给出核心源码的 checklist。
PART. 1--文章技术背景
一、运行时
通常而言,在计算机编程语言领域,“运行时”的概念和一些需要使用到 VM 的语言相关。程序的运行由两个部分组成:目标代码和“虚拟机”。比如最为典型的 JAVA,即 Java Class + JRE。
对于一些看似不需要“虚拟机”的编程语言,就不太会有“运行时”的概念,程序的运行只需要一个部分,即目标代码。但事实上,即使是 C/C++,也有“运行时”,即它所运行平台的 OS/Lib。
Go 也是一样,因为运行 Go 程序不需要前置部署类似于 JRE 的“运行时”,所以它看起来似乎跟“虚拟机”或者“运行时”没啥关系。但事实上,Go 语言的“运行时”被编译器编译成了二进制目标代码的一部分。
图 1-1. Java 程序、runtime 和 OS 关系
图 1-2. C/C++ 程序、runtime 和 OS 关系
图 1-3. Go 程序、runtime 和 OS 关系
二、Go 原生插件机制
作为一个看起来更贴近 C/C++ 技术栈的 Go 语言来说,支持类似动态链接库的扩展一直是社区中较为强烈的诉求。
如图 1-5,Go 在标准库中专门提供了一个 plugin 包,作为插件的语言级编程界面,src/plugin 包的本质是使用 cgo 机制调用 unix 的标准接口:dlopen() 和 dlsym() 。因此,它给 C/C++ 背景的程序员一种“这题我会”的错觉。
图 1-4. C/C++ 程序加载动态链接库
图 1-5. Go 程序加载动态链接库
PART. 2--典型问题解决
很遗憾,与 C/C++ 技术栈相比,Go 的插件的产出物虽然也是一个动态链接库文件,但它对于插件的开发、使用有一系列很复杂的内置约束。更令人头大的是,Go 语言不但没有对这些约束进行系统性的介绍,甚至写了一些比较差的设计和实现,导致插件相关问题的排错非常反人类。
本章节重点跟大家一起看下,在开发、使用 Go 插件,主要是编译、加载插件的时候,最常见、但必须定位到 Go 标准库 (主要包括编译器、链接器、打包器和运行时部分) 源码才能完全弄明白的几个问题,及对应的解决方法。
简而言之,Go 的主程序在加载 plugin 时,会在“runtime”里对两者进行一堆约束检查,包括但不限于:
- go version 一致
- go path 一致
- go dependency 的交集一致
- 代码一致
- path 一致