[转] Azure Redis Best Practices - ASP.Net Session State Provider & StackExchange.Redis
ASP.Net Session State Provider
Session State Best Practices
- Enable session state only on required pages - This will avoid known session state provider performance problems.
- You can disable session state by setting the web.config enableSessionState option to false.
<system.web> <pages enableSessionState=false>
- You can enable it on specific pages by setting the page directive's EnableSessionState option to true
<%@ Page EnableSessionState=true %>
- Mark pages using Session State as ReadOnly whenever possible - this helps avoid locking contention.
<%@ Page EnableSessionState=ReadOnly %>
- You can disable session state by setting the web.config enableSessionState option to false.
- Avoid Session State (or at least use ReadOnly) on pages that have long load times - When a page with write-access to the session state takes a long time to load, it will hold the lock for that session until the load completes. This can prevent other requests for other pages for the same session from loading. Also, the session state module in ASP.NET will, in the background, continue to ask for the session lock for any additional requests for that same session until the lock is available or until the executionTime is exceeded for the lock. This can generate additional load on your session state store.
- Make sure you understand the impact of session state locks. Read this article for an example of why this is important.
- Select your httpRuntime/executionTime carefully - The executionTime you select is the duration that the session lock is held should the app crash without releasing the lock. Select a value that is as low as possible while still meeting your application's specific requirements.
Note:
None of these recommendations are specific to Redis - they are good recommendations regardless of which SessionStateProvider you use. Also, some of these recommendations are based on this article, which has additional recommendations beyond those specifically called out here.
StackExchange.Redis
General Guidance
-
Set AbortConnect to false, then let the ConnectionMultiplexer reconnect automatically. See here for details
-
Reuse the ConnectionMultiplexer - do not create a new one for each request. The
Lazy<ConnectionMultiplexer>
pattern shown here is strongly recommended. -
Configure your ThreadPool settings to avoid timeouts.
-
Be aware of the performance costs associated with different operations you are running. For instance, the "KEYS" command is an O(n) operation and should be avoided. The redis.io site has details around the time complexity for each operation that it supports.
-
Consider turning on "Server GC". "Workstation GC" is the default and can impact the latencies when garbage collection is in happening.
-
Most customers find that a single ConnectionMultiplexer is sufficient for their needs. However, if you have high throughput requirements, you may consider slowly increasing the number of connections to see if you get an improvement in throughput. Avoid setting it to an arbitrarily large number of connections as it may not give the desired benefit.
-
Configure supported TLS settings. If you are targeting .NET 4.7 or later, you should not have to do anything because StackExchange.Redis will automatically use the OS level settings when deciding which TLS versions to support (which is a good thing in most cases). If you are targeting an older version of .NET, then you should configure the client to use the highest TLS version that your client system supports (typically TLS 1.2). Once you move to a newer version of the .NET framework, then you should probably remove this configuration and let the OS settings take precedence. This can configured through the sslProtocols connection string entry (requires NuGet package version 1.2.3 or later), or through the ConfigurationOptions class as show here:
var options = ConfigurationOptions.Parse(connectionString);
options.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
ConnectionMultiplexer.Connect(options);
Reconnecting with Lazy<T>
pattern
We have seen a few rare cases where StackExchange.Redis fails to reconnect after a connection blip (for example, due to patching). Restarting the client or creating a new ConnectionMultiplexer will fix the issue. Here is some sample code that still uses the recommended Lazy<ConnectionMultiplexer>
pattern while allowing apps to force a reconnection periodically. Make sure to update code calling into the ConnectionMultiplexer so that they handle any ObjectDisposedException
errors that occur as a result of disposing the old one.
Sample Code:

1 using System; 2 using System.Threading; 3 using StackExchange.Redis; 4 5 static class Redis 6 { 7 static long lastReconnectTicks = DateTimeOffset.MinValue.UtcTicks; 8 static DateTimeOffset firstError = DateTimeOffset.MinValue; 9 static DateTimeOffset previousError = DateTimeOffset.MinValue; 10 11 static object reconnectLock = new object(); 12 13 // In general, let StackExchange.Redis handle most reconnects, 14 // so limit the frequency of how often this will actually reconnect. 15 public static TimeSpan ReconnectMinFrequency = TimeSpan.FromSeconds(60); 16 17 // if errors continue for longer than the below threshold, then the 18 // multiplexer seems to not be reconnecting, so re-create the multiplexer 19 public static TimeSpan ReconnectErrorThreshold = TimeSpan.FromSeconds(30); 20 21 static string connectionString = "TODO: CALL InitializeConnectionString() method with connection string"; 22 static Lazy<ConnectionMultiplexer> multiplexer = CreateMultiplexer(); 23 24 public static ConnectionMultiplexer Connection { get { return multiplexer.Value; } } 25 26 public static void InitializeConnectionString(string cnxString) 27 { 28 if (string.IsNullOrWhiteSpace(cnxString)) 29 throw new ArgumentNullException(nameof(cnxString)); 30 31 connectionString = cnxString; 32 } 33 34 /// <summary> 35 /// Force a new ConnectionMultiplexer to be created. 36 /// NOTES: 37 /// 1. Users of the ConnectionMultiplexer MUST handle ObjectDisposedExceptions, which can now happen as a result of calling ForceReconnect() 38 /// 2. Don't call ForceReconnect for Timeouts, just for RedisConnectionExceptions or SocketExceptions 39 /// 3. Call this method every time you see a connection exception, the code will wait to reconnect: 40 /// a. for at least the "ReconnectErrorThreshold" time of repeated errors before actually reconnecting 41 /// b. not reconnect more frequently than configured in "ReconnectMinFrequency" 42 43 /// </summary> 44 public static void ForceReconnect() 45 { 46 var utcNow = DateTimeOffset.UtcNow; 47 var previousTicks = Interlocked.Read(ref lastReconnectTicks); 48 var previousReconnect = new DateTimeOffset(previousTicks, TimeSpan.Zero); 49 var elapsedSinceLastReconnect = utcNow - previousReconnect; 50 51 // If mulitple threads call ForceReconnect at the same time, we only want to honor one of them. 52 if (elapsedSinceLastReconnect > ReconnectMinFrequency) 53 { 54 lock (reconnectLock) 55 { 56 utcNow = DateTimeOffset.UtcNow; 57 elapsedSinceLastReconnect = utcNow - previousReconnect; 58 59 if (firstError == DateTimeOffset.MinValue) 60 { 61 // We haven't seen an error since last reconnect, so set initial values. 62 firstError = utcNow; 63 previousError = utcNow; 64 return; 65 } 66 67 if (elapsedSinceLastReconnect < ReconnectMinFrequency) 68 return; // Some other thread made it through the check and the lock, so nothing to do. 69 70 var elapsedSinceFirstError = utcNow - firstError; 71 var elapsedSinceMostRecentError = utcNow - previousError; 72 73 var shouldReconnect = 74 elapsedSinceFirstError >= ReconnectErrorThreshold // make sure we gave the multiplexer enough time to reconnect on its own if it can 75 && elapsedSinceMostRecentError <= ReconnectErrorThreshold; //make sure we aren't working on stale data (e.g. if there was a gap in errors, don't reconnect yet). 76 77 // Update the previousError timestamp to be now (e.g. this reconnect request) 78 previousError = utcNow; 79 80 if (shouldReconnect) 81 { 82 firstError = DateTimeOffset.MinValue; 83 previousError = DateTimeOffset.MinValue; 84 85 var oldMultiplexer = multiplexer; 86 CloseMultiplexer(oldMultiplexer); 87 multiplexer = CreateMultiplexer(); 88 Interlocked.Exchange(ref lastReconnectTicks, utcNow.UtcTicks); 89 } 90 } 91 } 92 } 93 94 private static Lazy<ConnectionMultiplexer> CreateMultiplexer() 95 { 96 return new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(connectionString)); 97 } 98 99 private static void CloseMultiplexer(Lazy<ConnectionMultiplexer> oldMultiplexer) 100 { 101 if (oldMultiplexer != null) 102 { 103 try 104 { 105 oldMultiplexer.Value.Close(); 106 } 107 catch (Exception) 108 { 109 // Example error condition: if accessing old.Value causes a connection attempt and that fails. 110 } 111 } 112 } 113 }
当在复杂的环境中面临问题,格物之道需:浊而静之徐清,安以动之徐生。 云中,恰是如此!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?