我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Go开发-使用Goroutine如何控制HTTP请求的并发量

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Go开发-使用Goroutine如何控制HTTP请求的并发量

一、明确需求

我们使用 go 并发调用接口发起 HTTP 请求时,只需要在 func() 前面加上 go 关键字就很容易完成了,就是因为让并发变得如此简单,所以有的时候我们就需要控制一下并发请求的数量。

现在有个需求:本地有一千万条手机号,需要调用聚合数据 手机号码归属地 接口,并记录省份、城市、区号、邮编、运营商等查询结果数据。

Tips: 测试的接口为聚合数据免费的: 手机号码归属地 接口,如果需要测试下面的用例,建议先去注册申请接口即可。而且聚合数据还提供很多免费的接口供开发者调试使用。

c02d6e1ad7f36ecd9381e2967c500c27.jpg
我们先模拟不加控制协程数量导致的宕机问题,新建一个 warning.go 内容如下:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
)

func main() {
    // 接口请求URL
    apiUrl := "http://localhost" // 不要使用接口地址测试
    max := 1<<63 - 1             // 最大整数,模拟大量数据

    // 初始化参数
    param := url.Values{}
    // 配置请求参数,方法内部已处理urlencode问题,中文参数可以直接传参
    param.Set("a", "test") // 需要查询的手机号码或手机号码前7位

    for i := 0; i < max; i++ {
        go func(i int) {
            // 一些逻辑代码...
            fmt.Printf("start func: %d\n", i)
            // 发送请求
            data, err := Get(apiUrl, param)
            if err != nil {
                fmt.Println(err)
                return
            }
            fmt.Println(string(data))
        }(i)
    }
}

// Get 方式发起网络请求
func Get(apiURL string, params url.Values) (rs []byte, err error) {
    var Url *url.URL
    Url, err = url.Parse(apiURL)
    if err != nil {
        fmt.Printf("解析url错误:\r\n%v", err)
        return nil, err
    }
    //如果参数中有中文参数,这个方法会进行URLEncode
    Url.RawQuery = params.Encode()
    resp, err := http.Get(Url.String())
    if err != nil {
        fmt.Println("err:", err)
        return nil, err
    }
    defer resp.Body.Close()
    return ioutil.ReadAll(resp.Body)
}

高能命令:!!!不要执行!!!

go run waning.go

打开瞬间 top 命令的截图如下,可以看到 CPU 瞬间起来:

WX202010142054032x.png

由于没有限制 goroutine 数量,如果我们把全部任务都放到并发 Goroutine 中去执行,虽然效率比较高。但当不加控制的 goroutine 疯狂创建时候,服务器系统资源使用率飙升宕机,直到进程被自动 kill 不然无法提供任何其它服务。

二、用带缓冲区的 channel 控制并发量

上面的案例是我们不加控制 goroutine 数量限制从而导致宕机的,因此只要我们控制了 goroutine 数量就能避免这种问题!

下面是使用 sync.WaitGroup{} 控制并发量的代码,新建一个 main.go 内容如下:

package main

import (
    "fmt"
    "io/ioutil"
    "math/rand"
    "net/http"
    "net/url"
    "sync"
    "time"
)

type Limit struct {
    number  int
    channel chan struct{}
}

// Limit struct 初始化
func New(number int) *Limit {
    return &Limit{
        number:  number,
        channel: make(chan struct{}, number),
    }
}

// Run 方法:创建有限的 go f 函数的 goroutine
func (limit *Limit) Run(f func()) {
    limit.channel <- struct{}{}
    go func() {
        f()
        <-limit.channel
    }()
}

// WaitGroup 对象内部有一个计数器,从0开始
// 有三个方法:Add(), Done(), Wait() 用来控制计数器的数量
var wg = sync.WaitGroup{}

const (
    concurrency = 5 // 控制并发量
)

func main() {
    start := time.Now()
    limit := New(concurrency) // New Limit 控制并发量
    // 接口请求URL
    apiUrl := "http://apis.juhe.cn/mobile/get" // 不要使用接口地址测试
    //max := int(math.Pow10(8))                  // 模拟一千万数据
    max := 5                                    // 先测试5次吧

    // 初始化参数
    param := url.Values{}
    param.Set("key", "您申请的KEY") // 接口请求Key

    for i := 0; i < max; i++ {
        wg.Add(1)
        value := i
        goFunc := func() {
            fmt.Printf("start func: %d\n", value)
            // 配置请求参数,方法内部已处理urlencode问题,中文参数可以直接传参
            phone := RandMobile()
            param.Set("phone", phone) // 需要查询的手机号码或手机号码前7位
            // 发送请求
            data, err := Get(apiUrl, param)
            if err != nil {
                fmt.Println(err)
                return
            }
            // 其它逻辑代码...
            fmt.Println("phone: ", phone, string(data))
            wg.Done()
        }
        limit.Run(goFunc)
    }

    // 阻塞代码防止退出
    wg.Wait()

    fmt.Printf("耗时: %fs", time.Now().Sub(start).Seconds())
}

// Get 方式发起网络请求
func Get(apiURL string, params url.Values) (rs []byte, err error) {
    var Url *url.URL
    Url, err = url.Parse(apiURL)
    if err != nil {
        fmt.Printf("解析url错误:\r\n%v", err)
        return nil, err
    }
    //如果参数中有中文参数,这个方法会进行URLEncode
    Url.RawQuery = params.Encode()
    resp, err := http.Get(Url.String())
    if err != nil {
        fmt.Println("err:", err)
        return nil, err
    }
    defer resp.Body.Close()
    return ioutil.ReadAll(resp.Body)
}

var MobilePrefix = [...]string{"130", "131", "132", "133", "134", "135", "136", "137", "138", "139", "145", "147", "150", "151", "152", "153", "155", "156", "157", "158", "159", "170", "176", "177", "178", "180", "181", "182", "183", "184", "185", "186", "187", "188", "189"}

// GeneratorPhone 生成手机号码
func RandMobile() string {
    return MobilePrefix[RandInt(0, len(MobilePrefix))] + fmt.Sprintf("%0*d", 8, RandInt(0, 100000000))
}

// 指定范围随机 int
func RandInt(min, max int) int {
    rand.Seed(time.Now().UnixNano())
    return min + rand.Intn(max-min)
}

这时候我们执行 go run main.go ,然后执行结果如下:

gosafe.png

使用 go 带缓冲区的通道来控制 goroutine 的并发数量更加简单实用, 而且我们还可以把 New()Run() 方法封装起来外部项目直接引入使用。

三、扩展思考

下面有两个思考问题,大家可以尝试着去思考一下。

思考1:为什么我们要使用 sync.WaitGroup

如果我们不使用 sync.WaitGroup 阻塞主进程的话,当主程序执行结束后,子协程未执行也会被终止掉的。因此剩余的 goroutine 没来及执行,程序就已经结束了。

思考2:代码中 channel 数据结构为什么定义 struct,而不定义成 bool 类型呢?

因为空结构体变量的内存占用大小为 0,而 bool 类型内存占用大小为 1,这样可以最大化利用服务器的内存空间。

func main(){
  a :=struct{}{}
  b := true
  fmt.Println(unsafe.Sizeof(a))  // 0
  fmt.Println(unsafe.Sizeof(b))  // 1
}

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Go开发-使用Goroutine如何控制HTTP请求的并发量

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何使用jMeter构造大量并发的随机HTTP请求

这篇文章主要讲解了“如何使用jMeter构造大量并发的随机HTTP请求”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何使用jMeter构造大量并发的随机HTTP请求”吧!在前一篇文章使用j
2023-06-02

如何处理Go语言中的并发网络请求的流量控制问题?

如何处理Go语言中的并发网络请求的流量控制问题?在现代的网络应用中,对于高并发的网络请求,流量控制是非常重要的。合理地控制网络请求的并发数量可以保证系统的性能和稳定性,避免出现过载的情况。在Go语言中,我们可以利用并发编程的特性来实现对网络
2023-10-22

如何处理Go语言中的并发网络请求的流量控制问题

在Go语言中处理并发网络请求的流量控制问题,可以使用以下方法:1. 使用Go的goroutine和channel机制:通过创建goroutine来处理并发请求,并使用channel来控制并发量。可以创建一个有固定大小的channel,每个请
2023-10-09

在Go语言中如何解决并发网络请求的请求流量控制和限流问题

在Go语言中,可以使用一些库或模式来解决并发网络请求的请求流量控制和限流问题。以下是一些常见的解决方案:1. 使用goroutine池:可以创建一个固定大小的goroutine池,限制同时进行的最大并发请求数量。可以使用`sync.Wait
2023-10-09

在Go语言中如何解决并发网络请求的请求流量控制和限流问题?

在Go语言中如何解决并发网络请求的请求流量控制和限流问题?在现代的网络应用中,大量的并发网络请求是非常常见的情况。对于服务器来说,如果无法有效地控制和限制这些请求的流量,可能会导致服务器负载过高,甚至崩溃。因此,在Go语言中如何解决并发网络
2023-10-22

在Go语言中如何解决并发网络请求的请求限速和流量控制问题?

在Go语言中如何解决并发网络请求的请求限速和流量控制问题?Go语言是一门非常适合进行并发编程的语言,它提供了丰富的并发原语和工具,可以方便地实现请求限速和流量控制。本文将介绍如何使用Go语言来解决并发网络请求的请求限速和流量控制问题,并提供
2023-10-22

Go语言如何使用标准库发起HTTP请求

本篇内容介绍了“Go语言如何使用标准库发起HTTP请求”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!HTTP请求的基本结构在发起HTTP请求
2023-07-05

PHP开发中如何处理请求限流和流量控制

随着互联网用户数量的不断增加,网站和应用程序面临的流量压力也越来越大。为了保护服务器的稳定性和性能,我们需要对请求进行限流和流量控制。本文将介绍PHP开发中如何处理请求限流和流量控制,并提供具体的代码示例。一、请求限流请求限流是指对访问频率
2023-10-21

如何使用jMeter构造大量并发HTTP请求进行微服务性能测试

这篇文章将为大家详细讲解有关如何使用jMeter构造大量并发HTTP请求进行微服务性能测试,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。比如我开发好了一个微服务,想测试其在大并发请求下的性能表现如何。比较
2023-06-02

在Go语言中如何解决并发网络请求的请求合并和批量处理问题?

在Go语言中如何解决并发网络请求的请求合并和批量处理问题?在现代互联网应用中,网络请求已经成为了不可或缺的一部分,而对于高并发的情况下,如何有效地管理和处理大量的网络请求成了一个亟待解决的问题。为了提高请求的效率和减少网络开销,我们常常需要
2023-10-22

Go语言的高并发场景中如何使用WaitGroup进行并行控制?

在 go 语言中,使用 waitgroup 进行并行控制的步骤如下:初始化一个 waitgroup 实例。使用 add 方法添加要等待的 goroutine 数量。等待所有 goroutine 完成后,使用 wait 方法阻塞当前 goro
Go语言的高并发场景中如何使用WaitGroup进行并行控制?
2024-05-10

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录