如何配置Microsoft.Extensions.Caching.StackExchangeRedis禁用Eval命令
解决Microsoft.Extensions.Caching.StackExchangeRedis禁用EVAL权限后的SET命令替代方案
问题根源
Microsoft.Extensions.Caching.StackExchangeRedis的默认RedisCache实现依赖EVAL命令来原子性处理缓存操作(比如同时设置值与过期时间、滑动过期更新),当Redis用户无EVAL权限时就会抛出NOPERM异常。
解决方案:自定义分布式缓存实现
由于无法启用EVAL权限,我们可以直接基于StackExchange.Redis实现一个自定义的分布式缓存类,使用Redis原生的SET命令(带过期参数)替代EVAL。
1. 实现自定义Redis缓存类
创建CustomRedisCache类,实现IDistributedCache接口,直接调用StackExchange.Redis的字符串操作API:
using Microsoft.Extensions.Caching.Distributed; using StackExchange.Redis; using System; public class CustomRedisCache : IDistributedCache { private readonly IDatabase _redisDatabase; public CustomRedisCache(IConnectionMultiplexer connectionMultiplexer) { _redisDatabase = connectionMultiplexer.GetDatabase(); } public byte[] Get(string key) { return _redisDatabase.StringGet(key); } public async Task<byte[]> GetAsync(string key, CancellationToken token = default) { return await _redisDatabase.StringGetAsync(key).ConfigureAwait(false); } public void Set(string key, byte[] value, DistributedCacheEntryOptions options) { var expiry = ResolveExpiry(options); _redisDatabase.StringSet(key, value, expiry); } public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default) { var expiry = ResolveExpiry(options); await _redisDatabase.StringSetAsync(key, value, expiry).ConfigureAwait(false); } public void Refresh(string key) { // 滑动过期/刷新操作依赖EVAL,禁用EVAL时不支持此功能 throw new NotSupportedException("Refresh operation requires EVAL, which is not permitted for the current Redis user."); } public async Task RefreshAsync(string key, CancellationToken token = default) { throw new NotSupportedException("Refresh operation requires EVAL, which is not permitted for the current Redis user."); } public void Remove(string key) { _redisDatabase.KeyDelete(key); } public async Task RemoveAsync(string key, CancellationToken token = default) { await _redisDatabase.KeyDeleteAsync(key).ConfigureAwait(false); } private TimeSpan? ResolveExpiry(DistributedCacheEntryOptions options) { if (options.AbsoluteExpirationRelativeToNow.HasValue) { return options.AbsoluteExpirationRelativeToNow; } if (options.AbsoluteExpiration.HasValue) { var expiry = options.AbsoluteExpiration.Value - DateTimeOffset.UtcNow; return expiry > TimeSpan.Zero ? expiry : null; } // 滑动过期依赖EVAL实现原子更新,禁用EVAL时不支持 if (options.SlidingExpiration.HasValue) { throw new NotSupportedException("Sliding expiration requires EVAL, which is not permitted for the current Redis user."); } return null; } }
2. 替换默认缓存服务注册
在.NET 6的Program.cs中,注册StackExchange.Redis连接复用器,并将默认的IDistributedCache实现替换为自定义类:
var builder = WebApplication.CreateBuilder(args); // 注册StackExchange.Redis连接复用器 builder.Services.AddSingleton<IConnectionMultiplexer>(sp => { var redisConfig = ConfigurationOptions.Parse(builder.Configuration.GetConnectionString("Redis")); return ConnectionMultiplexer.Connect(redisConfig); }); // 替换默认分布式缓存为自定义实现 builder.Services.AddSingleton<IDistributedCache, CustomRedisCache>(); // 其他服务配置... var app = builder.Build(); // 中间件配置... app.Run();
注意事项
- 滑动过期限制:自定义实现不支持滑动过期和Refresh操作,因为这些功能需要EVAL命令来原子性更新缓存过期时间。如果业务依赖滑动过期,只能考虑启用Redis用户的EVAL权限,或者调整业务逻辑避免使用滑动过期。
- 版本兼容性:StackExchange.Redis 2.2.88完全支持
StringSetAsync的过期参数,无需担心版本兼容问题。
内容的提问来源于stack exchange,提问作者EdCastanio
相关产品推荐
相关产品推荐

