在高并发的互联网应用中,Redis作为缓存和数据存储的核心组件,其性能直接影响整个系统的响应速度。然而,很多C#开发者在使用Redis时,往往只关注基本的读写操作,却忽略了连接管理、异常处理、性能监控等关键环节。本文将通过一个完整的Redis服务封装案例,带你掌握从连接优化到性能压测的全套技能。
许多项目中Redis连接管理存在严重问题:频繁创建连接、连接泄漏、超时配置不当,这些都会导致性能下降甚至系统崩溃。
Redis操作中的网络超时、连接断开等异常往往被忽略,导致程序在生产环境中表现不稳定。
缺乏有效的性能监控手段,无法及时发现Redis的性能瓶颈和潜在问题。
首先,我们来看核心的Redis服务封装类:
C#public class SafeRedisService : IDisposable
{
private static readonly Lazy<ConnectionMultiplexer> _connection =
new Lazy<ConnectionMultiplexer>(() =>
{
var config = new ConfigurationOptions
{
EndPoints = { "47.105.156.186:6379" },
// 优化连接参数
ConnectTimeout = 30000, // 连接超时30秒
SyncTimeout = 30000, // 同步超时30秒
AsyncTimeout = 30000, // 异步超时30秒
ConnectRetry = 5, // 重试5次
AbortOnConnectFail = false, // 连接失败不终止
KeepAlive = 60, // 保持连接60秒
// 网络配置优化
ReconnectRetryPolicy = new ExponentialRetry(5000, 60000), // 指数退避重连
CheckCertificateRevocation = false,
Password = "Iseeyou123"
};
return ConnectionMultiplexer.Connect(config);
});
private static ConnectionMultiplexer Connection => _connection.Value;
public static IDatabase Database => Connection.GetDatabase();
}
核心优化点:
Lazy<T>
确保连接对象的线程安全单例在所有Redis操作中,我们都需要妥善处理异常:
C#public async Task<T> GetAsync<T>(string key) where T : class
{
try
{
var value = await Database.StringGetAsync(key);
return value.HasValue ? JsonSerializer.Deserialize<T>(value) : null;
}
catch (RedisTimeoutException ex)
{
Console.WriteLine($"Redis超时: {ex.Message}");
return null;
}
}
public async Task<bool> SetAsync<T>(string key, T value, TimeSpan? expiry = null)
{
try
{
var jsonValue = JsonSerializer.Serialize(value);
return await Database.StringSetAsync(key, jsonValue, expiry);
}
catch (Exception ex)
{
Console.WriteLine($"Redis写入失败: {ex.Message}");
return false;
}
}
关键实践:
RedisTimeoutException
等特定异常单个操作在高并发场景下效率低下,批量操作是必备技能:
C#public async Task<Dictionary<string, T>> BatchGetAsync<T>(string[] keys) where T : class
{
try
{
var batch = Database.CreateBatch();
var tasks = keys.Select(key => new
{
Key = key,
Task = batch.StringGetAsync(key)
}).ToArray();
batch.Execute();
await Task.WhenAll(tasks.Select(t => t.Task));
var result = new Dictionary<string, T>();
foreach (var item in tasks)
{
if (item.Task.Result.HasValue)
{
try
{
result[item.Key] = JsonSerializer.Deserialize<T>(item.Task.Result);
}
catch (JsonException)
{
// 反序列化失败,跳过该项
}
}
}
return result;
}
catch (Exception ex)
{
Console.WriteLine($"Redis批量读取失败: {ex.Message}");
return new Dictionary<string, T>();
}
}
性能提升关键:
CreateBatch()
将多个操作打包Task.WhenAll
并发等待所有操作完成实时监控Redis连接状态是运维的重要环节:
C#public async Task<string> GetConnectionStatsAsync()
{
try
{
var sb = new StringBuilder();
sb.AppendLine("=== Redis连接统计 ===");
var endpoints = Connection.GetEndPoints();
foreach (var endpoint in endpoints)
{
var server = Connection.GetServer(endpoint);
if (server.IsConnected)
{
// 测试连接延迟
var pingTime = await server.PingAsync();
sb.AppendLine($"Ping延迟: {pingTime.TotalMilliseconds:F2}ms");
// 获取服务器信息
var info = await server.InfoAsync();
ProcessServerInfo(info, sb);
}
}
return sb.ToString();
}
catch (Exception ex)
{
return $"获取连接统计失败: {ex.Message}";
}
}
定期的健康检查能够及时发现问题:
C#public async Task<(bool IsHealthy, string Message, TimeSpan Latency)> HealthCheckAsync()
{
try
{
var testKey = $"health_check_{Guid.NewGuid()}";
var testValue = "ok";
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// 写入测试
await Database.StringSetAsync(testKey, testValue, TimeSpan.FromSeconds(10));
// 读取测试
var result = await Database.StringGetAsync(testKey);
// 清理测试数据
await Database.KeyDeleteAsync(testKey);
stopwatch.Stop();
if (result == testValue)
{
return (true, "健康检查通过", stopwatch.Elapsed);
}
else
{
return (false, "数据读写不一致", stopwatch.Elapsed);
}
}
catch (Exception ex)
{
return (false, $"健康检查失败: {ex.Message}", TimeSpan.Zero);
}
}
C#public async Task TestSingleOperationsAsync(int count = 1000)
{
Console.WriteLine($"\n=== 单个操作性能测试 (数量: {count}) ===");
var users = GenerateTestUsers(count);
// 写入测试
var writeStart = Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
await _redis.SetAsync($"user:{i}", users[i], TimeSpan.FromMinutes(10));
}
writeStart.Stop();
Console.WriteLine($"写入耗时: {writeStart.ElapsedMilliseconds}ms");
Console.WriteLine($"平均写入耗时: {(double)writeStart.ElapsedMilliseconds / count:F2}ms/操作");
// 读取测试
var readStart = Stopwatch.StartNew();
var successCount = 0;
for (int i = 0; i < count; i++)
{
var user = await _redis.GetAsync<User>($"user:{i}");
if (user != null) successCount++;
}
readStart.Stop();
Console.WriteLine($"读取耗时: {readStart.ElapsedMilliseconds}ms");
Console.WriteLine($"读取成功率: {(double)successCount / count * 100:F1}%");
}
C#public async Task TestConcurrentOperationsAsync(int concurrency = 10, int operationsPerTask = 100)
{
Console.WriteLine($"\n=== 并发操作测试 (并发数: {concurrency}, 每任务操作数: {operationsPerTask}) ===");
var totalOperations = concurrency * operationsPerTask;
var users = GenerateTestUsers(totalOperations);
// 并发写入测试
var writeTasks = new List<Task>();
for (int i = 0; i < concurrency; i++)
{
int startIndex = i * operationsPerTask;
writeTasks.Add(ConcurrentWriteTask(startIndex, operationsPerTask, users));
}
var stopwatch = Stopwatch.StartNew();
await Task.WhenAll(writeTasks);
var writeTime = stopwatch.ElapsedMilliseconds;
Console.WriteLine($"并发写入耗时: {writeTime}ms");
Console.WriteLine($"总体QPS: {totalOperations * 1000.0 / writeTime:F0} 操作/秒");
}
C#using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using StackExchange.Redis;
namespace AppRedis
{
public class SafeRedisService : IDisposable
{
private static readonly Lazy<ConnectionMultiplexer> _connection =
new Lazy<ConnectionMultiplexer>(() =>
{
var config = new ConfigurationOptions
{
EndPoints = { "47.105.156.186:6379" },
// 优化连接参数
ConnectTimeout = 30000, // 连接超时30秒
SyncTimeout = 30000, // 同步超时30秒
AsyncTimeout = 30000, // 异步超时30秒
ConnectRetry = 5, // 重试5次
AbortOnConnectFail = false, // 连接失败不终止
KeepAlive = 60, // 保持连接60秒
// 连接池配置
DefaultDatabase = 0,
AllowAdmin = false,
ResolveDns = false,
// 网络配置
ConfigCheckSeconds = 60,
ReconnectRetryPolicy = new ExponentialRetry(5000, 60000), // 指数退避重连
CheckCertificateRevocation = false,
Password = "Iseeyou123"
};
return ConnectionMultiplexer.Connect(config);
});
private static ConnectionMultiplexer Connection => _connection.Value;
public static IDatabase Database => Connection.GetDatabase();
// 基础异步方法
public async Task<T> GetAsync<T>(string key) where T : class
{
try
{
var value = await Database.StringGetAsync(key);
return value.HasValue ? JsonSerializer.Deserialize<T>(value) : null;
}
catch (RedisTimeoutException ex)
{
Console.WriteLine($"Redis超时: {ex.Message}");
return null;
}
}
public async Task<bool> SetAsync<T>(string key, T value, TimeSpan? expiry = null)
{
try
{
var jsonValue = JsonSerializer.Serialize(value);
return await Database.StringSetAsync(key, jsonValue, expiry);
}
catch (Exception ex)
{
Console.WriteLine($"Redis写入失败: {ex.Message}");
return false;
}
}
// 扩展功能方法
public async Task<bool> ExistsAsync(string key)
{
try
{
return await Database.KeyExistsAsync(key);
}
catch (Exception ex)
{
Console.WriteLine($"Redis检查存在失败: {ex.Message}");
return false;
}
}
public async Task<bool> DeleteAsync(string key)
{
try
{
return await Database.KeyDeleteAsync(key);
}
catch (Exception ex)
{
Console.WriteLine($"Redis删除失败: {ex.Message}");
return false;
}
}
public async Task<long> IncrementAsync(string key, long value = 1)
{
try
{
return await Database.StringIncrementAsync(key, value);
}
catch (Exception ex)
{
Console.WriteLine($"Redis递增失败: {ex.Message}");
return 0;
}
}
// 批量操作
public async Task<Dictionary<string, T>> BatchGetAsync<T>(string[] keys) where T : class
{
try
{
var batch = Database.CreateBatch();
var tasks = keys.Select(key => new
{
Key = key,
Task = batch.StringGetAsync(key)
}).ToArray();
batch.Execute();
await Task.WhenAll(tasks.Select(t => t.Task));
var result = new Dictionary<string, T>();
foreach (var item in tasks)
{
if (item.Task.Result.HasValue)
{
try
{
result[item.Key] = JsonSerializer.Deserialize<T>(item.Task.Result);
}
catch (JsonException)
{
// 反序列化失败,跳过该项
}
}
}
return result;
}
catch (Exception ex)
{
Console.WriteLine($"Redis批量读取失败: {ex.Message}");
return new Dictionary<string, T>();
}
}
public async Task<bool[]> BatchSetAsync<T>(Dictionary<string, T> keyValues, TimeSpan? expiry = null)
{
try
{
var batch = Database.CreateBatch();
var tasks = new List<Task<bool>>();
foreach (var kvp in keyValues)
{
var jsonValue = JsonSerializer.Serialize(kvp.Value);
tasks.Add(batch.StringSetAsync(kvp.Key, jsonValue, expiry));
}
batch.Execute();
return await Task.WhenAll(tasks);
}
catch (Exception ex)
{
Console.WriteLine($"Redis批量写入失败: {ex.Message}");
return new bool[keyValues.Count];
}
}
// 连接状态信息获取方法
public string GetConnectionInfo()
{
try
{
var endpoints = Connection.GetEndPoints();
var sb = new StringBuilder();
sb.AppendLine("=== Redis连接状态 ===");
sb.AppendLine($"连接状态: {(Connection.IsConnected ? "已连接" : "未连接")}");
sb.AppendLine($"端点数量: {endpoints.Length}");
sb.AppendLine($"客户端名称: {Connection.ClientName}");
sb.AppendLine($"配置信息: {Connection.Configuration}");
foreach (var endpoint in endpoints)
{
try
{
var server = Connection.GetServer(endpoint);
sb.AppendLine($"端点: {endpoint}");
sb.AppendLine($" - 连接状态: {(server.IsConnected ? "已连接" : "未连接")}");
sb.AppendLine($" - 服务器类型: {server.ServerType}");
sb.AppendLine($" - 是否副本: {server.IsReplica}");
// 尝试获取服务器信息
try
{
var info = server.Info();
if (info != null && info.Length > 0)
{
// 查找服务器信息组
var serverGroup = info.FirstOrDefault(g => g.Key == "server");
if (serverGroup.Any())
{
var version = serverGroup.FirstOrDefault(kv => kv.Key == "redis_version");
if (!string.IsNullOrEmpty(version.Value))
{
sb.AppendLine($" - Redis版本: {version.Value}");
}
}
// 查找内存信息组
var memoryGroup = info.FirstOrDefault(g => g.Key == "memory");
if (memoryGroup.Any())
{
var usedMemory = memoryGroup.FirstOrDefault(kv => kv.Key == "used_memory_human");
if (!string.IsNullOrEmpty(usedMemory.Value))
{
sb.AppendLine($" - 已用内存: {usedMemory.Value}");
}
}
}
}
catch
{
sb.AppendLine($" - 服务器信息: 无法获取");
}
}
catch (Exception ex)
{
sb.AppendLine($"端点 {endpoint} 信息获取失败: {ex.Message}");
}
}
return sb.ToString();
}
catch (Exception ex)
{
return $"获取连接信息失败: {ex.Message}";
}
}
// 连接统计信息获取方法
public async Task<string> GetConnectionStatsAsync()
{
try
{
var sb = new StringBuilder();
sb.AppendLine("=== Redis连接统计 ===");
// 基本连接信息
sb.AppendLine($"连接状态: {(Connection.IsConnected ? " 已连接" : "❌ 未连接")}");
sb.AppendLine($"操作超时时间: {Connection.TimeoutMilliseconds}ms");
var endpoints = Connection.GetEndPoints();
foreach (var endpoint in endpoints)
{
try
{
var server = Connection.GetServer(endpoint);
sb.AppendLine($"\n--- 服务器: {endpoint} ---");
sb.AppendLine($"连接状态: {(server.IsConnected ? " 已连接" : "❌ 未连接")}");
if (server.IsConnected)
{
try
{
// 尝试执行PING命令测试连接
var pingTime = await server.PingAsync();
sb.AppendLine($"Ping延迟: {pingTime.TotalMilliseconds:F2}ms");
// 获取服务器信息
var info = await server.InfoAsync();
if (info != null && info.Length > 0)
{
// 处理客户端信息
var clientsGroup = info.FirstOrDefault(g => g.Key == "clients");
if (clientsGroup.Any())
{
sb.AppendLine("客户端信息:");
var connectedClients = clientsGroup.FirstOrDefault(kv => kv.Key == "connected_clients");
var blockedClients = clientsGroup.FirstOrDefault(kv => kv.Key == "blocked_clients");
if (!string.IsNullOrEmpty(connectedClients.Value))
sb.AppendLine($" 连接的客户端数: {connectedClients.Value}");
if (!string.IsNullOrEmpty(blockedClients.Value))
sb.AppendLine($" 阻塞的客户端数: {blockedClients.Value}");
}
// 处理内存信息
var memoryGroup = info.FirstOrDefault(g => g.Key == "memory");
if (memoryGroup.Any())
{
sb.AppendLine("内存信息:");
var usedMemory = memoryGroup.FirstOrDefault(kv => kv.Key == "used_memory_human");
var peakMemory = memoryGroup.FirstOrDefault(kv => kv.Key == "used_memory_peak_human");
if (!string.IsNullOrEmpty(usedMemory.Value))
sb.AppendLine($" 当前内存使用: {usedMemory.Value}");
if (!string.IsNullOrEmpty(peakMemory.Value))
sb.AppendLine($" 峰值内存使用: {peakMemory.Value}");
}
// 处理服务器信息
var serverGroup = info.FirstOrDefault(g => g.Key == "server");
if (serverGroup.Any())
{
sb.AppendLine("服务器信息:");
var version = serverGroup.FirstOrDefault(kv => kv.Key == "redis_version");
var uptime = serverGroup.FirstOrDefault(kv => kv.Key == "uptime_in_seconds");
if (!string.IsNullOrEmpty(version.Value))
sb.AppendLine($" Redis版本: {version.Value}");
if (!string.IsNullOrEmpty(uptime.Value) && long.TryParse(uptime.Value, out long uptimeSeconds))
{
var uptimeSpan = TimeSpan.FromSeconds(uptimeSeconds);
sb.AppendLine($" 运行时间: {uptimeSpan.Days}天 {uptimeSpan.Hours}小时 {uptimeSpan.Minutes}分钟");
}
}
// 处理统计信息
var statsGroup = info.FirstOrDefault(g => g.Key == "stats");
if (statsGroup.Any())
{
sb.AppendLine("统计信息:");
var totalConnections = statsGroup.FirstOrDefault(kv => kv.Key == "total_connections_received");
var totalCommands = statsGroup.FirstOrDefault(kv => kv.Key == "total_commands_processed");
if (!string.IsNullOrEmpty(totalConnections.Value))
sb.AppendLine($" 总连接数: {totalConnections.Value}");
if (!string.IsNullOrEmpty(totalCommands.Value))
sb.AppendLine($" 总命令数: {totalCommands.Value}");
}
}
}
catch (Exception ex)
{
sb.AppendLine($"获取服务器详细信息失败: {ex.Message}");
}
}
}
catch (Exception ex)
{
sb.AppendLine($"处理端点 {endpoint} 时出错: {ex.Message}");
}
}
return sb.ToString();
}
catch (Exception ex)
{
return $"获取连接统计失败: {ex.Message}";
}
}
// 健康检查方法
public async Task<(bool IsHealthy, string Message, TimeSpan Latency)> HealthCheckAsync()
{
try
{
var testKey = $"health_check_{Guid.NewGuid()}";
var testValue = "ok";
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// 写入测试
await Database.StringSetAsync(testKey, testValue, TimeSpan.FromSeconds(10));
// 读取测试
var result = await Database.StringGetAsync(testKey);
// 清理测试数据
await Database.KeyDeleteAsync(testKey);
stopwatch.Stop();
if (result == testValue)
{
return (true, "健康检查通过", stopwatch.Elapsed);
}
else
{
return (false, "数据读写不一致", stopwatch.Elapsed);
}
}
catch (Exception ex)
{
return (false, $"健康检查失败: {ex.Message}", TimeSpan.Zero);
}
}
public void Dispose()
{
if (_connection.IsValueCreated)
{
_connection.Value?.Dispose();
}
}
}
}
Redis作为现代应用架构中的关键组件,其使用方式直接影响系统的整体性能。通过本文的完整实战案例,我们学会了如何构建一个企业级的Redis客户端封装,掌握了从连接优化到性能监控的全套技能。
记住这三个关键要点:连接要复用、操作要批量、异常要处理。在实际项目中,建议大家先从基础的封装开始,逐步添加监控和压测功能,持续优化Redis的使用效果。
你在使用Redis时遇到过哪些性能问题?你的项目中是如何处理Redis连接管理的?欢迎在评论区分享你的经验和遇到的坑点,让我们一起交流学习!
觉得这篇实战指南对你有帮助吗?请转发给更多需要Redis性能优化的同行朋友!关注我,获取更多C#高性能编程实战技巧。
相关信息
通过网盘分享的文件:AppRedis.zip 链接: https://pan.baidu.com/s/19lTegPd29mIxzZv2rukxiw?pwd=hpsu 提取码: hpsu --来自百度网盘超级会员v9的分享:::
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!