.NET Core Session源码探究

作者:

文章目录
  •   通过上面注册的相关方法我们大概了解到了 Session 的工作原理。接下来我们查看 UseSession 中间件里的代码,探究这里究竟做了什么操作。我们找到 UseSession 方法所在的地方 SessionMiddlewareExtensions 找到第一个方法 public static IApplicationBuilder UseSession (this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException (nameof (app)); } return app.UseMiddleware<SessionMiddleware>(); }   SessionMiddleware 的源码 public class SessionMiddleware { private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create (); private const int SessionKeyLength = 36; // “382c74c3-721d-4f34-80e5-57657b6cbc27” private static readonly Func<bool> ReturnTrue = () => true; private readonly RequestDelegate _next; private readonly SessionOptions _options; private readonly ILogger _logger; private readonly ISessionStore _sessionStore; private readonly IDataProtector _dataProtector; public SessionMiddleware ( RequestDelegate next, ILoggerFactory loggerFactory, IDataProtectionProvider dataProtectionProvider, ISessionStore sessionStore, IOptions<SessionOptions> options) { if (next == null) { throw new ArgumentNullException (nameof (next)); } if (loggerFactory == null) { throw new ArgumentNullException (nameof (loggerFactory)); } if (dataProtectionProvider == null) { throw new ArgumentNullException (nameof (dataProtectionProvider)); } if (sessionStore == null) { throw new ArgumentNullException (nameof (sessionStore)); } if (options == null) { throw new ArgumentNullException (nameof (options)); } _next = next; _logger = loggerFactory.CreateLogger<SessionMiddleware>(); _dataProtector = dataProtectionProvider.CreateProtector (nameof (SessionMiddleware)); _options = options.Value; //Session 操作类在这里被注入的 _sessionStore = sessionStore; } public async Task Invoke (HttpContext context) { var isNewSessionKey = false; Func<bool> tryEstablishSession = ReturnTrue; var cookieValue = context.Request.Cookies[_options.Cookie.Name]; var sessionKey = CookieProtection.Unprotect (_dataProtector, cookieValue, _logger); //会话首次建立 if (string.IsNullOrWhiteSpace (sessionKey) || sessionKey.Length != SessionKeyLength) { //将会话唯一标识通过 Cookie 返回到客户端 var guidBytes = new byte[16]; CryptoRandom.GetBytes (guidBytes); sessionKey = new Guid (guidBytes) .ToString (); cookieValue = CookieProtection.Protect (_dataProtector, sessionKey); var establisher = new SessionEstablisher (context, cookieValue, _options); tryEstablishSession = establisher.TryEstablishSession; isNewSessionKey = true; } var feature = new SessionFeature (); //创建 Session feature.Session = _sessionStore.Create (sessionKey, _options.IdleTimeout, _options.IOTimeout, tryEstablishSession, isNewSessionKey); //放入到 ISessionFeature,给 HttpContext 中的 Session 数据提供具体实例 context.Features.Set<ISessionFeature>(feature); try { await _next (context); } finally { //置空为了在请求结束后可以回收掉 Session context.Features.Set<ISessionFeature>(null); if (feature.Session != null) { try { //请求完成后提交保存 Session 字典里的数据到 DistributedCache 存储里 await feature.Session.CommitAsync (); } catch (OperationCanceledException) { _logger.SessionCommitCanceled (); } catch (Exception ex) { _logger.ErrorClosingTheSession (ex); } } } } private class SessionEstablisher { private readonly HttpContext _context; private readonly string _cookieValue; private readonly SessionOptions _options; private bool _shouldEstablishSession; public SessionEstablisher (HttpContext context, string cookieValue, SessionOptions options) { _context = context; _cookieValue = cookieValue; _options = options; context.Response.OnStarting (OnStartingCallback, state: this); } private static Task OnStartingCallback (object state) { var establisher = (SessionEstablisher) state; if (establisher._shouldEstablishSession) { establisher.SetCookie (); } return Task.FromResult (0); } private void SetCookie () { //会话标识写入到 Cookie 操作 var cookieOptions = _options.Cookie.Build (_context); var response = _context.Response; response.Cookies.Append (_options.Cookie.Name, _cookieValue, cookieOptions); var responseHeaders = response.Headers; responseHeaders[HeaderNames.CacheControl] = “no-cache”; responseHeaders[HeaderNames.Pragma] = “no-cache”; responseHeaders[HeaderNames.Expires] = “-1”; } internal bool TryEstablishSession () { return (_shouldEstablishSession |= !_context.Response.HasStarted); } } }   通过 SessionMiddleware 中间件里的代码我们了解到了每次请求 Session 的创建,以及 Session 里的数据保存到 DistributedCache 都是在这里进行的。不过这里仍存在一个疑问由于调用 CommitAsync 是在中间件执行完成后统一进行存储的,也就是说中途对 Session 进行的 Set Remove Clear 的操作都是在 Session 方法的本地字典里进行的,并没有同步到 DistributedCache 里,如果中途出现程序异常结束的情况下,保存到 Session 里的数据,并没有真正的存储下来,会出现丢失的情况,不知道在设计这部分逻辑的时候是出于什么样的考虑。
  •   通过阅读 Session 相关的部分源码大致了解了 Session 的原理,工作三要素,IDistributedCache 存储 Session 里的数据,SessionStore 是 Session 的实现类,UseSession 是 Session 被创建到当前请求的地方。同时也留下了几点疑问 针对每个会话存储到 IDistributedCache 的其实都在一个 Key 里,就是以当前会话唯一标识为 key 的 value 里,为什么没有采取组合会话 key 单独存储。 每次请求第一次操作 Session,都会把 IDistributedCache 里针对当前会话的数据全部加载到本地字典里,一般来说每次会话操作 Session 的次数并不会很多,感觉并不会节约性能。 调用 CommitAsync 是在中间件执行完成后统一进行存储的,也就是说中途对 Session 进行的 Set Remove Clear 的操作都是在 Session 方法的本地字典里进行的,并没有同步到 DistributedCache 里,如果中途出现程序异常结束的情况下,保存到 Session 里的数据,并没有真正的存储下来,会出现丢失的情况。   对于以上疑问,不知道是个人理解不足,还是在设计的时候出于别的考虑。欢迎在评论区多多沟通交流,希望能从大家那里得到更好的解释和答案。
  •   前言

      随着互联网的兴起,技术的整体架构设计思路有了质的提升,曾经 Web 开发必不可少的内置对象 Session 已经被慢慢的遗弃。主要原因有两点,一是 Session 依赖 Cookie 存放 SessionID,即使不通过 Cookie 传递,也要依赖在请求参数或路径上携带 Session 标识,对于目前前后端分离项目来说操作起来限制很大,比如跨域问题。二是 Session 数据跨服务器同步问题,现在基本上项目都使用负载均衡技术,Session 同步存在一定的弊端,虽然可以借助 Redis 或者其他存储系统实现中心化存储,但是略显鸡肋。虽然存在一定的弊端,但是在 .NET Core 也并没有抛弃它,而且结合了更好的实现方式提升了设计思路。接下来我们通过分析源码的方式,大致了解下新的工作方式。

      Session 如何使用

      .NET Core 的 Session 使用方式和传统的使用方式有很大的差别,首先它依赖存储系统 IDistributedCache 来存储数据,其次它依赖 SessionMiddleware 为每一次请求提供具体的实例。所以使用 Session 之前需要配置一些操作,详细介绍可参阅微软官方文档会话状态。大致配置流程,如下:

    public class Startup
    {
        public Startup (IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }
    
        public void ConfigureServices (IServiceCollection services)
        {
            services.AddDistributedMemoryCache ();
            services.AddSession (options =>
            {
                options.IdleTimeout = TimeSpan.FromSeconds (10);
                options.Cookie.HttpOnly = true;
                options.Cookie.IsEssential = true;
            });
        }
    
        public void Configure (IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseSession ();
        }
    }

      注册的地方设计到了两个扩展方法 AddDistributedMemoryCache 和 AddSession. 其中 AddDistributedMemoryCache 这是借助 IDistributedCache 为 Session 数据提供存储,AddSession 是 Session 实现的核心的注册操作。

      上面的示例中示例中使用的是基于本地内存存储的方式,也可以使用 IDistributedCache 针对 Redis 和数据库存储的扩展方法。实现也非常简单就是给 IDistributedCache 注册存储操作实例:

    public static IServiceCollection AddDistributedMemoryCache (this IServiceCollection services)
    {
        if (services == null)
        {
            throw new ArgumentNullException (nameof (services));
        }
        services.AddOptions ();
        services.TryAdd (ServiceDescriptor.Singleton<IDistributedCache, MemoryDistributedCache>());
        return services;
    }

      关于 IDistributedCache 的其他使用方式请参阅官方文档的分布式缓存篇,关于分布式缓存源码实现可以通过 Cache 的 Github 地址自行查阅。

      AddSession 是 Session 实现的核心的注册操作,具体实现代码来自扩展类 SessionServiceCollectionExtensions,AddSession 扩展方法大致实现如下:

    public static IServiceCollection AddSession (this IServiceCollection services)
    {
        if (services == null)
        {
            throw new ArgumentNullException (nameof (services));
        }
        services.TryAddTransient<ISessionStore, DistributedSessionStore>();
        services.AddDataProtection ();
        return services;
    }

      这个方法就做了两件事,一个是注册了 Session 的具体操作,另一个是添加了数据保护保护条例支持。和 Session 真正相关的其实只有 ISessionStore,话不多说,继续向下看 DistributedSessionStore 实现

    public class DistributedSessionStore : ISessionStore
    {
        private readonly IDistributedCache _cache;
        private readonly ILoggerFactory _loggerFactory;
    
        public DistributedSessionStore (IDistributedCache cache, ILoggerFactory loggerFactory)
        {
            if (cache == null)
            {
                throw new ArgumentNullException (nameof (cache));
            }
            if (loggerFactory == null)
            {
                throw new ArgumentNullException (nameof (loggerFactory));
            }
            _cache = cache;
            _loggerFactory = loggerFactory;
        }
        public ISession Create (string sessionKey, TimeSpan idleTimeout, TimeSpan ioTimeout, Func<bool> tryEstablishSession, bool isNewSessionKey)
        {
            if (string.IsNullOrEmpty (sessionKey))
            {
                throw new ArgumentException (Resources.ArgumentCannotBeNullOrEmpty, nameof (sessionKey));
            }
            if (tryEstablishSession == null)
            {
                throw new ArgumentNullException (nameof (tryEstablishSession));
            }
            return new DistributedSession (_cache, sessionKey, idleTimeout, ioTimeout, tryEstablishSession, _loggerFactory, isNewSessionKey);
        }
    }

      这里的实现也非常简单就是创建 Session 实例 DistributedSession,在这里我们就可以看出创建 Session 是依赖 IDistributedCache 的,这里的 sessionKey 其实是 SessionID,当前会话唯一标识。继续向下找到 DistributedSession 实现,这里的代码比较多,因为这是封装 Session 操作的实现类。老规矩先找到我们最容易下手的 Get 方法:

    public bool TryGetValue (string key, out byte[] value)
    {
        Load ();
        return _store.TryGetValue (new EncodedKey (key), out value);
    }

      我们看到调用 TryGetValue 之前先调用了 Load 方法,这是内部的私有方法

    private void Load ()
    {
        //判断当前会话中有没有加载过数据
        if (!_loaded)
        {
            try
            {
                //根据会话唯一标识在 IDistributedCache 中获取数据
                var data = _cache.Get (_sessionKey);
                if (data != null)
                {
                    //由于存储的是按照特定的规则得到的二进制数据,所以获取的时候要将数据反序列化
                    Deserialize (new MemoryStream (data));
                }
                else if (!_isNewSessionKey)
                {
                    _logger.AccessingExpiredSession (_sessionKey);
                }
                //是否可用标识
                _isAvailable = true;
            }
            catch (Exception exception)
            {
                _logger.SessionCacheReadException (_sessionKey, exception);
                _isAvailable = false;
                _sessionId = string.Empty;
                _sessionIdBytes = null;
                _store = new NoOpSessionStore ();
            }
            finally
            {
               //将数据标识设置为已加载状态
                _loaded = true;
            }
        }
    }
    
    private void Deserialize (Stream content)
    {
        if (content == null || content.ReadByte () != SerializationRevision)
        {
            // Replace the un-readable format.
            _isModified = true;
            return;
        }
    
        int expectedEntries = DeserializeNumFrom3Bytes (content);
        _sessionIdBytes = ReadBytes (content, IdByteCount);
    
        for (int i = 0; i < expectedEntries; i++)
        {
            int keyLength = DeserializeNumFrom2Bytes (content);
            //在存储的数据中按照规则获取存储设置的具体 key
            var key = new EncodedKey (ReadBytes (content, keyLength));
            int dataLength = DeserializeNumFrom4Bytes (content);
            //将反序列化之后的数据存储到_store
            _store[key] = ReadBytes (content, dataLength);
        }
    
        if (_logger.IsEnabled (LogLevel.Debug))
        {
            _sessionId = new Guid (_sessionIdBytes) .ToString ();
            _logger.SessionLoaded (_sessionKey, _sessionId, expectedEntries);
        }
    }

      通过上面的代码我们可以得知 Get 数据之前之前先 Load 数据,Load 其实就是在 IDistributedCache 中获取数据然后存储到了_store 中,通过当前类源码可知_store 是本地字典,也就是说 Session 直接获取的其实是本地字典里的数据。

    private IDictionary<EncodedKey, byte[]> _store;

      这里其实产生两点疑问:

    • 1. 针对每个会话存储到 IDistributedCache 的其实都在一个 Key 里,就是以当前会话唯一标识为 key 的 value 里,为什么没有采取组合会话 key 单独存储。
    • 2. 每次请求第一次操作 Session,都会把 IDistributedCache 里针对当前会话的数据全部加载到本地字典里,一般来说每次会话操作 Session 的次数并不会很多,感觉并不会节约性能。

      接下来我们在再来查看另一个我们比较熟悉的方法 Set 方法

    public void Set (string key, byte[] value)
    {
        if (value == null)
        {
            throw new ArgumentNullException (nameof (value));
        }
        if (IsAvailable)
        {
            //存储的 key 是被编码过的
            var encodedKey = new EncodedKey (key);
            if (encodedKey.KeyBytes.Length > KeyLengthLimit)
            {
                throw new ArgumentOutOfRangeException (nameof (key),
                    Resources.FormatException_KeyLengthIsExceeded (KeyLengthLimit));
            }
            if (!_tryEstablishSession ())
            {
                throw new InvalidOperationException (Resources.Exception_InvalidSessionEstablishment);
            }
            //是否修改过标识
            _isModified = true;
            //将原始内容转换为 byte 数组
            byte[] copy = new byte[value.Length];
            Buffer.BlockCopy (src: value, srcOffset: 0, dst: copy, dstOffset: 0, count: value.Length);
            //将数据存储到本地字典_store
            _store[encodedKey] = copy;
        }
    }

      这里我们可以看到 Set 方法并没有将数据放入到存储系统,只是放入了本地字典里。我们再来看其他方法

    public void Remove (string key)
    {
        Load ();
        _isModified |= _store.Remove (new EncodedKey (key));
    }
    
    public void Clear ()
    {
        Load ();
        _isModified |= _store.Count > 0;
        _store.Clear ();
    }

      这些方法都没有对存储系统 DistributedCache 里的数据进行操作,都只是操作从存储系统 Load 到本地的字典数据。那什么地方进行的存储呢,也就是说我们要找到调用_cache.Set 方法的地方,最后在这个地方找到了 Set 方法,而且看这个方法名就知道是提交 Session 数据的地方

    public async Task CommitAsync (CancellationToken cancellationToken = default)
    {
        //超过_ioTimeout CancellationToken 将自动取消
        using (var timeout = new CancellationTokenSource (_ioTimeout))
        {
            var cts = CancellationTokenSource.CreateLinkedTokenSource (timeout.Token, cancellationToken);
            //数据被修改过
            if (_isModified)
            {
                if (_logger.IsEnabled (LogLevel.Information))
                {
                    try
                    {
                        cts.Token.ThrowIfCancellationRequested ();
                        var data = await _cache.GetAsync (_sessionKey, cts.Token);
                        if (data == null)
                        {
                            _logger.SessionStarted (_sessionKey, Id);
                        }
                    }
                    catch (OperationCanceledException)
                    {
                    }
                    catch (Exception exception)
                    {
                        _logger.SessionCacheReadException (_sessionKey, exception);
                    }
                }
                var stream = new MemoryStream ();
                //将_store 字典里的数据写到 stream 里
                Serialize (stream);
                try
                {
                    cts.Token.ThrowIfCancellationRequested ();
                    //将读取_store 的流写入到 DistributedCache 存储里
                    await _cache.SetAsync (
                        _sessionKey,
                        stream.ToArray (),
                        new DistributedCacheEntryOptions () .SetSlidingExpiration (_idleTimeout),
                        cts.Token);
                    _isModified = false;
                    _logger.SessionStored (_sessionKey, Id, _store.Count);
                }
                catch (OperationCanceledException oex)
                {
                    if (timeout.Token.IsCancellationRequested)
                    {
                        _logger.SessionCommitTimeout ();
                        throw new OperationCanceledException ("Timed out committing the session.", oex, timeout.Token);
                    }
                    throw;
                }
            }
            else
            {
                try
                {
                    await _cache.RefreshAsync (_sessionKey, cts.Token);
                }
                catch (OperationCanceledException oex)
                {
                    if (timeout.Token.IsCancellationRequested)
                    {
                        _logger.SessionRefreshTimeout ();
                        throw new OperationCanceledException ("Timed out refreshing the session.", oex, timeout.Token);
                    }
                    throw;
                }
            }
        }
    }
    
    private void Serialize (Stream output)
    {
        output.WriteByte (SerializationRevision);
        SerializeNumAs3Bytes (output, _store.Count);
        output.Write (IdBytes, 0, IdByteCount);
        //将_store 字典里的数据写到 Stream 里
        foreach (var entry in _store)
        {
            var keyBytes = entry.Key.KeyBytes;
            SerializeNumAs2Bytes (output, keyBytes.Length);
            output.Write (keyBytes, 0, keyBytes.Length);
            SerializeNumAs4Bytes (output, entry.Value.Length);
            output.Write (entry.Value, 0, entry.Value.Length);
        }
    }

      那么问题来了当前类里并没有地方调用 CommitAsync,那么到底是在什么地方调用的该方法呢?姑且别着急,我们之前说过使用 Session 的三要素,现在才说了两个,还有一个 UseSession 的中间件没有提及到呢。

      通过上面注册的相关方法我们大概了解到了 Session 的工作原理。接下来我们查看 UseSession 中间件里的代码,探究这里究竟做了什么操作。我们找到 UseSession 方法所在的地方 SessionMiddlewareExtensions 找到第一个方法

    public static IApplicationBuilder UseSession (this IApplicationBuilder app)
    {
        if (app == null)
        {
            throw new ArgumentNullException (nameof (app));
        }
        return app.UseMiddleware<SessionMiddleware>();
    }

      SessionMiddleware 的源码

    public class SessionMiddleware
    {
      private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create ();
      private const int SessionKeyLength = 36; // "382c74c3-721d-4f34-80e5-57657b6cbc27"
      private static readonly Func<bool> ReturnTrue = () => true;
      private readonly RequestDelegate _next;
      private readonly SessionOptions _options;
      private readonly ILogger _logger;
      private readonly ISessionStore _sessionStore;
      private readonly IDataProtector _dataProtector;
    
      public SessionMiddleware (
          RequestDelegate next,
          ILoggerFactory loggerFactory,
          IDataProtectionProvider dataProtectionProvider,
          ISessionStore sessionStore,
          IOptions<SessionOptions> options)
      {
          if (next == null)
          {
              throw new ArgumentNullException (nameof (next));
          }
          if (loggerFactory == null)
          {
              throw new ArgumentNullException (nameof (loggerFactory));
          }
          if (dataProtectionProvider == null)
          {
              throw new ArgumentNullException (nameof (dataProtectionProvider));
          }
          if (sessionStore == null)
          {
              throw new ArgumentNullException (nameof (sessionStore));
          }
          if (options == null)
          {
              throw new ArgumentNullException (nameof (options));
          }
          _next = next;
          _logger = loggerFactory.CreateLogger<SessionMiddleware>();
          _dataProtector = dataProtectionProvider.CreateProtector (nameof (SessionMiddleware));
          _options = options.Value;
         //Session 操作类在这里被注入的
          _sessionStore = sessionStore;
      }
    
      public async Task Invoke (HttpContext context)
      {
          var isNewSessionKey = false;
          Func<bool> tryEstablishSession = ReturnTrue;
          var cookieValue = context.Request.Cookies[_options.Cookie.Name];
          var sessionKey = CookieProtection.Unprotect (_dataProtector, cookieValue, _logger);
          //会话首次建立
          if (string.IsNullOrWhiteSpace (sessionKey) || sessionKey.Length != SessionKeyLength)
          {
              //将会话唯一标识通过 Cookie 返回到客户端
              var guidBytes = new byte[16];
              CryptoRandom.GetBytes (guidBytes);
              sessionKey = new Guid (guidBytes) .ToString ();
              cookieValue = CookieProtection.Protect (_dataProtector, sessionKey);
              var establisher = new SessionEstablisher (context, cookieValue, _options);
              tryEstablishSession = establisher.TryEstablishSession;
              isNewSessionKey = true;
          }
          var feature = new SessionFeature ();
          //创建 Session
          feature.Session = _sessionStore.Create (sessionKey, _options.IdleTimeout, _options.IOTimeout, tryEstablishSession, isNewSessionKey);
          //放入到 ISessionFeature,给 HttpContext 中的 Session 数据提供具体实例
          context.Features.Set<ISessionFeature>(feature);
          try
          {
              await _next (context);
          }
          finally
          {
              //置空为了在请求结束后可以回收掉 Session
              context.Features.Set<ISessionFeature>(null);
              if (feature.Session != null)
              {
                  try
                  {
                      //请求完成后提交保存 Session 字典里的数据到 DistributedCache 存储里
                      await feature.Session.CommitAsync ();
                  }
                  catch (OperationCanceledException)
                  {
                      _logger.SessionCommitCanceled ();
                  }
                  catch (Exception ex)
                  {
                      _logger.ErrorClosingTheSession (ex);
                  }
              }
          }
      }
    
      private class SessionEstablisher
      {
          private readonly HttpContext _context;
          private readonly string _cookieValue;
          private readonly SessionOptions _options;
          private bool _shouldEstablishSession;
    
          public SessionEstablisher (HttpContext context, string cookieValue, SessionOptions options)
          {
              _context = context;
              _cookieValue = cookieValue;
              _options = options;
              context.Response.OnStarting (OnStartingCallback, state: this);
          }
    
          private static Task OnStartingCallback (object state)
          {
              var establisher = (SessionEstablisher) state;
              if (establisher._shouldEstablishSession)
              {
                  establisher.SetCookie ();
              }
              return Task.FromResult (0);
          }
    
          private void SetCookie ()
          {
              //会话标识写入到 Cookie 操作
              var cookieOptions = _options.Cookie.Build (_context);
              var response = _context.Response;
              response.Cookies.Append (_options.Cookie.Name, _cookieValue, cookieOptions);
              var responseHeaders = response.Headers;
              responseHeaders[HeaderNames.CacheControl] = "no-cache";
              responseHeaders[HeaderNames.Pragma] = "no-cache";
              responseHeaders[HeaderNames.Expires] = "-1";
          }
    
          internal bool TryEstablishSession ()
          {
              return (_shouldEstablishSession |= !_context.Response.HasStarted);
          }
      }
    }

      通过 SessionMiddleware 中间件里的代码我们了解到了每次请求 Session 的创建,以及 Session 里的数据保存到 DistributedCache 都是在这里进行的。不过这里仍存在一个疑问由于调用 CommitAsync 是在中间件执行完成后统一进行存储的,也就是说中途对 Session 进行的 Set Remove Clear 的操作都是在 Session 方法的本地字典里进行的,并没有同步到 DistributedCache 里,如果中途出现程序异常结束的情况下,保存到 Session 里的数据,并没有真正的存储下来,会出现丢失的情况,不知道在设计这部分逻辑的时候是出于什么样的考虑。

      通过阅读 Session 相关的部分源码大致了解了 Session 的原理,工作三要素,IDistributedCache 存储 Session 里的数据,SessionStore 是 Session 的实现类,UseSession 是 Session 被创建到当前请求的地方。同时也留下了几点疑问

    • 针对每个会话存储到 IDistributedCache 的其实都在一个 Key 里,就是以当前会话唯一标识为 key 的 value 里,为什么没有采取组合会话 key 单独存储。
    • 每次请求第一次操作 Session,都会把 IDistributedCache 里针对当前会话的数据全部加载到本地字典里,一般来说每次会话操作 Session 的次数并不会很多,感觉并不会节约性能。
    • 调用 CommitAsync 是在中间件执行完成后统一进行存储的,也就是说中途对 Session 进行的 Set Remove Clear 的操作都是在 Session 方法的本地字典里进行的,并没有同步到 DistributedCache 里,如果中途出现程序异常结束的情况下,保存到 Session 里的数据,并没有真正的存储下来,会出现丢失的情况。

      对于以上疑问,不知道是个人理解不足,还是在设计的时候出于别的考虑。欢迎在评论区多多沟通交流,希望能从大家那里得到更好的解释和答案。

    评论

    发表回复

    您的邮箱地址不会被公开。 必填项已用 * 标注

    站内搜索