基于Golang 高并发问题的解决方案


Posted in Golang onMay 08, 2021

Golang 高并发问题的解决

Golang在高并发问题上,由于协程的使用,相对于其他编程语言,已经有了很大的优势,即相同的配置上,Golang可以以更低的代价处理更多的线程,同样的线程数,占用更低的资源!及时这样,只是解决了一部分问题而已,因为在每个协程里,处理逻辑还是会有问题。

高并发时,还是要考虑服务器所能承受的最大压力,数据库读取时的io问题,连接数问题,带宽问题等等

研究了一下并发解决方案,在此记录一下

参考文章:Handling 1 Million Requests per Minute with Go

地址:http://marcio.io/2015/07/handling-1-million-requests-per-minute-with-golang/

代码如下:

//==================================
//  * Name:Jerry
//  * Tel:18017448610
//  * DateTime:2019/2/24 14:02
//==================================
package main
import (
	"github.com/lunny/log"
	"runtime"
	"sync"
	"time"
)
//工厂模型
type Factory struct {
	Wg        *sync.WaitGroup //任务监控系统
	MaxWorker int             //最大机器数
	MaxJobs   int             //最大工作数量
	JobQueue  chan int        //工作队列管道
	Quit      chan bool       //是否关闭机器
}
//创建工厂模型
func NewFactory(maxWorker int, wg *sync.WaitGroup) Factory {
	return Factory{
		Wg:        wg,                        //引用任务监控系统
		MaxWorker: maxWorker,                 //机器数量(数量多少,根据服务器性能而定)
		JobQueue:  make(chan int, maxWorker), //工作管道,数量大于等于机器数
		Quit:      make(chan bool),
	}
}
//设置最大订单数量
func (f *Factory) SetMaxJobs(taskNum int) {
	f.MaxJobs = taskNum
}
//开始上班
func (f *Factory) Start() {
	//机器开机,MaxWorker
	for i := 0; i < f.MaxWorker; i++ {
		//每一台机器开启后,去工作吧
		go func() {
			//等待下发命令
			for {
				select {
				case i := <-f.JobQueue:
					//接到工作,开工!
					f.doWork(i)
				case <-f.Quit:
					log.Println("机器关机")
					return
				}
			}
		}()
	}
}
//分配每个任务到管道中
func (f *Factory) AddTask(taskNum int) {
	//系统监控任务 +1
	f.Wg.Add(1)
	//分配任务到管道中
	f.JobQueue <- taskNum
}
//模拟耗时工作
func (f *Factory) doWork(taskNum int) {
	//生产产品的工作
	time.Sleep(200 * time.Millisecond)
	//完成工作报告
	f.Wg.Done()
	//log.Println("完工:", taskNum)
}
//创建工厂
func Begin() {
	//配置工作核数
	gomaxprocs := runtime.GOMAXPROCS(runtime.NumCPU())
	log.Println("核数:", gomaxprocs)
	//配置监控系统
	wg := new(sync.WaitGroup)
	//开工厂
	factory := NewFactory(1000, wg)
	//订单量
	factory.SetMaxJobs(10000)
	//开始上班
	factory.Start()
	log.Println("开始生产")
	//讲所有的订单,添加到任务队列
	for i := 0; i < factory.MaxJobs; i++ {
		factory.AddTask(i)
	}
	factory.Wg.Wait()
	log.Println("所有订单任务生产完成")
}

测试代码及结果

上面代码中,MaxWorker的数量很重要,取决于服务器所能承受的压力,当然也不能无限增大,合理数值效率最高(具体多少合适,自己测试)

代码:

func Benchmark_Begin(b *testing.B) {
 Begin()
}

结果:

1000台机器(协程),10000的工作量,我的个人PC测试结果如下:

2019/02/26 16:42:31 核数: 4

2019/02/26 16:42:31 开始生产

2019/02/26 16:42:33 所有订单任务生产完成

goos: windows

goarch: amd64

pkg: day11

Benchmark_hight2-4 1 2035574000 ns/op

PASS

Process finished with exit code 0

总结:

此方法仅仅是在代码层面解决一定的问题,高并发 产生的原因还包括其他原因,如带宽,数据库读取速度等等,还需加大带宽,多级数据库,优化数据的检索等等方法

补充:golang 高并发任务处理方案

这个主要用golang 的chan 和routine属性做的,比很多语言方便多了,可以参考参考

//任务的请求
type MtaskRequest struct {
    Ceshi int
    // [redacted]
}
 
//job队列+work池
var (
    MaxWorker = os.Getenv("MAX_WORKERS")
    MaxQueue  = os.Getenv("MAX_QUEUE")
)
 
// Job represents the job to be run
type Job struct {
    MtaskRequest MtaskRequest
}
 
// A buffered channel that we can send work requests on.
 
// var JobQueue chan Job ---这样申明会卡主,没有初始化
var JobQueue = make(chan Job)
 
// Worker represents the worker that executes the job
type Worker struct {
    WorkerPool chan chan Job
    JobChannel chan Job
    quit       chan bool
}
 
func NewWorker(workerPool chan chan Job) Worker {
    return Worker{
        WorkerPool: workerPool,
        JobChannel: make(chan Job),
        quit:       make(chan bool)}
}
 
// Stop signals the worker to stop listening for work requests.
func (w Worker) Stop() {
    go func() {
        w.quit <- true
    }()
}
 
type Dispatcher struct {
    // A pool of workers channels that are registered with the dispatcher
    WorkerPool chan chan Job
    maxWorkers int
}
 
func NewDispatcher(maxWorkers int) *Dispatcher {
    pool := make(chan chan Job, maxWorkers)
    return &Dispatcher{WorkerPool: pool, maxWorkers: maxWorkers}
}
 
// Start method starts the run loop for the worker, listening for a quit channel in
// case we need to stop it
func (w Worker) Start() {
    go func() {
        for {
            // register the current worker into the worker queue.
            w.WorkerPool <- w.JobChannel
            select {
            case <-w.JobChannel:
                time.Sleep(5 * time.Second)
                // we have received a work request.
                fmt.Println("调起worker")
            case <-w.quit:
                // we have received a signal to stop
                return
                //不能写default
            }
        }
    }()
}
 
func (d *Dispatcher) Run() {
    //启动一定数量的worker
    fmt.Println("启动一定数量的worker")
    for i := 0; i < d.maxWorkers; i++ {
        worker := NewWorker(d.WorkerPool)
        worker.Start()
    }
 
    go d.dispatch()
}
 
//分派任务
func (d *Dispatcher) dispatch() {
    for {
        select {
        case job := <-JobQueue: //接收一个job请求
            fmt.Println("JobQueue 收到请求")
 
            go func(job Job) {
                // try to obtain a worker job channel that is available.
                // this will block until a worker is idle
                jobChannel := <-d.WorkerPool
                // dispatch the job to the worker job channel
                jobChannel <- job
            }(job)
        }
    }
}
 
//接收到红包数据
func (this *TaskRedbao) UserGetRedbao(red_id, uid, shop_id, rand_arr, Amoney string) error {
    fmt.Println("收到 接收到红包数据 http请求")
    mtaskRequest := MtaskRequest{67}
    work := Job{MtaskRequest: mtaskRequest}
 
    JobQueue <- work
    return nil
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
go 原生http web 服务跨域restful api的写法介绍
Apr 27 Golang
解决go在函数退出后子协程的退出问题
Apr 30 Golang
Golang Gob编码(gob包的使用详解)
May 07 Golang
go 实现简易端口扫描的示例
May 22 Golang
go语言中http超时引发的事故解决
Jun 02 Golang
再次探讨go实现无限 buffer 的 channel方法
Jun 13 Golang
详解Go语言Slice作为函数参数的使用
Jul 02 Golang
Golang并发操作中常见的读写锁详析
Aug 30 Golang
golang实现一个简单的websocket聊天室功能
Oct 05 Golang
Go 中的空白标识符下划线
Mar 25 Golang
Go并发4种方法简明讲解
Apr 06 Golang
详解Go语言中配置文件使用与日志配置
Jun 01 Golang
使用golang编写一个并发工作队列
May 08 #Golang
Go 在 MongoDB 中常用查询与修改的操作
May 07 #Golang
golang 实现时间戳和时间的转化
May 07 #Golang
Golang Gob编码(gob包的使用详解)
May 07 #Golang
go mod 安装依赖 unkown revision问题的解决方案
解决golang 关于全局变量的坑
May 06 #Golang
Goland使用Go Modules创建/管理项目的操作
You might like
php中用文本文件做数据库的实现方法
2008/03/27 PHP
php 将bmp图片转为jpg等其他任意格式的图片
2009/06/29 PHP
《PHP编程最快明白》第八讲:php启发和小结
2010/11/01 PHP
php mysql 判断update之后是否更新了的方法
2012/01/10 PHP
IIS6.0 开启Gzip方法及PHP Gzip函数分享
2014/06/08 PHP
php字符串比较函数用法小结(strcmp,strcasecmp,strnatcmp及strnatcasecmp)
2016/07/18 PHP
php实现的读取CSV文件函数示例
2017/02/07 PHP
php操作mongodb封装类与用法实例
2018/09/01 PHP
javascript 实现父窗口引用弹出窗口的值的脚本
2007/08/07 Javascript
用js实现的模拟jquery的animate自定义动画(2.5K)
2010/07/20 Javascript
用JS实现3D球状标签云示例代码
2013/12/01 Javascript
禁止页面刷新让F5快捷键及右键都无效
2014/01/22 Javascript
用jQuery模拟select下拉框的简单示例代码
2014/01/26 Javascript
jquery获取当前元素索引值用法实例
2015/06/10 Javascript
基于jQuery Easyui实现登陆框界面
2017/07/10 jQuery
JQuery基于FormData异步提交数据文件
2020/09/01 jQuery
js实现Element中input组件的部分功能并封装成组件(实例代码)
2021/03/02 Javascript
[59:35]DOTA2上海特级锦标赛主赛事日 - 3 败者组第三轮#1COL VS Alliance第二局
2016/03/04 DOTA
[55:48]VGJ.S vs TNC Supermajor 败者组 BO3 第二场 6.6
2018/06/07 DOTA
通过C++学习Python
2015/01/20 Python
Python将阿拉伯数字转换为罗马数字的方法
2015/07/10 Python
分享python数据统计的一些小技巧
2016/07/21 Python
PHP统计代码行数的小代码
2019/09/19 Python
Django 实现外键去除自动添加的后缀‘_id’
2019/11/15 Python
python模块hashlib(加密服务)知识点讲解
2019/11/25 Python
Reebonz中国官网:新加坡奢侈品购物网站
2017/03/17 全球购物
英国时尚运动品牌的合集:The Sports Edit
2017/12/20 全球购物
美国豪华的多品牌精品店:The Webster
2019/07/31 全球购物
机械电子工程毕业生自荐信
2013/11/23 职场文书
物业保安员岗位职责
2014/03/14 职场文书
物业总经理助理岗位职责
2014/06/29 职场文书
化工实习心得体会
2014/09/09 职场文书
置业顾问岗位职责
2015/02/09 职场文书
党员转正介绍人意见
2015/06/03 职场文书
疑《守望先锋2》A测截图泄露 或将推出新模式、新界面
2022/04/03 其他游戏
MySQL分区路径子分区再分区
2022/04/13 MySQL