本文首发于 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.ini
var f embed.FS

func 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

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

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

embedcfg的应用

initEmbedfileStringSym


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提案,置信这个文件系统会逐步齐全。

结语

ioutil.ReadFileembed.FS.ReadFile