.NET8 × Redis 实战宝典:从配置到落地,搞定高并发缓存就这篇!

引言

Redis作为介于应用与数据库间的高性能内存数据层,其核心特点是将极致的内存速度与丰富的内置数据结构(如字符串、哈希、列表、集合、有序集合)相融合,并通过提供原子操作,使开发者能够直接、高效地实现会话、社交图谱、排行榜及消息队列等复杂业务逻辑,从而有效解决了传统数据库在高并发实时场景下面临的性能瓶颈。

实践

第一步:项目准备

  1. 安装NuGet包在你的.NET 8项目(Web API、MVC或Console等)中,通过NuGet包管理器或命令行安装以下包:

    复制代码
    Install-Package StackExchange.Redis 
    Install-Package Microsoft.Extensions.Caching.StackExchangeRedis
  2. 配置连接字符串 :在 appsettings.json 文件中添加Redis配置:

    json

    json 复制代码
    {
      "Redis": {
        "ConnectionString": "localhost:6379,abortConnect=false,connectRetry=5" // 替换为你的实际地址
      }
    }

    连接字符串关键参数说明

    • abortConnect=false:确保在网络故障时,客户端会自动重连而非直接失败。
    • connectRetry=5:初始连接失败时的重试次数。
    • 生产环境建议 :启用SSL并使用密码,格式如:your-redis-host:6380,password=your-password,ssl=True,abortConnect=false

第二步:核心帮助类与配置

  1. 创建核心帮助类 :新建 IRedisService.cs与RedisService
  • 异步优先 :StackExchange.Redis 的异步方法(Async 后缀)性能更佳,建议一直使用。
C# 复制代码
/// <summary>
/// Redis 缓存服务接口
/// </summary>
public interface IRedisService
{
    #region 默认数据库操作方法

    /// <summary>
    /// 设置字符串值到默认数据库
    /// </summary>
    /// <param name="key">键</param>
    /// <param name="value">值</param>
    /// <param name="expiry">过期时间</param>
    /// <returns>异步任务</returns>
    Task SetStringAsync(string key, string value, TimeSpan? expiry = null);

    /// <summary>
    /// 从默认数据库获取字符串值
    /// </summary>
    /// <param name="key">键</param>
    /// <returns>字符串值</returns>
    Task<string> GetStringAsync(string key);

    /// <summary>
    /// 设置对象值到默认数据库(序列化为 JSON)
    /// </summary>
    /// <typeparam name="T">对象类型</typeparam>
    /// <param name="key">键</param>
    /// <param name="value">对象值</param>
    /// <param name="expiry">过期时间</param>
    /// <returns>异步任务</returns>
    Task SetObjectAsync<T>(string key, T value, TimeSpan? expiry = null);

    /// <summary>
    /// 从默认数据库获取对象值(从 JSON 反序列化)
    /// </summary>
    /// <typeparam name="T">对象类型</typeparam>
    /// <param name="key">键</param>
    /// <returns>对象值</returns>
    Task<T> GetObjectAsync<T>(string key);

    /// <summary>
    /// 从默认数据库删除键
    /// </summary>
    /// <param name="key">键</param>
    /// <returns>删除成功返回 true,否则返回 false</returns>
    Task<bool> DeleteAsync(string key);

    /// <summary>
    /// 检查默认数据库中键是否存在
    /// </summary>
    /// <param name="key">键</param>
    /// <returns>存在返回 true,否则返回 false</returns>
    Task<bool> ExistsAsync(string key);

    /// <summary>
    /// 设置默认数据库中键的过期时间
    /// </summary>
    /// <param name="key">键</param>
    /// <param name="expiry">过期时间</param>
    /// <returns>设置成功返回 true,否则返回 false</returns>
    Task<bool> ExpireAsync(string key, TimeSpan expiry);

    #endregion

    #region Hash 操作

    /// <summary>
    /// 在默认数据库中设置哈希字段值
    /// </summary>
    /// <param name="key">哈希键</param>
    /// <param name="field">字段名</param>
    /// <param name="value">字段值</param>
    /// <returns>异步任务</returns>
    Task HashSetAsync(string key, string field, string value);

    /// <summary>
    /// 在默认数据库中获取哈希字段值
    /// </summary>
    /// <param name="key">哈希键</param>
    /// <param name="field">字段名</param>
    /// <returns>字段值</returns>
    Task<string> HashGetAsync(string key, string field);

    /// <summary>
    /// 在默认数据库中获取所有哈希字段和值
    /// </summary>
    /// <param name="key">哈希键</param>
    /// <returns>字段值字典</returns>
    Task<Dictionary<string, string>> HashGetAllAsync(string key);

    #endregion

    #region List 操作

    /// <summary>
    /// 在默认数据库中向列表左侧推入元素
    /// </summary>
    /// <param name="key">列表键</param>
    /// <param name="value">值</param>
    /// <returns>推入后列表长度</returns>
    Task<long> ListLeftPushAsync(string key, string value);

    /// <summary>
    /// 在默认数据库中从列表右侧弹出元素
    /// </summary>
    /// <param name="key">列表键</param>
    /// <returns>弹出的值</returns>
    Task<string> ListRightPopAsync(string key);

    #endregion

    /// <summary>
    /// 获取指定数据库编号的操作服务
    /// </summary>
    /// <param name="database">数据库编号</param>
    /// <returns>指定数据库的 Redis 服务实例</returns>
    IRedisService Database(int database);

    #region 发布订阅操作

    /// <summary>
    /// 向指定频道发布消息
    /// </summary>
    /// <param name="channel">频道名称</param>
    /// <param name="message">消息内容</param>
    /// <returns>接收到消息的订阅者数量</returns>
    Task<long> PublishAsync(string channel, string message);

    /// <summary>
    /// 订阅指定频道的消息
    /// </summary>
    /// <param name="channel">频道名称</param>
    /// <param name="handler">消息处理回调</param>
    /// <returns>异步任务</returns>
    Task SubscribeAsync(string channel, Action<string, string> handler);

    /// <summary>
    /// 取消订阅指定频道
    /// </summary>
    /// <param name="channel">频道名称</param>
    /// <returns>异步任务</returns>
    Task UnsubscribeAsync(string channel);

    #endregion
}

如果不指定DataBase 那么就使用默认的 0,如果需要自己指定DataBase可以先调用Database()方法指定对应的库

C# 复制代码
/// <summary>
/// Redis 缓存服务实现
/// </summary>
public class RedisService : IRedisService
{
    private readonly IConnectionMultiplexer _connection;
    private readonly ILogger<RedisService> _logger;
    private readonly int _databaseNumber;
    private readonly IDatabase _database;

    private readonly ISubscriber _subscriber;


    /// <summary>
    /// 构造函数 - 创建默认数据库实例
    /// </summary>
    /// <param name="connection">Redis 连接</param>
    /// <param name="logger">日志记录器</param>
    public RedisService(IConnectionMultiplexer connection, ILogger<RedisService> logger)
        : this(connection, logger, 0) // 0 表示使用 Redis 默认数据库
    {
    }

    /// <summary>
    /// 私有构造函数 - 创建指定数据库实例
    /// </summary>
    /// <param name="connection">Redis 连接</param>
    /// <param name="logger">日志记录器</param>
    /// <param name="databaseNumber">数据库编号</param>
    private RedisService(IConnectionMultiplexer connection, ILogger<RedisService> logger, int databaseNumber)
    {
        _connection = connection;
        _logger = logger;
        _databaseNumber = databaseNumber;
        _database = connection.GetDatabase(databaseNumber);

        _subscriber = _connection.GetSubscriber();
    }

    /// <summary>
    /// 获取指定数据库编号的操作服务
    /// </summary>
    public IRedisService Database(int database)
    {
        // 返回指定数据库的新实例
        return new RedisService(_connection, _logger, database);
    }

    #region String 操作

    public async Task SetStringAsync(string key, string value, TimeSpan? expiry = null)
    {
        try
        {
            await _database.StringSetAsync(key, value, expiry, When.Always);
            _logger.LogDebug($"Set string value for key: {key} in database: {GetDatabaseDisplay()}");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error setting string value for key: {key} in database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    public async Task<string> GetStringAsync(string key)
    {
        try
        {
            var value = await _database.StringGetAsync(key);
            _logger.LogDebug($"Got string value for key: {key} from database: {GetDatabaseDisplay()}");
            return value;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error getting string value for key: {key} from database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    public async Task SetObjectAsync<T>(string key, T value, TimeSpan? expiry = null)
    {
        try
        {
            var json = JsonSerializer.Serialize(value, new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            });
            await SetStringAsync(key, json, expiry);
            _logger.LogDebug($"Set object value for key: {key} in database: {GetDatabaseDisplay()}");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error setting object value for key: {key} in database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    public async Task<T> GetObjectAsync<T>(string key)
    {
        try
        {
            var json = await GetStringAsync(key);
            if (string.IsNullOrEmpty(json))
                return default(T);

            var value = JsonSerializer.Deserialize<T>(json, new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            });
            _logger.LogDebug($"Got object value for key: {key} from database: {GetDatabaseDisplay()}");
            return value;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error getting object value for key: {key} from database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    public async Task<bool> DeleteAsync(string key)
    {
        try
        {
            var result = await _database.KeyDeleteAsync(key);
            _logger.LogDebug($"Deleted key: {key} from database: {GetDatabaseDisplay()}, result: {result}");
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error deleting key: {key} from database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    public async Task<bool> ExistsAsync(string key)
    {
        try
        {
            var result = await _database.KeyExistsAsync(key);
            _logger.LogDebug($"Checked key existence: {key} in database: {GetDatabaseDisplay()}, result: {result}");
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error checking key existence: {key} in database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    public async Task<bool> ExpireAsync(string key, TimeSpan expiry)
    {
        try
        {
            var result = await _database.KeyExpireAsync(key, expiry);
            _logger.LogDebug($"Set expiration for key: {key} in database: {GetDatabaseDisplay()}, result: {result}");
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error setting expiration for key: {key} in database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    #endregion

    #region Hash 操作

    public async Task HashSetAsync(string key, string field, string value)
    {
        try
        {
            await _database.HashSetAsync(key, field, value);
            _logger.LogDebug($"Set hash field for key: {key}, field: {field} in database: {GetDatabaseDisplay()}");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error setting hash field for key: {key}, field: {field} in database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    public async Task<string> HashGetAsync(string key, string field)
    {
        try
        {
            var value = await _database.HashGetAsync(key, field);
            _logger.LogDebug($"Got hash field for key: {key}, field: {field} from database: {GetDatabaseDisplay()}");
            return value;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error getting hash field for key: {key}, field: {field} from database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    public async Task<Dictionary<string, string>> HashGetAllAsync(string key)
    {
        try
        {
            var hashEntries = await _database.HashGetAllAsync(key);
            var result = hashEntries.ToDictionary(x => x.Name.ToString(), x => x.Value.ToString());
            _logger.LogDebug($"Got all hash fields for key: {key} from database: {GetDatabaseDisplay()}, count: {result.Count}");
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error getting all hash fields for key: {key} from database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    #endregion

    #region List 操作

    public async Task<long> ListLeftPushAsync(string key, string value)
    {
        try
        {
            var result = await _database.ListLeftPushAsync(key, value);
            _logger.LogDebug($"Pushed value to left of list key: {key} in database: {GetDatabaseDisplay()}, new length: {result}");
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error pushing value to left of list key: {key} in database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    public async Task<string> ListRightPopAsync(string key)
    {
        try
        {
            var result = await _database.ListRightPopAsync(key);
            _logger.LogDebug($"Popped value from right of list key: {key} in database: {GetDatabaseDisplay()}");
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error popping value from right of list key: {key} in database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    #endregion

     #region 发布订阅操作

    public async Task<long> PublishAsync(string channel, string message)
    {
        try
        {
            var result = await _subscriber.PublishAsync(channel, message);
            _logger.LogDebug($"Published message to channel: {channel} in database: {GetDatabaseDisplay()}, subscribers: {result}");
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error publishing message to channel: {channel} in database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    public async Task SubscribeAsync(string channel, Action<string, string> handler)
    {
        try
        {
            // 订阅频道
            await _subscriber.SubscribeAsync(channel, (redisChannel, message) =>
            {
                try
                {
                    handler(redisChannel, message);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, $"Error in subscription handler for channel: {redisChannel}");
                }
            });

            _logger.LogDebug($"Subscribed to channel: {channel} in database: {GetDatabaseDisplay()}");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error subscribing to channel: {channel} in database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    public async Task UnsubscribeAsync(string channel)
    {
        try
        {
            // 取消订阅
            await _subscriber.UnsubscribeAsync(channel, null);

            _logger.LogDebug($"Unsubscribed from channel: {channel} in database: {GetDatabaseDisplay()}");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error unsubscribing from channel: {channel} in database: {GetDatabaseDisplay()}");
            throw;
        }
    }

    #endregion

    private string GetDatabaseDisplay()
    {
        return _databaseNumber == 0 ? "default" : _databaseNumber.ToString();
    }
}
  1. 在Program.cs中注册服务
  • 这个帮助类采用单例模式管理 ConnectionMultiplexer,以确保其在整个应用生命周期内被重用,这是官方推荐的最佳实践。
  • 处理连接故障 :代码中已将 AbortOnConnectFail 设为 false,库会自动重连。避免在代码中频繁检查 IsConnected 属性并手动重建连接。
csharp 复制代码
// 使用Autofac来作为依赖注入容器
 builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

 builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
 {
     // 在 Program.cs 的 Autofac 配置中保持简单注册
     containerBuilder.Register(c =>
     {
         var config = c.Resolve<IConfiguration>();
         var connectionString = config.GetConnectionString("Redis") ?? "localhost:6379";
         var configuration = ConfigurationOptions.Parse(connectionString, true);
         configuration.AbortOnConnectFail = false; // 允许自动重连
         configuration.ConnectRetry = 5;
         configuration.ClientName = AppDomain.CurrentDomain.FriendlyName; // 便于诊断
         return ConnectionMultiplexer.Connect(configuration);
     }).As<IConnectionMultiplexer>().SingleInstance();
     // IConnectionMultiplexers 要使用单例来注册。
     
     // 注册 Redis服务类
     containerBuilder.RegisterType<RedisService>().As<IRedisService>();

     containerBuilder.RegisterModule(new AutofacModule());
 });

第三步:在应用中使用

csharp 复制代码
[Route("api/[controller]")]
[ApiController]
public class RedisTestController : ControllerBase
{
    private readonly IRedisService _redisService;
    private readonly ILogger<RedisTestController> _logger;

    public RedisTestController(IRedisService redisService, ILogger<RedisTestController> logger)
    {
        _redisService = redisService;
        _logger = logger;
    }

    #region 默认数据库操作

    [HttpPost("string")]
    public async Task<IActionResult> SetString([FromQuery] string key, [FromQuery] string value, [FromQuery] int? expireSeconds = null)
    {
        try
        {
            var expiry = expireSeconds.HasValue ? TimeSpan.FromSeconds(expireSeconds.Value) : TimeSpan.FromSeconds(1);
            await _redisService.SetStringAsync(key, value, expiry);
            return Ok(new { message = "String value set successfully in default database" });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error setting string value");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    [HttpGet("string")]
    public async Task<IActionResult> GetString([FromQuery] string key)
    {
        try
        {
            var value = await _redisService.GetStringAsync(key);
            return Ok(new { key, value });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting string value");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    [HttpPost("object")]
    public async Task<IActionResult> SetObject([FromQuery] string key, [FromBody] object data, [FromQuery] int? expireSeconds = null)
    {
        try
        {
            var expiry = expireSeconds.HasValue ? TimeSpan.FromSeconds(expireSeconds.Value) : TimeSpan.FromSeconds(1);
            await _redisService.SetObjectAsync(key, data, expiry);
            return Ok(new { message = "Object set successfully in default database" });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error setting object");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    [HttpGet("object")]
    public async Task<IActionResult> GetObject([FromQuery] string key)
    {
        try
        {
            var value = await _redisService.GetObjectAsync<object>(key);
            return Ok(new { key, value });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting object");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    [HttpDelete]
    public async Task<IActionResult> Delete([FromQuery] string key)
    {
        try
        {
            var result = await _redisService.DeleteAsync(key);
            return Ok(new { key, deleted = result });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error deleting key");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    #endregion

    #region 指定数据库操作

    [HttpPost("db/{database}/string")]
    public async Task<IActionResult> SetStringInDatabase(int database, [FromQuery] string key, [FromQuery] string value)
    {
        try
        {
            await _redisService.Database(database).SetStringAsync(key, value, TimeSpan.FromMinutes(30));
            return Ok(new { message = $"String value set successfully in database {database}" });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error setting string value in database {database}");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    [HttpGet("db/{database}/string")]
    public async Task<IActionResult> GetStringFromDatabase(int database, [FromQuery] string key)
    {
        try
        {
            var value = await _redisService.Database(database).GetStringAsync(key);
            return Ok(new { key, value, database });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error getting string value from database {database}");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    [HttpPost("db/{database}/object")]
    public async Task<IActionResult> SetObjectInDatabase(int database, [FromQuery] string key, [FromBody] object data)
    {
        try
        {
            await _redisService.Database(database).SetObjectAsync(key, data, TimeSpan.FromMinutes(30));
            return Ok(new { message = $"Object set successfully in database {database}" });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Error setting object in database {database}");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    #endregion

    #region Hash 操作

    [HttpPost("hash")]
    public async Task<IActionResult> HashSet([FromQuery] string key, [FromQuery] string field, [FromQuery] string value)
    {
        try
        {
            await _redisService.HashSetAsync(key, field, value);
            return Ok(new { message = "Hash field set successfully" });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error setting hash field");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    [HttpGet("hash")]
    public async Task<IActionResult> HashGet([FromQuery] string key, [FromQuery] string field)
    {
        try
        {
            var value = await _redisService.HashGetAsync(key, field);
            return Ok(new { key, field, value });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting hash field");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    [HttpGet("hash-all")]
    public async Task<IActionResult> HashGetAll([FromQuery] string key)
    {
        try
        {
            var values = await _redisService.HashGetAllAsync(key);
            return Ok(new { key, values });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting all hash fields");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    #endregion

    #region List 操作

    [HttpPost("list/push")]
    public async Task<IActionResult> ListLeftPush([FromQuery] string key, [FromQuery] string value)
    {
        try
        {
            var length = await _redisService.ListLeftPushAsync(key, value);
            return Ok(new { key, value, newListLength = length });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error pushing to list");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    [HttpGet("list/pop")]
    public async Task<IActionResult> ListRightPop([FromQuery] string key)
    {
        try
        {
            var value = await _redisService.ListRightPopAsync(key);
            return Ok(new { key, value });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error popping from list");
            return StatusCode(500, new { error = "Internal server error" });
        }
    }

    #endregion
}
相关推荐
帅那个帅9 小时前
go的雪花算法代码分享
开发语言·后端·golang
酒酿萝卜皮10 小时前
Elastic Search 聚合查询
后端
程序员清风10 小时前
阿里二面:新生代垃圾回收为啥使用标记复制算法?
java·后端·面试
sino爱学习10 小时前
Java 三元表达式(?:)的常见坑总结
java·后端
❀͜͡傀儡师10 小时前
Spring Boot函数式编程:轻量级路由函数替代传统Controller
java·spring boot·后端
Drift_Dream10 小时前
Node.js 第二课:用核心模块构建你的第一个服务器
前端·后端
superman超哥10 小时前
仓颉Actor模型的实现机制深度解析
开发语言·后端·python·c#·仓颉
用户990450177800911 小时前
若依审批流-转交
后端
PFinal社区_南丞11 小时前
服务器进程日志分析:从头皮发麻到AI解救
运维·后端