为了账号安全,请及时绑定邮箱和手机立即绑定

获取 singleflight.Group.Do 重试

获取 singleflight.Group.Do 重试

Go
撒科打诨 2023-02-06 19:39:12
我正在尝试使用开箱即用的 singleflight 来缓存请求。我想更进一步,随后对同一密钥重试失败(错误请求)。为此,我正在调用 group.Forget(Key)。但是后续调用似乎只是重用先前的结果而不是重试。type Result struct {    v int    k string}var group singleflight.Group// see https://encore.dev/blog/advanced-go-concurrencyfunc 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
查看完整描述

1 回答

?
守候你守候我

TA贡献1802条经验 获得超10个赞

这里的问题是时间和Forget方法行为的结合。正如文档中所述:


Forget 告诉 singleflight 忘记一把钥匙。未来为此键调用 Do 将调用该函数,而不是等待较早的调用完成。


那里的Future意味着所有对 的调用都group.Do发生在对 的调用之后group.Forget。在您的示例中,对的所有调用都group.Do发生在调用之前group.Forget,并且所有调用都获得了第一个失败调用的结果。可能的方法是在 `group.Do 调用之外进行触发重试。是这样的:


package main


import (

    "context"

    "errors"

    "log"

    "math/rand"

    "sync/atomic"

    "time"


    "golang.org/x/sync/singleflight"

)


type Result struct {

    v int

    k string

}


var group singleflight.Group


func main() {

    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")


    for {

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

            return doExpensive(ctx, key)

        })


        if err != nil {

            log.Printf("Normal call error: %s. Will retry \n", err)

            continue

        }


        log.Printf("Normal call results: %v [shared=%v]\n", results, shared)


        return results.(*Result), err

    }

}


var returnedFirstErr atomic.Bool


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

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

    defer func() {

        log.Printf("Result of Expensive function: [%v, %s] for %s\n", r, e, key)

    }()

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


    dice := rand.Int31n(10)


    if !returnedFirstErr.Load() {

        returnedFirstErr.Store(true)

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

    }


    return &Result{

        v: int(dice),

        k: key,

    }, nil

}

旁问。您确定 的行为singleflight是您所需要的,也许您应该改用sync.Once?如果singleflight您阻止多个调用同时发生,即仍然会执行稍后完成的调用。如果sync.Once调用在进程的生命周期内恰好完成一次


查看完整回答
反对 回复 2023-02-06
  • 1 回答
  • 0 关注
  • 98 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信