业务需求上经常存在需要对同一个文件进行双上传,上传到不同云存储桶,以防出现某一个云厂商因各种意外导致自身服务出现不可用的情况,当然,还有其他措施可以避免,现在只针对通过程序业务代码而双写存储的这个场景。
业务场景
文件A上传到oss a,同时也需要将这个文件A异步上传到oss b,而文件A在主协程会被remove。
下面有份伪代码去描述这个场景
func ABC() {
file, err := os.Open(path)
if err != nil {
return
}
defer func() {
file.Close()
os.Remove(path)
}()
...
// 上传到oss a
upload2OssA(file)
...
// TODO 异步上传到oss b
}
我们可以看到主routine打开了一个文件,并且上传到oss a,程序结束后会close文件且remove文件了,现在希望对这个文件异步上传到oss b
方式一
将文件内容读取出来上传
func ABC() {
file, err := os.Open(path)
if err != nil {
return
}
defer func() {
file.Close()
os.Remove(path)
}()
...
// 上传到oss a
upload2OssA(file)
...
// TODO 异步上传到oss b
method1(file)
}
func method1(file *os.File) {
if _, err := file.Seek(0, 0); err != nil {
return
}
b, err := ioutil.ReadAll(file)
if err != nil {
return
}
go upload2OssB(b)
}
在主routine将文件偏移量重置,将文件全都读取到内存了,异步routine上传到oss b
- 优点:实现简单
- 缺点:占用资源多
- 总结:虽然实现简单,但使内存消耗增加
方式二
新创建文件,用新文件句柄去上传
func ABC() {
file, err := os.Open(path)
if err != nil {
return
}
defer func() {
file.Close()
os.Remove(path)
}()
...
// 上传到oss a
upload2OssA(file)
...
// TODO 异步上传到oss b
method2(file)
}
func method2(file *os.File) {
if _, err := file.Seek(0, 0); err != nil {
return
}
tmpF, err := os.CreateTemp(os.TempDir(), "")
if err != nil {
return
}
defer func() {
tmpF.Close()
os.Remove(tmpF.Name())
}()
if _, err = io.Copy(tmpF, file); err != nil {
return
}
go upload2OssB(tmpF)
}
在主routine将文件偏移量重置,create了一个临时文件,通过io.Copy将文件内容拷贝到临时文件,异步routine读取新文件上传到oss b
- 优点:实现简单
- 缺点:占用资源多
- 总结:虽然实现简单,但使文件读写io和磁盘占用都增加了
方式三
同文件多句柄操作
func ABC() {
file, err := os.Open(path)
if err != nil {
return
}
defer func() {
file.Close()
os.Remove(path)
}()
// 打开同一个文件,用新句柄去异步上传
file2, err := os.Open(path)
if err != nil {
return
}
...
// 上传到oss a
upload2OssA(file)
...
// TODO 异步上传到oss b
method3(file2)
}
func method3(file *os.File) {
go func() {
upload2OssB(file)
file.Close()
}()
}
在主routine打开同一个文件,用新文件的句柄,在异步routine上传到oss b
- 优点:代码简洁
- 缺点:需要维护多个句柄
- 总结:利用了文件系统的引用计数,打开同一个文件,不同的fd,只要新句柄没有被释放,那么就可以进行异步上传
方式四
硬链接文件
unc ABC() {
file, err := os.Open(path)
if err != nil {
return
}
defer func() {
file.Close()
os.Remove(path)
}()
...
// 上传到oss a
upload2OssA(file)
...
// TODO 异步上传到oss b
method4(path)
}
func method4(path string) {
go func() {
if err := os.Link(path, newpath); err != nil {
return
}
file, err := os.Open(newpath)
if err != nil {
return
}
defer func() {
file.Close()
os.Remove(path)
}()
upload2OssB(file)
}()
}
在异步routine, 创建硬链接文件,上传到oss b
- 优点:代码简洁
- 缺点:需要维护硬链接
- 总结:利用了文件系统的引用计数,硬链同一个文件,只要将硬链接当成普通文件处理,进行异步上传
总结
任何一个方式都需要结合业务场景进行权衡,没有高下之分,仅提供思路,以上代码都以伪代码的形式,如有其他方案,欢迎提供。
巨人的肩膀
从他人的工作中汲取经验来避免自己的错误重复,正如我们是站在巨人的肩膀上才能做出更好的成绩。
VChat
一个没有哆啦A梦和静香的IT码农,不专业Gopher