Skip to content

Latest commit

 

History

History
331 lines (283 loc) · 8.93 KB

0007-go-track-client-conn.md

File metadata and controls

331 lines (283 loc) · 8.93 KB

ดูการใช้งาน Connections ใน http.Client ใน Go

บางทีเราอยากจะรู้ว่าตอนนี้ http.Client มี idle connection อยู่ใน pool เท่าไร เขียน หรืออ่าน ไปกี่ bytes แล้ว

เราสามารถ track connection ได้ง่าย ๆ แค่

  1. สร้างตัวแปรที่จะเก็บสิ่งที่เราอยากจะ track (ในตัวอย่างขอเก็บไว้ในตัวแปร global ละกัน)

    var (
        currentConns int64
        totalWrite   int64
        totalRead    int64
    )
  2. เพิ่มคำสั่ง print stats มาดูหน่อย

    func printStats() {
        currentConns := atomic.LoadInt64(&currentConns)
        totalWrite := atomic.LoadInt64(&totalWrite)
        totalRead := atomic.LoadInt64(&totalRead)
    
        fmt.Printf("connections: %d\nwrite: %d bytes\nread: %d bytes\n", currentConns, totalWrite, totalRead)
    }
  3. เขียน struct มาครอบ net.Conn

    type trackConn struct {
        net.Conn
        closed int32
    }
  4. เวลาสร้าง trackConn ใหม่ ให้เพิ่ม currentConns

    func newTrackConn(conn net.Conn) *trackConn {
        atomic.AddInt64(&currentConns, 1)
        return &trackConn{Conn: conn}
    }
  5. ถ้า trackConn ถูก Close ให้ลด currentConns

    สิ่งที่ต้องระวังคือ ถ้าเรียก Close มากกว่า 1 ครั้ง จะต้องลด currentConns แค่ 1 เท่านั้น

    func (conn *trackConn) Close() error {
        if !atomic.CompareAndSwapInt32(&conn.closed, 0, 1) {
            return nil
        }
        atomic.AddInt64(&currentConns, -1)
        return conn.Conn.Close()
    }
  6. ถ้า 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
    }
  7. เช่นเดียวกัน ถ้า 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
    }
  8. ตอนเรียกใช้ เวลาที่ 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
            },
        },
    }
  9. เวลา 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()
    }
  10. ลอง 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
  11. ลองปิด 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 ทันที

Source Code เต็ม ๆ

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(&currentConns)
    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(&currentConns, 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(&currentConns, -1)
    return conn.Conn.Close()
}