zoukankan      html  css  js  c++  java
  • 第三节 MemcachedProviders之SesstionStateProvider(关于Session的讨论)

    本节讨论问题Memcached缓存有效期及SesstionStateProvider管理Session。
    • DefaultExpireTime 和 对象序列化存储
    • SesstionStateProvider
    MemcachedProvider是如何控制存储数据的有效期的
    一、DefaultExpireTime 和 对象序列化存储
    配置文件方式
    View Code
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <configSections>
    <section name="cacheProvider" type="MemcachedProviders.Cache.CacheProviderSection, MemcachedProviders"
    allowDefinition
    ="MachineToApplication" restartOnExternalChanges="true"/>
    <sectionGroup name="enyim.com">
    <section name="memcached" type="Enyim.Caching.Configuration.MemcachedClientSection, Enyim.Caching" />
    </sectionGroup>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
    </configSections>
    <enyim.com>
    <memcached>
    <servers>
    <!-- put your own server(s) here-->
    <add address="127.0.0.1" port="11211" />
    </servers>
    <socketPool minPoolSize="10" maxPoolSize="100"
    connectionTimeout
    ="00:00:10" deadTimeout="00:02:00" />
    </memcached>
    </enyim.com>
    <cacheProvider defaultProvider="MemcachedCacheProvider">
    <providers>
    <add name="MemcachedCacheProvider"
    type
    ="MemcachedProviders.Cache.MemcachedCacheProvider, MemcachedProviders" keySuffix="_MySuffix_" defaultExpireTime="2000"/>
    </providers>
    </cacheProvider>
    <log4net>
    <!-- Define some output appenders -->
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
    <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
    </layout>
    </appender>
    <!--<threshold value="OFF" />-->
    <!-- Setup the root category, add the appenders and set the default priority -->
    <root>
    <priority value="WARN"/>
    <appender-ref ref="ConsoleAppender">
    <filter type="log4net.Filter.LevelRangeFilter">
    <levelMin value="WARN"/>
    <levelMax value="FATAL"/>
    </filter>
    </appender-ref>
    </root>
    </log4net>
    </configuration>
    在配置文件中可以看到使用了DistCache使用用的缓存策略是MemcachedCacheProvider,如果不需要分布式缓存我们利用WebCache可以继承CacheProvider重写一个缓存Provider注入到Discache中。
    keySuffix代表 Key后缀  即给你的Key加后缀,以方便使用MemcachedCacheProvider的不同客户端区分各自的缓存数据
    DefaultExpireTime 缓存数据有效期 毫秒为单位,虽然匹配了但默认不使用的,必须通过指定的方法实现
    看一下测试代码
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using MemcachedProviders.Cache;
    using MemcachedProviders.Session;
    using System.Threading;

    namespace DemoTest
    {
    class Program
    {
    static void Main(string[] args)
    {
    //服务端缓存通行证数据10秒
    DistCache.DefaultExpireTime = 10000;

    //假如用户登录成功
    User user = new User();
    user.Name
    = "小怜香";
    user.Age
    = 20;
    user.Birthday
    = Convert.ToDateTime("1991-1-1");
    user.Sex
    = false;
    user.IsLoginSucess
    = true;

    //生成通过证
    string passCode = Guid.NewGuid().ToString();
    Console.WriteLine(
    "首次缓存数据-------------------8秒后读取");
    //缓存用户信息(10秒) ,并将PassCode保存到Cookie中
    DistCache.Add(passCode, user, true);

    //如何让用户在操作期间 缓存不过期呢?
    Thread.Sleep(8000);
    Console.WriteLine(DistCache.Get
    <User>(passCode).Name);

    Console.WriteLine(
    "重新缓存--------------9秒后再读取");
    //从Cookie中获取PassCode ,通过PassCode从缓存中读取用户数据后重新缓存
    //User newUser = DistCache.Get<User>(passCode);
    //DistCache.Remove(passCode);
    DistCache.Add(passCode, user, DistCache.DefaultExpireTime); //Set方式重新缓存 (MemcacheProvider封装不是很好)用第一方式设置还是Add


    Thread.Sleep(
    8000); //如果上面的代码没有刷新缓存至10秒 那下面的代码肯定会超时
    if(DistCache.Get<User>(passCode)!=null)
    Console.WriteLine(DistCache.Get
    <User>(passCode).Name);
    else
    Console.WriteLine(
    "因为没有操作,缓存未刷新,导致数据过期");

    Console.WriteLine(
    "3秒后再次读取数据----------");
    Thread.Sleep(
    3000);
    if (DistCache.Get<User>(passCode) != null)
    Console.WriteLine(DistCache.Get
    <User>(passCode).Name);
    else
    Console.WriteLine(
    "因为没有操作,缓存未刷新,导致数据过期");

    //以上服务端的过程 即服务端用户登录数据先过期,PassCode取不到用户登录数据,可以判定为登录过期
    //还有一种情况客户端Cookie先过期 导致无法取到PassCode,因此服务端就无法获取用户数据,可以判定为登录过期。
    Console.ReadLine();
    }

    [Serializable]
    public class User
    {
    public string Name { set; get; }
    public bool Sex { set; get; }
    public int Age { set; get; }
    public DateTime Birthday { set; get; }
    public bool IsLoginSucess { set; get; }
    }

    }
    }
    结果
    可以看到通过DistCache.Add("Name2", "小凤仙我不认识", true); 存储的数据在3秒后再取时已经过期清除了。
    另外我们在使用MemcachedCacheProvider方法存储对象时,该对象一定要标记为可序列化Serializable。当然你也可以不使用MemcachedCacheProvider,自己先将对象先序列化再存储,取出来的时候自己在反序列化。(没有压缩功能)
    既然MemcachedCacheProvider有缓存过期功能,是不是可以利用这一点+Cookie实现一个自定义Session功能呢
    当用户登录了系统,这个时候生成一个GUID的PassCode通过行证做为缓存Key,并将用户登录信息保存到缓存中,同时PassCode根据请求输出到客户端Cookie中保存。此时Cookie也设置了有效期,服务端缓存也设置了有效期,因此可通过这两种情况来验证用户是否登录过期。
    当Cookie中PassCode未过期,服务端可以从Cookie中获取PassCode 去缓存中读取用户登录数据,如果用户登录数据有效,刷重新设置用户数据有效期,并充许执行相关操作。当用户数据过期时则认为用户登录过期需重新登录。
    当Cookie中PassCode过期,则服务端无法读取PassCode,则认为用户登录过期。 看一下服务端示例代码,因为客户端只要判断一下PassCode是否存在就可以了。
    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using MemcachedProviders.Cache;
    using MemcachedProviders.Session;
    using System.Threading;

    namespace DemoTest
    {
    class Program
    {
    static void Main(string[] args)
    {
    //服务端缓存通行证数据10秒
    DistCache.DefaultExpireTime = 10000;

    //假如用户登录成功
    User user = new User();
    user.Name
    = "小怜香";
    user.Age
    = 20;
    user.Birthday
    = Convert.ToDateTime("1991-1-1");
    user.Sex
    = false;
    user.IsLoginSucess
    = true;

    //生成通过证
    string passCode = Guid.NewGuid().ToString();
    Console.WriteLine(
    "首次缓存数据-------------------9秒后读取");
    //缓存用户信息(10秒) ,并将PassCode保存到Cookie中
    DistCache.Add(passCode, user, true);

    //如何让用户在操作期间 缓存不过期呢?当用户每一次操作或访问就刷新一下服务端的缓存
    Thread.Sleep(9000);
    Console.WriteLine(DistCache.Get
    <User>(passCode).Name);

    Console.WriteLine(
    "取出数据重新缓存--------------9秒后再读取");
    //从Cookie中获取PassCode ,通过PassCode从缓存中读取用户数据后重新缓存
    User newUser = DistCache.Get<User>(passCode);
    DistCache.Remove(passCode);
    DistCache.Add(passCode, newUser,
    true);

    Thread.Sleep(
    9000); //如果上面的代码没有刷新缓存至10秒 那下面的代码肯定会超时
    if(DistCache.Get<User>(passCode)!=null)
    Console.WriteLine(DistCache.Get
    <User>(passCode).Name);
    else
    Console.WriteLine(
    "因为没有操作,缓存未刷新,导致数据过期");

    Console.WriteLine(
    "持续11秒没有刷新操作,再次读取数据----------");
    Thread.Sleep(
    2000);
    if (DistCache.Get<User>(passCode) != null)
    Console.WriteLine(DistCache.Get
    <User>(passCode).Name);
    else
    Console.WriteLine(
    "因为没有操作,缓存未刷新,导致数据过期");

    //以上服务端的过程 即服务端用户登录数据先过期,PassCode取不到用户登录数据,可以判定为登录过期
    //还有一种情况客户端Cookie先过期 导致无法取到PassCode,因此服务端就无法获取用户数据,可以判定为登录过期。
    Console.ReadLine();
    }

    [Serializable]
    public class User
    {
    public string Name { set; get; }
    public bool Sex { set; get; }
    public int Age { set; get; }
    public DateTime Birthday { set; get; }
    public bool IsLoginSucess { set; get; }
    }

    }
    }
     
    上面处理实现Session会话(不是ASP.NET的Session)的优点:
    1.解决跨域,跨窗口的会话认证(针对ASP.NET的Session不跨窗口),解决A站点转B站点或重新打开浏览器访问的Session认证问题。
    2.分布式缓存Session或PassCode,实现认证服务器负载均横。
    3.效率要高于数据库。
    缺点:
    1.一台缓存服务器Down掉后,该服务器上所有Session或PassCode丢失,导致用户访问过期。
    2.受分布式缓存读取数据命中率影响,一旦未命中则导致用户访问过期。
    3.分布式缓存效率要低于传统缓存。
    4.Cookie欺骗,伪造在有效期内的PassCode去骗取服务器认证。(这个问题无法避免,即使你用ASP.NET Session)
          缺点的第1,2两条,我们可以通过重写MemcachedCacheProvider,增加写入数据库功能,同时这样做了也会因反复读写更新数据库而导致性能降低。当用户Cookie的PassCode还在有效期内,通过PassCode去找查缓存服务器中的用户登录信息,如果未查到(数据已过期或未命中亦或服务器Down掉),则去数据库中查找并确认是否已过期,如果未过期则读取并写入缓存中(针对服务器Down掉和丢失两种情况)。第3条是没有办法,实现分布式总是损失部分性能的。至于第4条可以通过加密解密的方式处理。 个人觉得在不是特别的情况下没必要增加数据库备份功能,因为你要反复读写更新数据库中这个缓存的有效期,以及过期后删除等操作。如果一定要保证Session不丢失,那建议还是直接保存数据库中,并在每次操作时更新数据的有效期。
    Cookies跨域:正常的cookie只能在一个应用中共享,即一个cookie只能由创建它的应用获得。关于如何跨域读取Cookie方法很多,这里不介绍了。
    URL后缀跨域:可将PassCode或SessionID通过URL后缀转到其它站点。(相比Cookie就暴露了,虽然可以加密弥补但是还是无法防止别人借用)
    下面来看一下Memcahed实现Asp.net的Session缓存功能,这个是即实现了写入缓存中,又写入数据库中并实时更新有效期。
    二、SesstionStateProvider
         Session是由应用服务器维持的一个服务器端的存储空间,用户在连接服务器时,会由服务器生成一个唯一的SessionID,用该SessionID 为标识符来存取服务器端的Session存储空间。而SessionID这一数据则是保存到客户端,用Cookie保存的,用户提交页面时,会将这一 SessionID提交到服务器端,来存取Session数据。这一过程,是不用开发人员干预的。所以一旦客户端禁用Cookie,那么Session可能会失效,也可能会传Url传递即xxx.aspx?SessionID=xxxxxxxxx的形式。
        
          这么看来,Session的原理其实和我们前面的设计的Cookie+PassCode+缓存功能是一致的。即打开浏览器请求后会产生一个Session会话,该会话有一个唯一标识SessionID(相当于PassCode),当用户登录后成功后,将用户登录信息缓存至对应用SessionID的Session存储中(相当于缓存)。当响应返回客户端后,SessionID被保存在Cookie中。当下一次请求回来时,服务端会根据Cookie中的SessionID从Session存储中取出对应的Session,绑到上下文中。然后从上下文中的Session获取相应的用户信息。
    既然原理相同,那不可避免的都会碰到同样类似的问题:
    1.Session丢失
    2.Cookie或URL后缀参数SessionID一样可以欺骗
    3.跨站点,跨浏览器,跨窗口等等访问一样无法通行。
          有人说利用Cookie+Session ,即Session存储的用户信息及其它可能更多的数据保存到Cookie中。
    1.在Cookie有效期内Session丢失了,即从Cookie中取回所有数据绑回HttpContext的Session中。
    2.跨站点,跨浏览器,跨窗口时,在Cookie有效期内将Session绑至其它站点。
    3.加密解决欺骗问题
    这个时候Cookie除了传递认证标识,还兼具了弥补Session丢失和跨域的一个存储载体了,即取代了Session备份数据库的功能。那谁主导数据有效期控制呢?Session本身过期了,而Cookie没有过期,这样导致了客户端一直有权限访问站点。并且大量的信息被保存在客户端中,虽然加密了,但仍可借用骗取通行。因此不提倡这种做法,Cookie本身是轻量型的,最好只担当SessionID或PassCode等标识或一些客户端自定义性的数据的保存和传递工作。所归根到底,关于会话凭据及会话信息的备份工作是由谁来做?无疑这一块还是交给数据库来做最放心了,但实时的验证工作还是交给缓存或Session存储本身来处理。
     
          当然你可以选择使用Cookie+asp.net Session+SQL备份兼跨域,或直接使用Cookie+PassCode+分布式缓存跨域+SQL备份的方案。
    甚至也可以两者接合或 Cookie+asp.net Session+分布式缓存跨域+SQL备份。终于扯到了下面的内容了:
    Asp.net 本身提供了sessionState的策略注入方案,看一下web.config的配置文件
    View Code
    <system.web>
    <sessionState cookieless="false" regenerateExpiredSessionId="true" mode="Custom"
    customProvider
    ="MemcachedSessionProvider">
    <providers>
    <add name="MemcachedSessionProvider"
    type
    ="MemcachedProviders.Session.SessionStateProvider,MemcachedProviders"
    connectionStringName
    ="SqlSessionServices" dbType="SQL" writeExceptionsToEventLog="false" />
    </providers>
    </sessionState>
    </system.web>
         
         通过继承SessionStateStoreProviderBase重写Provider可以实现对Session的进一步处理。即HttpApplication管线进入HttpHandler的时候,SessionStateStoreProviderBase会重新创建Session绑定到HttpContext中,并根据SessionID从对应的存储体读取数据初始化这个Session,当HttpHandler结束请求时,又会调用SessionStateStoreProviderBase将从HttpContext中读取新进Session的数据保存到对应的存储体(字典集合,缓存,数据库,分布式缓存等),并且清除掉过期的数据,至此Session清除,整个过程是循环往复的,也就是说Session只存在HttpHandler请求期间。
    MemcachedProviders/SessionStateProvider在HttpHandler处理请求结束前,从HttpContext读取Session并写入或更新到Memcahed缓存服务器中,同时清除掉Memcahed中过期的数据,同时也触发了数据库相应的操作(更新缓存有效期,删除过期数据)。当下次请求至HttpHandler阶段,Memcached又根SessionID读取缓存中的Session绑回到HttpContext中。所以我们从HttpContext读取的Session,都是从Memcahed缓存中读取的。
    可以看到providers的节点中MemcachedSessionProvider的属性connectionStringName="SqlSessionServices" 指向了一个数据库连接。
    View Code
    <connectionStrings>
    <add name="SqlSessionServices" connectionString="Data Source= LIULJ2576\SQL2008;Initial Catalog = Session;User Id = sa;Password = sa;"/>
    </connectionStrings>
     MemcachedProvider提供该数据库的脚本,一张表tblSessions和相关操作存储过程
    proc_CleanExpiredData是清除过期数据的存储过程。由于过期的数据或者直接关闭浏览器导致Session没有清除,而MemcachedSessionProvider也没有自动清除这些Session,所以只能通过调用此存储过程定期清除掉一些过期的会话。我们也可以通过SQL代理实现这一功能。
    看一下Web访问之后的数据表结果
    当同一个Session会话持续不断,Expires和LockDate会不断的刷新,以防止过期。Timeout为默认或设置的过期时间(分钟)。
    规则是这样,当存储的数据过了有效期后,当有访问这些数据发生时,则MemcahedSesstionProvider会从表里清除这条对应的SessionID记录。同时系统弹出登录过期,要求用户重新登录,这个时候只要用户不是关闭浏览器登录进来的,还是会用之前的SessionID再写入tblSessions表中。关闭浏览器IE会自动清除Cookie上的SessionID,所以这个时候再打开浏览器就新产生了一个SessionID。通过SessionStateProvider在Memcahed中保存了Session,同时也会实时更新至数据库。
          既然Session都写入数据库了,完全可以在跨域时或者丢失从数据库里读取在有效期内的Session,而没必要通过分布式缓存读取。当不是跨域时访问时,又可以直接利用Session存储而没有必要通过Memcached读取。(以上是误解)事实是这样的吗? 通过对SessionStateProvider源码分析,SessionStateProvider继承重写了SessionStateStoreProviderBase,使用Memcached已经替代了原先Session的存储机制,而数据库则是充当Memcahed的备份。SessionStateProvider并没有画蛇添足,并且确实考虑了所有情况,确实是个很完美的方案。
          相反另一个问题就来了,为什么说Session会丢失,Session是在什么情况下丢失的?高负载,高并发?我没有碰到这些情况,假如这些情况容易发生,相信放到Memcached中也会碰到这种问题,如果是这样那在不注重效率的情况下,看来只有直接利数据库存储Session是最安全的了。web.config SessionState默认支持存入数据库存中的,只要配置一下就可以了。
    View Code
    <sessionState mode="SQLServer" allowCustomSqlDatabase="true"
    sqlConnectionString
    ="server=.;uid=sa;password=;initial catalog=sd"
    cookieless
    ="false"
    timeout
    ="20">
    </sessionState>
    另外微软也提供了一种会话分布式缓存解决方案:
    Windows Server AppFabric 为 ASP.NET Web 应用程序提供了自定义的会话状态提供程序,Web 应用程序可以分散缓存群集中的会话对象,从而提供可伸缩性。鉴于 AppFabric 缓存功能的性质,您放入会话中的对象必须可序列化。有时间在写一篇关于Windows Server AppFabric
  • 相关阅读:
    C#实现二维码生成与解码
    js中正则表达式使用
    Busybox镜像
    linux删除文件后,空间未释放的一种情况,使用lsof查看
    linux中.nfsxxxx引起的文件无法删除
    linux中的查找命令find,locate,which,whereis
    openj9
    Ali流量控制中间件Sentinel
    LDAP认证模式简介
    nginx支持ipv6
  • 原文地址:https://www.cnblogs.com/aaa6818162/p/2255824.html
Copyright © 2011-2022 走看看