什么,你的服务有长尾?来来来,一次性解决它!

Saner-Lee · · 515 次点击 · 开始浏览    置顶
# backup request 传送门:[github](https://github.com/Saner-Lee/backup-request),创作不易,希望各位看官老爷能给个star,要是能fork过去一起创作就更好了。 ## what `go`语言实现的`backup request`基础库。 ## why `backup request`其实主要是为了解决长尾请求,见名知意,长尾请求就是说处理时间比较长的请求,它的具体场景如下: > 客户端超时配置不合理,给了一个比较大的值,比如5s。 > > 请求发到服务端后,服务端因为资源问题或者其它问题,请求被阻塞无法返回。 > > 结果:5s后客户端超时收到错误,发起重试。 从上面的例子可以看到,传统的重试也可以解决问题,但是从客户端来说这个请求的时延会比较大。 当后端阻塞后其实可以有两种情况: 1. 一部分后端实例资源不足 2. 全部后端实例资源不足 对于第2种情况来说,普通重试和`backup request`都无法解决,但是都有一些正向收益和负向收益,对于`backup request`: - 正向收益:资源在过载边缘,收到重试请求后一小段时间有资源释放,可以立即处理 - 负向收益:资源严重过载,收到重试请求只能使得后端过载更严重 但是正常情况下,资源都是充足的,而且现在的线上服务大多都会有限流,熔断等措施,所以总的来说,通过对资源的消耗来保证低延时,它的正向收益远大于负向收益。 虽然`backup request`需求很简单,但是还是有很多细节问题有待考虑,包括模块解耦,接口设计,防御编程等。 为了避免你重复撸轮子,所以有了这个库,它拥有的能力包括: - 重试时机算法 - 固定时长 - 二进制退避抖动 - 重试准入 - "非法"请求错误回调 放心大胆的使用它,如果有什么需求可以直接`issue`搞起,长期维护。 ## how 在`example`中附带了一个小例子,为了方便阅读,内容直接`copy`到下面了。 ```go package main import ( "context" "fmt" "math/rand" "net/http" "net/http/httptest" "strconv" "sync/atomic" "time" "github.com/Saner-Lee/backup-request/request" "github.com/Saner-Lee/backup-request/request/retrygroup" "github.com/Saner-Lee/backup-request/retry" ) func NewServer() *httptest.Server { rand.Seed(time.Now().UnixNano()) var ( cnt int32 ) return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { num := atomic.AddInt32(&cnt, 1) base := rand.Int63() % 20 fmt.Printf("server: req seq %d, sleep %dms\n", num, base) time.Sleep(time.Duration(base) * time.Millisecond) w.Header().Set("seq", strconv.Itoa(int(num))) w.WriteHeader(http.StatusOK) })) } func main() { // 启动一个用于测试的http server srv := NewServer() defer srv.Close() // 构造原始请求 task := func() (interface{}, error) { req, err := http.NewRequest(http.MethodHead, srv.URL, nil) if err != nil { panic(err) } return http.DefaultClient.Do(req) } // 请求回调处理函数 // backup request相当于并发的重试,只接收第一次的响应 // 非第一次返回的请求才会调用回调进行处理异常或回收资源等 taskErrCallback := func(err error) { fmt.Printf("rece err: %v\n", err) } // 构造retry group,为构造backup request做准备 // 返回的ctx也是构造backup request的参数 rg, ctx := retrygroup.NewRetryGroup(context.Background(), task, retrygroup.WithErrHandler(taskErrCallback)) var cnt int32 // 重试时间算法 event := request.WithEvent(retry.Fixed(time.Millisecond * 10)) // 重试准入算法 access := request.WithAccess(func(_ context.Context) bool { if atomic.LoadInt32(&cnt) > 5 { return false } atomic.AddInt32(&cnt, 1) return true }) // 构造backup request req, err := request.NewRequest(ctx, rg, event, access) if err != nil { panic(err) } // 发起请求,并发的重试,阻塞等待结果 resp, err := req.Do() if request.IsKilled(err) { fmt.Println("backup request is killed") } fmt.Printf("val is %v, err %v\n", resp, err) } ```

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077

515 次点击  
加入收藏 微博
2 回复  |  直到 2021-08-25 16:24:51
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传