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請求初始化它們。