Go 自带的 http/server.go 的连接解析 与 如何结合 master-worker 并发模式,提高单机并发能力
作者:林冠宏 / 指尖下的幽灵
掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8
博客:http://www.cnblogs.com/linguanh/
GitHub : https://github.com/af913337456/
腾讯云专栏: https://cloud.tencent.com/developer/user/1148436/activities
关于
server.go
源码的解析可以去搜下,已经有很多且还不错的文章。
正文:
从我们启动http.ListenAndServe(port,router)
开始,server.go
内部最终在一个for
循环中的 accept
方法中不停地等待客户端
的连接到来。
每接收到一个accept
就启动一个 gorutine
去处理当前ip
的连接。也就是源码里的go c.serve(ctx)
。这一个步骤在 c.serve(ctx)
它并不是简单的形式:
请求-->处理请求-->返回结果-->断开这个连接-->结束当前的 gorutine
根据我的调试结果
与源码分析
显示,正确的形式是下面这样的:
- 为每一个连接的用户启动了一个长连接,
serve
方法内部有个超时的设置是c.rwc.SetReadDeadline(time.Time{})
,这样子的情况,如果内部不出错,当前的连接断开的条件是客户端
自己断开,或nat
超时。 - 这个连接建立后,以
ip
为单位,当前的客户端
,此时它的所有http请求
,例如get
,post
,它们都会在这个启动的gorutine
内进行分发
与被处理
。 - 也就是说,同一个
ip
,多个不同的
请求,这里不会触发另一个accept
,不会再去启动一个go c.serve(ctx)
上述我们得出结论:
- 如果有
100万
个accept
,就证明有100万
个连接,100万
个ip
与当前server
连接。即是我们说的百万连接
-
百万连接
不是百万请求
- 每一个连接,它可以进行多个
http请求
,它的请求都在当前启动这个连接的gorutine
里面进行。 -
c.serve(...)
源码中的for 死循环
就是负责读取每个请求再分发 for { w, err := c.readRequest(ctx) // 读取一个 http 请求 //... ServeHTTP(...) } - 我们的
100万
连接里面,有可能并发更多的请求,例如几百万请求,一个客户端
快速调用多个请求api
图解总结
结合 master-worker 并发模式
根据我们上面的分析,每一个新连接到来,go 就会启动一个 gorutine
,在源码里面也没有看到有一个量级的限制,也就是达到多少连接就不再接收。我们也知道,服务器是有处理瓶颈的。
所以,在这里插播一个优化点
,就是在server.go
内部做一个连接数目的限制。
master-worker
模式本身是启动多个worker
线程,去并发读取
有界队列里面的任务,并执行。
我自身已经实现了一个go版本
的master-worker
,做过下面的尝试:
- 在
go c.serve(ctx)
处做修改,如下。
if srv.masterWorkerModel {
// lgh --- way to execute
PoolMaster.AddJob(
masterworker.Job{
Tag:" http server ",
Handler: func() {
c.serve(ctx)
fmt.Println("finish job") // 这一句在当前 ip 断开连接后才会输出
},
})
}else{
go c.serve(ctx)
}
func (m Master) AddJob(job Job) {
fmt.Println("add a job ")
m.JobQueue <- job // jobQueue 是具备缓冲的
}
// worker
func (w Worker) startWork(master *Master) {
go func() {
for {
select {
case job := <-master.JobQueue:
job.doJob(master)
}
}
}()
}
// job
func (j Job) doJob(master *Master) {
go func() {
fmt.Println(j.Tag+" --- doing job...")
j.Handler()
}()
}
不难理解它的模式。
现在我们使用生产者--消费者模式
进行假设,连接的产生
是生产者
,<-master.JobQueue
是消费者
,因为每一次消费就是启动一个处理的gorutine
。
因为我们在accept
一个请求到<-master.JobQueue
,管道输出一个的这个过程中,可以说是没有耗时操作的,这个job
,它很快就被输出了管道。也就是说,消费很快
,那么实际的生产环境
中,我们的worker
工作协程
启动5~10
个就有余了。
考虑如果出现了消费跟不上
的情况,那么多出来的job
将会被缓冲到channel
里面。这种情况可能出现的情景是:
短时间十万+级别连接的建立,就会导致
worker
读取不过来。不过,即使发生了,也是很快就取完的。因为间中的耗时几乎可以忽略不计!
也就说,短时间
大量连接的建立,它的瓶颈在队列的缓冲数
。但是即使瓶颈发生了,它又能很快被分发处理掉。所以说:
- 我的这个第一点的尝试的意义事实上没有多大的。只不过是换了一种方式去分发
go c.serve(ctx)
。
- 这个是第二种结合方式,把
master-worker
放置到ServeHTTP
的分发阶段。例如下面代码,是常见的http handler
写法,我们就可以嵌套进去。
func (x XHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//...
if x.MasterWorker {
poolMaster.AddJob(master_worker.Job{
Tag:"normal",
XContext:xc,
Handler: func(context model.XContext) {
x.HandleFunc(w,r)
},
})
return
}
x.HandleFunc(w,r)
//...
}
这样的话,我们就能控制所有连接的并发请求最大数。超出的将会进行排队,等待被执行,而不会因为短时间 http 请求数目不受控暴增
而导致服务器
挂掉。
此外上述第二种
还存在一个:读,过早关闭问题
,这个留给读者尝试解决。
- T-SQL Enhancement in SQL Server 2005[下篇]
- JS原型,a和b是不是失散多年的兄弟?
- Linux shell 程序设计1——安装及入门
- 偶遇--《坑新人--前端专用面试题》
- 简单的说下,(function(){...})() 与 (function(){...}()) 有什么区别?
- ASP.NET Process Model之二:ASP.NET Http Runtime Pipeline[上篇]
- Shell常用命令小结
- 插入法排序
- ASP.NET Process Model之二:ASP.NET Http Runtime Pipeline - Part II
- 震惊了!这样的js面试题让所有人-男默女泪
- 前端知识学了却不会用,都是没走心
- var a="xx";a=a+"ss";a的值变了,但"xx"字符串并没有变
- 先行者计划--1109微课总结 | 《通过二个demo初识webPack》
- 先行者计划--1107微课 《什么是Vuex?》| 文字简版
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 两种使用代码获得SAP CRM product sales status的办法
- C#: "$"作用和用法
- SAP ABAP字符和字符串变量隐式转换的一些规则
- MySQL information_schema详解 COLLATION_CHARACTER_SET_APPLICABILITY
- 如何处理Angular项目在Visual Studio Code打开报关于@Decorators的警告信息
- QT 中普通数值与字符串之间的转换
- C# 时间与时间戳互转 13位
- 在C#中ref和out具体怎么使用?在什么情况下使用?
- Vs code 创建vue模版
- [892]python中re.split()方法
- MacOS抓包工具Charles
- PYTHON替代MATLAB在线性代数学习中的应用(使用Python辅助MIT 18.06 Linear Algebra学习)
- C# TCP/IP 文件(图片、视频等)收发系统
- 反制面试官 | 14张原理图 | 再也不怕被问 volatile!
- Javascript之其实我觉得原型链没有难的那么夸张!