介紹 使用 testcontainers 是在本地開發 Golang 應用程式的一個高效方式。這可以讓我們在不需要依賴外部環境的情況下,模擬應用程式在實際生產環境中的運行狀況。
安裝 testcontainers 在 Go 專案中,我們可以通過以下指令來導入 testcontainers
go get github.com/testcontainers/testcontainers-go 透過 Redis 實踐一個 rate limiter package user type Limiter struct { client *redis.Client limit int limitPeriod time.Duration // 1 hour for limitPeriod counterWindow time.Duration // 1 minute for example, 1/60 of the period } func NewLimiter(client *redis.Client, limit int, period, expiry time.Duration) *Limiter { return &Limiter{ client: client, limit: limit, limitPeriod: period, counterWindow: expiry, } } func (r *Limiter) AllowRequest(ctx context.Context, key string, incr int) error { now := time.Now() timestamp := fmt.Sprint(now.Truncate(r.counterWindow).Unix()) val, err := r.client.HIncrBy(ctx, key, timestamp, int64(incr)).Result() if err != nil { return err } if val >= int64(r.limit) { return ErrRateLimitExceeded(0, r.limit, r.limitPeriod, now.Add(r.limitPeriod)) } r.client.Expire(ctx, key, r.limitPeriod) result, err := r.client.HGetAll(ctx, key).Result() if err != nil { return err } threshold := fmt.Sprint(now.Add(-r.limitPeriod).Unix()) total := 0 for k, v := range result { if k > threshold { i, _ := strconv.Atoi(v) total += i } else { r.client.HDel(ctx, key, k) } } if total >= int(r.limit) { return ErrRateLimitExceeded(0, r.limit, r.limitPeriod, now.Add(r.limitPeriod)) } return nil } type RateLimitExceeded struct { Remaining int Limit int Period time.Duration Reset time.Time } func ErrRateLimitExceeded(remaining int, limit int, period time.Duration, reset time.Time) error { return RateLimitExceeded{ Remaining: remaining, Limit: limit, Period: period, Reset: reset, } } func (e RateLimitExceeded) Error() string { return fmt.Sprintf( "rate limit of %d per %v has been exceeded and resets at %v", e.Limit, e.Period, e.Reset) } 創建和啟動 Redis 容器 正式的產品通常會使用 config 來管理 Redis 位置,這個 demo 中直接使用 localhost:6379 來展示。
...