首页 > 其他分享 >Singleflight(合并请求)

Singleflight(合并请求)

时间:2024-05-17 18:40:59浏览次数:19  
标签:return 请求 err nil 合并 call key Singleflight func

简介

看到一个有意思的库:

SingleFlight是Go语言提供的一个扩展包。作用是当有多个goroutine同时调用同一个函数的时候,只允许一个goroutine去调用这个函数,等到这个调用的goroutine返回结果的时候,再把结果返回给这几个同时调用的goroutine,这样可以减少并发调用的数量。

Singleflight是以阻塞读的方式来控制向下游请求的并发量的,在第一个goroutine请求没有返回前,所有的请求都将被阻塞。极端情况下可能导致我们的程序hang住,由于连锁反应甚至导致我们整个系统挂掉。

可以传递ctx,制定超时时间,避免线程的长时间阻塞。

假设第一个线程超时了,后续只要有结果还是可以正确返回值的。

 

实现原理

Singleflight使用互斥锁Muext和Map来实现,Mutext用来保证并发时的读写安全,Map用来保存同一个key的正在处理(in flight)的请求。

type Group struct {
  mu sync.Mutex       // protects m
  m  map[string]*call // lazily initialized
}

同时,Singleflight定义了一个call对象,call代表了正在执行的fn函数的请求或者是已经执行完成的请求。

// call is an in-flight or completed singleflight.Do call
type call struct {
  wg sync.WaitGroup

  // These fields are written once before the WaitGroup is done
  // and are only read after the WaitGroup is done.
  val interface{}
  err error

  // These fields are read and written with the singleflight
  // mutex held before the WaitGroup is done, and are read but
  // not written after the WaitGroup is done.
  dups  int
  chans []chan<- Result
}

主要看下Do方法,DoChan方法和Do方法类似

func (g *Group) Do(key string, fn func() (interface{}, error)) (v interface{}, err error, shared bool) {
  g.mu.Lock()
  if g.m == nil {
    g.m = make(map[string]*call)
  }
  if c, ok := g.m[key]; ok { // 存在相同的key
    c.dups++
    g.mu.Unlock()
    c.wg.Wait() // 等待这个key的第一个调用完成

    // ...
    
    return c.val, c.err, true // 复用第一个key的请求结果
  }
  c := new(call) // 第一个调用创建一个call
  c.wg.Add(1)
  g.m[key] = c
  g.mu.Unlock()

  g.doCall(c, key, fn) // 调用方法
  return c.val, c.err, c.dups > 0
}
// doCall方法会实际的执行函数fn
func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
  defer func() {
    // ...
    
    g.mu.Lock()
    defer g.mu.Unlock()
    c.wg.Done() // 阻塞等待完成
    if g.m[key] == c {
      delete(g.m, key) // 调用完成删除这个key
    }
    
    // ...
  }()

  func() {
    // ...
    
    c.val, c.err = fn() // 真正调用,结果赋值给call
    
    // ...
  }()
}

应用场景

在缓存击穿的时候有用,假设大量的请求进来缓存miss,请求击穿到数据库

有的数据当时是热点,可能很快就变成了冷数据。针对类似的场景是不太合适设置不过期的,这个时候的Singleflight就派上用场了,可以避免大量的请求击穿到数据库,从而保护我们的服务稳定。

伪代码实现:

func getDataSingleFlight(key string) (interface{}, error) {
  v, err, _ := g.Do(key, func() (interface{}, error) {
    // 查缓存
    data, err := getDataFromCache(key)
    if err == nil {
      return data, nil
    }
    if errors.Is(err, ErrNotFound) {
      // 查DB
      data, err := getDataFromDB(key)
      if err == nil {
        setCache(data) // 设置缓存
        return data, nil
      }
      return nil, err
    }
    return nil, err // 缓存出错直接返回,防止穿透到DB
  })
  if err != nil {
    return nil, err
  }
  
  return v, nil
}

 

标签:return,请求,err,nil,合并,call,key,Singleflight,func
From: https://www.cnblogs.com/twh233/p/18198397

相关文章

  • springboot2 - 请求相关的兼容配置
    StandardServletMultipartResolverStandardServletMultipartResolver在spring4和spring5代码是不一样的。在低版本spring环境下,文件只能通过POST请求提交。对程序的影响可能不大,因为现在的做法,基本形成统一的定式:文件表单和业务表单分离,先将文件上传,返回一段url,再将......
  • aiohttp初识(请求&响应)
    aiohttp初识(请求&响应)  aiohttp(用于asyncio和Python的异步HTTP客户端/服务器)初识1|0aiohttp客户端使用用于asyncio和Python的异步HTTP客户端/服务器:AsynchronousHTTPClient/ServerforasyncioandPython.1|1发起请求让我们从导入aiohttp模块开始:importai......
  • 源服务器开启gzip,CDN上没开启,CDN节点请求是以gzip大小算还是按原来的大小算
    今天学到一个知识:当源服务器开启了gzip压缩,而CDN上没有开启gzip时,CDN节点请求的流量计算通常是基于未压缩的原始文件大小来计算的。这是因为CDN通常是根据其接收到的内容来计算流量,而不会主动去解压缩已压缩的内容来计算。具体来说,当CDN节点从源服务器获取内容时,如果源服务器返......
  • url并发请求
    functionbatchRequest(urls,maxNum){returnnewPromise(resolve=>{if(urls.length===0){resolve([]);return;}constresults=[];letindex=0;letfinishCount=0;asyncfunction......
  • drf之请求和响应
    drf之请求和响应一、drf之请求【1】源码分析#Request类的对象fromrest_framework.requestimportRequest#1新的request#2request.data前端传入的请求体中得数据,无论那种编码#3用起来跟之前一样#4老的request在request._request【2】配置视图类能处理的编......
  • 03请求数据封装request、版本管理
    请求数据封装request、版本管理一、请求数据再封装以前我们通过django开发项目时,视图中的request是django.core.handlers.wsgi.WSGIRequest类的对象,其中包含了请求相关的所有数据。而在使用drf框架时,视图中的request是rest_framework.request.Request类的对象,其是又对dja......
  • ASP.NET Core的全局拦截器(在页面回发时,如果判断当前请求不合法,不执行OnPost处理器)
    ASP.NETCoreRazorPages中,我们可以在页面模型基类中重载OnPageHandlerExecuting方法。下面的例子中,BaseModel继承自PageModel,是所有页面模型的基类。推荐方案:在BaseModel.cs中,重载OnPageHandlerExecuting方法(看下面代码中的注释):publicoverridevoidOnPageHandlerExecuting......
  • 用javax.ws.rs.client.Invocation queryParam 执行url中带参数的请求
    来自于百度AI,为了实际需要,改成我自己的环境。importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;importorg.apache.commons.lang3.StringUtils;importorg.glassfish.jersey.client.ClientConfig;importorg.glassfish.jersey.client......
  • Libz打包/合并DLL与可执行文件(EXE)
    Libz是一个.NET的库,它提供了一种方法将多个DLLs和其他资源打包进一个单独的可执行文件(EXE)或另一个动态链接库(DLL)。Libz的功能类似于ILMerge,但它提供了更多的灵活性和功能,特别是对于处理压缩和资源管理。Libz使用了自定义的加载器来在运行时解压和加载程序集和资源,这样可以减少部署......
  • 解决ajax请求参数过多导致参数被截断的问题
    最近发现了个问题:ajaxpost请求查询参数数量动态变化有200-250000个,当参数超过一定数量N时,post传到后台接的参数就只有N个,多出的参数都没附到请求中,这也是奇怪的事情,浏览器对参数的个数有限制。jsconstpayload={date:"2024-05-10",sn:[]};for(leti=1;i<1000......