golang shutdown分析

测试代码

客户端

package main

import (
	"net/http"
	log "github.com/sirupsen/logrus"
	"io/ioutil"
	"fmt"
	"bytes"
	"sync"
	_"time"
)

func main() {
	var wg sync.WaitGroup
	var count int
	var rw sync.RWMutex
TEST:
	for i := 0; i < 1; i++ {
		wg.Add(1)
		go func () {
			defer wg.Done()
			tr := http.Transport{DisableKeepAlives: false}
			client := &http.Client{Transport: &tr}
			for {
				f, err := ioutil.ReadFile("data")
				if err != nil {
					fmt.Println("read file err", err)
					return
				}
				fmt.Println(len(f))
				reader := bytes.NewReader(f)
				rw.Lock()
				count += 1
				index := count
				rw.Unlock()
				resp, err := client.Post("http://0.0.0.0:8888", "application/x-www-form-urlencoded", reader)
				if err != nil {
					rw.RLock()
					currentCount := count
					rw.RUnlock()
					log.Fatal(err, index, currentCount)
				}
				defer resp.Body.Close()
				data, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					log.Fatal(err)
				}
				log.Printf("data[%s]", string(data))
				// time.Sleep(time.Second)
			}
		}()
	}
	wg.Wait()
	goto TEST
}复制代码

服务端

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"
	"context"
)

type myHandler struct {

}

func (h myHandler)ServeHTTP(w http.ResponseWriter, r *http.Request) {
	//print header
	// fmt.Println("header", r.Header)
	//debug body
	_, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Println("read body error", err)
		io.WriteString(w, "read you body error!")
		return
	}
	// fmt.Println("data len", len(data))

	io.WriteString(w, "goad it")
	return
}

func main() {
	// http.HandleFunc("/", myHandler)
	// err := http.ListenAndServe("0.0.0.0:8888", nil)
	// if err != nil {
	// 	fmt.Println("ListenAndServe error", err)
	// 	return
	// }
	server := &http.Server {
		Addr: "0.0.0.0:8888",
		Handler: myHandler{},
	}

	d := time.Duration(time.Second*10)
	t := time.NewTimer(d)
	defer t.Stop()
	go func (){
			<- t.C
			shutdown(server)
		}()
	server.ListenAndServe()
	for {
		fmt.Println(1)
		time.Sleep(time.Second)
	}
	fmt.Println(2)
	return
}

func shutdown(server *http.Server) {
	ctx, cancel := context.WithTimeout(context.TODO(), 3600)
	defer cancel()
	server.Shutdown(ctx)
}
复制代码

实验
代码:
服务端执行位置:
执行完这个之后会影响keepalive的执行(其实抓包可发现每15秒服务端会发送一次tcp的keepalive)

func (s *Server) doKeepAlives() bool {        return atomic.LoadInt32(&s.disableKeepAlives) == 0 && !s.shuttingDown()}func (s *Server) shuttingDown() bool {        return atomic.LoadInt32(&s.inShutdown) != 0}复制代码

上边doKeepAlives()在服务端handler完本次请求之后会执行:

//在go1.10 net/http/server.go 1845行                if !w.conn.server.doKeepAlives() {                        // We're in shutdown mode. We might've replied                        // to the user without "Connection: close" and                        // they might think they can send another                        // request, but such is life with HTTP/1.1.                        return                }复制代码
服务端就是上边调试截图的位置
客户端就是在等待POST响应的数据

抓包现象


可以看到服务端发送的连接断开,客户端也断开了连接,然后重新发起请求,黑色的区域忽略,那是后边调试程序停止导致的。
在closeIdles中
程序执行状态如下,右上图是server 端的调试,左下是client的状态
对应的包:
所以可以分析下如果shutdown已经执行到循环关闭当前所有连接的时候对应不同连接状态的效果
1.准备建连
拒绝连接
2.active
等待idle然后关闭连接
3.idle
直接发起关闭
4.closed
忽略

复制代码

真的没有任何问题吗?
其实shutdown里边在执行空闲连接关闭的时候我跟了下,一直跟到asm的汇编上实在看不懂了,我看着Wireshark的监控,此时在刚进入系统调用的时候,服务端和客户端还没有包交换信息,除了keepalive,
在执行:
图中的系统调用之后

上边看到的包每没有RST是因为我在调试,client没有发包,我放一张不调试的

所以在怀疑close的方式导致出现这种情况,所以针对长连接,如果客户端持续的发送数据可能会出现这种情况。

为了验证下,我改了closeIdles的代码,改成了只关闭服务端的写,让客户端能把数据发送过来


稳定是有四次挥手的,但是客户端还是收到了EOF,服务端不是优雅的。

再改动下,让shutdown退出之前sleep了500毫秒


红色的RST忽略,那是客户端发起的重新连接

所以初步结论就是server无法做到真正的graceful。


转载于:https://juejin.im/post/5d033c435188255c636e2221

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值