登录
首页 >  Golang >  Go问答

单次执行 singleflight.Group.Do 并进行重试

来源:stackoverflow

时间:2024-02-16 13:00:25 169浏览 收藏

今日不肯埋头,明日何以抬头!每日一句努力自己的话哈哈~哈喽,今天我将给大家带来一篇《单次执行 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 的调用之间的等待,因此第二次调用实际上忘记了密钥。 但 doexpense 函数似乎只被调用一次。

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

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


正确答案


此处的问题是 forget 方法的计时和行为的组合。正如 documentation某种程度上所述:

forget 告诉 singleflight 忘记一个密钥。 将来调用此键的 do 将调用该函数,而不是等待先前的调用完成。

未来意味着对 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 的情况下,调用在进程的生命周期中只执行一次

本篇关于《单次执行 singleflight.Group.Do 并进行重试》的介绍就到此结束啦,但是学无止境,想要了解学习更多关于Golang的相关知识,请关注golang学习网公众号!

声明:本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>