分享
  1. 首页
  2. 文章

golang并发编程的两种限速方法

jesse_joygenio · · 2605 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

引子

golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU、内存、带宽等),我们就需要对程序限速,以防止goroutine将资源耗尽。
以下面伪代码为例,看看goroutine如何拖垮一台DB。假设userList长度为10000,先从数据库中查询userList中的user是否在数据库中存在,存在则忽略,不存在则创建。

//不使用goroutine,程序运行时间长,但数据库压力不大
for _,v:=range userList {
 user:=db.user.Get(v.ID)
 if user==nil {
 newUser:=user{ID:v.ID,UserName:v.UserName}
 db.user.Insert(newUser)
 }
}
//使用goroutine,程序运行时间短,但数据库可能被拖垮
for _,v:=range userList {
 u:=v
 go func(){
 user:=db.user.Get(u.ID)
 if user==nil {
 newUser:=user{ID:u.ID,UserName:u.UserName}
 db.user.Insert(newUser)
 }
 }()
}
select{}

在示例中,DB在1秒内接收10000次读操作,最大还会接受10000次写操作,普通的DB服务器很难支撑。针对DB,可以在连接池上做手脚,控制访问DB的速度,这里我们讨论两种通用的方法。

方案一

在限速时,一种方案是丢弃请求,即请求速度太快时,对后进入的请求直接抛弃。

实现

实现逻辑如下:

package main
import (
 "sync"
 "time"
)
//LimitRate 限速
type LimitRate struct {
 rate int
 begin time.Time
 count int
 lock sync.Mutex
}
//Limit Limit
func (l *LimitRate) Limit() bool {
 result := true
 l.lock.Lock()
 //达到每秒速率限制数量,检测记数时间是否大于1秒
 //大于则速率在允许范围内,开始重新记数,返回true
 //小于,则返回false,记数不变
 if l.count == l.rate {
 if time.Now().Sub(l.begin) >= time.Second {
 //速度允许范围内,开始重新记数
 l.begin = time.Now()
 l.count = 0
 } else {
 result = false
 }
 } else {
 //没有达到速率限制数量,记数加1
 l.count++
 }
 l.lock.Unlock()
 return result
}
//SetRate 设置每秒允许的请求数
func (l *LimitRate) SetRate(r int) {
 l.rate = r
 l.begin = time.Now()
}
//GetRate 获取每秒允许的请求数
func (l *LimitRate) GetRate() int {
 return l.rate
}

测试

下面是测试代码:

package main
import (
 "fmt"
)
func main() {
 var wg sync.WaitGroup
 var lr LimitRate
 lr.SetRate(3)
 
 for i:=0;i<10;i++{
 wg.Add(1)
 go func(){
 if lr.Limit() {
 fmt.Println("Got it!")//显示3次Got it!
 } 
 wg.Done()
 }()
 }
 wg.Wait()
}

运行结果

Got it!
Got it!
Got it!

只显示3次Got it!,说明另外7次Limit返回的结果为false。限速成功。

方案二

在限速时,另一种方案是等待,即请求速度太快时,后到达的请求等待前面的请求完成后才能运行。这种方案类似一个队列。

实现

//LimitRate 限速
type LimitRate struct {
 rate int
 interval time.Duration
 lastAction time.Time
 lock sync.Mutex
}
//Limit 限速
package main
import (
 "sync"
 "time"
)
func (l *LimitRate) Limit() bool {
 result := false
 for {
 l.lock.Lock()
 //判断最后一次执行的时间与当前的时间间隔是否大于限速速率
 if time.Now().Sub(l.lastAction) > l.interval {
 l.lastAction = time.Now()
 result = true
 }
 l.lock.Unlock()
 if result {
 return result
 }
 time.Sleep(l.interval)
 }
}
//SetRate 设置Rate
func (l *LimitRate) SetRate(r int) {
 l.rate = r
 l.interval = time.Microsecond * time.Duration(1000*1000/l.Rate)
}
//GetRate 获取Rate
func (l *LimitRate) GetRate() int {
 return l.rate 
}

测试

package main
import (
 "fmt"
 "sync"
 "time"
)
func main() {
 var wg sync.WaitGroup
 var lr LimitRate
 lr.SetRate(3)
 
 b:=time.Now()
 for i := 0; i < 10; i++ {
 wg.Add(1)
 go func() {
 if lr.Limit() {
 fmt.Println("Got it!")
 }
 wg.Done()
 }()
 }
 wg.Wait()
 fmt.Println(time.Since(b))
}

运行结果

Got it!
Got it!
Got it!
Got it!
Got it!
Got it!
Got it!
Got it!
Got it!
Got it!
3.004961704s

与方案一不同,显示了10次Got it!但是运行时间是3.00496秒,同样每秒没有超过3次。限速成功。

改造

回到最初的例子中,我们将限速功能加进去。这里需要注意,我们的例子中,请求是不能被丢弃的,只能排队等待,所以我们使用方案二的限速方法。

var lr LimitRate//方案二
//限制每秒运行20次,可以根据实际环境调整限速设置,或者由程序动态调整。
lr.SetRate(20)
//使用goroutine,程序运行时间短,但数据库可能被拖垮
for _,v:=range userList {
 u:=v
 go func(){
 lr.Limit()
 user:=db.user.Get(u.ID)
 if user==nil {
 newUser:=user{ID:u.ID,UserName:u.UserName}
 db.user.Insert(newUser)
 }
 }()
}
select{}

如果您有更好的方案欢迎交流与分享。

内容为作者原创,未经允许请勿转载,谢谢合作。


关于作者:
Jesse,目前在Joygenio工作,从事golang语言开发与架构设计。
正在开发维护的产品:www.botposter.com


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:jesse_joygenio

查看原文:golang并发编程的两种限速方法

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
2605 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏