StackExchange.Redis 公开了少许的方法和类型来开启性能分析。因为其异步性和多路复用行为,性能分析是一个有点复杂的话题。git
性能分析接口是由这些组成的:IProfiler,ConnectionMultiplexer.RegisterProfiler(IProfiler),ConnectionMultiplexer.BeginProfiling(object), ConnectionMultiplexer.FinishProfiling(object) 还有 IProfiledCommand。github
你能够用 ConnectionMultiplexer 的实例来注册一个 IProfiler 接口,注册后它不能被更改。经过调用 BeginProfiling(object)方法开始分析一个给定的上下文(例如:Thread,HttpRequest等等),而后调用 FinishProfiling(object) 方法完成分析;FinishProfiling(object) 方法返回一个 IProfiledCommand 的集合,该集合包含计时信息的全部命令都被发送到Redis;使用给定的上下文参数,经过已配置的 ConnectionMultiplexer 对像来调用 (Begin|Finish)Profiling (也就是BeginProfiling & FinishProfiling) 方法。数据库
在具体的应用中什么样的 "上下文" 对象应该使用。服务器
StackExchange.Redis公共的信息有:异步
因为StackExchange.Redis的异步接口,分析须要外部协助来组织相关的命令。开始分析和结束分析都是经过给定的上下文对象来实现的(经过 BeginProfiling(object) & FinishProfiling(object) 方法实现),经过 IProfiler 接口的 GetContext 方法取得上下文对象。async
下面是一个从不少不一样的线程发出相关命令的示例:性能
class ToyProfiler : IProfiler { public ConcurrentDictionary<Thread, object> Contexts = new ConcurrentDictionary<Thread, object>(); public object GetContext() { object ctx; if(!Contexts.TryGetValue(Thread.CurrentThread, out ctx)) ctx = null; return ctx; } } // ... ConnectionMultiplexer conn = /* initialization */; var profiler = new ToyProfiler(); var thisGroupContext = new object(); //注册实现了IProfiler接口的对象 conn.RegisterProfiler(profiler); var threads = new List<Thread>(); for (var i = 0; i < 16; i++) { var db = conn.GetDatabase(i); var thread = new Thread( delegate() { var threadTasks = new List<Task>(); for (var j = 0; j < 1000; j++) { var task = db.StringSetAsync("" + j, "" + j); threadTasks.Add(task); } Task.WaitAll(threadTasks.ToArray()); } ); profiler.Contexts[thread] = thisGroupContext; threads.Add(thread); } //分析开始 conn.BeginProfiling(thisGroupContext); threads.ForEach(thread => thread.Start()); threads.ForEach(thread => thread.Join()); //分析结束,而且返回了含定时信息的全部命令集合 IEnumerable<IProfiledCommand> timings = conn.FinishProfiling(thisGroupContext);
在结束后,timings 包含了16,000个 IProfiledCommand 对象:每个命令都会被发送到Redis。this
替代方案,你能够按照以下作:spa
ConnectionMultiplexer conn = /* initialization */;
var profiler = new ToyProfiler(); conn.RegisterProfiler(profiler); var threads = new List<Thread>(); var perThreadTimings = new ConcurrentDictionary<Thread, List<IProfiledCommand>>(); for (var i = 0; i < 16; i++) { var db = conn.GetDatabase(i); var thread = new Thread( delegate() { var threadTasks = new List<Task>(); conn.BeginProfiling(Thread.CurrentThread); for (var j = 0; j < 1000; j++) { var task = db.StringSetAsync("" + j, "" + j); threadTasks.Add(task); } Task.WaitAll(threadTasks.ToArray()); perThreadTimings[Thread.CurrentThread] = conn.FinishProfiling(Thread.CurrentThread).ToList(); } ); profiler.Contexts[thread] = thread; threads.Add(thread); } threads.ForEach(thread => thread.Start()); threads.ForEach(thread => thread.Join());
perThreadTimings 最终会包含16项1,000个 IProfilingCommand 记录,以线程做为键来获取perThreadTimings集合中的值来发送它们。线程
让咱们忘记玩具示例,这里展现的是一个在MVC5应用中配置StackExchange.Redis的示例:
首先注册 IProfiler 接口,而不是 ConnectionMultiplexer :
public class RedisProfiler : IProfiler { const string RequestContextKey = "RequestProfilingContext"; public object GetContext() { var ctx = HttpContext.Current; if (ctx == null) return null; return ctx.Items[RequestContextKey]; } public object CreateContextForCurrentRequest() { var ctx = HttpContext.Current; if (ctx == null) return null; object ret; ctx.Items[RequestContextKey] = ret = new object(); return ret; } }
那么,添加下面的代码到你的Global.asax.cs文件中:
protected void Application_BeginRequest() { var ctxObj = RedisProfiler.CreateContextForCurrentRequest(); if (ctxObj != null) { RedisConnection.BeginProfiling(ctxObj); } } protected void Application_EndRequest() { var ctxObj = RedisProfiler.GetContext(); if (ctxObj != null) { var timings = RedisConnection.FinishProfiling(ctxObj); // 在这里你可使用`timings`作你想作的 } }
这些实现会组织全部的Redis命令,包括 async/await 并随着http请求初始化它们。