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

Saner-Lee · 2021-08-25 16:02:44 · 1822 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2021-08-25 16:02:44 的主题,其中的信息可能已经有所发展或是发生改变。

backup request

传送门:github,创作不易,希望各位看官老爷能给个star,要是能fork过去一起创作就更好了。

what

go语言实现的backup request基础库。

why

backup request其实主要是为了解决长尾请求,见名知意,长尾请求就是说处理时间比较长的请求,它的具体场景如下:

客户端超时配置不合理,给了一个比较大的值,比如5s。

请求发到服务端后,服务端因为资源问题或者其它问题,请求被阻塞无法返回。

结果:5s后客户端超时收到错误,发起重试。

从上面的例子可以看到,传统的重试也可以解决问题,但是从客户端来说这个请求的时延会比较大。

当后端阻塞后其实可以有两种情况:

  1. 一部分后端实例资源不足
  2. 全部后端实例资源不足

对于第2种情况来说,普通重试和backup request都无法解决,但是都有一些正向收益和负向收益,对于backup request

  • 正向收益:资源在过载边缘,收到重试请求后一小段时间有资源释放,可以立即处理
  • 负向收益:资源严重过载,收到重试请求只能使得后端过载更严重

但是正常情况下,资源都是充足的,而且现在的线上服务大多都会有限流,熔断等措施,所以总的来说,通过对资源的消耗来保证低延时,它的正向收益远大于负向收益。

虽然backup request需求很简单,但是还是有很多细节问题有待考虑,包括模块解耦,接口设计,防御编程等。

为了避免你重复撸轮子,所以有了这个库,它拥有的能力包括:

  • 重试时机算法
    • 固定时长
    • 二进制退避抖动
  • 重试准入
  • "非法"请求错误回调

放心大胆的使用它,如果有什么需求可以直接issue搞起,长期维护。

how

example中附带了一个小例子,为了方便阅读,内容直接copy到下面了。

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群:692541889

1822 次点击  
加入收藏 微博
2 回复  |  直到 2021-08-25 16:24:51
polaris
polaris · #1 · 4年之前

支持!

Saner-Lee
Saner-Lee · #2 · 4年之前

哇,感谢大佬!

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传