.net使用redis

本文详细介绍Redis在.NET项目中的安装、配置及使用方法,包括ServiceStack.Redis、StackExchange.Redis和CSRedis等客户端的选择与对比。同时,文章还介绍了Redis的缓存清除命令和分布式锁的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、redis安装、卸载、配置

 

2、.net项目中使用redis

https://www.cnblogs.com/itmu89/p/7118474.html

ServiceStack.Redis是商业版,免费版有限制;

StackExchange.Redis是免费版,早期有Timeout Bug,当前版本使用需要全部使用异步方法方可解决;

CSRedis于2016年开始支持.NETCore一直迭代至今(解决上述Bug),实现了低门槛、高性能,和分区高级玩法的redis-cli SDK;https://github.com/2881099/csredis

 3、redis清除所有缓存命令

flushall

 

4、redis中分布式锁 https://yq.aliyun.com/articles/331961

https://github.com/xiaopotian1990/RedisDemo/blob/master/RedisDemo/Lock/Program.cs

string key="lockKey";//实际项目秒杀此处可换成商品ID
RedisValue token = "123";//Environment.MachineName
var redisDB = cacheService.GetRedisDB();
//LockTake涉及到三个参数:key,token和TimeSpan,分别表示redis数据库中该锁的名称、锁的持有者标识和有效时间
if (redisDB.LockTake(key, token, TimeSpan.FromSeconds(10)))//分布式锁,保证后台和接口中心互斥
{
    try
    {
     
    }
    finally
    {
        redisDB.LockRelease(key, token);
    }
}

 

转载于:https://www.cnblogs.com/slyzly/articles/10436159.html

<think>我们正在讨论的是在.NET环境中使用Redis实现幂等性。幂等性是指无论操作执行一次还是多次,结果都是相同的。在分布式系统中,由于网络重试等原因,客户端可能会重复发送请求,因此需要幂等性来保证数据的一致性。 根据引用[1]和引用[2],我们可以借鉴Java中的实现思路,在.NET中实现类似的幂等性校验。主要思路如下: 1. 使用自定义注解(在.NET中对应的是自定义特性)来标记需要幂等性校验的方法。 2. 在特性中定义必要的参数,如模块名(用于区分不同业务)、过期时间、请求方法等。 3. 使用Redis来存储和校验token(或唯一标识),确保同一请求只被处理一次。 具体步骤: - 客户端在请求业务接口前,先请求一个token(通常是一个唯一标识,如GUID),并将token存入Redis,设置一定的过期时间。 - 客户端在请求业务接口时,将此token放在请求头(Header)中。 - 服务端通过一个拦截器(在.NET中可以是中间件或Action过滤器)来校验token: a. 检查请求头中是否包含token。 b. 检查Redis中是否存在该token,如果不存在,说明请求已处理过(或者token已过期),则拒绝请求。 c. 如果存在,则删除Redis中的token,并继续处理请求。 这样,同一个token只能被使用一次,从而避免了重复请求。 下面我们来实现这个方案: ### 1. 定义自定义特性 在.NET中,我们可以定义一个特性(Attribute)来标记需要幂等性校验的Action方法。 ```csharp [AttributeUsage(AttributeTargets.Method)] public class CheckIdempotentAttribute : Attribute { public string ModuleName { get; set; } // 业务模块名称,用于区分不同的业务 public int Expire { get; set; } = 10; // token过期时间,默认10秒 // 注意:在.NET中,我们通常不需要指定请求方法,因为可以通过Action方法的特性(如HttpPost)来区分。 } ``` ### 2. 创建幂等性校验过滤器 我们将创建一个Action过滤器,用于在Action执行前进行幂等性校验。 ```csharp public class IdempotentCheckFilter : IActionFilter { private readonly IDistributedCache _cache; public IdempotentCheckFilter(IDistributedCache cache) { _cache = cache; } public void OnActionExecuting(ActionExecutingContext context) { // 检查Action方法是否标记了CheckIdempotent特性 var idempotentAttr = context.ActionDescriptor.EndpointMetadata .OfType<CheckIdempotentAttribute>() .FirstOrDefault(); if (idempotentAttr == null) { return; // 没有标记,直接返回 } // 从请求头中获取token if (!context.HttpContext.Request.Headers.TryGetValue("Idempotent-Token", out var tokenHeader)) { context.Result = new BadRequestObjectResult("缺少幂等性Token"); return; } string token = tokenHeader.FirstOrDefault(); if (string.IsNullOrEmpty(token)) { context.Result = new BadRequestObjectResult("无效的Token"); return; } // 构造Redis中存储的key,可以加入模块名以区分不同业务 string cacheKey = $"Idempotent:{idempotentAttr.ModuleName}:{token}"; // 尝试从Redis中获取并删除该token(原子操作) // 注意:Redis中删除操作需要保证原子性,我们可以使用Lua脚本或者Redis的分布式锁,但这里我们使用简单的获取并删除,因为分布式缓存接口不支持原子操作,所以我们可以先获取,然后删除,但这样不是原子的,在高并发下可能有问题。 // 为了解决原子性问题,我们可以使用Redis的StringGetDelete方法(StackExchange.Redis支持),但IDistributedCache接口没有提供原子操作的方法。因此,我们可能需要直接使用StackExchange.Redis。 // 方案调整:如果使用IDistributedCache,由于接口限制,我们无法保证原子性。因此,这里我们使用StackExchange.Redis客户端来实现原子操作。 // 为了简化,我们假设这里使用了StackExchange.Redis的IDatabase。实际项目中,我们可以将过滤器改为使用IDatabase,或者封装一个服务。 // 由于在过滤器中直接使用StackExchange.Redis需要配置连接,我们这里为了演示,先使用IDistributedCache,但注意这不是原子操作,在并发极高的情况下可能有问题(两个请求同时查到存在,然后都删除,但只有一个能成功删除,另一个会失败)。但考虑到重复请求同时到达的概率较低,且过期时间较短,可以接受。 // 尝试获取并删除 var cache = _cache; var value = cache.GetString(cacheKey); if (value == null) { // token不存在,可能是已经使用过或者过期 context.Result = new BadRequestObjectResult("重复请求或Token已过期"); return; } // 删除key cache.Remove(cacheKey); // 注意:这里存在一个时间窗口,在检查到存在和删除之间,可能被其他请求占用。因此,严格场景下应使用原子操作。 // 继续执行Action } public void OnActionExecuted(ActionExecutedContext context) { // 不需要处理 } } ``` ### 3. 在Startup中注册过滤器 在`Startup.cs`的`ConfigureServices`方法中注册过滤器: ```csharp services.AddScoped<IdempotentCheckFilter>(); // 在控制器配置中添加全局过滤器,或者可以在具体的Action上使用特性来添加 services.AddControllers(options => { options.Filters.Add<IdempotentCheckFilter>(); }); ``` 或者,我们也可以选择在需要的方法上添加该过滤器,而不是全局添加。这样更灵活。 ### 4. 提供获取Token的接口 客户端在请求业务接口前,需要先获取一个Token。我们提供一个接口来生成Token并存入Redis。 ```csharp [ApiController] [Route("api/[controller]")] public class IdempotentController : ControllerBase { private readonly IDistributedCache _cache; public IdempotentController(IDistributedCache cache) { _cache = cache; } [HttpGet("token")] public IActionResult GenerateToken(string moduleName, int expireInSeconds = 10) { // 生成一个唯一的token string token = Guid.NewGuid().ToString("N"); string cacheKey = $"Idempotent:{moduleName}:{token}"; // 将token存入Redis,设置过期时间 _cache.SetString(cacheKey, "1", new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(expireInSeconds) }); return Ok(token); } } ``` ### 5. 使用示例 在需要幂等性保护的Action方法上添加`[CheckIdempotent]`特性: ```csharp [HttpPost("create-order")] [CheckIdempotent(ModuleName = "CreateOrder", Expire = 10)] public IActionResult CreateOrder([FromBody] Order order) { // 处理订单创建 // ... return Ok(); } ``` 客户端流程: 1. 先调用`GET /api/idempotent/token?moduleName=CreateOrder`获取token。 2. 在创建订单的请求头中加上`Idempotent-Token: <token>`。 3. 服务端在`CreateOrder`方法执行前会进行校验。 ### 注意事项 1. **原子性问题**:上述过滤器中使用`IDistributedCache`的`GetString`和`Remove`不是原子操作,在极高并发下可能两个请求同时通过检查,然后都执行删除,但只有第一个删除成功,第二个删除时可能已经不存在,但业务逻辑已经执行了两次。为了避免这种情况,我们可以使用Redis的Lua脚本来保证原子性,或者使用分布式锁。但更简单的方式是使用Redis的`StringGetDelete`命令(在StackExchange.Redis中为`IDatabase.StringGetDelete`)。因此,建议在过滤器中直接使用StackExchange.Redis的`IDatabase`接口。 2. **异常处理**:如果业务处理失败,可能需要让token失效,但我们的流程是在校验通过后立即删除token,因此如果业务失败,客户端需要重新获取token并重试。 3. **过期时间**:设置合理的过期时间,避免token长期占用内存。 4. **模块名**:使用模块名可以支持不同业务使用不同的token池,避免token冲突。 ### 改进:使用StackExchange.Redis保证原子性 修改过滤器,使用StackExchange.Redis的`IDatabase`: 首先,安装`StackExchange.Redis`包。 然后,修改过滤器: ```csharp public class IdempotentCheckFilter : IActionFilter { private readonly IConnectionMultiplexer _redis; public IdempotentCheckFilter(IConnectionMultiplexer redis) { _redis = redis; } public void OnActionExecuting(ActionExecutingContext context) { var idempotentAttr = ... // 同上 // 获取token // ... string cacheKey = ... // 同上 var db = _redis.GetDatabase(); // 使用Lua脚本保证原子性:获取并删除 // 因为Redis的GETDEL命令(6.2.0及以上版本支持)可以原子地获取并删除,如果版本低于6.2,可以使用以下Lua脚本: // 脚本:如果key存在,则删除并返回1;否则返回0 var result = (int)db.ScriptEvaluate(@"if redis.call('GET', KEYS[1]) ~= false then redis.call('DEL', KEYS[1]) return 1 else return 0 end", new RedisKey[] { cacheKey }); if (result == 0) { context.Result = new BadRequestObjectResult("重复请求或Token已过期"); return; } } // OnActionExecuted 同上 } ``` 在Startup中注册`IConnectionMultiplexer`: ```csharp services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect("your_redis_connection_string")); ``` 这样,我们就实现了原子性的获取并删除操作,保证了高并发下的幂等性。 ### 总结 在.NET使用Redis实现幂等性的关键在于: 1. 生成唯一token并预存到Redis。 2. 在业务请求中携带token。 3. 在服务端通过过滤器(或中间件)校验token,并在校验通过后立即删除token(原子操作)。 这种方法可以有效地防止重复提交,保证接口的幂等性。 §§相关问题§§ 1..NET中如何保证Redis操作的原子性? 2. 除了使用Redis,还有哪些方法可以实现幂等性? 3. 如何处理在幂等性校验通过后业务执行失败的情况?</think>### .NET 环境下使用 Redis 实现幂等性方案 幂等性指**无论操作执行一次还是多次,结果都相同**。在分布式系统中,通过 Redis 实现幂等性可有效防止重复提交、重复支付等问题。以下是完整实现方案: --- #### 一、核心实现步骤 1. **定义幂等性校验特性** ```csharp [AttributeUsage(AttributeTargets.Method)] public class RedisIdempotentAttribute : Attribute { public string ModuleName { get; } // 业务标识(如:OrderPayment) public int ExpireSeconds { get; } // Redis 键过期时间(秒) public RedisIdempotentAttribute(string moduleName, int expireSeconds = 10) { ModuleName = moduleName; ExpireSeconds = expireSeconds; } } ``` 2. **创建幂等性校验中间件** ```csharp public class IdempotentMiddleware { private readonly RequestDelegate _next; private readonly IDistributedCache _cache; public IdempotentMiddleware(RequestDelegate next, IDistributedCache cache) { _next = next; _cache = cache; } public async Task InvokeAsync(HttpContext context) { var endpoint = context.GetEndpoint(); var attribute = endpoint?.Metadata.GetMetadata<RedisIdempotentAttribute>(); if (attribute == null) { await _next(context); return; } // 从 Header 获取 Token if (!context.Request.Headers.TryGetValue("Idempotent-Token", out var token)) { context.Response.StatusCode = 400; await context.Response.WriteAsync("Missing Idempotent-Token"); return; } // 构造 Redis Key(格式:Idempotent:{Module}:{Token}) var cacheKey = $"Idempotent:{attribute.ModuleName}:{token}"; // 原子性校验:存在则拒绝,不存在则写入 if (await _cache.GetStringAsync(cacheKey) != null) { context.Response.StatusCode = 409; // Conflict await context.Response.WriteAsync("Duplicate request detected"); return; } // 写入 Redis 并设置过期时间 await _cache.SetStringAsync( cacheKey, "processed", new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(attribute.ExpireSeconds) } ); await _next(context); // 继续处理请求 } } ``` 3. **注册中间件(Startup.cs)** ```csharp public void Configure(IApplicationBuilder app) { app.UseMiddleware<IdempotentMiddleware>(); // 其他中间件... } ``` 4. **生成 Token 的接口** ```csharp [ApiController] [Route("api/token")] public class TokenController : ControllerBase { [HttpGet] public IActionResult GenerateToken() { // 实际生产中应结合业务标识加密 return Ok(Guid.NewGuid().ToString("N")); } } ``` --- #### 二、使用示例 1. **客户端流程**: - 调用 `GET /api/token` 获取 Token - 发起业务请求时在 Header 添加:`Idempotent-Token: <token>` 2. **服务端标记需要幂等性的方法**: ```csharp [HttpPost("pay")] [RedisIdempotent("OrderPayment", expireSeconds: 15)] // 启用幂等性校验 public IActionResult ProcessPayment([FromBody] PaymentRequest request) { // 业务处理逻辑 return Ok(new { Success = true }); } ``` --- #### 三、关键机制说明 1. **Token 生命周期**: - 客户端每次操作前获取新 Token - Token 使用后立即在 Redis 标记(设置较短过期时间) - 相同 Token 的重复请求被拦截 [^2] 2. **原子操作保证**: - 使用 Redis 的 `SET key value EX seconds NX` 命令 ```csharp // 等效原子操作(StackExchange.Redis 实现) var database = _cache.GetDatabase(); bool isNew = database.StringSet(cacheKey, "1", TimeSpan.FromSeconds(expire), When.NotExists); ``` 3. **异常处理**: - 409 Conflict:重复请求 - 400 Bad Request:Token 缺失 - 通过中间件统一拦截,业务代码无侵入 --- #### 四、优化建议 1. **Token 增强安全性**: ```csharp // 生成带签名的 Token string GenerateSecureToken(string userId) { var payload = $"{userId}_{Guid.NewGuid()}"; return HMACSHA256(payload, secretKey); } ``` 2. **Redis 集群支持**: ```csharp services.AddStackExchangeRedisCache(options => { options.Configuration = "redis1:6379,redis2:6379"; options.InstanceName = "IdempotentDB"; }); ``` 3. **监控与告警**: - 监控 Redis 命中率 - 报警 409 状态码突增 > **注意**:此方案适用于短时操作的幂等性(如支付、下单),长时间操作需结合状态机机制[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值