![見出し画像](https://assets.st-note.com/production/uploads/images/144208596/rectangle_large_type_2_4f8a90d75d4a1c2a3297b138412a7034.jpeg?width=1200)
Go言語でRedisを用いて、マイクロサービスのAPIレートリミット
microservice-workspace/api-gateway-service/cmd/api/main.go
``` go
package main
import (
"broker/cmd/api/handlers"
"broker/internal/config"
"broker/middlewares"
"fmt"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
amqp "github.com/rabbitmq/amqp091-go"
"github.com/redis/go-redis/v9"
"log"
"math"
"net/http"
"os"
"time"
)
const webPort = "80"
func main() {
// Initialize the REDIS here
redisClient := redis.NewClient(&redis.Options{
Addr: "redis:6379",
Password: "",
DB: 0,
})
// connect to rabbitmq
rabbitConn, err := connect()
if err != nil {
log.Println(err)
os.Exit(1)
}
defer rabbitConn.Close()
// setting configuration for global use
apiConfig := &config.Config{
Rabbit: rabbitConn,
RedisClient: redisClient,
}
localApiConfig := &handlers.LocalApiConfig{
Config: apiConfig,
}
// initialize the gin router
router := gin.Default()
// Configure CORS
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:3000", "http://localhost", "https://*", "http://*"}, // Specify the exact origin of your Next.js app
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
AllowCredentials: true, // Important: Must be true when credentials are included
MaxAge: 12 * time.Hour,
}))
// apply rate limiting middleware here
router.Use(middlewares.RateLimitMiddleware(redisClient, 2, time.Minute))
// routes
router.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
router.POST("/", localApiConfig.Broker)
router.POST("/handle", localApiConfig.HandleSubmission)
// routes for calling grpc services
router.POST("/log-grpc", localApiConfig.LogViaGRPC)
router.POST("/payment", localApiConfig.PaymentViaGRPC)
// start the server
log.Printf("Starting broker service on port %s\n", webPort)
log.Fatal(router.Run(":" + webPort))
}
// connect to rabbitmq api-gateway
func connect() (*amqp.Connection, error) {
var counts int64
var backOffTime = 1 * time.Second
var connection *amqp.Connection
for {
c, err := amqp.Dial("amqp://guest:guest@rabbitmq:5672/")
if err != nil {
fmt.Println("RabbitMQ not yet ready...")
counts++
} else {
connection = c
break
}
if counts > 5 {
fmt.Println(err)
return nil, err
}
backOffTime = time.Duration(math.Pow(float64(counts), 2)) * time.Second
log.Println("backing off...")
time.Sleep(backOffTime)
continue
}
log.Println("Connected to RabbitMQ...")
return connection, nil
}
```
microservice-workspace/api-gateway-service/middlewares/rate_limit.go
``` go
package middlewares
import (
"broker/helpers"
"fmt"
"github.com/gin-gonic/gin"
"github.com/redis/go-redis/v9"
"net/http"
"time"
)
func RateLimitMiddleware(redisClient *redis.Client, rateLimit int, duration time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
ip := c.ClientIP()
key := "rate_limit_" + ip
// Increment request count
count, err := redisClient.Incr(c, key).Result()
if err != nil {
helpers.ErrorJSON(c, err, http.StatusInternalServerError)
c.Abort()
return
}
// set expiration for the key if this is the first request.
if count == 1 {
redisClient.Expire(c, key, duration)
}
// check rate limit
if count > int64(rateLimit) {
resetTime, _ := redisClient.TTL(c, key).Result()
helpers.ErrorJSON(c, fmt.Errorf("rate limit exceeded, try again in %.0f seconds", resetTime.Seconds()), http.StatusTooManyRequests)
c.Abort()
return
}
c.Next()
}
}
```
docker-compose.yml -> Redis service
``` yml
redis:
image: 'redis:6.2-alpine'
ports:
- "6379:6379"
deploy:
mode: replicated
replicas: 1
volumes:
- ./db-data/redis/:/data/db
command: ["redis-server", "--appendonly", "yes"]
```