如何封装go-retry的RetryFunc以适配任意GitHub API调用
问题:适配任意GitHub API的重试封装
我在使用go-retry包对GitHub API调用实现重试逻辑,该包的RetryFunc仅接受func(ctx context.Context) error类型的闭包。但我有大量参数、返回值各不相同的GitHub API需要添加重试,目前每次都要重复编写嵌套闭包和重试判断代码,希望改写retryWithExpBackoff,让它能适配任意GitHub API调用。
原示例代码:
import ( "context" "fmt" "net/http" "strings" "time" "github.com/google/go-github/v61/github" "github.com/sethvargo/go-retry" ) func retryDecision(resp *github.Response) error { if resp.StatusCode == 0 || (resp.StatusCode >= 500 && resp.StatusCode != http.StatusNotImplemented) { return retry.RetryableError(fmt.Errorf("unexpected HTTP status %s", resp.Status)) } return nil } func retryWithExpBackoff(ctx context.Context, f func(ctx context.Context) error) error { b := retry.NewExponential(1 * time.Second) b = retry.WithMaxRetries(5, b) b = retry.WithMaxDuration(5*time.Second, b) return retry.Do(ctx, b, f) } func NewGitHubClient(ctx context.Context, token, repo string) (*github.Client, error) { r := strings.Split(repo, "/") client := github.NewClient(nil).WithAuthToken(token) if err := retryWithExpBackoff(ctx, func(ctx context.Context) error { _, resp, err := client.Repositories.Get(ctx, r[0], r[1]) if retryErr := retryDecision(resp); retryErr != nil { return retryErr } return err }); err != nil { return nil, err } return client, nil }
重复编写的冗余代码块:
if err := retryWithExpBackoff(ctx, func(ctx context.Context) error { // <-------- 需要重试的API ---------> //_, resp, err := client.Repositories.Get(ctx, r[0], r[1]) if retryErr := retryDecision(resp); retryErr != nil { return retryErr } return err }); err != nil { return nil, err }
解决方案:用泛型封装通用重试逻辑
利用Go 1.18+的泛型特性,我们可以封装一个适配GitHub API通用签名(返回(结果, *github.Response, error))的重试函数,彻底消除重复代码:
1. 改写通用重试函数
// retryGitHubAPI 通用重试函数,支持所有返回 (T, *github.Response, error) 的GitHub API func retryGitHubAPI[T any](ctx context.Context, apiFunc func(ctx context.Context) (T, *github.Response, error)) (T, error) { var result T err := retry.Do( ctx, retry.WithMaxDuration(5*time.Second, retry.WithMaxRetries(5, retry.NewExponential(1*time.Second))), func(ctx context.Context) error { var resp *github.Response result, resp, err = apiFunc(ctx) // 复用原有的重试判断逻辑 if retryErr := retryDecision(resp); retryErr != nil { return retryErr } return err }, ) return result, err }
2. 简化API调用的重试实现
以原有的NewGitHubClient为例,改写后无需重复嵌套闭包:
func NewGitHubClient(ctx context.Context, token, repo string) (*github.Client, error) { r := strings.Split(repo, "/") client := github.NewClient(nil).WithAuthToken(token) // 直接传入API调用逻辑,重试逻辑完全封装在retryGitHubAPI中 _, err := retryGitHubAPI(ctx, func(ctx context.Context) (*github.Repository, *github.Response, error) { return client.Repositories.Get(ctx, r[0], r[1]) }) if err != nil { return nil, err } return client, nil }
3. 适配其他GitHub API的示例
比如调用创建Issue的API,只需传入对应API的闭包即可:
func createGitHubIssue(ctx context.Context, client *github.Client, owner, repo string, issueReq *github.IssueRequest) (*github.Issue, error) { return retryGitHubAPI(ctx, func(ctx context.Context) (*github.Issue, *github.Response, error) { return client.Issues.Create(ctx, owner, repo, issueReq) }) }
补充说明
- 该泛型函数适配绝大多数GitHub API的返回签名,如果遇到少数返回值结构不同的API,可以新增对应签名的泛型重载函数
- 重试策略(指数退避、最大重试次数、超时)可以根据需求在
retryGitHubAPI中统一调整,无需逐个修改API调用处
内容的提问来源于stack exchange,提问作者Inian
相关产品推荐
相关产品推荐

