当前位置:   article > 正文

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

golang 对进程限速

引子

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

  1. //不使用goroutine,程序运行时间长,但数据库压力不大
  2. for _,v:=range userList {
  3. user:=db.user.Get(v.ID)
  4. if user==nil {
  5. newUser:=user{ID:v.ID,UserName:v.UserName}
  6. db.user.Insert(newUser)
  7. }
  8. }
  9. //使用goroutine,程序运行时间短,但数据库可能被拖垮
  10. for _,v:=range userList {
  11. u:=v
  12. go func(){
  13. user:=db.user.Get(u.ID)
  14. if user==nil {
  15. newUser:=user{ID:u.ID,UserName:u.UserName}
  16. db.user.Insert(newUser)
  17. }
  18. }()
  19. }
  20. select{}

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

方案一

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

实现

实现逻辑如下:

  1. package main
  2. import (
  3. "sync"
  4. "time"
  5. )
  6. //LimitRate 限速
  7. type LimitRate struct {
  8. rate int
  9. begin time.Time
  10. count int
  11. lock sync.Mutex
  12. }
  13. //Limit Limit
  14. func (l *LimitRate) Limit() bool {
  15. result := true
  16. l.lock.Lock()
  17. //达到每秒速率限制数量,检测记数时间是否大于1
  18. //大于则速率在允许范围内,开始重新记数,返回true
  19. //小于,则返回false,记数不变
  20. if l.count == l.rate {
  21. if time.Now().Sub(l.begin) >= time.Second {
  22. //速度允许范围内,开始重新记数
  23. l.begin = time.Now()
  24. l.count = 0
  25. } else {
  26. result = false
  27. }
  28. } else {
  29. //没有达到速率限制数量,记数加1
  30. l.count++
  31. }
  32. l.lock.Unlock()
  33. return result
  34. }
  35. //SetRate 设置每秒允许的请求数
  36. func (l *LimitRate) SetRate(r int) {
  37. l.rate = r
  38. l.begin = time.Now()
  39. }
  40. //GetRate 获取每秒允许的请求数
  41. func (l *LimitRate) GetRate() int {
  42. return l.rate
  43. }

测试

下面是测试代码:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. var wg sync.WaitGroup
  7. var lr LimitRate
  8. lr.SetRate(3)
  9. for i:=0;i<10;i++{
  10. wg.Add(1)
  11. go func(){
  12. if lr.Limit() {
  13. fmt.Println("Got it!")//显示3次Got it!
  14. }
  15. wg.Done()
  16. }()
  17. }
  18. wg.Wait()
  19. }

运行结果

  1. Got it!
  2. Got it!
  3. Got it!

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

方案二

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

实现

  1. //LimitRate 限速
  2. type LimitRate struct {
  3. rate int
  4. interval time.Duration
  5. lastAction time.Time
  6. lock sync.Mutex
  7. }
  8. //Limit 限速
  9. package main
  10. import (
  11. "sync"
  12. "time"
  13. )
  14. func (l *LimitRate) Limit() bool {
  15. result := false
  16. for {
  17. l.lock.Lock()
  18. //判断最后一次执行的时间与当前的时间间隔是否大于限速速率
  19. if time.Now().Sub(l.lastAction) > l.interval {
  20. l.lastAction = time.Now()
  21. result = true
  22. }
  23. l.lock.Unlock()
  24. if result {
  25. return result
  26. }
  27. time.Sleep(l.interval)
  28. }
  29. }
  30. //SetRate 设置Rate
  31. func (l *LimitRate) SetRate(r int) {
  32. l.rate = r
  33. l.interval = time.Microsecond * time.Duration(1000*1000/l.Rate)
  34. }
  35. //GetRate 获取Rate
  36. func (l *LimitRate) GetRate() int {
  37. return l.rate
  38. }

测试

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. func main() {
  8. var wg sync.WaitGroup
  9. var lr LimitRate
  10. lr.SetRate(3)
  11. b:=time.Now()
  12. for i := 0; i < 10; i++ {
  13. wg.Add(1)
  14. go func() {
  15. if lr.Limit() {
  16. fmt.Println("Got it!")
  17. }
  18. wg.Done()
  19. }()
  20. }
  21. wg.Wait()
  22. fmt.Println(time.Since(b))
  23. }

运行结果

  1. Got it!
  2. Got it!
  3. Got it!
  4. Got it!
  5. Got it!
  6. Got it!
  7. Got it!
  8. Got it!
  9. Got it!
  10. Got it!
  11. 3.004961704s

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

改造

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

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

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

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


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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/230049
推荐阅读
  

闽ICP备14008679号