Go 自带的 http/server.go 的连接解析 与 如何结合 master-worker 并发模式,提高单机并发能力

作者:林冠宏 / 指尖下的幽灵

掘金:

博客:

GitHub :

腾讯云专栏:

关于 server.go 源码的解析可以去搜下,已经有很多且还不错的文章。

从我们启动http.ListenAndServe(port,router)开始,server.go 内部最终在一个for 循环中的 accept 方法中不停地等待客户端的连接到来。

每接收到一个accept 就启动一个 gorutine 去处理当前ip的连接。也就是源码里的go c.serve(ctx)。这一个步骤在 c.serve(ctx) 它并不是简单的形式:

请求-->处理请求-->返回结果-->断开这个连接-->结束当前的 gorutine

调试结果与源码分析显示,正确的形式是下面这样的:

  1. 为每一个连接的用户启动了一个长连接,serve 方法内部有个超时的设置是c.rwc.SetReadDeadline(time.Time{}),这样子的情况,如果内部不出错,当前的连接断开的条件是客户端自己断开,或nat超时。

  2. 这个连接建立后,以ip为单位,当前的客户端,此时它的所有http请求,例如getpost,它们都会在这个启动的gorutine 内进行分发被处理

  3. 也就是说,同一个ip,多个不同的请求,这里不会触发另一个 accept,不会再去启动一个go c.serve(ctx)

  1. 如果有 100万accept,就证明有100万个连接,100万ip与当前server连接。即是我们说的百万连接

  2. 百万连接 不是百万请求

  3. 每一个连接,它可以进行多个http请求,它的请求都在当前启动这个连接的gorutine里面进行。

  4. c.serve(...) 源码中的for 死循环就是负责读取每个请求再分发
for {
    w,err := c.readRequest(ctx) // 读取一个 http 请求
    //...
    ServeHTTP(...)
}
  1. 我们的100万 连接里面,有可能并发更多的请求,例如几百万请求,一个客户端快速调用多个请求api

根据我们上面的分析,每一个新连接到来,go 就会启动一个 gorutine,在源码里面也没有看到有一个量级的限制,也就是达到多少连接就不再接收。我们也知道,服务器是有处理瓶颈的。

所以,在这里插播一个优化点,就是在server.go 内部做一个连接数目的限制。

master-worker 模式本身是启动多个worker 线程,去并发读取有界队列里面的任务,并执行。

我自身已经实现了一个go版本master-worker,做过下面的尝试:

  1. 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 是具备缓冲的
}


<pre class="golang">// worker
func (w Worker) startWork(master Master) {
go func() {
for {
select {
case job := <-master.JobQueue:
job.doJob(master)
}
}
}()
}

<pre class="golang">// 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)

  1. 这个是第二种结合方式,把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 请求数目不受控暴增 而导致服务器挂掉。

此外上述第二种还存在一个:读,过早关闭问题,这个留给读者尝试解决。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


类型转换 1、int转string 2、string转int 3、string转float 4、用户结构类型转换
package main import s &quot;strings&quot; import &quot;fmt&quot; var p = fmt.Println func main() { p(&quot;Contains: &quot;, s.Contains(&quot;test&quo
类使用:实现一个people中有一个sayhi的方法调用功能,代码如下: 接口使用:实现上面功能,代码如下:
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件信息 4、删除文件,成功返回true,失败返回false
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http://www.golangtc.com/downloadBeego开发文档地址:http://beego.me/docs/intro/ 安装步骤: 一、GO环境安装 二、配置系统变量 三、Beego安装 一、GO环境安装 根
golang获取程序运行路径:
Golang的文档和社区资源:为什么它可以帮助开发人员快速上手?
Golang:AI 开发者的实用工具
Golang的标准库:为什么它可以大幅度提高开发效率?
Golang的部署和运维:如何将应用程序部署到生产环境中?
高性能AI开发:Golang的优势所在
本篇文章和大家了解一下go语言开发优雅得关闭协程的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1.简介本文将介绍首先为什么需要主...
这篇文章主要介绍了Go关闭goroutine协程的方法,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。1.简介本文将介绍首先为什么需要主动关闭gor...
本篇文章和大家了解一下go关闭GracefulShutdown服务的几种方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。目录Shutdown方法Regi...
这篇文章主要介绍了Go语言如何实现LRU算法的核心思想和实现过程,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。GO实现Redis的LRU例子常
今天小编给大家分享的是Go简单实现多租户数据库隔离的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会...
这篇“Linux系统中怎么安装NSQ的Go语言客户端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希
本文小编为大家详细介绍“怎么在Go语言中实现锁机制”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在Go语言中实现锁机制”文章能帮助大家解决疑惑,下面...
今天小编给大家分享一下Go语言中interface类型怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考