.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
}
相关推荐
用户8356290780511 小时前
如何使用 Python 从 Word 文档中批量提取表格数据
后端·python
l***37091 小时前
spring 跨域CORS Filter
java·后端·spring
aiopencode1 小时前
APP 公钥与 MD5 信息在工程中的价值 一次签名排查过程带来的经验总结
后端
ServBay2 小时前
Django 6.0 发布,新增原生任务队列与 CSP 支持
后端·python·django
用户2190326527352 小时前
Spring Boot 4.0 整合 RabbitMQ 注解方式使用指南
后端
PPPPickup3 小时前
easychat---创建,获取,获取详细,退群,解散,添加与移除群组
java·开发语言·后端·maven
回家路上绕了弯3 小时前
大表优化实战指南:从千万到亿级数据的性能蜕变
分布式·后端
Home3 小时前
23 种设计模式--桥接(Bridge)模式(结构型模式二)
java·后端
编程修仙3 小时前
第九篇 Spring中的代理思想
java·后端·spring