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
更多关于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中实现请求的顺序执行,确保同一用户的多个请求按顺序处理。

