TCP可以基于滑动窗口进行流量控制,使用setsockopt系统调用实现,可以限定客户端或者服务端连接的入网或出网流量,http是基于TCP协议的,因此http也可以基于TCP滑动窗口实现流量控制。golang自有的net包不支持server端TCP窗口设置,因此无法直接实现基于TCP窗口的流量控制。今天我们要对一个基于gin实现的微服务进行流量限制。
首先,gin自带的r.Run()启动的http肯定是不行的,然后http包中的http.ListenAndServer()也是不行的,那么我们就基于TCP来实现,但是golang得net包中的net.Listen()也是不行的。这时候我们只有调用底层的系统调用了(不是cgo),我们可以使用syscall包来实现系统调用。我们分为五步:创建socket,设置socket选项,绑定端口地址,转换为golang的listener,listen。
创建原生的socket
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0) if err != nil { log.Println("create socket failed, err:", err.Error()) return }
设置socket选项
// set receive buffer here err = syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_RCVBUF, 2350) if err != nil { log.Println("set socket option receive buffer failed, err:", err.Error()) return } // set send buffer here err = syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_SNDBUF, 2450) if err != nil { log.Println("set socket option send buffer failed, err:", err.Error()) return }
绑定端口地址
err = syscall.Bind(s, &syscall.SockaddrInet4{Port: 8099, Addr: inetAddr("192.168.31.11")}) if err != nil { log.Println("bind socket failed, err:", err.Error()) return }
转换为golang的listener
f := os.NewFile(uintptr(s), "") ln, err := net.FileListener(f) if err != nil { log.Println("create listener failed, err:", err.Error()) return }
listen
err = syscall.Listen(s, 0) if err != nil { log.Println("listen failed, err:", err.Error()) return }
最后我们把我们自定义的支持限流的listener应用于gin上
r := gin.Default() r.GET("/", func(context *gin.Context) { context.File("socket") }) err = http.Serve(ln, r) if err != nil { log.Println("create http server failed, err:", err.Error()) return }
一个支持限流的http server就此实现。