Golang中实现类似Laravel的Redis锁功能

Golang中实现类似Laravel的Redis锁功能 我希望针对特定用户的同一API的多个请求能够顺序执行。如果同一用户对该API的3个请求同时到达,那么第一个请求应立即处理,第二个和第三个请求应等待。第二个请求只有在第一个请求完成后才开始执行。第三个请求只有在第二个请求完成后才开始执行,依此类推。

例如,用户连续进行3次扫码支付:

Request R1 POST create-txn/user001  {amount : 50}
Request R2 POST create-txn/user001  {amount : 100}
Request R3 POST create-txn/user001  {amount : 10}

在 Laravel 中,我们这样处理:

use Illuminate\Support\Facades\Cache;
use Illuminate\Contracts\Cache\LockTimeoutException;


function createTxn()
{
    $lock = Cache::lock("create_txn_" . $postdata['user_id'] . '_lock', 30);
    try {
        $lock->block(5); // Lock acquired after waiting maximum of 5 seconds..

        // code for creating txn
    } catch (LockTimeoutException $e) {
        throw new ServiceErrorException('create txn api LockTimeout Exception', 105);
    } finally {
        optional($lock)->release();
    }
}

我想要上述逻辑的 Go 等效代码。我尝试了 bsm/redislock 和互斥锁,但没有得到预期的结果。需要 Go 语言的示例。

我尝试了下面的代码,但也没有成功:

package main
import (
  "context"
  "fmt"
  "time"

  "github.com/bsm/redislock"
  "github.com/redis/go-redis/v9"
   "github.com/gin-gonic/gin"
)

func main() {

router := gin.Default()

    router.GET("/test/:id", getTestByID)
    router.Run("localhost:8080")
    }
    
    

func getTestByID(c *gin.Context) {

    id := c.Param("id")
    lockKey := "test_"+id;
    fmt.Println("recieved", id, lockKey)
	// Connect to redis.
	client := redis.NewClient(&redis.Options{
		Network:	"tcp",
		Addr:		"127.0.0.1:6379",
		DB:      9,
	})
	defer client.Close()

	// Create a new lock client.
	locker := redislock.New(client)

	ctx := context.Background()

	// Try to obtain lock.
	lock, _ := locker.Obtain(ctx, lockKey, 5*time.Second, nil)
	


	// Don't forget to defer Release.
	defer lock.Release(ctx)
	fmt.Println("I have a lock!")

	// Sleep and check the remaining TTL.
	
	time.Sleep(4*time.Second)
	fmt.Println(5*time.Second)
	
	//lock.Release(ctx)

}

当多次调用 localhost:8080/test/4 时,第一次打印可以立即为所有调用执行,但由于锁的存在,从第二个请求开始,第二个和第三个打印语句应该被延迟。


更多关于Golang中实现类似Laravel的Redis锁功能的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中实现类似Laravel的Redis锁功能的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中实现Redis分布式锁来确保同一用户的请求顺序执行,可以使用bsm/redislock包,但需要注意锁的获取和释放逻辑。以下是针对您需求的完整示例:

package main

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/bsm/redislock"
	"github.com/gin-gonic/gin"
	"github.com/redis/go-redis/v9"
)

var (
	redisClient *redis.Client
	locker      *redislock.Client
)

func initRedis() {
	redisClient = redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
		DB:   0,
	})
	locker = redislock.New(redisClient)
}

func createTxnHandler(c *gin.Context) {
	userID := c.Param("user_id")
	lockKey := "create_txn_" + userID + "_lock"
	
	ctx := context.Background()
	
	// 尝试获取锁,最多等待5秒
	lock, err := locker.Obtain(ctx, lockKey, 30*time.Second, &redislock.Options{
		RetryStrategy: redislock.LinearBackoff(100 * time.Millisecond),
	})
	
	if err == redislock.ErrNotObtained {
		c.JSON(http.StatusConflict, gin.H{
			"error": "Could not obtain lock after 5 seconds",
			"code":  105,
		})
		return
	} else if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Internal server error",
		})
		return
	}
	
	// 确保锁被释放
	defer func() {
		if err := lock.Release(ctx); err != nil {
			fmt.Printf("Failed to release lock: %v\n", err)
		}
	}()
	
	// 模拟业务处理
	fmt.Printf("Processing request for user %s at %v\n", userID, time.Now())
	time.Sleep(2 * time.Second) // 模拟处理时间
	
	// 实际业务逻辑
	var requestBody struct {
		Amount float64 `json:"amount"`
	}
	
	if err := c.ShouldBindJSON(&requestBody); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	
	// 这里执行实际的交易创建逻辑
	fmt.Printf("Created transaction for user %s: amount %.2f\n", userID, requestBody.Amount)
	
	c.JSON(http.StatusOK, gin.H{
		"status":  "success",
		"user_id": userID,
		"amount":  requestBody.Amount,
	})
}

func main() {
	initRedis()
	
	router := gin.Default()
	router.POST("/create-txn/:user_id", createTxnHandler)
	
	router.Run(":8080")
}

对于更精确的控制,可以使用带重试的锁获取方式:

func createTxnHandlerWithRetry(c *gin.Context) {
	userID := c.Param("user_id")
	lockKey := "create_txn_" + userID + "_lock"
	
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	
	// 使用重试策略获取锁
	lock, err := locker.Obtain(ctx, lockKey, 30*time.Second, &redislock.Options{
		RetryStrategy: redislock.LimitRetry(
			redislock.LinearBackoff(100*time.Millisecond),
			50, // 最多重试50次,即5秒
		),
	})
	
	if err != nil {
		if err == context.DeadlineExceeded {
			c.JSON(http.StatusConflict, gin.H{
				"error": "Lock timeout after 5 seconds",
				"code":  105,
			})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	
	defer lock.Release(ctx)
	
	// 业务逻辑
	fmt.Printf("Acquired lock for user %s\n", userID)
	
	var requestBody struct {
		Amount float64 `json:"amount"`
	}
	
	if err := c.ShouldBindJSON(&requestBody); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	
	// 模拟处理时间
	time.Sleep(1 * time.Second)
	
	c.JSON(http.StatusOK, gin.H{
		"status":  "processed",
		"user_id": userID,
		"amount":  requestBody.Amount,
	})
}

如果需要确保锁的自动续期,可以添加监控:

func createTxnHandlerWithRefresh(c *gin.Context) {
	userID := c.Param("user_id")
	lockKey := "create_txn_" + userID + "_lock"
	
	ctx := context.Background()
	
	// 获取锁
	lock, err := locker.Obtain(ctx, lockKey, 10*time.Second, nil)
	if err != nil {
		c.JSON(http.StatusConflict, gin.H{"error": "Could not obtain lock"})
		return
	}
	
	// 创建刷新上下文
	refreshCtx, refreshCancel := context.WithCancel(ctx)
	defer refreshCancel()
	
	// 启动锁刷新协程
	go func() {
		ticker := time.NewTicker(5 * time.Second)
		defer ticker.Stop()
		
		for {
			select {
			case <-refreshCtx.Done():
				return
			case <-ticker.C:
				if ok, _ := lock.TTL(ctx); ok {
					lock.Refresh(ctx, 10*time.Second, nil)
				}
			}
		}
	}()
	
	// 确保锁被释放
	defer func() {
		refreshCancel()
		lock.Release(ctx)
	}()
	
	// 业务逻辑
	var requestBody struct {
		Amount float64 `json:"amount"`
	}
	
	if err := c.ShouldBindJSON(&requestBody); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	
	// 长时间处理
	time.Sleep(15 * time.Second)
	
	c.JSON(http.StatusOK, gin.H{
		"status":  "completed",
		"user_id": userID,
		"amount":  requestBody.Amount,
	})
}

这些示例展示了如何使用Redis锁在Go中实现请求的顺序执行,确保同一用户的多个请求按顺序处理。

回到顶部