引子

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 =
} 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() for i:=;i<;i++{
wg.Add()
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(*/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() b:=time.Now()
for i := ; i < ; i++ {
wg.Add()
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() //使用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{}

Go -- 并发编程的两种限速方法的更多相关文章

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

    引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止goroutine将资源耗 ...

  2. Perl 面向对象编程的两种实现和比较:

    <pre name="code" class="html">https://www.ibm.com/developerworks/cn/linux/ ...

  3. angular2系列教程(十)两种启动方法、两个路由服务、引用类型和单例模式的妙用

    今天我们要讲的是ng2的路由系统. 例子

  4. git两种合并方法 比较merge和rebase

    18:01 2015/11/18git两种合并方法 比较merge和rebase其实很简单,就是合并后每个commit提交的id记录的顺序而已注意:重要的是如果公司用了grrit,grrit不允许用m ...

  5. 两种Ajax方法

    两种Ajax方法 Ajax是一种用于快速创建动态网页的技术,他通过在后台与服务器进行少量的数据交换,可以实现网页的异步更新,不需要像传统网页那样重新加载页面也可以做到对网页的某部分作出更新,现在这项技 ...

  6. mysql in 的两种使用方法

    简述MySQL 的in 的两种使用方法: 他们各自是在 in keyword后跟一张表(记录集).以及在in后面加上字符串集. 先讲后面跟着一张表的. 首先阐述三张表的结构: s(sno,sname. ...

  7. C#中的两种debug方法

    这篇文章主要介绍了C#中的两种debug方法介绍,本文讲解了代码用 #if DEBUG 包裹.利用宏定义两种方法,需要的朋友可以参考下   第一种:需要把调试方法改成debug代码用 #if DEBU ...

  8. Service的两种启动方法

    刚才看到一个ppt,介绍service的两种启动方法以及两者之间的区别. startService 和 bindService startService被形容为我行我素,而bindService被形容 ...

  9. jQuery ajax调用后台aspx后台文件的两种常见方法(不是ashx)

    在asp.net webForm开发中,用Jquery ajax调用aspx页面的方法常用的有两种:下面我来简单介绍一下. [WebMethod] public static string SayHe ...

随机推荐

  1. windows2012安装sql2012报错

    一. 处理

  2. 【css】【动画】【转发】旋转动画

    <!DOCTYPE HTML> <html> <head>     <meta charset="utf-8">     <s ...

  3. python logging with yaml

    Recently, I was made a service which can provide a simple way to get best model. so, i spent lot of ...

  4. 元组-tuple

    Python内置的元组数据类型:tuple tuple和list非常类似,但tuple一旦初始化就不能修改,因此它没有append(),insert()这样的方法 当定义一个tuple时,tuple的 ...

  5. 00048_this关键字

    1.this调用构造方法 (1)构造方法之间的调用,可以通过this关键字来完成: (2)构造方法调用格式 this(参数列表); (3)小案例 class Person { // Person的成员 ...

  6. Java异常架构图及面试题---https://www.cnblogs.com/gaoweixiao99/p/4905860.html

    https://www.cnblogs.com/gaoweixiao99/p/4905860.html 红色为检查异常,就是eclipse要提示你是try catch 还是throws. 非检查异常, ...

  7. 有关git的配置

    git版本控制器总结 关于部分内容请参考:https://www.cnblogs.com/smuxiaolei/p/7484678.htmlgit是一个版本控制器,分布式管理:可以记录每次文件的改动, ...

  8. MHA 高可用集群搭建(二)

    MHA 高可用集群搭建安装scp远程控制http://www.cnblogs.com/kevingrace/p/5662839.html yum install openssh-clients mys ...

  9. mysql5.7.23版本环境配置

    亲身实践安装mysql,用时居然花费了三个小时,在有那么多教程的情况下,依然在不该花费时间的路上浪费了太多时间.希望这篇文章能够帮助大家少走弯路~~ 1.下载我下载的是64位. 2.解压下载之后,我选 ...

  10. BZOJ 3240 [Noi2013]矩阵游戏 ——费马小定理 快速幂

    发现是一个快速幂,然而过不去. 怎么办呢? 1.十进制快速幂,可以用来练习卡时. 2.费马小定理,如果需要乘方的地方,可以先%(p-1)再计算,其他地方需要%p,所以需要保存两个数. 然后就是分类讨论 ...