为什么需要一个SDK?

  1. 频繁遇到签名报错问题,排查起来费时费力;
  2. 对接人水平参差不齐,遇到天选之人,让人头大;
    比如,有老哥不知道query参数需要转义,有人甚至不知道要把http响应body读取并解析
  3. 简化环境对齐步骤,降低对本服务部署方式、域名选择等知识的理解成本
  4. 消除客户端、服务器因为实现语言不同而带来的技术细节问题

实现一个什么目标?

隐藏请求签名和请求处理的复杂性,让对接的人闭嘴,让自己省心省力。

最终将业务功能封装成一个本地方法调用,传入参数对象,得到结果对象。

DoYourWork(request) response

一个SDK中有什么?

  1. Http Client

使用http.Client,golang标准库里面有个全局的变量DefaultClient,建议不要直接使用,因为很可能会被整个项目共用,如果需要对此Client做配置,则可能存在冲突。

建议自定义一个Client示例,或者克隆一个

httpClient := &http.Client{
            Transport: http.DefaultTransport.(*http.Transport).Clone(), // 克隆DefaultTransport
        }

标准库中对于DefaultTransport的设置如下:

var DefaultTransport RoundTripper = &Transport{
    Proxy: ProxyFromEnvironment,
    DialContext: defaultTransportDialContext(&net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
    }),
    ForceAttemptHTTP2:     true,
    MaxIdleConns:          100,
    IdleConnTimeout:       90 * time.Second,
    TLSHandshakeTimeout:   10 * time.Second,
    ExpectContinueTimeout: 1 * time.Second,
}

一些可以设置的选项:

  • 请求的连接建立超时
  • 请求的读写超时: 对于golang而言,可以用请求的context控制
  • HTTP请求的KeepAlive超时
  • 空闲连接超时
  1. Signer

每个业务的签名算法可能不太一样,但是基本上都是对http请求签名,可以做一个类似下面的签名方法:

AWS: public void sign(SignableRequest<?> request, AWSCredentials credentials);

腾讯:func signRequest(request tchttp.Request, credential CredentialIface, method string) (err error)

  1. Logger

通常,我们可以把SDK的所有报错信息都返回给调用方。

但是,如果实现的SDK比较复杂,或者希望有调试信息帮助排查问题,可以定义一个 Logger接口,让调用方去实现,然后在SDK里面使用Logger的方法来打印一些重要日志。

// 比如腾讯云SDK中定义了一个Logger接口
type Logger interface {
    Printf(format string, args ...interface{})
}

一个策略是,如果接入方不提供,就不打印相关日志;

另一方面,SDK中可以准备一个内置的日志打印方式。

但是,自行设计日志打印时关注两个问题:

  • 打印到哪里,是标准输出、标准错误还是某个文件,最好还是让接入方传入一个Writer
  • 并发打印问题,通常应该在写入时加把锁
mu.Lock()
defer mu.Unlock()
w.Write(logs)
  1. 包装请求、响应、错误

请求、响应中,尽可能增加用于追踪的trace_id,比如增加 request_id

依照具体业务,精细化错误分类。

  1. 请求重试 Retry

思考几个问题:

  1. 确定业务场景是否可以重试,重试会不会给业务带来副作用;
  2. 确定哪些错误类型是可以重试的;

    1. 网络超时
    2. IO错误
    3. 限流错误
    4. 网关错误:5xx错误,比如AWS将500,502,503,504作为可重试场景
  3. 由谁来重试更好,是接入方自行重试,还是内置重试策略。

最大重试次数:

对于一般的HTTP请求而言,重试次数不应该设置很大,AWS默认设置为2;

举个AWS SDK中的例子,退避策略:

  • 固定延时退避(Fixed Delay Backoff)
  • 指数退避(Exponential Backoff)
  • 完全随机退避策略(Full Jitter Backoff)
  • 等间隔随机退避策略(Equal Jitter Backoff)
  1. 断路器 Breaker

首先确定业务场景是否支持,需要你的服务有备选方案。一般断路器可以根据最近一段时间内请求的错误率,来判断某个连接地址当前是否可用,如果不可用,则切换备用地址,或者做降级处理。

// 一个腾讯云的例子
type breakerSetting struct {
    // backupEndpoint
    // the default is "ap-guangzhou.tencentcloudapi.com"
    backupEndpoint string
    // max fail nums
    // the default is 5
    maxFailNum int
    // max fail percentage
    // the default is 75/100
    maxFailPercentage int
    // windowInterval decides when to reset counter if the state is StateClosed
    // the default is 5minutes
    windowInterval time.Duration
    // timeout decides when to turn StateOpen to StateHalfOpen
    // the default is 60s
    timeout time.Duration
    // maxRequests decides when to turn StateHalfOpen to StateClosed
    maxRequests int
}

代码格式上的一些考虑

  1. 仓库组织

更详细的介绍见golang官网博客

每个仓库对应一个服务的sdk

每个仓库对应多个服务的sdk

  • Module path: example.com/mymodules/module1
  • Version tag: module1/v1.2.3
  • Package path imported by a user: example.com/mymodules/module1/package1
  • Module path as given in a user's require directive: example.com/mymodules/module1` module1/v1.2.3`
  1. 版本更新和兼容性

更详细的介绍见golang官网博客

关于兼容性,引用golang官方回答:

If an old package and a new package have the same import path, the new package must be backwards compatible with the old package.

如果两个包导入路径相同,则一定要保持向后兼容。

如果出现了不兼容,则需要更改导入路径,比如 xxx/v2,增加了 v2 后缀。

出现 v2 版本之后,怎么组织和维护代码呢?

  • 使用 v2 文件夹
  • 使用分支
  1. 注释、文档和测试用例

更详细的介绍见golang官网博客

通常想要快速了解一个包对外暴露的接口、对象、常量等,我们可以使用go doc命令。

该命令会将文档中的具有Export属性的内容按照规范形式显示出来,所以在写我们自己的sdk时,我们也要注意一下go doc的输出形式。


user_IaP54NlE
1 声望0 粉丝

生也有涯,而知也无涯