简体   繁体   English

如何在 Golang 中更快地调用 api?

[英]How to make an api call faster in Golang?

I am trying to upload bunch of files using the company's api to the storage service they provide.我正在尝试使用公司的 api 将一堆文件上传到他们提供的存储服务。 (basically to my account). (基本上到我的帐户)。 I have got lots of files like 40-50 or something.我有很多文件,比如 40-50 之类的。 I got the full path of the files and utilize the os.Open , so that, I can pass the io.Reader.我得到了文件的完整路径并利用了os.Open ,这样我就可以通过 io.Reader。 I did try to use client.Files.Upload() without goroutines but it took so much time to upload them and decided to use goroutines .我确实尝试在没有goroutines的情况下使用client.Files.Upload()但上传它们花了很多时间并决定使用goroutines Here the implementation that I tried.这是我尝试过的实现。 When I run the program it just uploads one file which is the one that has the lowest size or something that it waits for a long time.当我运行程序时,它只会上传一个文件,该文件是最小的文件或等待很长时间的文件。 What is wrong with it?它有什么问题? Is it not like every time for loops run it creates a goroutine continue its cycle and creates for every file ?是不是每次 for 循环运行它都会创建一个goroutine继续其循环并为每个file创建? How to make it as fast as possible with goroutines ?如何使用goroutines使其尽可能快?

var filePaths []string
var wg sync.WaitGroup

// fills the string of slice with fullpath of files.
func fill() {
    filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
        if !info.IsDir() {
            filePaths = append(filePaths, path)
        }
        if err != nil {
            fmt.Println("ERROR:", err)
        }
        return nil
    })
}

func main() {
    fill()

    tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
    oauthClient := oauth2.NewClient(context.TODO(), tokenSource)
    client := putio.NewClient(oauthClient)

    for _, path := range filePaths {
        wg.Add(1)

        go func() {
            defer wg.Done()

            f, err := os.Open(path)
            if err != nil {
                log.Println("err:OPEN", err)
            }

            upload, err := client.Files.Upload(context.TODO(), f, path, 0)
            if err != nil {
                log.Println("error uploading file:", err)
            }
            fmt.Println(upload)
        }()
    }
    wg.Wait()
}

Consider a worker pool pattern like this: https://go.dev/play/p/p6SErj3L6Yc考虑这样的工作池模式: https://go.dev/play/p/p6SErj3L6Yc

In this example application, I've taken out the API call and just list the file names.在这个示例应用程序中,我取出了 API 调用,只列出了文件名。 That makes it work on the playground.这使它在操场上工作。

  • A fixed number of worker goroutines are started.启动了固定数量的工作 goroutine。 We'll use a channel to distribute their work and we'll close the channel to communicate the end of the work.我们将使用一个频道来分发他们的工作,我们将关闭频道以传达工作的结束。 This number could be 1 or 1000 routines, or more.这个数字可以是 1 个或 1000 个例程,或者更多。 The number should be chosen based on how many concurrent API operations your putio API can reasonably be expected to support.应根据您的 putio API 可以合理地预期支持多少并发 API 操作来选择数量。
  • paths is a chan string we'll use for this purpose. paths是我们将用于此目的的chan string
  • workers range over paths channel to receive new file paths to upload工作人员在paths通道range接收要上传的新文件路径
package main

import (
    "fmt"
    "os"
    "path/filepath"
    "sync"
)

func main() {
    paths := make(chan string)
    var wg = new(sync.WaitGroup)
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go worker(paths, wg)
    }
    if err := filepath.Walk("/usr", func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return fmt.Errorf("Failed to walk directory: %T %w", err, err)
        }
        if info.IsDir() {
            return nil
        }
        paths <- path
        return nil
    }); err != nil {
        panic(fmt.Errorf("failed Walk: %w", err))
    }
    close(paths)
    wg.Wait()
}

func worker(paths <-chan string, wg *sync.WaitGroup) {
    defer wg.Done()
    for path := range paths {
        // do upload.
        fmt.Println(path)
    }
}

This pattern can handle an indefinitely large amount of files without having to load the entire list in memory before processing it.此模式可以处理无限量的文件,而无需在处理之前将整个列表加载到 memory 中。 As you can see, this doesn't make the code more complicated - actually, it's simpler.如您所见,这并没有使代码更复杂——实际上,它更简单。

When I run the program it just uploads one file which is the one当我运行程序时,它只上传一个文件

Function literals inherit the scope in which they were defined. Function 文字继承了定义它们的 scope。 This is why our code only listed one path - the path variable scope in the for loop was shared to each go routine, so when that variable changed, all routines picked up the change.这就是为什么我们的代码只列出了一个路径 - for 循环中的path变量 scope 被共享给每个 go 例程,所以当该变量发生变化时,所有例程都会接受更改。

Avoid function literals unless you actually want to inherit scope.避免使用 function 文字,除非你真的继承 scope。 Functions defined at the global scope don't inherit any scope, and you must pass all relevant variables to those functions instead.在全局 scope 中定义的函数不会继承任何 scope,您必须将所有相关变量传递给这些函数。 This is a good thing - it makes the functions more straightforward to understand and makes variable "ownership" transitions more explicit.这是一件好事——它使函数更易于理解,并使变量“所有权”转换更明确。

Speaking of scope, global variables should be avoided unless their scope of usage is truly global.说到 scope,应该避免使用全局变量,除非它们的 scope 的使用是真正全局的。 Prefer passing variables between functions to sharing global variables.更喜欢在函数之间传递变量而不是共享全局变量。 Again, this makes variable ownership explicit and makes it easy to understand which functions do and don't access which variables.同样,这使变量所有权变得明确,并且易于理解哪些函数可以访问和不访问哪些变量。 Neither your wait group nor your filePaths have any cause to be global.您的等待组和filePaths都没有任何理由成为全球性的。

            f, err := os.Open(path)

Don't forget to close any files you open.不要忘记关闭您打开的任何文件。 When you're dealing with 40 or 50 files, letting all those open file handles pile up until the program ends isn't so bad, but it's a time bomb in your program that will go off when the number of files exceeds the ulimit of allowed open files.当您处理 40 或 50 个文件时,让所有打开的文件句柄堆积起来直到程序结束并不是那么糟糕,但它是您程序中的一个定时炸弹,当文件数超过ulimit允许打开的文件。 Because the function execution greatly exceeds the part where the file needs to be open, defer doesn't make sense in this case.因为 function 执行大大超出了需要打开文件的部分,所以这种情况下defer没有意义。 I would use an explicit f.Close() after uploading the file.上传文件后,我会使用明确的f.Close()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM