Http Golang检测飞行中的请求

Http Golang检测飞行中的请求,http,caching,concurrency,go,request,Http,Caching,Concurrency,Go,Request,我想知道是否已经有一个图书馆可以做到这一点,或者可能有一个解决以下问题的建议: 客户端A请求资源A,这是一个长时间运行的请求,因为资源A很昂贵,并且会导致缓存丢失。同时客户端B请求资源A,现在仍然是缓存未命中,因为客户端A的请求尚未返回并填充缓存。因此,当客户端a的请求完成并填充了缓存时,客户端B应该阻塞并得到通知,而不是发出新的请求来生成资源a 我认为组缓存库具有类似的功能,但我无法浏览代码以了解它们是如何实现的,我也不想将实现与之绑定,并将其用作依赖项 到目前为止,我唯一的解决方案是发布子类

我想知道是否已经有一个图书馆可以做到这一点,或者可能有一个解决以下问题的建议:

客户端A请求资源A,这是一个长时间运行的请求,因为资源A很昂贵,并且会导致缓存丢失。同时客户端B请求资源A,现在仍然是缓存未命中,因为客户端A的请求尚未返回并填充缓存。因此,当客户端a的请求完成并填充了缓存时,客户端B应该阻塞并得到通知,而不是发出新的请求来生成资源a

我认为组缓存库具有类似的功能,但我无法浏览代码以了解它们是如何实现的,我也不想将实现与之绑定,并将其用作依赖项

到目前为止,我唯一的解决方案是发布子类型的东西,其中我们有一个当前飞行中请求的全局映射,其中reqID是一个键。当req1出现时,它在映射中设置其ID,req2出现并检查其ID是否在映射中,因为它请求的是与它相同的资源,所以我们在通知程序通道上阻塞。当req1完成时,它会做3件事:

  • 将其ID从地图中逐出
  • 将条目保存在缓存中
  • 将带有其ID的广播发送到通知程序频道 req2接收通知,取消阻止并从缓存中提取
  • 由于go没有内置对广播的支持,因此可能有1个用户正在收听广播频道,然后为每个请求保留一个要广播到的订阅者列表,或者我们将映射更改为reqId=>list(broadcastChannelSubscribers)。沿着这些路线的东西


    如果您认为使用Go的原语有更好的方法,任何输入都将不胜感激。这个解决方案中唯一困扰我的是这个全局地图,它被锁包围着,我想它很快就会成为一个瓶颈。如果你有一些非锁定的想法,即使它们是随机的,我也很高兴听到它们。

    这让我想起一个问题,有人在实施类似的事情:

    我给出了一个实现这种中间层的示例。我认为这与您的想法是一致的:有一个例行程序来跟踪对相同资源的请求,并防止它们并行重新计算

    如果您有一个单独的例程负责接收请求和管理对缓存的访问,那么您不需要显式锁(尽管在通道中隐藏了一个锁)。无论如何,我不知道您的应用程序的具体情况,但考虑到您需要检查缓存(可能是锁定的)和(偶尔)对丢失的条目执行昂贵的计算,地图锁定查找对我来说似乎不是一个大问题。如果您认为这会有所帮助,那么您也可以跨越更多这样的中间层例程,但是您需要一种确定的请求路由方式(因此每个缓存条目都由一个例程管理)


    很抱歉没有为您提供银弹解决方案,但听起来您已经找到了解决问题的好方法。

    缓存和性能问题总是很棘手,您应该始终制定一个基本的解决方案来进行基准测试,以确保您的假设是正确的。但是,如果我们知道瓶颈是获取资源,并且缓存将提供显著的回报,那么您可以使用Go的通道来实现排队。假设
    response
    是您的资源类型

    type request struct {
         back chan *response
    }
    
    func main() {
        c := make(chan request,10) // non-blocking
        go func(input chan request){
            var cached *response
            for _,i := range input {
                if cached == nil { // only make request once
                    cached = makeLongRunningRequest()
                }
                i.back <- cached
            }
        }(c)
    
        resp := make(chan *response)
    
        c <- request{resp} // cache miss
        c <- request{resp} // will get queued
        c <- request{resp} // will get queued
    
        for _,r := range resp {
            // do something with response
        }
    }
    
    使用互斥锁。同样,如果获取资源是瓶颈,那么锁定地图的成本应该可以忽略不计。如果锁定地图是一个问题,请考虑使用.< /P>
    总的来说,你似乎进展顺利。我建议尽量让你的设计尽可能简单,如果可能的话,用通道代替锁。它们确实可以防止严重的并发错误。

    一种解决方案是并发非阻塞缓存,如第9章的详细讨论

    这两个版本非常值得一看,因为作者带您浏览了几个版本(memo1、memo2等),说明了竞争条件的问题,使用互斥来保护地图,以及一个只使用通道的版本

    也考虑到它有类似的概念,并处理取消飞行请求。


    将内容复制到这个答案中是不切实际的,所以希望这些链接是有用的。

    这已经由Golang作为单程航班提供了

    对于您的用例,只需在单个航班上使用一些额外的逻辑即可。考虑下面的代码片段:

    func main() {
        http.HandleFunc("/github", func(w http.ResponseWriter, r *http.Request) {
            var key = "facebook"
            var requestGroup singleflight.Group
            // Search The Cache, if found in cache return from cache, else make single flight request
            if res, err := searchCache(); err != nil{
                return res
            } 
            // Cache Miss-> Make Single Flight Request, and Cache it
            v, err, shared := requestGroup.Do(key, func() (interface{}, error) {
                // companyStatus() returns string, error, which statifies interface{}, error, so we can return the result directly.
                if err != nil {
                    return interface{}, err
                }
                return companyStatus(), nil
            })
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            //Set the Cache Here
            setCache(key, v)
    
            status := v.(string)
    
            log.Printf("/Company handler requst: status %q, shared result %t", status, shared)
    
            fmt.Fprintf(w, "Company Status: %q", status)
        })
    
        http.ListenAndServe("127.0.0.1:8080", nil)
    }
    
    // companyStatus retrieves Comapny's API status
    func getCompanyStatus() (string, error) {
        log.Println("Making request to Some API")
        defer log.Println("Request to Some API Complete")
    
        time.Sleep(1 * time.Second)
    
        resp, err := http.Get("Get URL")
        if err != nil {
            return "", err
        }
        defer resp.Body.Close()
    
        if resp.StatusCode != 200 {
            return "", fmt.Errorf("Upstream response: %s", resp.Status)
        }
    
        r := struct{ Status string }{}
    
        err = json.NewDecoder(resp.Body).Decode(&r)
    
        return r.Status, err
    }
    
    

    我希望代码片段是不言自明的,您可以参考以深入研究单次飞行。

    我认为这正是我的建议,但是我认为您的方法:
    map[resourceId]chan request
    不起作用,因为每个resourceId需要多个通道,由于您可以在同一个resourceId上挂起多个未来请求,并且您不能通过一个频道向多个订户广播,因此每个后续请求都需要一个频道,更像:
    map[res\u id]->[slice[channels\u to\u notify]
    @Feras是的,我认为它可以工作(但我还没有测试它)。这种广播模式在围棋中并不常见,这不是广播。goroutine一次只知道一个请求。对于来自输入通道的每个
    请求
    ,它在输出通道上放置一个
    *响应
    。拥有一个输入通道是一项功能,因为当您同时发送多个输入时,它充当线程安全队列。使其无缓冲将阻止您想要的执行。在实际的应用程序中,您可能需要不同的输出通道,您可以像这样获得
    c,作为奖励,当您关闭输入通道时,goroutine将干净地退出,缓存的
    *响应将被垃圾收集。如果您只想在缓存中保留有限的时间,则此功能非常有用。所以大部分复杂性都保存在t中
    
    func main() {
        http.HandleFunc("/github", func(w http.ResponseWriter, r *http.Request) {
            var key = "facebook"
            var requestGroup singleflight.Group
            // Search The Cache, if found in cache return from cache, else make single flight request
            if res, err := searchCache(); err != nil{
                return res
            } 
            // Cache Miss-> Make Single Flight Request, and Cache it
            v, err, shared := requestGroup.Do(key, func() (interface{}, error) {
                // companyStatus() returns string, error, which statifies interface{}, error, so we can return the result directly.
                if err != nil {
                    return interface{}, err
                }
                return companyStatus(), nil
            })
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            //Set the Cache Here
            setCache(key, v)
    
            status := v.(string)
    
            log.Printf("/Company handler requst: status %q, shared result %t", status, shared)
    
            fmt.Fprintf(w, "Company Status: %q", status)
        })
    
        http.ListenAndServe("127.0.0.1:8080", nil)
    }
    
    // companyStatus retrieves Comapny's API status
    func getCompanyStatus() (string, error) {
        log.Println("Making request to Some API")
        defer log.Println("Request to Some API Complete")
    
        time.Sleep(1 * time.Second)
    
        resp, err := http.Get("Get URL")
        if err != nil {
            return "", err
        }
        defer resp.Body.Close()
    
        if resp.StatusCode != 200 {
            return "", fmt.Errorf("Upstream response: %s", resp.Status)
        }
    
        r := struct{ Status string }{}
    
        err = json.NewDecoder(resp.Body).Decode(&r)
    
        return r.Status, err
    }