我试着用单机缓存请求,这是开箱即用的。
我想再做一次,然后重试失败(错误的请求)以获得相同的密钥。为此,我打电话给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函数似乎只被调用过一次。
在这里可以找到我的代码的复制。
发布于 2022-09-05 12:07:45
这里的问题是Forget方法的时间和行为的结合。正如文档中所指出的那样
忘记告诉单飞忘记一把钥匙。以后对该键的调用将调用该函数,而不是等待更早的调用完成。
未来意味着对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的情况下,调用在进程的生命周期中精确执行一次。
https://stackoverflow.com/questions/73605910
复制相似问题