在现代应用开发中,高并发和高可用性是客户最为关注的性能指标。而Redis,作为一种优秀的内存数据库,凭借其卓越的性能优势与数据结构,成为了许多开发者的得力助手。然而,很多开发者在使用Redis的过程中,由于缺乏深入的理解,常常会陷入性能瓶颈和数据一致性的问题中。 本文将深入剖析如何在C#开发中高效使用Redis,提升你应用的性能与稳定性。
在开发过程中,尤其是面对用户请求激增时,后端数据库的响应速度往往成为了性能瓶颈。传统的关系型数据库在处理大量读写请求时,容易导致慢查询和锁竞争,这时Redis的引入便显得尤为重要。然而,许多开发者在使用Redis时,常常面临以下几个痛点:
针对以上问题,我们可以采取以下解决方案:
下面是一个完整的C# Redis使用示例,展示如何实现一个简单的Redis服务,以解决上述提到的问题。
c#using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppRedisService
{
/// <summary>
/// Redis缓存服务接口
/// </summary>
public interface IRedisService : IDisposable
{
/// <summary>
/// 连接状态
/// </summary>
bool IsConnected { get; }
// 字符串操作
Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default) where T : class;
Task<string?> GetStringAsync(string key, CancellationToken cancellationToken = default);
Task<bool> SetAsync<T>(string key, T value, TimeSpan? expiry = null, CancellationToken cancellationToken = default);
Task<bool> SetStringAsync(string key, string value, TimeSpan? expiry = null, CancellationToken cancellationToken = default);
// 哈希操作
Task<T?> HashGetAsync<T>(string hashKey, string field, CancellationToken cancellationToken = default) where T : class;
Task<bool> HashSetAsync<T>(string hashKey, string field, T value, CancellationToken cancellationToken = default);
Task<Dictionary<string, T>> HashGetAllAsync<T>(string hashKey, CancellationToken cancellationToken = default) where T : class;
Task<bool> HashDeleteAsync(string hashKey, string field, CancellationToken cancellationToken = default);
// 列表操作
Task<long> ListPushAsync<T>(string key, T value, CancellationToken cancellationToken = default);
Task<T?> ListPopAsync<T>(string key, CancellationToken cancellationToken = default) where T : class;
Task<List<T>> ListRangeAsync<T>(string key, long start = 0, long stop = -1, CancellationToken cancellationToken = default) where T : class;
// 集合操作
Task<bool> SetAddAsync<T>(string key, T value, CancellationToken cancellationToken = default);
Task<bool> SetRemoveAsync<T>(string key, T value, CancellationToken cancellationToken = default);
Task<List<T>> SetMembersAsync<T>(string key, CancellationToken cancellationToken = default) where T : class;
Task<bool> SetContainsAsync<T>(string key, T value, CancellationToken cancellationToken = default);
// 通用操作
Task<bool> KeyExistsAsync(string key, CancellationToken cancellationToken = default);
Task<bool> KeyDeleteAsync(string key, CancellationToken cancellationToken = default);
Task<long> KeyDeleteAsync(IEnumerable<string> keys, CancellationToken cancellationToken = default);
Task<bool> KeyExpireAsync(string key, TimeSpan expiry, CancellationToken cancellationToken = default);
Task<TimeSpan?> KeyTimeToLiveAsync(string key, CancellationToken cancellationToken = default);
// 发布订阅
Task<long> PublishAsync<T>(string channel, T message, CancellationToken cancellationToken = default);
Task SubscribeAsync<T>(string channel, Func<string, T, Task> handler, CancellationToken cancellationToken = default) where T : class;
Task UnsubscribeAsync(string channel, CancellationToken cancellationToken = default);
// 事务操作
Task<bool> ExecuteTransactionAsync(Func<ITransaction, Task> operations, CancellationToken cancellationToken = default);
// 锁操作
Task<IDisposable?> AcquireLockAsync(string lockKey, TimeSpan expiry, CancellationToken cancellationToken = default);
}
}
c#using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace AppRedisService
{
/// <summary>
/// Redis缓存服务实现
/// </summary>
public class RedisService : IRedisService
{
private readonly Lazy<ConnectionMultiplexer> _connectionMultiplexer;
private readonly RedisOptions _options;
private readonly ILogger<RedisService> _logger;
private readonly JsonSerializerOptions _jsonOptions;
private readonly SemaphoreSlim _connectionSemaphore = new(1, 1);
private bool _disposed = false;
public RedisService(IOptions<RedisOptions> options, ILogger<RedisService> logger)
{
_options = options.Value ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
_connectionMultiplexer = new Lazy<ConnectionMultiplexer>(CreateConnection);
_logger.LogInformation("Redis服务已初始化,连接字符串: {ConnectionString}", _options.ConnectionString);
}
public bool IsConnected => _connectionMultiplexer.IsValueCreated &&
_connectionMultiplexer.Value.IsConnected;
private ConnectionMultiplexer CreateConnection()
{
try
{
var config = new ConfigurationOptions
{
EndPoints = { _options.ConnectionString },
ConnectTimeout = _options.ConnectTimeout,
SyncTimeout = _options.SyncTimeout,
AsyncTimeout = _options.AsyncTimeout,
ConnectRetry = _options.ConnectRetry,
AbortOnConnectFail = _options.AbortOnConnectFail,
KeepAlive = _options.KeepAlive,
DefaultDatabase = _options.Database
};
if (!string.IsNullOrEmpty(_options.Password))
config.Password = _options.Password;
if (_options.UseSsl)
config.Ssl = true;
var connection = ConnectionMultiplexer.Connect(config);
// 注册连接事件
connection.ConnectionFailed += OnConnectionFailed;
connection.ConnectionRestored += OnConnectionRestored;
connection.ErrorMessage += OnErrorMessage;
_logger.LogInformation("Redis连接创建成功");
return connection;
}
catch (Exception ex)
{
_logger.LogError(ex, "创建Redis连接失败");
throw;
}
}
private IDatabase GetDatabase()
{
if (_disposed)
throw new ObjectDisposedException(nameof(RedisService));
return _connectionMultiplexer.Value.GetDatabase(_options.Database);
}
private ISubscriber GetSubscriber()
{
if (_disposed)
throw new ObjectDisposedException(nameof(RedisService));
return _connectionMultiplexer.Value.GetSubscriber();
}
#region 字符串操作
public async Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default) where T : class
{
return await ExecuteWithRetryAsync(async () =>
{
var value = await GetDatabase().StringGetAsync(FormatKey(key));
if (!value.HasValue) return null;
return JsonSerializer.Deserialize<T>(value!, _jsonOptions);
}, cancellationToken);
}
public async Task<string?> GetStringAsync(string key, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
var value = await GetDatabase().StringGetAsync(FormatKey(key));
return value.HasValue ? value.ToString() : null;
}, cancellationToken);
}
public async Task<bool> SetAsync<T>(string key, T value, TimeSpan? expiry = null, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
var jsonValue = JsonSerializer.Serialize(value, _jsonOptions);
return await GetDatabase().StringSetAsync(FormatKey(key), jsonValue, expiry);
}, cancellationToken);
}
public async Task<bool> SetStringAsync(string key, string value, TimeSpan? expiry = null, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
return await GetDatabase().StringSetAsync(FormatKey(key), value, expiry);
}, cancellationToken);
}
#endregion
#region 哈希操作
public async Task<T?> HashGetAsync<T>(string hashKey, string field, CancellationToken cancellationToken = default) where T : class
{
return await ExecuteWithRetryAsync(async () =>
{
var value = await GetDatabase().HashGetAsync(FormatKey(hashKey), field);
if (!value.HasValue) return null;
return JsonSerializer.Deserialize<T>(value!, _jsonOptions);
}, cancellationToken);
}
public async Task<bool> HashSetAsync<T>(string hashKey, string field, T value, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
var jsonValue = JsonSerializer.Serialize(value, _jsonOptions);
return await GetDatabase().HashSetAsync(FormatKey(hashKey), field, jsonValue);
}, cancellationToken);
}
public async Task<Dictionary<string, T>> HashGetAllAsync<T>(string hashKey, CancellationToken cancellationToken = default) where T : class
{
return await ExecuteWithRetryAsync(async () =>
{
var hash = await GetDatabase().HashGetAllAsync(FormatKey(hashKey));
var result = new Dictionary<string, T>();
foreach (var item in hash)
{
if (item.Value.HasValue)
{
var deserializedValue = JsonSerializer.Deserialize<T>(item.Value!, _jsonOptions);
if (deserializedValue != null)
result[item.Name!] = deserializedValue;
}
}
return result;
}, cancellationToken);
}
public async Task<bool> HashDeleteAsync(string hashKey, string field, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
return await GetDatabase().HashDeleteAsync(FormatKey(hashKey), field);
}, cancellationToken);
}
#endregion
#region 列表操作
public async Task<long> ListPushAsync<T>(string key, T value, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
var jsonValue = JsonSerializer.Serialize(value, _jsonOptions);
return await GetDatabase().ListLeftPushAsync(FormatKey(key), jsonValue);
}, cancellationToken);
}
public async Task<T?> ListPopAsync<T>(string key, CancellationToken cancellationToken = default) where T : class
{
return await ExecuteWithRetryAsync(async () =>
{
var value = await GetDatabase().ListLeftPopAsync(FormatKey(key));
if (!value.HasValue) return null;
return JsonSerializer.Deserialize<T>(value!, _jsonOptions);
}, cancellationToken);
}
public async Task<List<T>> ListRangeAsync<T>(string key, long start = 0, long stop = -1, CancellationToken cancellationToken = default) where T : class
{
return await ExecuteWithRetryAsync(async () =>
{
var values = await GetDatabase().ListRangeAsync(FormatKey(key), start, stop);
var result = new List<T>();
foreach (var value in values)
{
if (value.HasValue)
{
var deserializedValue = JsonSerializer.Deserialize<T>(value!, _jsonOptions);
if (deserializedValue != null)
result.Add(deserializedValue);
}
}
return result;
}, cancellationToken);
}
#endregion
#region 集合操作
public async Task<bool> SetAddAsync<T>(string key, T value, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
var jsonValue = JsonSerializer.Serialize(value, _jsonOptions);
return await GetDatabase().SetAddAsync(FormatKey(key), jsonValue);
}, cancellationToken);
}
public async Task<bool> SetRemoveAsync<T>(string key, T value, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
var jsonValue = JsonSerializer.Serialize(value, _jsonOptions);
return await GetDatabase().SetRemoveAsync(FormatKey(key), jsonValue);
}, cancellationToken);
}
public async Task<List<T>> SetMembersAsync<T>(string key, CancellationToken cancellationToken = default) where T : class
{
return await ExecuteWithRetryAsync(async () =>
{
var values = await GetDatabase().SetMembersAsync(FormatKey(key));
var result = new List<T>();
foreach (var value in values)
{
if (value.HasValue)
{
var deserializedValue = JsonSerializer.Deserialize<T>(value!, _jsonOptions);
if (deserializedValue != null)
result.Add(deserializedValue);
}
}
return result;
}, cancellationToken);
}
public async Task<bool> SetContainsAsync<T>(string key, T value, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
var jsonValue = JsonSerializer.Serialize(value, _jsonOptions);
return await GetDatabase().SetContainsAsync(FormatKey(key), jsonValue);
}, cancellationToken);
}
#endregion
#region 通用操作
public async Task<bool> KeyExistsAsync(string key, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
return await GetDatabase().KeyExistsAsync(FormatKey(key));
}, cancellationToken);
}
public async Task<bool> KeyDeleteAsync(string key, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
return await GetDatabase().KeyDeleteAsync(FormatKey(key));
}, cancellationToken);
}
public async Task<long> KeyDeleteAsync(IEnumerable<string> keys, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
var redisKeys = keys.Select(k => new RedisKey(FormatKey(k))).ToArray();
return await GetDatabase().KeyDeleteAsync(redisKeys);
}, cancellationToken);
}
public async Task<bool> KeyExpireAsync(string key, TimeSpan expiry, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
return await GetDatabase().KeyExpireAsync(FormatKey(key), expiry);
}, cancellationToken);
}
public async Task<TimeSpan?> KeyTimeToLiveAsync(string key, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
return await GetDatabase().KeyTimeToLiveAsync(FormatKey(key));
}, cancellationToken);
}
#endregion
#region 发布订阅
public async Task<long> PublishAsync<T>(string channel, T message, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
var jsonMessage = JsonSerializer.Serialize(message, _jsonOptions);
return await GetSubscriber().PublishAsync(channel, jsonMessage);
}, cancellationToken);
}
public async Task SubscribeAsync<T>(string channel, Func<string, T, Task> handler, CancellationToken cancellationToken = default) where T : class
{
await ExecuteWithRetryAsync(async () =>
{
await GetSubscriber().SubscribeAsync(channel, async (ch, message) =>
{
try
{
if (message.HasValue)
{
var deserializedMessage = JsonSerializer.Deserialize<T>(message!, _jsonOptions);
if (deserializedMessage != null)
await handler(ch!, deserializedMessage);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "处理订阅消息时发生错误,频道: {Channel}", channel);
}
});
return true;
}, cancellationToken);
}
public async Task UnsubscribeAsync(string channel, CancellationToken cancellationToken = default)
{
await ExecuteWithRetryAsync(async () =>
{
await GetSubscriber().UnsubscribeAsync(channel);
return true;
}, cancellationToken);
}
#endregion
#region 事务操作
public async Task<bool> ExecuteTransactionAsync(Func<ITransaction, Task> operations, CancellationToken cancellationToken = default)
{
return await ExecuteWithRetryAsync(async () =>
{
var transaction = GetDatabase().CreateTransaction();
await operations(transaction);
return await transaction.ExecuteAsync();
}, cancellationToken);
}
#endregion
#region 分布式锁
public async Task<IDisposable?> AcquireLockAsync(string lockKey, TimeSpan expiry, CancellationToken cancellationToken = default)
{
var key = FormatKey($"lock:{lockKey}");
var value = Guid.NewGuid().ToString();
var acquired = await ExecuteWithRetryAsync(async () =>
{
return await GetDatabase().StringSetAsync(key, value, expiry, When.NotExists);
}, cancellationToken);
if (acquired)
{
return new RedisLock(GetDatabase(), key, value, _logger);
}
return null;
}
#endregion
#region 辅助方法
private string FormatKey(string key)
{
return string.IsNullOrEmpty(_options.InstanceName) ? key : $"{_options.InstanceName}:{key}";
}
private async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation, CancellationToken cancellationToken, int maxRetries = 3)
{
var retryCount = 0;
Exception? lastException = null;
while (retryCount <= maxRetries && !cancellationToken.IsCancellationRequested)
{
try
{
return await operation();
}
catch (RedisTimeoutException ex)
{
lastException = ex;
_logger.LogWarning(ex, "Redis操作超时,重试次数: {RetryCount}/{MaxRetries}", retryCount + 1, maxRetries + 1);
}
catch (RedisConnectionException ex)
{
lastException = ex;
_logger.LogWarning(ex, "Redis连接异常,重试次数: {RetryCount}/{MaxRetries}", retryCount + 1, maxRetries + 1);
}
catch (Exception ex)
{
_logger.LogError(ex, "Redis操作发生未预期异常");
throw;
}
retryCount++;
if (retryCount <= maxRetries)
{
await Task.Delay(TimeSpan.FromMilliseconds(100 * Math.Pow(2, retryCount)), cancellationToken);
}
}
_logger.LogError(lastException, "Redis操作重试{MaxRetries}次后仍然失败", maxRetries + 1);
throw lastException ?? new InvalidOperationException("Redis操作失败");
}
#endregion
#region 事件处理
private void OnConnectionFailed(object? sender, ConnectionFailedEventArgs e)
{
_logger.LogError("Redis连接失败: {EndPoint}, {FailureType}, {Exception}",
e.EndPoint, e.FailureType, e.Exception?.Message);
}
private void OnConnectionRestored(object? sender, ConnectionFailedEventArgs e)
{
_logger.LogInformation("Redis连接已恢复: {EndPoint}", e.EndPoint);
}
private void OnErrorMessage(object? sender, RedisErrorEventArgs e)
{
_logger.LogError("Redis错误消息: {EndPoint}, {Message}", e.EndPoint, e.Message);
}
#endregion
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
try
{
if (_connectionMultiplexer.IsValueCreated)
{
_connectionMultiplexer.Value.ConnectionFailed -= OnConnectionFailed;
_connectionMultiplexer.Value.ConnectionRestored -= OnConnectionRestored;
_connectionMultiplexer.Value.ErrorMessage -= OnErrorMessage;
_connectionMultiplexer.Value.Dispose();
}
_connectionSemaphore.Dispose();
}
catch (Exception ex)
{
_logger.LogError(ex, "释放Redis连接时发生错误");
}
finally
{
_disposed = true;
}
}
}
~RedisService()
{
Dispose(false);
}
#endregion
}
/// <summary>
/// Redis分布式锁
/// </summary>
internal class RedisLock : IDisposable
{
private readonly IDatabase _database;
private readonly string _key;
private readonly string _value;
private readonly ILogger _logger;
private bool _disposed = false;
public RedisLock(IDatabase database, string key, string value, ILogger logger)
{
_database = database;
_key = key;
_value = value;
_logger = logger;
}
public void Dispose()
{
if (!_disposed)
{
try
{
// 使用Lua脚本确保只释放自己持有的锁
const string script = @"
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end";
_database.ScriptEvaluate(script, new RedisKey[] { _key }, new RedisValue[] { _value });
}
catch (Exception ex)
{
_logger.LogError(ex, "释放Redis锁时发生错误,Key: {Key}", _key);
}
finally
{
_disposed = true;
}
}
}
}
}
c#using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppRedisService
{
/// <summary>
/// Redis服务扩展方法
/// </summary>
public static class RedisServiceExtensions
{
public static IServiceCollection AddRedisService(this IServiceCollection services, IConfiguration configuration)
{
// 配置选项
services.Configure<RedisOptions>(configuration.GetSection(RedisOptions.SectionName));
// 注册Redis服务为单例
services.AddSingleton<IRedisService, RedisService>();
return services;
}
public static IServiceCollection AddRedisService(this IServiceCollection services, Action<RedisOptions> configureOptions)
{
services.Configure(configureOptions);
services.AddSingleton<IRedisService, RedisService>();
return services;
}
}
}
c#using AppRedisService;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AppRedisService
{
internal class Program
{
static async Task Main(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
// 添加日志
builder.Services.AddLogging(loggingBuilder =>
{
loggingBuilder.AddConsole();
loggingBuilder.AddDebug();
});
// 添加Redis服务
builder.Services.AddRedisService(builder.Configuration);
// 或者使用委托配置
// builder.Services.AddRedisService(options =>
// {
// options.ConnectionString = "127.0.0.1:6379";
// options.Database = 0;
// options.InstanceName = "MyApp";
// });
var app = builder.Build();
// 测试Redis服务
var redisService = app.Services.GetRequiredService<IRedisService>();
await TestRedisOperations(redisService);
await app.RunAsync();
}
static async Task TestRedisOperations(IRedisService redis)
{
const string testKey = "test_key";
const string testValue = "Hello Redis!";
// 保存数据到Redis
Console.WriteLine("保存数据到Redis...");
bool setResult = await redis.SetStringAsync(testKey, testValue, TimeSpan.FromMinutes(5));
Console.WriteLine($"保存结果: {setResult}");
// 获取数据
Console.WriteLine("从Redis获取数据...");
string? getValue = await redis.GetStringAsync(testKey);
if (getValue != null)
{
Console.WriteLine($"获取到的数据: {getValue}");
}
else
{
Console.WriteLine("未找到该键的数据");
}
// 测试删除数据
Console.WriteLine("删除数据...");
bool deleteResult = await redis.KeyDeleteAsync(testKey);
Console.WriteLine($"删除结果: {deleteResult}");
// 尝试再次获取已删除的数据
Console.WriteLine("再次获取已删除的数据...");
getValue = await redis.GetStringAsync(testKey);
if (getValue == null)
{
Console.WriteLine("数据已成功删除");
}
}
}
}

这个简单的Redis服务可以被用作缓存用户会话、存储临时数据、或者作为应用数据的快速访问层。由于Redis的高性能和低延迟,它非常适合提供实时数据存取。
亲爱的读者,您在使用Redis时遇到过哪些问题?或者您有什么优化的实用技巧?欢迎在评论区分享您的经验和问题!
c#public async Task<bool> CacheDataAsync(string key, string value, TimeSpan? expiry = null)
{
return await Database.StringSetAsync(key, value, expiry);
}
public async Task<string?> RetrieveDataAsync(string key)
{
return await Database.StringGetAsync(key);
}
觉得这篇文章有用吗?请分享给更多同行,一起提升我们的C#开发技能! 万分感谢您的支持与转发!
本文介绍了如何合理使用Redis来解决C#开发中的性能瓶颈问题。通过引入合适的缓存策略、管理好Redis连接以及保持数据同步,我们能显著提升应用的响应速度与稳定性。希望这篇文章能给你的开发工作带来启发,帮助你创造出性能卓越的应用。
感谢您的阅读,期待与您在下次的技术交流中再见!
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!