这个周末给 tikv/pd 水了个 PR: *: Fix the wrong usage of go.mod replace

改动非常简单:

diff --git a/go.mod b/go.mod
index 3a469a0d74..4e78c4f7c6 100644
--- a/go.mod
+++ b/go.mod
@@ -45,6 +45,8 @@ require (
 	github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965
 	github.com/unrolled/render v1.0.1
 	github.com/urfave/negroni v0.3.0
+	// Fix panic in unit test with go >= 1.14, ref: etcd-io/bbolt#201 https://github.com/etcd-io/bbolt/pull/201
+	go.etcd.io/bbolt v1.3.5 // indirect
 	go.etcd.io/etcd v0.5.0-alpha.5.0.20191023171146-3cf2f69b5738
 	go.uber.org/goleak v1.1.10
 	go.uber.org/zap v1.16.0
@@ -52,6 +54,3 @@ require (
 	google.golang.org/grpc v1.26.0
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 )
-
-// Fix panic in unit test with go >= 1.14, ref: etcd-io/bbolt#201 https://github.com/etcd-io/bbolt/pull/201
-replace go.etcd.io/bbolt => go.etcd.io/bbolt v1.3.5
diff --git a/go.sum b/go.sum
index 1535577e89..54198a6b5b 100644
--- a/go.sum
+++ b/go.sum
@@ -455,6 +455,8 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yookoala/realpath v1.0.0/go.mod h1:gJJMA9wuX7AcqLy1+ffPatSCySA1FQ2S8Ya9AIoYBpE=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
 go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
 go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=

go.sum 的变更是自动生成的,我做的主要改动是去掉了 replace 改成了在 require 中直接声明。为什么要做这样的改动呢?

根据注释:

// Fix panic in unit test with go >= 1.14, ref: etcd-io/bbolt#201 https://github.com/etcd-io/bbolt/pull/201

特殊处理 bblot 的版本是为了解决 Fix unsafe pointer conversions caught by Go 1.14 checkptr,这个 PR 于 2020-03-20 被 merge,连同其他的相关 fix 一起在 v1.3.4v1.3.5 中被发布。以 commit 2fc6815 为例,只要最终构建出来的版本中包括了这一 commit 即可。

requirereplace 都能解决这个问题,但是方式不一样:

  • require 指定了最低版本要求:用于构建这个 module 的 bbolt 必须大于等于 v1.3.5
  • replace 则是进行了替换:无论最小版本依赖计算出来的结果是什么,最终用于构建的 bbolt 都会被替换成 v1.3.5

接下来我们分别从依赖和被依赖两个角度来考虑问题。

首先考虑 pd 内部的依赖。

使用 require 的好处在于,假如有天 pd 的依赖中使用了更高版本的 bbolt(修复了其他问题),那 pd 在进行 go mod tidy 或者 go build 时会发现这一点,并使用更高的版本来进行构建。而 replace 则会完全忽略,总是使用 v1.3.5,需要人工进行干预。

然后考虑那些依赖 pd 的库。

replace 仅作用于当前 Module,这一方面意味着 pd 进行了这样的 replace 之后,下游的库还是会被这个 bug 所影响,另一方面意味着下游的库需要做完全一样的 replace,正如我们在 tidb 中看到的这样。

总结

总的来说,除非真的需要,请尽量避免在 go.mod 中使用 replace

我遇到过的比较适合使用 replace 的场景如下:

  • 本地联合开发调试,replace 成对应的本地库便于开发
  • 项目的集成测试,replace 成相对路径,总是使用最新的代码来构建
  • 上游不愿意 merge 自己的改动,replace 成自己的 fork (需要考虑自己的下游无法获取该变更)

参考资料