English 简体中文 繁體中文 한국 사람 日本語 Deutsch русский بالعربية TÜRKÇE português คนไทย french
查看: 7|回复: 0

在 .NET Core中如何使用 Redis 创建分布式锁

[复制链接]
查看: 7|回复: 0

在 .NET Core中如何使用 Redis 创建分布式锁

[复制链接]
查看: 7|回复: 0

379

主题

0

回帖

1147

积分

金牌会员

积分
1147
wsz

379

主题

0

回帖

1147

积分

金牌会员

积分
1147
2025-2-6 13:42:01 | 显示全部楼层 |阅读模式
在 .NET Core WebApi 中使用 Redis 创建分布式锁可以通过 StackExchange.Redis 库来实现。分布式锁用于确保在分布式系统中,同一时间只有一个进程可以执行某段代码。
1. 场景描述

在支付系统中,可能会出现以下并发问题:

  • 用户同时发起多次支付请求,导致重复扣款。
  • 多个请求同时处理同一个订单,导致数据不一致。
通过分布式锁,可以确保同一时间只有一个请求能够执行关键操作(如扣款)。
<hr>2. 实现步骤

2.1 安装 StackExchange.Redis 包

首先,安装 Redis 客户端库:
dotnet add package StackExchange.Redis2.2 配置 Redis 连接

在 appsettings.json 中添加 Redis 连接字符串:
{  "ConnectionStrings": {    "Redis": "localhost:6379"  }}2.3 创建分布式锁工具类

创建一个工具类来封装 Redis 分布式锁的逻辑:
using StackExchange.Redis;using System;using System.Threading.Tasks;public class RedisDistributedLock{    private readonly IDatabase _redisDatabase;    private readonly string _lockKey;    private readonly string _lockValue;    private readonly TimeSpan _expiry;    public RedisDistributedLock(IDatabase redisDatabase, string lockKey, string lockValue, TimeSpan expiry)    {        _redisDatabase = redisDatabase;        _lockKey = lockKey;        _lockValue = lockValue;        _expiry = expiry;    }    public async Task<bool> AcquireLockAsync()    {        // 尝试设置锁,仅当键不存在时才成功        return await _redisDatabase.StringSetAsync(_lockKey, _lockValue, _expiry, When.NotExists);    }    public async Task ReleaseLockAsync()    {        // 使用 Lua 脚本确保只有锁的持有者才能释放锁        var luaScript = @"            if redis.call('GET', KEYS[1]) == ARGV[1] then                return redis.call('DEL', KEYS[1])            else                return 0            end";        await _redisDatabase.ScriptEvaluateAsync(luaScript, new RedisKey[] { _lockKey }, new RedisValue[] { _lockValue });    }}2.4 在 Web API 中使用分布式锁

在 Web API 的控制器中使用分布式锁来确保支付操作的原子性。
2.4.1 注册 Redis 服务

在 Startup.cs 或 Program.cs 中注册 Redis 服务:
// 添加 Redis 服务builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>ConnectionMultiplexer.Connect(builder.Configuration.GetConnectionString("Redis")));2.4.2 创建支付控制器

在 Controllers 文件夹中创建一个 PaymentController,并在其中使用分布式锁:
using Microsoft.AspNetCore.Mvc;using StackExchange.Redis;using System;using System.Threading.Tasks;    [ApiController]    [Route("api/[controller]")]    public class PaymentController : ControllerBase    {        private readonly IDatabase _redisDatabase;        public PaymentController(IConnectionMultiplexer redis)        {            _redisDatabase = redis.GetDatabase();        }        [HttpPost("pay")]        public async Task<IActionResult> ProcessPayment([FromBody] PaymentRequest request)        {            // 创建分布式锁            var lockKey = $"PaymentLock:{request.OrderId}"; // 锁的键,基于订单 ID            var lockValue = Guid.NewGuid().ToString(); // 锁的值,确保唯一性            var expiry = TimeSpan.FromSeconds(10); // 锁的过期时间            var distributedLock = new RedisDistributedLock(_redisDatabase, lockKey, lockValue, expiry);            try            {                // 尝试获取锁                if (await distributedLock.AcquireLockAsync())                {                    Console.WriteLine("已获取锁,正在处理付款...");                    // 模拟支付处理                    bool paymentSuccess = await ProcessPaymentAsync(request.UserId, request.OrderId, request.Amount);                    if (paymentSuccess)                    {                        return Ok(new { Message = "付款成功!" });                    }                    else                    {                        return BadRequest(new { Message = "付款失败!" });                    }                }                else                {                    return Conflict(new { Message = "正在处理此订单的另一个付款请求..." });                }            }            finally            {                // 释放锁                await distributedLock.ReleaseLockAsync();            }        }          }<hr>3. 代码说明

3.1 分布式锁的实现


  • AcquireLockAsync: 使用 Redis 的 SET key value NX EX 命令尝试获取锁。NX 表示仅在键不存在时设置,`EX 设置键的过期时间。
  • ReleaseLockAsync: 使用 Lua 脚本确保只有锁的持有者才能释放锁,避免误删其他请求的锁。
3.2 支付控制器的使用


  • 锁的键: 使用订单 ID 作为锁的键(如 PaymentLock:202501061410455506968463210),确保同一订单的支付请求串行化。
  • 锁的值: 使用 GUID 作为锁的值,确保锁的唯一性。
  • 锁的过期时间: 设置合理的过期时间(如 10 秒),防止锁被长时间占用。
3.3 支付处理逻辑


  • ProcessPaymentAsync: 模拟支付处理逻辑,包括调用支付网关、扣减余额等操作。
<hr>4. 测试 API

4.1 启动 Web API

运行项目,启动 Web API。
4.2 发送支付请求

使用工具(如 Postman 或 curl)发送支付请求:
POST /api/payment/payContent-Type: application/json{    "userId": "9527",    "orderId": "202501061410455506968463210"    }4.3 测试并发场景

同时发送多个相同的支付请求,观察是否只有一个请求能够成功获取锁并处理支付。

<hr>5. 注意事项


  • 锁的粒度:

    • 锁的粒度要适中。如果锁的粒度过大(如全局锁),可能导致性能问题;如果粒度过小,可能增加复杂性。
    • 在支付系统中,通常以订单 ID 或用户 ID 作为锁的粒度。

  • 锁的过期时间:

    • 设置合理的过期时间,避免锁被长时间占用导致死锁。
    • 如果业务逻辑执行时间较长,可以动态延长锁的过期时间。

  • 锁的可靠性:

    • Redis 需要高可用,否则可能导致锁失效。可以使用 Redis 集群或 Redlock 算法提高可靠性。

  • 异常处理:

    • 确保锁的释放操作放在 finally 块中,避免因异常导致锁无法释放。

  • 幂等性:

    • 支付系统需要支持幂等性,即使多次请求,也只会产生一次扣款。

<hr>6. 总结

在 .NET Core Web API 中使用 Redis 创建分布式锁,可以带来以下好处:

  • 解决并发问题,确保数据一致性。
  • 提高系统的可靠性和性能。
  • 简化代码逻辑,降低开发复杂度。
  • 支持高并发、分布式环境和高可用需求。
通过合理使用 Redis 分布式锁,可以构建高可靠、高性能的分布式系统,满足复杂的业务需求。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

379

主题

0

回帖

1147

积分

金牌会员

积分
1147

QQ|智能设备 | 粤ICP备2024353841号-1

GMT+8, 2025-3-10 14:57 , Processed in 3.513121 second(s), 30 queries .

Powered by 智能设备

©2025

|网站地图