You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何配置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 06:20:35