专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Go实现API并发请求的案例

场景:

有N 个并发请求来访问Api1时 ,如果数据库或者web服务器没有对请求做限制,那么所有请求都会访问一次数据库,很可能造成数据库压力比较大,而且 HTTP访问也比较耗时。

实现:

有N 个并发请求来访问Api1时, 只有一个请求可以访问到数据库,其他请求共享一个请求的结果。

安排:

1. 定义一个请求组,来存储所有的请求

type RequestGroup struct {
    mu sync.Mutex
    m  map[string]*Result  // 请求类型=>请求结果
}

我们使用使用Result类型来存储请求结果,mu 对请求的管理。(此处如果不清楚如何使用,后续详细讲解)

2. 定义一个请求结果的类型

type Result struct {
    wg  sync.WaitGroup
    val interface{}
    err error
}

好了,那么我们如何处理并发时来的请求呢? 所有请求的结果该如何处理呢?

首先,我们应该在有请求时,开始拦截验证是否同时有相同的请求访问,如果有,阻塞,直到第一个访问数据库的请求结束,所有请求获取到结果后结束。

代码演示:

func (g *RequestGroup ) Do (key string, getDataFunc func()(interface{}, error)) (interface{}, error) {
    g.mu.Lock() // 【1】
    if g.m == nil {
        g.m = make(map[string]*call)
    }
    if c, ok := g.m[key]; ok {
        g.mu.Unlock()
        c.wg.Wait() // 【4】
        return c.val, c.err
    }

    c := new(Result)
    c.wg.Add(1)
    g.m[key] = c
    // 首个请求类型已经存储
    g.mu.Unlock() // 【2】
    c.val, c.err = getDataFunc()
    c.wg.Done() // 【3】

    g.mu.Lock()
    delete(g.m, key) // 【5】每次完成将某个请求的标识删除
    g.mu.Unlock()
    return c.val, c.err
}
  • 假设N个并发请求,请求A 进入Do方法后,N-1个请求会被阻塞在【1】处,当A 将请求类型存储时,释放请求锁,然后N-1个请求依次(通过Lock =》 Unlock)进入,同时阻塞在【4】位置。等待【3】释放,整个正常进入最终获取结果阶段。
  • 程序最后要删除请求类型标识,否则下次请求进入还是缓存的数据。

验证:

模仿并发请求

func main() {
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(i int) {
            val, err := G.Do(i, "key", func() (interface{}, error) {
                time.Sleep(3 * time.Second) // 模仿数据库请求
                ff++
                return ff, nil
            })

            if err == nil {
                fmt.Println(i, "获取结果...", val)
            }
            wg.Done()
        }(i)
    }

    wg.Wait()

    fmt.Println("全部请求结束。。。。")
}

由于代码比较乱,为了方便演示,加入了一些标识在程序中,

完整代码如下:

package main

import (
    "fmt"
    "sync"
    "time"
)

type Result struct {
    wg  sync.WaitGroup
    val interface{}
    err error
}

type RequestGroup struct {
    mu sync.Mutex
    m  map[string]*Result
}

var G = &RequestGroup{
    m: make(map[string]*Result),
}
var ff = 0
var wg sync.WaitGroup

func main() {
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(i int) {
            val, err := G.Do(i, "key", func() (interface{}, error) {
                time.Sleep(3 * time.Second) // 模仿数据库请求
                ff++
                return ff, nil
            })

            if err == nil {
                fmt.Println(i, "获取结果...", val)
            }
            wg.Done()
        }(i)
    }

    wg.Wait()

    fmt.Println("全部请求结束。。。。")
}

// 并发时,将相同key的请求wait等待第一个请求获取结果
/**
非并发期间:key 请求 req1 -> 内存中没有key-> 创建key=>val -> 释放key=>val
并发期间: key 请求 req1 -> 内存中没有key-> 创建key=>val -> 释放key=>val

*/
func (g *RequestGroup) Do(idx int, key string, getDataFunc func() (interface{}, error)) (interface{}, error) {
    fmt.Println(idx, "阻塞")

    g.mu.Lock() // 【1】

    time.Sleep(2 * time.Second)
    fmt.Println(idx, "..进入了")

    if g.m == nil {
        g.m = make(map[string]*Result)
    }
    if c, ok := g.m[key]; ok {
        g.mu.Unlock()
        fmt.Println(idx, "......我阻塞再次.")
        c.wg.Wait() // 【4】
        return c.val, c.err
    }

    c := new(Result)
    c.wg.Add(1)
    g.m[key] = c

    // 首个请求类型存储
    time.Sleep(3 * time.Second)
    fmt.Println(idx, "....释放锁")
    g.mu.Unlock() // 【2】

    // 结果获取
    c.val, c.err = getDataFunc()
    time.Sleep(15 * time.Second)
    fmt.Println(idx, "..........释放锁2")
    c.wg.Done() // 【3】

    // 请求标识清理
    g.mu.Lock()
    delete(g.m, key) // 【5】每次完成将某个请求的标识删除
    g.mu.Unlock()
    return c.val, c.err
}

可以说很细致,明确的演示了整个程序的运行过程,如果觉得乱,可将原始代码删除。

文章永久链接:https://tech.souyunku.com/18976

未经允许不得转载:搜云库技术团队 » Go实现API并发请求的案例

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们