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 Requests và context 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?
- Goroutine rẻ: tạo 100.000 goroutine chỉ tốn khoảng 2,5 MB stack ban đầu; context switch không qua kernel.
- net/http chuẩn công nghiệp:
http.Transportcung cấp connection pool, HTTP/2 multiplexing, keep-alive tích hợp sẵn – điều mà Node.js hay Python phải vá thủ công. - Channel + select: giúp viết rate limiter, semaphore, back-pressure cực gọn.
- Hệ sinh thái:
sony/gobreaker(5.800+ ★ trên GitHub, được nhắc trong nhiều thread Reddit r/golang),ulule/limiter,go-resty/restyđều production-ready.
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