見出し画像

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"]
```


この記事が気に入ったらサポートをしてみませんか?