1.Redis緩存
1.1.設置
go get github.com/redis/go-redis/v9
package cache
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
type RedisCache struct {
client *redis.Client
}
func NewRedisCache(addr, password string, db int) *RedisCache {
client := redis.NewClient(&redis.Options{
Addr: addr,
Password: password,
DB: db,
PoolSize: 20,
MinIdleConns: 5,
DialTimeout: 5 * time.Second,
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
})
return &RedisCache{client: client}
}
// Generic cache methods
func (c *RedisCache) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
return c.client.Set(ctx, key, data, ttl).Err()
}
func (c *RedisCache) Get(ctx context.Context, key string, dest interface{}) error {
data, err := c.client.Get(ctx, key).Bytes()
if err != nil {
return err
}
return json.Unmarshal(data, dest)
}
func (c *RedisCache) Delete(ctx context.Context, keys ...string) error {
return c.client.Del(ctx, keys...).Err()
}
func (c *RedisCache) DeletePattern(ctx context.Context, pattern string) error {
iter := c.client.Scan(ctx, 0, pattern, 100).Iterator()
var keys []string
for iter.Next(ctx) {
keys = append(keys, iter.Val())
}
if len(keys) > 0 {
return c.client.Del(ctx, keys...).Err()
}
return nil
}
1.2.快取預留模式
func (s *UserService) GetUser(ctx context.Context, id uint) (*User, error) {
cacheKey := fmt.Sprintf("user:%d", id)
// 1. Try cache first
var user User
if err := s.cache.Get(ctx, cacheKey, &user); err == nil {
return &user, nil // Cache hit
}
// 2. Cache miss → query DB
dbUser, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, err
}
// 3. Store in cache
s.cache.Set(ctx, cacheKey, dbUser, 30*time.Minute)
return dbUser, nil
}
// Invalidate on update
func (s *UserService) UpdateUser(ctx context.Context, id uint, input UpdateInput) error {
if err := s.repo.Update(ctx, id, input); err != nil {
return err
}
// Invalidate cache
return s.cache.Delete(ctx, fmt.Sprintf("user:%d", id))
}
1.3.快取模式總結
| 圖案 | 閱讀 | 寫 | 使用案例 |
|---|---|---|---|
| 快取旁路 | 應用程式讀取快取→資料庫 | 應用程式寫入資料庫,使快取無效 | 一般 |
| 直寫式 | 應用程式讀取快取 | Cache同步寫入DB | 一致性很重要 |
| 後寫式 | 應用程式讀取快取 | 快取異步寫入資料庫 | 寫重 |
| 通讀 | 如果未命中,則從資料庫快取加載 | 應用程式寫入資料庫 | 重讀 |
1.4. Singleflight(緩存踩踏預防)
import "golang.org/x/sync/singleflight"
var group singleflight.Group
func (s *UserService) GetUserSingleflight(ctx context.Context, id uint) (*User, error) {
cacheKey := fmt.Sprintf("user:%d", id)
// Singleflight: chỉ 1 goroutine query DB, các goroutine khác chờ kết quả
result, err, _ := group.Do(cacheKey, func() (interface{}, error) {
// Try cache
var user User
if err := s.cache.Get(ctx, cacheKey, &user); err == nil {
return &user, nil
}
// Query DB
dbUser, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, err
}
// Cache result
s.cache.Set(ctx, cacheKey, dbUser, 30*time.Minute)
return dbUser, nil
})
if err != nil {
return nil, err
}
return result.(*User), nil
}
2. 文件上傳
2.1.基本文件上傳
func UploadHandler(c *gin.Context) {
// Max 32 MB
c.Request.ParseMultipartForm(32 << 20)
file, header, err := c.Request.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": "No file uploaded"})
return
}
defer file.Close()
// Validate file type
allowedTypes := map[string]bool{
"image/jpeg": true,
"image/png": true,
"image/webp": true,
"application/pdf": true,
}
contentType := header.Header.Get("Content-Type")
if !allowedTypes[contentType] {
c.JSON(400, gin.H{"error": "File type not allowed"})
return
}
// Validate file size (max 10MB)
if header.Size > 10<<20 {
c.JSON(400, gin.H{"error": "File too large (max 10MB)"})
return
}
// Generate safe filename
ext := filepath.Ext(header.Filename)
filename := fmt.Sprintf("%s%s", uuid.New().String(), ext)
// Save to disk
dst := filepath.Join("uploads", filename)
out, err := os.Create(dst)
if err != nil {
c.JSON(500, gin.H{"error": "Failed to save file"})
return
}
defer out.Close()
if _, err := io.Copy(out, file); err != nil {
c.JSON(500, gin.H{"error": "Failed to write file"})
return
}
c.JSON(200, gin.H{
"filename": filename,
"size": header.Size,
"url": fmt.Sprintf("/uploads/%s", filename),
})
}
2.2. S3 上傳(MinIO/AWS S3)
go get github.com/aws/aws-sdk-go-v2/service/s3
go get github.com/aws/aws-sdk-go-v2/config
package storage
import (
"context"
"fmt"
"io"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
type S3Storage struct {
client *s3.Client
bucket string
}
func NewS3Storage(ctx context.Context, bucket string) (*S3Storage, error) {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, err
}
return &S3Storage{
client: s3.NewFromConfig(cfg),
bucket: bucket,
}, nil
}
func (s *S3Storage) Upload(ctx context.Context, key string, body io.Reader, contentType string) (string, error) {
_, err := s.client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(s.bucket),
Key: aws.String(key),
Body: body,
ContentType: aws.String(contentType),
})
if err != nil {
return "", fmt.Errorf("upload to S3: %w", err)
}
return fmt.Sprintf("https://%s.s3.amazonaws.com/%s", s.bucket, key), nil
}
// Presigned URL (direct client upload)
func (s *S3Storage) GetPresignedURL(ctx context.Context, key, contentType string) (string, error) {
presignClient := s3.NewPresignClient(s.client)
req, err := presignClient.PresignPutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(s.bucket),
Key: aws.String(key),
ContentType: aws.String(contentType),
}, s3.WithPresignExpires(15*time.Minute))
if err != nil {
return "", err
}
return req.URL, nil
}
3. 使用 pprof 進行效能分析
import (
"net/http"
_ "net/http/pprof" // Import cho side-effect
)
func main() {
// Expose pprof endpoints
go func() {
// ⚠️ Chỉ expose trong development, KHÔNG expose public!
http.ListenAndServe("localhost:6060", nil)
}()
// Your main server...
}
# CPU profiling
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# Memory profiling
go tool pprof http://localhost:6060/debug/pprof/heap
# Goroutine analysis
go tool pprof http://localhost:6060/debug/pprof/goroutine
# Interactive commands trong pprof:
# top10 - Top 10 functions by CPU/memory
# list funcName - Show source code with annotations
# web - Open flame graph in browser
4. 基準測試
// user_service_test.go
func BenchmarkGetUser(b *testing.B) {
svc := setupService()
ctx := context.Background()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, _ = svc.GetUser(ctx, 1)
}
})
}
func BenchmarkGetUserCached(b *testing.B) {
svc := setupServiceWithCache()
ctx := context.Background()
// Warm cache
svc.GetUser(ctx, 1)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, _ = svc.GetUser(ctx, 1)
}
})
}
# Run benchmarks
go test -bench=. -benchmem -count=3 ./...
# Output:
# BenchmarkGetUser-8 5000 300000 ns/op 2048 B/op 20 allocs/op
# BenchmarkGetUserCached-8 500000 3000 ns/op 256 B/op 3 allocs/op
5. 連接池
// GORM connection pool
sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(25) // Max open connections
sqlDB.SetMaxIdleConns(10) // Max idle connections
sqlDB.SetConnMaxLifetime(5 * time.Minute) // Max connection lifetime
sqlDB.SetConnMaxIdleTime(5 * time.Minute) // Max idle time
// HTTP client pool
httpClient := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
Timeout: 30 * time.Second,
}
下一篇: Go 的微服務架構 — 服務網格、API 閘道和分散式系統。