引言
Redis作为介于应用与数据库间的高性能内存数据层,其核心特点是将极致的内存速度与丰富的内置数据结构(如字符串、哈希、列表、集合、有序集合)相融合,并通过提供原子操作,使开发者能够直接、高效地实现会话、社交图谱、排行榜及消息队列等复杂业务逻辑,从而有效解决了传统数据库在高并发实时场景下面临的性能瓶颈。
实践
第一步:项目准备
-
安装NuGet包:在你的.NET 8项目(Web API、MVC或Console等)中,通过NuGet包管理器或命令行安装以下包:
Install-Package StackExchange.Redis Install-Package Microsoft.Extensions.Caching.StackExchangeRedis -
配置连接字符串 :在
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。
第二步:核心帮助类与配置
- 创建核心帮助类 :新建
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();
}
}
- 在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
}