设为首页 收藏本站
查看: 736|回复: 0

[经验分享] golang并发编程的两种限速方法

[复制链接]
YunVN网友  发表于 2018-9-19 13:36:49 |阅读模式
引子
  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 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(&quot;Got it!&quot;)
  }
  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



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-594319-1-1.html 上篇帖子: 【GoLang】GoLang map 非线程安全 & 并发度写优化 下篇帖子: PHP与Golang如何通信?
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表