本文首发于 IEG增长中台技术团队公众号。

起因

上周部署一个新我的项目,我的项目的配置文件追随我的项目目录寄存,还未放到配置核心。构建镜像运行时,因为没有拷贝配置文件,我的项目编译后的二进制文件读不到配置,服务起不来。

这是很多Go开发者或多或少都会遇到的经典文件门路问题。

这时要么写个部署脚本,每次构建镜像,把配置文件跟编译后的二进制文件放在一起。

但这时我想到了Go embed,往年Go公布了1.16,1.17两个版本,其中1.16里边的Go embed及io/fs就解决了这个经典文件门路问题。

Go embed应用

传统的文件读取代码,配置文件须要追随二进制文件公布:

func main() {    fPath := "conf/conf.ini"    c, err := ioutil.ReadFile(fPath)    if err != nil {        log.Fatal(err)    }    fmt.Println(string(c))}

换成Go embed的形式,配置文件会编译进最终的二进制文件,公布不须要拷贝配置文件,只需注解申明一个embed配置和定义一个embed变量:

//go:embed conf/conf.inivar f embed.FSfunc main() {    fPath := "conf/conf.ini"    data, err := f.ReadFile(fPath)    if err != nil {        log.Fatal(err)    }    fmt.Println(string(data))}

源码实现

通过下面的例子,咱们能够发现这个Go embed是编译期的行为,而且还是以注解的形式嵌入,有点离奇。Go的注解,目前我能想到的只有两个,别离是go:generatego:build,后者还是1.17新推出的注解,这感觉是Go逐步Java化?

获取注解

执行go build -n查看embed形式代码的编译过程,上面是整个过程的局部截图。Go编译的外围有三个命令:compile/buildid/link(不在截图中),在compile动作之前,咱们能够看到跟embed注解相干的内容就是红框内的embedcfg文件,这里会将注解前面的文件门路"conf/conf.ini"信息结构化写到embedcfg文件。

Go编译期间的代码根本都集中在src/cmd/compile/internal/gc目录下,咱们在Main函数里边先找到go:embed注解的获取,就是下面的embedcfg:

一顿readEmbedCfg操作把刚刚的文件Unmarshal成正经的embedCfg struct

embedcfg的应用

embedcfg结构化之后,还是compile的Main函数,外面会调用一个initEmbed的办法查看embedcfg的文件信息,并读取该门路对应的文件,将文件内容读取进去,最终跟compile期间的其余所有内容写到一个两头文件_pkg_.a,检查和读取文件的要害子办法为下图的fileStringSym,这里能够看到咱们相熟的文件系统调用open跟stat。


Go embed背地的设计

上文咱们大抵理解了go:embed这个注解的应用和实现,但为什么要弄一个这种货色呢?难道就是为了解决配置文件部署问题吗?当然不是,go:embed的呈现是为了引人一个全新的文件系统库io/fs

网上冲浪理解到,Go的创始人之一--Rob Pike在2016年就弄了一个提案,1.16之前的os.File并不是个接口,文件系统设计不形象,只能用来示意系统文件。

而咱们都晓得世界上最好的文件系统形象就是Unix,Unix的设计哲学正是"一切都是文件"。事实上Rob Pike也曾是贝尔实验室(Bell Labs)的Unix团队和Plan 9操作系统打算的成员。

从这个设计思维登程,咱们先看go:embed关联的FS对象

从正文阐明中咱们也能够看到这个FS对象实现了io/fs包里的FS接口

这个接口提供的Open办法返回了一个File接口,这个File接口正是文件系统的形象所在

有了这个File接口,咱们就能够随心所欲,再也不必像以前那样,只能用os.File

当然,咱们也看到,目前的FS对象是个只读对象,不能写,File接口也没有提供Write的办法,是个完好的文件系统形象。事实上,也有人在go:embed进去不久就提出了Write办法的提案,参考Write提案,置信这个文件系统会逐步齐全。

结语

事实上,除了把这个文件系统形象之外,Go官网为了保障os/File的向前兼容性,还做了很多工作,但Go语言自身的接口设计思维很好地保障兼容改变。比方本文中文件配置的例子,以前是用ioutil.ReadFile,当初是embed.FS.ReadFile,返回后果都还是文件内容和谬误,开发者应用起来没啥差异,仍然丝滑。