golang併發編程的兩種限速方法

引子

golang提供了goroutine快速實現併發編程,在實際環境中,若是goroutine中的代碼要消耗大量資源時(CPU、內存、帶寬等),咱們就須要對程序限速,以防止goroutine將資源耗盡。
如下面僞代碼爲例,看看goroutine如何拖垮一臺DB。假設userList長度爲10000,先從數據庫中查詢userList中的user是否在數據庫中存在,存在則忽略,不存在則建立。golang

//不使用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。限速成功。post

方案二

在限速時,另外一種方案是等待,即請求速度太快時,後到達的請求等待前面的請求完成後才能運行。這種方案相似一個隊列。測試

實現

//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

相關文章
相關標籤/搜索