บางทีเราอยากจะรู้ว่าตอนนี้ http.Client
มี idle connection อยู่ใน pool เท่าไร
เขียน หรืออ่าน ไปกี่ bytes แล้ว
เราสามารถ track connection ได้ง่าย ๆ แค่
สร้างตัวแปรที่จะเก็บสิ่งที่เราอยากจะ track (ในตัวอย่างขอเก็บไว้ในตัวแปร global ละกัน)
var (
currentConns int64
totalWrite int64
totalRead int64
)
เพิ่มคำสั่ง print stats มาดูหน่อย
func printStats() {
currentConns := atomic.LoadInt64(¤tConns)
totalWrite := atomic.LoadInt64(&totalWrite)
totalRead := atomic.LoadInt64(&totalRead)
fmt.Printf("connections: %d\nwrite: %d bytes\nread: %d bytes\n", currentConns, totalWrite, totalRead)
}
เขียน struct มาครอบ net.Conn
type trackConn struct {
net.Conn
closed int32
}
เวลาสร้าง trackConn
ใหม่ ให้เพิ่ม currentConns
func newTrackConn(conn net.Conn) *trackConn {
atomic.AddInt64(¤tConns, 1)
return &trackConn{Conn: conn}
}
ถ้า trackConn
ถูก Close
ให้ลด currentConns
สิ่งที่ต้องระวังคือ ถ้าเรียก Close
มากกว่า 1 ครั้ง จะต้องลด currentConns
แค่ 1 เท่านั้น
func (conn *trackConn) Close() error {
if !atomic.CompareAndSwapInt32(&conn.closed, 0, 1) {
return nil
}
atomic.AddInt64(¤tConns, -1)
return conn.Conn.Close()
}
ถ้า trackConn
ถูก Read
ให้เก็บว่าอ่านไปกี่ bytes
func (conn *trackConn) Read(b []byte) (n int, err error) {
n, err = conn.Conn.Read(b)
atomic.AddInt64(&totalRead, int64(n))
return
}
เช่นเดียวกัน ถ้า trackConn
ถูก Write
ให้เก็บว่าเขียนไปกี่ bytes
func (conn *trackConn) Write(b []byte) (n int, err error) {
n, err = conn.Conn.Write(b)
atomic.AddInt64(&totalWrite, int64(n))
return
}
ตอนเรียกใช้ เวลาที่ dial ให้เอา trackConn
ของเราไปครอบ net.Conn
dialer := &net.Dialer{}
client := &http.Client{
Transport: &http.Transport{
// DisableKeepAlives: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
return newTrackConn(conn), nil
},
},
}
เวลา get ลอง print stats ออกมาดูด้วย
getURLAndPrintStats := func(url string) {
resp, err := client.Get(url)
if err != nil {
log.Println(err)
return
}
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
fmt.Println("get:", url)
printStats()
}
ลอง get หลาย ๆ เว็บมาดู
getURLAndPrintStats("https://www.google.com")
getURLAndPrintStats("https://www.facebook.com")
getURLAndPrintStats("https://www.youtube.com")
getURLAndPrintStats("https://github.com")
getURLAndPrintStats("https://www.pixiv.net")
getURLAndPrintStats("https://www.google.com")
getURLAndPrintStats("https://github.com")
getURLAndPrintStats("https://www.pixiv.net")
getURLAndPrintStats("https://www.google.com")
$ go run main.go
get: https://www.google.com
connections: 1
write: 732 bytes
read: 9596 bytes
get: https://www.facebook.com
connections: 2
write: 1554 bytes
read: 49719 bytes
get: https://www.youtube.com
connections: 3
write: 2815 bytes
read: 110209 bytes
get: https://github.com
connections: 4
write: 3260 bytes
read: 139970 bytes
get: https://www.pixiv.net
connections: 5
write: 3867 bytes
read: 155766 bytes
get: https://www.google.com
connections: 5
write: 4063 bytes
read: 162148 bytes
get: https://github.com
connections: 5
write: 4229 bytes
read: 188395 bytes
get: https://www.pixiv.net
connections: 5
write: 4315 bytes
read: 199187 bytes
get: https://www.google.com
connections: 5
write: 4557 bytes
read: 205815 bytes
ลองปิด Keep Alive แล้วรันอีกที
client := &http.Client{
Transport: &http.Transport{
DisableKeepAlives: true, // เอา comment ตรงนี้ออก
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
return newTrackConn(conn), nil
},
},
}
$ go run main.go
get: https://www.google.com
connections: 0
write: 675 bytes
read: 9386 bytes
get: https://www.facebook.com
connections: 0
write: 1318 bytes
read: 48831 bytes
get: https://www.youtube.com
connections: 0
write: 2564 bytes
read: 109332 bytes
get: https://github.com
connections: 0
write: 3059 bytes
read: 139196 bytes
get: https://www.pixiv.net
connections: 0
write: 3655 bytes
read: 154994 bytes
get: https://www.google.com
connections: 0
write: 4330 bytes
read: 164557 bytes
get: https://github.com
connections: 0
write: 4825 bytes
read: 194327 bytes
get: https://www.pixiv.net
connections: 0
write: 5463 bytes
read: 210157 bytes
get: https://www.google.com
connections: 0
write: 6138 bytes
read: 219647 bytes
จะเห็นว่า connection จะเป็น 0 ตลอด เพราะพอ get เสร็จก็จะปิด connection ทันที
package main
import (
"context"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"sync/atomic"
)
func main() {
dialer := &net.Dialer{}
client := &http.Client{
Transport: &http.Transport{
// DisableKeepAlives: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
return newTrackConn(conn), nil
},
},
}
getURLAndPrintStats := func(url string) {
resp, err := client.Get(url)
if err != nil {
log.Println(err)
return
}
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
fmt.Println("get:", url)
printStats()
}
getURLAndPrintStats("https://www.google.com")
getURLAndPrintStats("https://www.facebook.com")
getURLAndPrintStats("https://www.youtube.com")
getURLAndPrintStats("https://github.com")
getURLAndPrintStats("https://www.pixiv.net")
getURLAndPrintStats("https://www.google.com")
getURLAndPrintStats("https://github.com")
getURLAndPrintStats("https://www.pixiv.net")
getURLAndPrintStats("https://www.google.com")
}
var (
currentConns int64
totalWrite int64
totalRead int64
)
func printStats() {
currentConns := atomic.LoadInt64(¤tConns)
totalWrite := atomic.LoadInt64(&totalWrite)
totalRead := atomic.LoadInt64(&totalRead)
fmt.Printf("connections: %d\nwrite: %d bytes\nread: %d bytes\n", currentConns, totalWrite, totalRead)
}
type trackConn struct {
net.Conn
closed int32
}
func newTrackConn(conn net.Conn) *trackConn {
atomic.AddInt64(¤tConns, 1)
return &trackConn{Conn: conn}
}
func (conn *trackConn) Read(b []byte) (n int, err error) {
n, err = conn.Conn.Read(b)
atomic.AddInt64(&totalRead, int64(n))
return
}
func (conn *trackConn) Write(b []byte) (n int, err error) {
n, err = conn.Conn.Write(b)
atomic.AddInt64(&totalWrite, int64(n))
return
}
func (conn *trackConn) Close() error {
if !atomic.CompareAndSwapInt32(&conn.closed, 0, 1) {
return nil
}
atomic.AddInt64(¤tConns, -1)
return conn.Conn.Close()
}