获取 singleflight.Group.Do 重试

我正在尝试使用开箱即用的 singleflight 来缓存请求。


我想更进一步,随后对同一密钥重试失败(错误请求)。为此,我正在调用 group.Forget(Key)。但是后续调用似乎只是重用先前的结果而不是重试。


type Result struct {

    v int

    k string

}


var group singleflight.Group


// see https://encore.dev/blog/advanced-go-concurrency

func main() {


    if true {

        for k := 0; k <= 2; k++ {

            go doGroup(context.Background(), "sameKey")

        }


        <-time.Tick(5 * time.Second)


        for k := 0; k <= 3; k++ {

            go doGroup(context.Background(), "sameKey")

        }


        <-time.Tick(30 * time.Second)

    }


}

func doGroup(ctx context.Context, key string) (*Result, error) {


    log.Println("Inside normal call")


    results, err, shared := group.Do(key, func() (interface{}, error) {

        r, e := doExpensive(ctx, key)


        // Do this; so if it encountered an error;

        // subsequent calls will retry

        // didnt work

        // perhaps because of timing

        if e != nil {

            group.Forget(key)

        }


        return r, e

    })


    fmt.Printf("Call to multiple callers: %v\n", shared)

    // does not retry if error occured


    if err != nil {

        wrapped := fmt.Errorf("error bruh %s: %w", key, err)

        fmt.Printf("%s\n", wrapped.Error())

        return nil, wrapped

    }


    fmt.Printf("Results: %v\n", results)


    return results.(*Result), err

}


func doExpensive(ctx context.Context, key string) (*Result, error) {

    log.Printf("Inside Expensive function with key %s\n", key)

    <-time.Tick(time.Second * 10)


    dice := rand.Int31n(10)


    if true {

        // <-time.Tick(time.Millisecond * time.Duration(dice*100))

        return nil, errors.New("operation failed")

    }


    <-time.Tick(time.Second * time.Duration(dice))

    return &Result{

        v: int(dice),

        k: key,

    }, nil

}


我已经模拟了调用 doGroup 之间的等待,所以第二次调用实际上忘记了密钥。但是 doExpensive 函数似乎只被调用过一次。


可以在此处找到我的代码的复制品


https://go.dev/play/p/psGjFTypU6C


撒科打诨
浏览 99回答 1
1回答

守候你守候我

这里的问题是时间和Forget方法行为的结合。正如文档中所述:Forget 告诉 singleflight 忘记一把钥匙。未来为此键调用 Do 将调用该函数,而不是等待较早的调用完成。那里的Future意味着所有对 的调用都group.Do发生在对 的调用之后group.Forget。在您的示例中,对的所有调用都group.Do发生在调用之前group.Forget,并且所有调用都获得了第一个失败调用的结果。可能的方法是在 `group.Do 调用之外进行触发重试。是这样的:package mainimport (&nbsp; &nbsp; "context"&nbsp; &nbsp; "errors"&nbsp; &nbsp; "log"&nbsp; &nbsp; "math/rand"&nbsp; &nbsp; "sync/atomic"&nbsp; &nbsp; "time"&nbsp; &nbsp; "golang.org/x/sync/singleflight")type Result struct {&nbsp; &nbsp; v int&nbsp; &nbsp; k string}var group singleflight.Groupfunc main() {&nbsp; &nbsp; for k := 0; k <= 2; k++ {&nbsp; &nbsp; &nbsp; &nbsp; go doGroup(context.Background(), "sameKey")&nbsp; &nbsp; }&nbsp; &nbsp; <-time.Tick(5 * time.Second)&nbsp; &nbsp; for k := 0; k <= 3; k++ {&nbsp; &nbsp; &nbsp; &nbsp; go doGroup(context.Background(), "sameKey")&nbsp; &nbsp; }&nbsp; &nbsp; <-time.Tick(30 * time.Second)}func doGroup(ctx context.Context, key string) (*Result, error) {&nbsp; &nbsp; log.Println("Inside normal call")&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; results, err, shared := group.Do(key, func() (interface{}, error) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return doExpensive(ctx, key)&nbsp; &nbsp; &nbsp; &nbsp; })&nbsp; &nbsp; &nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log.Printf("Normal call error: %s. Will retry \n", err)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; continue&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; log.Printf("Normal call results: %v [shared=%v]\n", results, shared)&nbsp; &nbsp; &nbsp; &nbsp; return results.(*Result), err&nbsp; &nbsp; }}var returnedFirstErr atomic.Boolfunc doExpensive(ctx context.Context, key string) (r *Result, e error) {&nbsp; &nbsp; log.Printf("Inside Expensive function with key %s\n", key)&nbsp; &nbsp; defer func() {&nbsp; &nbsp; &nbsp; &nbsp; log.Printf("Result of Expensive function: [%v, %s] for %s\n", r, e, key)&nbsp; &nbsp; }()&nbsp; &nbsp; <-time.Tick(time.Second * 10)&nbsp; &nbsp; dice := rand.Int31n(10)&nbsp; &nbsp; if !returnedFirstErr.Load() {&nbsp; &nbsp; &nbsp; &nbsp; returnedFirstErr.Store(true)&nbsp; &nbsp; &nbsp; &nbsp; return nil, errors.New("operation failed")&nbsp; &nbsp; }&nbsp; &nbsp; return &Result{&nbsp; &nbsp; &nbsp; &nbsp; v: int(dice),&nbsp; &nbsp; &nbsp; &nbsp; k: key,&nbsp; &nbsp; }, nil}旁问。您确定 的行为singleflight是您所需要的,也许您应该改用sync.Once?如果singleflight您阻止多个调用同时发生,即仍然会执行稍后完成的调用。如果sync.Once调用在进程的生命周期内恰好完成一次
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go