Pau*_*oon 11 c# redis stackexchange.redis
我正在使用Stackexchange Redis客户端实现Redis缓存层,现在的性能接近于无法使用.
我有一个本地环境,其中Web应用程序和redis服务器在同一台机器上运行.我对我的Redis服务器运行了Redis基准测试,结果实际上非常好(我只是在编写中包含set和get操作):
C:\Program Files\Redis>redis-benchmark -n 100000
====== PING_INLINE ======
100000 requests completed in 0.88 seconds
50 parallel clients
3 bytes payload
keep alive: 1
====== SET ======
100000 requests completed in 0.89 seconds
50 parallel clients
3 bytes payload
keep alive: 1
99.70% <= 1 milliseconds
99.90% <= 2 milliseconds
100.00% <= 3 milliseconds
111982.08 requests per second
====== GET ======
100000 requests completed in 0.81 seconds
50 parallel clients
3 bytes payload
keep alive: 1
99.87% <= 1 milliseconds
99.98% <= 2 milliseconds
100.00% <= 2 milliseconds
124069.48 requests per second
Run Code Online (Sandbox Code Playgroud)
因此,根据基准测试,我看到每秒超过100,000套和100,000套.我写了一个单元测试来做300,000 set/gets:
private string redisCacheConn = "localhost:6379,allowAdmin=true,abortConnect=false,ssl=false";
[Fact]
public void PerfTestWriteShortString()
{
CacheManager cm = new CacheManager(redisCacheConn);
string svalue = "t";
string skey = "testtesttest";
for (int i = 0; i < 300000; i++)
{
cm.SaveCache(skey + i, svalue);
string valRead = cm.ObtainItemFromCacheString(skey + i);
}
}
Run Code Online (Sandbox Code Playgroud)
这使用以下类通过Stackexchange客户端执行Redis操作:
using StackExchange.Redis;
namespace Caching
{
public class CacheManager:ICacheManager, ICacheManagerReports
{
private static string cs;
private static ConfigurationOptions options;
private int pageSize = 5000;
public ICacheSerializer serializer { get; set; }
public CacheManager(string connectionString)
{
serializer = new SerializeJSON();
cs = connectionString;
options = ConfigurationOptions.Parse(connectionString);
options.SyncTimeout = 60000;
}
private static readonly Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(options));
private static ConnectionMultiplexer Connection => lazyConnection.Value;
private static IDatabase cache => Connection.GetDatabase();
public string ObtainItemFromCacheString(string cacheId)
{
return cache.StringGet(cacheId);
}
public void SaveCache<T>(string cacheId, T cacheEntry, TimeSpan? expiry = null)
{
if (IsValueType<T>())
{
cache.StringSet(cacheId, cacheEntry.ToString(), expiry);
}
else
{
cache.StringSet(cacheId, serializer.SerializeObject(cacheEntry), expiry);
}
}
public bool IsValueType<T>()
{
return typeof(T).IsValueType || typeof(T) == typeof(string);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我的JSON序列化程序只使用Newtonsoft.JSON:
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Caching
{
public class SerializeJSON:ICacheSerializer
{
public string SerializeObject<T>(T cacheEntry)
{
return JsonConvert.SerializeObject(cacheEntry, Formatting.None,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
}
public T DeserializeObject<T>(string data)
{
return JsonConvert.DeserializeObject<T>(data, new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
}
}
}
Run Code Online (Sandbox Code Playgroud)
我的测试时间大约是21秒(300,000套和300,000套).这给了我每秒大约28,500次操作(至少比我预期的使用基准测试慢3倍).我转换为使用Redis的应用程序非常繁琐,某些繁重的请求可以对Redis进行大约200,000次操作.显然,我并没有期待在使用系统运行时缓存时获得的任何时间,但是在此更改之后的延迟非常重要.我的实施方式有问题,有没有人知道为什么我的基准数字比我的Stackechange测试数据快得多?
谢谢,保罗
Mar*_*ell 12
我的结果来自以下代码:
Connecting to server...
Connected
PING (sync per op)
1709ms for 1000000 ops on 50 threads took 1.709594 seconds
585137 ops/s
SET (sync per op)
759ms for 500000 ops on 50 threads took 0.7592914 seconds
658761 ops/s
GET (sync per op)
780ms for 500000 ops on 50 threads took 0.7806102 seconds
641025 ops/s
PING (pipelined per thread)
3751ms for 1000000 ops on 50 threads took 3.7510956 seconds
266595 ops/s
SET (pipelined per thread)
1781ms for 500000 ops on 50 threads took 1.7819831 seconds
280741 ops/s
GET (pipelined per thread)
1977ms for 500000 ops on 50 threads took 1.9772623 seconds
252908 ops/s
Run Code Online (Sandbox Code Playgroud)
===
服务器配置:确保禁用持久性等
你应该在基准测试中做的第一件事是:基准测试.目前,你要包含大量的序列化开销,这无助于获得清晰的图像.理想情况下,对于类似的基准测试,您应该使用3字节的固定有效负载,因为:
3字节有效载荷
接下来,您需要查看并行性:
50个并行客户端
目前尚不清楚您的测试是否是并行的,但如果不是,我们绝对应该期望看到更少的原始吞吐量.方便的是,SE.Redis易于并行化:您可以通过多个线程来启动与同一连接的通信(这实际上还具有避免数据包碎片的优势,因为每个数据包最终可能会有多个消息,其中 -因为单线程同步方法保证每个数据包最多使用一个消息).
最后,我们需要了解列出的基准测试的作用.它在做:
(send, receive) x n
Run Code Online (Sandbox Code Playgroud)
或者它在做什么
send x n, receive separately until all n are received
Run Code Online (Sandbox Code Playgroud)
?两种选择都是可能的.您的同步API使用率是第一个,但第二个测试同样定义明确,而且据我所知:这就是它所测量的内容.有两种方法可以模拟第二种设置:
*Async
API用于所有消息,仅限Wait()
或await
最后一个消息Task
这是我在上面使用的基准测试,它显示了"每个操作的同步"(通过同步API)和"每个线程的管道"(使用*Async
API并且只是等待每个线程的最后一个任务),两者都使用50个线程:
using StackExchange.Redis;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
static class P
{
static void Main()
{
Console.WriteLine("Connecting to server...");
using (var muxer = ConnectionMultiplexer.Connect("127.0.0.1"))
{
Console.WriteLine("Connected");
var db = muxer.GetDatabase();
RedisKey key = "some key";
byte[] payload = new byte[3];
new Random(12345).NextBytes(payload);
RedisValue value = payload;
DoWork("PING (sync per op)", db, 1000000, 50, x => { x.Ping(); return null; });
DoWork("SET (sync per op)", db, 500000, 50, x => { x.StringSet(key, value); return null; });
DoWork("GET (sync per op)", db, 500000, 50, x => { x.StringGet(key); return null; });
DoWork("PING (pipelined per thread)", db, 1000000, 50, x => x.PingAsync());
DoWork("SET (pipelined per thread)", db, 500000, 50, x => x.StringSetAsync(key, value));
DoWork("GET (pipelined per thread)", db, 500000, 50, x => x.StringGetAsync(key));
}
}
static void DoWork(string action, IDatabase db, int count, int threads, Func<IDatabase, Task> op)
{
object startup = new object(), shutdown = new object();
int activeThreads = 0, outstandingOps = count;
Stopwatch sw = default(Stopwatch);
var threadStart = new ThreadStart(() =>
{
lock(startup)
{
if(++activeThreads == threads)
{
sw = Stopwatch.StartNew();
Monitor.PulseAll(startup);
}
else
{
Monitor.Wait(startup);
}
}
Task final = null;
while (Interlocked.Decrement(ref outstandingOps) >= 0)
{
final = op(db);
}
if (final != null) final.Wait();
lock(shutdown)
{
if (--activeThreads == 0)
{
sw.Stop();
Monitor.PulseAll(shutdown);
}
}
});
lock (shutdown)
{
for (int i = 0; i < threads; i++)
{
new Thread(threadStart).Start();
}
Monitor.Wait(shutdown);
Console.WriteLine($@"{action}
{sw.ElapsedMilliseconds}ms for {count} ops on {threads} threads took {sw.Elapsed.TotalSeconds} seconds
{(count * 1000) / sw.ElapsedMilliseconds} ops/s");
}
}
}
Run Code Online (Sandbox Code Playgroud)