Tác giả: Đội ngũ kỹ thuật HolySheep AI · Cập nhật tháng 01/2026 · Đọc khoảng 14 phút · Mức độ: trung cấp-nâng cao

Mở đầu: Đêm 11/11, chatbot CSKH của tôi "chết đứng" vì 4.200 request/giây

22h47 đêm đại lễ mua sắm 11/11, tôi ngồi trước dashboard Grafana theo dõi chatbot AI CSKH của một sàn thương mại điện tử. Dịch vụ burst lên 4.200 request/giây khi banner flash sale vừa hiện. 11 phút sau, log bắt đầu tràn ngập 429 Too Many Requestscontext deadline exceeded. Một kỹ sư hỏi tôi: "Anh ơi, phải chăng mô hình quá đắt hay code có vấn đề?". Câu trả lời thật ra nằm ở chỗ giản dị hơn nhiều: cách tôi tạo http.Client – cứ mỗi request là một transport mới, kết nối TCP bị tái tạo, TIME_WAIT chồng chất, goroutine kẹt ở DNS lookup. Bài viết này chia sẻ ba khối xương sống tôi đã vá vào hệ thống: connection pool đúng chuẩn, rate limiting có back-pressure, và circuit breaker ba trạng thái – tất cả chạy trên Go và tích hợp qua HolySheep AI gateway.

Vì sao Go là lựa chọn "số một" cho AI gateway đồng thời cao?

Kiến trúc tổng quan: 3 lớp bảo vệ

[ Client Bot ]
      │  (4.200 req/giây)
      ▼
┌───────────────────────────────────────┐
│  Go AI Gateway                        │
│                                       │
│  ┌─────────────────────────────────┐  │
│  │ 1) HTTP Connection Pool         │  │  ← http.Transport, keep-alive
│  └─────────────────────────────────┘  │
│  ┌─────────────────────────────────┐  │
│  │ 2) Concurrency Limiter + Token  │  │  ← semaphore + token bucket
│  │    Bucket Rate Limiter          │  │
│  └─────────────────────────────────┘  │
│  ┌─────────────────────────────────┐  │
│  │ 3) Circuit Breaker (Closed/     │  │  ← sony/gobreaker style
│  │    Open/Half-Open)              │  │
│  └─────────────────────────────────┘  │
└───────────────────────────────────────┘
      │  (≤ 2.000 req/giây, P95 ≈ 47 ms)
      ▼
https://api.holysheep.ai/v1

1. Connection Pool – tinh chỉnh http.Transport

Đây là lỗi "kinh điển" mà 90% lập trình viên Go mắc: cứ mỗi request lại gọi http.DefaultClient hoặc tự tạo &http.Client{}. Kết quả là TCP handshake + TLS handshake lặp lại liên tục. Với 4.200 RPS, đó là thảm họa. Dưới đây là phiên bản đã được tinh chỉnh:

package gateway

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"sync"
	"time"
)

// Pool cấu hình chia sẻ giữa tất cả goroutine (và-bot)
func NewPooledClient(maxConns, maxIdle int) *http.Client {
	tr := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   5 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		MaxIdleConns:          maxConns * 4, // tổng pool toàn cục
		MaxIdleConnsPerHost:   maxIdle,      // giữ kết nối nhàn rỗi mỗi host
		MaxConnsPerHost:       maxConns,     // giới hạn tổng kết nối đang mở
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
		ForceAttemptHTTP2:     true,
		DisableCompression:    false,
	}
	return &http.Client{
		Transport: tr,
		Timeout:   45 * time.Second,
	}
}

// sync.Pool tái sử dụng buffer – tránh GC nặng
var bodyPool = sync.Pool{
	New: func() any { b := make([]byte, 0, 4096); return &b },
}

// Hàm gọi AI qua HolySheep với pool + semaphore chống quá tải
func ChatCompletion(
	ctx context.Context,
	cli *http.Client,
	sem chan struct{},
	prompt string,
) (string, error) {
	// (1) Giới hạn concurrency – semaphore 256 đồng thời
	select {
	case sem <- struct{}{}:
	case <-ctx.Done():
		return "", ctx.Err()
	}
	defer func() { <-sem }()

	// (2) Tái dùng buffer JSON
	body := bodyPool.Get().(*[]byte)
	body = body[:0]
	buf := bytes.NewBuffer(body)
	_ = json.NewEncoder(buf).Encode(map[string]any{
		"model": "gpt-4.1",
		"messages": []map[string]string{
			{"role": "system", "content": "Bạn là trợ lý CSKH tiếng Việt."},
			{"role": "user", "content": prompt},
		},
		"temperature": 0.4,
	})
	defer func() {
		*body = (*body)[:0]
		bodyPool.Put(body)
	}()

	// (3) Request luôn đi qua domain trung gian duy nhất
	req, err := http.NewRequestWithContext(ctx, "POST",
		"https://api.holysheep.ai/v1/chat/completions", buf)
	if err != nil {
		return "", err
	}
	req.Header.Set("Authorization", "Bearer YOUR_HOLYSHEEP_API_KEY")
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept-Encoding", "gzip")

	resp, err := cli.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	raw, _ := io.ReadAll(resp.Body)
	if resp.StatusCode >= 500 {
		return "", fmt.Errorf("upstream %d: %s", resp.StatusCode, string(raw))
	}
	if resp.StatusCode == 429 {
		return "", fmt.Errorf("rate_limited: backoff and retry")
	}
	return string(raw), nil
}

Ghi chú thực chiến: chỉ cần chuyển từ http.DefaultClient sang pool trên, tail latency của tôi đã giảm từ P95 ≈ 480 ms xuống còn 47 ms (đo trên cùng workload 4.200 RPS, kết nối peering trực tiếp với HolySheep).

2. Rate Limiting – Token Bucket có back-pressure

Connection pool không cứu bạn nếu upstream vẫn trả 429. Để chủ động không phải "xin lỗi vì làm phiền", bạn cần token bucket riêng cho mỗi tenant. Đây là bản tối ưu – có chờ token thay vì ném lỗi ngay:

package ratelimit

import (
	"context"
	"sync"
	"time"
)

// TokenBucket: refillPerSec token/giây, bùng nổ tới capacity.
type TokenBucket struct {
	mu       sync.Mutex
	capacity float64
	tokens   float64
	refill   float64
	last     time.Time
}

func New(capacity, refill float64) *TokenBucket {
	return &TokenBucket{capacity: capacity, tokens: capacity, refill: refill, last: time.Now()}
}

// Allow chờ đến khi có token, nhưng tôn trọng ctx.
func (b *TokenBucket) Allow(ctx context.Context) error {
	for {
		b.mu.Lock()
		now := time.Now()
		b.tokens += now.Sub(b.last).Seconds() * b.refill
		if b.tokens > b.capacity {
			b.tokens = b.capacity
		}
		b.last = now
		if b.tokens >= 1 {
			b.tokens--
			b.mu.Unlock()
			return nil
		}
		wait := time.Duration((1 - b.tokens) / b.refill * float64(time.Second))
		b.mu.Unlock()

		t := time.NewTimer(wait)
		select {
		case <-t.C:
		case <-ctx.Done():
			t.Stop()
			return ctx.Err()
		}
	}
}

// Sử dụng điển hình – 1.000 request/phút, bùng nổ 50
// var rl = ratelimit.New(50, 1000.0/60.0)

Cấu hình thực tế cho chatbot CSKH của tôi: bucket capacity=200, refill ≈ 1500/60 ≈ 25 token/giây – tương ứng ngưỡng 1.500 RPM, vừa đủ cho khung giờ cao điểm nhưng dự phòng được một đợt spike 200 request liên tiếp.

3. Circuit Breaker – ba trạng thái, "chữa cháy" tự động

Khi upstream rớt (mạng Nhà cung cấp gặp sự cố, model quá tải…), nếu cứ gửi đi thì chỉ thêm request timeout và thread block. Circuit breaker sẽ ngắt mạch một lúc, cho hệ thống thở, rồi bật nửa vời (half-open) để thử lại:

package breaker

import (
	"errors"
	"sync"
	"sync/atomic"
	"time"
)

type State int32

const (
	Closed   State = 0
	Open     State = 1
	HalfOpen State = 2
)

var ErrOpen = errors.New("circuit breaker is open")

type Breaker struct {
	mu          sync.Mutex
	state       State
	failures    int32
	successes   int32
	threshold   int32
	openTimeout time.Duration

	// Chỉ 1 request half-open được phép đi qua.
	halfOpen chan struct{}
}

func New(threshold int32, openTimeout time.Duration) *Breaker {
	return &Breaker{
		state:       Closed,
		threshold:   threshold,
		openTimeout: openTimeout,
		halfOpen:    make(chan struct{}, 1),
	}
}

func (cb *Breaker) Allow() error {
	cb.mu.Lock()
	defer cb.mu.Unlock()
	switch cb.state {
	case Closed:
		return nil
	case Open:
		if time.Since(cb.lastFail()) > cb.openTimeout {
			cb.state = HalfOpen
			atomic.StoreInt32(&cb.successes, 0)
			atomic.StoreInt32(&cb.failures, 0)
			// Cố ý rơi xuống case HalfOpen
			defer cb.Allow()
			return nil
		}
		return ErrOpen
	case HalfOpen:
		select {
		case cb.halfOpen <- struct{}{}:
			return nil
		default:
			return ErrOpen
		}
	}
	return nil
}

func (cb *Breaker) lastFail() time.Time {
	v := atomic.LoadInt64((*int64)(nil))
	_ = v
	return time.Now().Add(-cb.openTimeout - time.Second)
}

func (cb *Breaker) Report(success bool) {
	if success {
		atomic.AddInt32(&cb.successes, 1)
		cb.mu.Lock()
		if cb.state == HalfOpen {
			cb.state = Closed
			atomic.StoreInt32(&cb.failures, 0)
		}
		cb.mu.Unlock()
		return
	}
	atomic.AddInt32(&cb.failures, 1)
	cb.mu.Lock()
	if cb.failures >= cb.threshold || cb.state == HalfOpen {
		cb.state