C# 与基准测试相比,StackExchange redis客户端速度非常慢
我正在使用StackExchangeRedis客户端实现一个Redis缓存层,目前的性能几乎无法使用 我有一个本地环境,其中web应用程序和redis服务器在同一台机器上运行。我在我的Redis服务器上运行了Redis基准测试,结果真的很好(我只是在我的编写中包含了set和get操作):C# 与基准测试相比,StackExchange redis客户端速度非常慢,c#,redis,stackexchange.redis,C#,Redis,Stackexchange.redis,我正在使用StackExchangeRedis客户端实现一个Redis缓存层,目前的性能几乎无法使用 我有一个本地环境,其中web应用程序和redis服务器在同一台机器上运行。我在我的Redis服务器上运行了Redis基准测试,结果真的很好(我只是在我的编写中包含了set和get操作): C:\ProgramFiles\Redis>Redis基准-n 100000 =========PING_INLINE====== 在0.88秒内完成100000个请求 50个并行客户端 3字节有效负载 保持活
C:\ProgramFiles\Redis>Redis基准-n 100000
=========PING_INLINE======
在0.88秒内完成100000个请求
50个并行客户端
3字节有效负载
保持活力:1
=======套======
在0.89秒内完成100000个请求
50个并行客户端
3字节有效负载
保持活力:1
99.70%您以同步方式获取数据(50个客户端并行,但每个客户端的请求是同步而不是异步进行的)
一种选择是使用async/await方法(StackExchange.Redis支持该方法)
如果您需要一次获取多个键(例如,假设您每天保存访问者计数器键,则构建网站访问者的每日图表),那么您应该尝试使用异步方式从redis获取数据,这将为您提供更好的性能 下面代码中的我的结果:
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
===
服务器配置:确保禁用持久性等
在基准测试中,您应该做的第一件事是:基准测试一件事。目前,您包含了大量的序列化开销,这无助于获得清晰的图像。理想情况下,对于同类基准测试,您应该使用3字节的固定负载,因为:
3字节有效负载
接下来,您需要了解并行性:
50个并行客户端
目前还不清楚您的测试是否是并行的,但如果不是,我们完全可以预期原始吞吐量会降低。为了方便起见,SE.Redis被设计为易于并行化:您只需启动多个线程与同一个连接进行通信(这实际上还具有避免数据包碎片化的优点,因为每个数据包可以有多条消息,而单线程同步方法保证每个数据包最多使用一条消息)
最后,我们需要了解列出的基准在做什么。它在做什么
(send, receive) x n
或者它在做什么
send x n, receive separately until all n are received
??这两种选择都是可能的。您的syncapi使用是第一个测试,但第二个测试同样定义良好,据我所知:这就是它所衡量的。有两种方法可以模拟第二种设置:
- 发送第一(n-1)条带有“开火并忘记”标志的消息,这样您实际上只等待最后一条消息
- 对所有消息使用
*Async
API,并且仅Wait()
或Wait
最后一个任务
下面是我在上面使用的一个基准测试,它显示了“每操作同步”(通过同步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");
}
}
}
使用StackExchange.Redis;
使用制度;
使用系统诊断;
使用系统线程;
使用System.Threading.Tasks;
静态P类
{
静态void Main()
{
Console.WriteLine(“连接到服务器…”);
使用(var muxer=ConnectionMultiplexer.Connect(“127.0.0.1”))
{
控制台。写入线(“连接”);
var db=muxer.GetDatabase();
RedisKey=“some key”;
字节[]有效载荷=新字节[3];
新随机(12345)。下一个字节(有效载荷);
再贴现价值=有效载荷;
DoWork(“PING(每操作同步)”,db,1000000,50,x=>{x.PING();返回null;});
DoWork(“SET(sync per op)”,db,500000,50,x=>{x.StringSet(key,value);返回null;});
DoWork(“GET(每操作同步)”,db,500000,50,x=>{x.StringGet(key);返回null;});
DoWork(“PING(每线程流水线)”,db,1000000,50,x=>x.PingAsync();
DoWork(“SET(每线程流水线)”,db,500000,50,x=>x.StringSetAsync(key,value));
DoWork(“GET(每线程流水线)”,db,500000,50,x=>x.StringGetAsync(key));
}
}
静态void DoWork(字符串操作、IDatabase数据库、int计数、int线程、Func op)
{
对象启动=新对象(),关闭=新对象();
int activeThreads=0,outstandingOps=count;
秒表sw=默认值(秒表);
var threadStart=新的threadStart(()=>
{
锁定(启动)
{
如果(++activeThreads==线程)
{
sw=秒表。开始新();
监视器。脉冲密封(启动);
}
其他的
{
监视器。等待(启动);
}
}
任务final=null;
while(联锁减量(参考未完成操作)>=0)
{
最终=op(db);
}
如果(final!=null)final.Wait();
锁定(关闭)
{
如果(--activeThreads==0)
{
sw.Stop();
监视器。脉冲密封(关闭);
}
}
});
锁定(关闭)
{
对于(int i=0;i
旧版本的StackExchange redis客户端存在性能问题。
升级至最新版本。请在此处阅读更多信息:
在本文中:
这是回购协议中的问题:
我想知道为什么流水线产生的吞吐量比sync-proc低
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
(send, receive) x n
send x n, receive separately until all n are received
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");
}
}
}