为什么需要一个SDK?
- 频繁遇到签名报错问题,排查起来费时费力;
- 对接人水平参差不齐,遇到天选之人,让人头大;
比如,有老哥不知道query参数需要转义,有人甚至不知道要把http响应body读取并解析 - 简化环境对齐步骤,降低对本服务部署方式、域名选择等知识的理解成本
- 消除客户端、服务器因为实现语言不同而带来的技术细节问题
实现一个什么目标?
隐藏请求签名和请求处理的复杂性,让对接的人闭嘴,让自己省心省力。
最终将业务功能封装成一个本地方法调用,传入参数对象,得到结果对象。
DoYourWork(request) response
一个SDK中有什么?
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超时
- 空闲连接超时
Signer
每个业务的签名算法可能不太一样,但是基本上都是对http请求签名,可以做一个类似下面的签名方法:
AWS: public void sign(SignableRequest<?> request, AWSCredentials credentials);
腾讯:func signRequest(request tchttp.Request, credential CredentialIface, method string) (err error)
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)
包装请求、响应、错误
请求、响应中,尽可能增加用于追踪的trace_id,比如增加 request_id
依照具体业务,精细化错误分类。
请求重试 Retry
思考几个问题:
- 确定业务场景是否可以重试,重试会不会给业务带来副作用;
确定哪些错误类型是可以重试的;
- 网络超时
- IO错误
- 限流错误
- 网关错误:5xx错误,比如AWS将500,502,503,504作为可重试场景
- 由谁来重试更好,是接入方自行重试,还是内置重试策略。
最大重试次数:
对于一般的HTTP请求而言,重试次数不应该设置很大,AWS默认设置为2;
举个AWS SDK中的例子,退避策略:
- 固定延时退避(Fixed Delay Backoff)
- 指数退避(Exponential Backoff)
- 完全随机退避策略(Full Jitter Backoff)
- 等间隔随机退避策略(Equal Jitter Backoff)
断路器 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
}
代码格式上的一些考虑
仓库组织
更详细的介绍见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`
版本更新和兼容性
更详细的介绍见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 文件夹
- 使用分支
注释、文档和测试用例
更详细的介绍见golang官网博客
通常想要快速了解一个包对外暴露的接口、对象、常量等,我们可以使用go doc
命令。
该命令会将文档中的具有Export属性的内容按照规范形式显示出来,所以在写我们自己的sdk时,我们也要注意一下go doc
的输出形式。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。