golang并发编程的两种限速方法
引子
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
golang并发编程的两种限速方法的更多相关文章
- Go -- 并发编程的两种限速方法
引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止goroutine将资源耗 ...
- golang并发编程
golang并发编程 引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止go ...
- Perl 面向对象编程的两种实现和比较:
<pre name="code" class="html">https://www.ibm.com/developerworks/cn/linux/ ...
- Golang - 并发编程
目录 Golang - 并发编程 1. 并行和并发 2. go语言并发优势 3. goroutine是什么 4. 创建goroutine 5. runtime包 6. channel是什么 7. ch ...
- angular2系列教程(十)两种启动方法、两个路由服务、引用类型和单例模式的妙用
今天我们要讲的是ng2的路由系统. 例子
- git两种合并方法 比较merge和rebase
18:01 2015/11/18git两种合并方法 比较merge和rebase其实很简单,就是合并后每个commit提交的id记录的顺序而已注意:重要的是如果公司用了grrit,grrit不允许用m ...
- 两种Ajax方法
两种Ajax方法 Ajax是一种用于快速创建动态网页的技术,他通过在后台与服务器进行少量的数据交换,可以实现网页的异步更新,不需要像传统网页那样重新加载页面也可以做到对网页的某部分作出更新,现在这项技 ...
- mysql in 的两种使用方法
简述MySQL 的in 的两种使用方法: 他们各自是在 in keyword后跟一张表(记录集).以及在in后面加上字符串集. 先讲后面跟着一张表的. 首先阐述三张表的结构: s(sno,sname. ...
- C#中的两种debug方法
这篇文章主要介绍了C#中的两种debug方法介绍,本文讲解了代码用 #if DEBUG 包裹.利用宏定义两种方法,需要的朋友可以参考下 第一种:需要把调试方法改成debug代码用 #if DEBU ...
随机推荐
- oracle listener.ora的host不能使localhost,而应该是该机器名,否则不能用ip地址进行连接
# listener.ora Network Configuration File: /u01/app/oracle/product/11.2.0/dbhome_1/network/admin/lis ...
- 百度UEditor图片上传、SpringMVC、Freemarker、Tomcat、Nginx、静态资源
个人官网.公司项目都需要 可视化编辑器,百度UEditor做得很不错,就用的这个.项目后台用到了SpringMVC.Freemarker,开发过程中部署在Jetty,线上部署用Tomcat,最后可能配 ...
- 在ArcEngine下实现图层属性过滤的两种方法
转自chanyinhelv原文 在ArcEngine下实现图层属性过滤的两种方法 Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE /* ...
- 如何在PHP页面中原样输出HTML代码(是该找本php的数来看了)
如何在PHP页面中原样输出HTML代码(是该找本php的数来看了) 一.总结 一句话总结:字符串与HTML之间的相互转换主要应用htmlentities()函数来完成. 1.php中的html标签如何 ...
- LUOGU 1525 关押罪犯 - 并查集拆点(对立点) / 二分+二分图染色
传送门 分析: 并查集: 第一步先将所有矛盾从大至小排序,显然先将矛盾值大的分成两部分会更优. 普通的并查集都只能快速合并两个元素至同一集合,却不能将两个元素分至不同集合. 对于将很多数分成两个集合, ...
- [转]Redis 与Mysql通信
http://blog.csdn.net/hpb21/article/details/7852934 找了点资料看了下.学习心得如下: 1 Mysql更新Redis Mysql更新Redis借鉴mem ...
- WPF实现射线效果动画
原文:WPF实现射线效果动画 最近的一个项目中有个需求是:从一个点向其它多个点发出射线,要求这些射线同时发出,同时到达. 我就想到了用WPF的动画来实现.WPF中有Line类用于绘制直线,但这个类中好 ...
- scala读写文件 comparing values of types Unit and Int using `!=' will always yield true
由于scala没有对写入文件的支持,所以写文件时通常借助java进行IO操作 //方式一(小文件) /* val s1 = Source.fromFile("D:\\inputword\\h ...
- [GeekBand ] 利用 pass by reference -to -const 编写高效规范的 c++代码
本文参考资料 : GeekBand 侯捷老师,学习笔记 Effective C ++ 侯捷译 条款20 开发环境采用:VS2013版本 首先:分析值传递的缺点 (一) class Person{ p ...
- C++程序员经常问的11个问题
下面的这些要点是对所有的C++程序员都适用的.我之所以说它们是最重要的,是因为这些要点中提到的是你通常在C++书中或网站上无法找到的.如:指向成员的指针,这是许多资料中都不愿提到的地方,也是经常出错的 ...