Heim  >  Artikel  >  Datenbank  >  Einführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis

Einführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis

尚
nach vorne
2020-05-12 09:19:023416Durchsuche

Einführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis

Sitzungsimplementierungsprinzip

Sitzung und Cookie sind zwei Objekte, die häufig in der Webentwicklung verwendet werden. Gibt es eine Verbindung? zwischen ihnen?

PHP Chinesisch-Website-Lernthema: PHP-Sitzung (einschließlich Bilder, Videos, Fälle)

Was sind Cookies?

Ein Cookie ist eine kleine Textinformation, die zusammen mit Benutzeranfragen und Seiten zwischen dem Webserver und dem Browser übertragen wird. Cookies enthalten Informationen, die eine Webanwendung jedes Mal lesen kann, wenn ein Benutzer eine Website besucht.

Hinweis: Bei jeder HTTP-Anfrage werden Cookies an den Server übergeben, mit Ausnahme statischer Dateien wie JS, CSS, Bilder usw. Dieser Prozess kann von Fiddler oder der mit IE gelieferten Netzwerküberwachung analysiert werden Leistung. Sie können damit beginnen, Cookies zu minimieren

Der Prozess des Schreibens von Cookies in den Browser: Wir können den folgenden Code verwenden, um ein Cookie im Asp.net-Projekt zu schreiben und es an den Browser des Kunden zu senden (der Einfachheit halber Andere Attribute habe ich nicht gesetzt).

HttpCookie cookie = new HttpCookie("RedisSessionId", "string value");Response.Cookies.Add(cookie);

Einführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis

Wir können sehen, dass das auf dem Server geschriebene Cookie über den Antwortheader Set-Cookie in den Browser geschrieben wird.

Was ist eine Sitzung?

Sitzung Wir können damit problemlos einige sitzungsbezogene Informationen auf der Serverseite speichern. Zum Beispiel allgemeine Anmeldeinformationen.

Prinzip der Sitzungsimplementierung?

Das HTTP-Protokoll ist zustandslos. Bei mehreren Anfragen eines Browsers kann der WEB-Server nicht unterscheiden, ob sie vom selben Browser stammen. Um diesen Prozess zu unterscheiden, unterscheidet der Server die Anfrage anhand einer Sitzungs-ID. Wie wird diese Sitzungs-ID an den Server gesendet?

Wie bereits erwähnt, wird das Cookie bei jeder Anfrage an den Server gesendet und ist für den Benutzer unsichtbar. Es ist am besten, es zum Speichern dieser Sitzungs-ID zu verwenden.

Session["UserId"] = 123;

Einführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis

Die Beziehung zwischen Sitzung und Cookie wird durch das obige Bild erneut überprüft. Der Server generiert einen Cookie-Einstellungsvorgang. Die Sitzungs-ID wird hier zur Unterscheidung des Browsers verwendet. Um mit verschiedenen Browsern zu experimentieren, können Sie versuchen, sich im IE anzumelden und dann dieselbe Seite in Chrome zu öffnen. Sie werden feststellen, dass Sie sich immer noch in Chrome anmelden müssen, da Chrome derzeit keine Sitzungs-ID hat. httpOnly bedeutet, dass dieses Cookie nicht über js auf der Browserseite ausgeführt wird, um eine künstliche Änderung der Sitzungs-ID zu verhindern.

Der Standard-Session-ID-Schlüsselwert von asp.net ist ASP.NET_SessionId. Diese Standardkonfiguration kann in web.config

<sessionState mode="InProc" cookieName="MySessionId"></sessionState>

Serverseitiges Sitzungslesen

Wie liest der Server den Wert der Sitzung, Session["Schlüsselwert"]. Die Frage ist also: Warum kann dieses Sitzungsobjekt in der Datei Defaule.aspx.cs abgerufen werden und wann wurde dieses Sitzungsobjekt initialisiert?

Um dieses Problem zu klären, können wir es anhand der Definition betrachten.

System.Web.UI.Page ->HttpSessionState(Session)


protected internal override HttpContext Context {
[System.Runtime.TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
  get {
       if (_context == null) {
           _context = HttpContext.Current;
       }
       return _context;
    }
 }
 public virtual HttpSessionState Session {
        get {
            if (!_sessionRetrieved) {
                /* try just once to retrieve it */
                _sessionRetrieved = true;

                try {
                    _session = Context.Session;
                }
                catch {
                    //  Just ignore exceptions, return null.
                }
            }

            if (_session == null) {
                throw new HttpException(SR.GetString(SR.Session_not_enabled));
            }

            return _session;
        }
    }

Im obigen Absatz initialisiert das Page-Objekt das Session-Objekt. Sie können sehen, dass der Wert von Die Sitzung stammt aus HttpContext .Current, und wann wurde HttpContext.Current initialisiert?

public sealed class HttpContext : IServiceProvider, IPrincipalContainer
    {

        internal static readonly Assembly SystemWebAssembly = typeof(HttpContext).Assembly;
        private static volatile bool s_eurlSet;
        private static string s_eurl;

        private IHttpAsyncHandler  _asyncAppHandler;   // application as handler (not always HttpApplication)
        private AsyncPreloadModeFlags _asyncPreloadModeFlags;
        private bool               _asyncPreloadModeFlagsSet;
        private HttpApplication    _appInstance;
        private IHttpHandler       _handler;
        [DoNotReset]
        private HttpRequest        _request;
        private HttpResponse       _response;
        private HttpServerUtility  _server;
        private Stack              _traceContextStack;
        private TraceContext       _topTraceContext;
        [DoNotReset]
        private Hashtable          _items;
        private ArrayList          _errors;
        private Exception          _tempError;
        private bool               _errorCleared;
        [DoNotReset]
        private IPrincipalContainer _principalContainer;
        [DoNotReset]
        internal ProfileBase       _Profile;
        [DoNotReset]
        private DateTime           _utcTimestamp;
        [DoNotReset]
        private HttpWorkerRequest  _wr;
        private VirtualPath        _configurationPath;
        internal bool              _skipAuthorization;
        [DoNotReset]
        private CultureInfo        _dynamicCulture;
        [DoNotReset]
        private CultureInfo        _dynamicUICulture;
        private int                _serverExecuteDepth;
        private Stack              _handlerStack;
        private bool               _preventPostback;
        private bool               _runtimeErrorReported;
        private PageInstrumentationService _pageInstrumentationService = null;
        private ReadOnlyCollection<string> _webSocketRequestedProtocols;
}

HttpContext enthält unsere häufig verwendeten Request-, Response- und anderen Objekte. HttpContext beginnt mit der ASP.NET-Pipeline. Am Beispiel von IIS 6.0 wird im Arbeitsprozess w3wp.exe Aspnet_ispai.dll zum Laden der .NET-Laufzeitumgebung verwendet (sofern die .NET-Laufzeitumgebung noch nicht geladen wurde).

IIS 6.0 führt das Konzept des Anwendungspools ein. Ein Arbeitsprozess entspricht einem Anwendungspool. Ein Anwendungspool kann eine oder mehrere Webanwendungen hosten, und jede Webanwendung wird einem virtuellen IIS-Verzeichnis zugeordnet. Wie IIS 5.x wird jede Webanwendung in ihrer eigenen Anwendungsdomäne ausgeführt.

Wenn die von HTTP.SYS empfangene HTTP-Anfrage der erste Zugriff auf die Webanwendung ist, wird nach erfolgreichem Laden der Laufzeit eine Anwendungsdomäne (AppDomain) für die Webanwendung über die AppDomainFactory erstellt.

Anschließend wird eine spezielle Laufzeit IsapiRuntime geladen. IsapiRuntime ist in der Assembly System.Web definiert und der entsprechende Namespace ist System.Web.Hosting.

IsapiRuntime übernimmt die HTTP-Anfrage. IsapiRuntime erstellt zunächst ein IsapiWorkerRequest-Objekt, um die aktuelle HTTP-Anfrage zu kapseln, und übergibt das IsapiWorkerRequest-Objekt an die ASP.NET-Laufzeit: HttpRuntime. Ab diesem Zeitpunkt gelangt die HTTP-Anfrage offiziell in die ASP.NET-Pipeline.

Gemäß dem IsapiWorkerRequest-Objekt erstellt HttpRuntime ein Kontextobjekt (Context), das zur Darstellung der aktuellen HTTP-Anforderung verwendet wird: HttpContext.

Zu diesem Zeitpunkt glaube ich, dass jeder den Sitzungsinitialisierungsprozess und die Beziehung zwischen Sitzung und Cookie gut versteht. Beginnen wir mit dem Implementierungsplan für die Sitzungsfreigabe.

Sitzungsfreigabe-Implementierungsplan

StateServer-Methode

这种是asp.net提供的一种方式,还有一种是SQLServer方式(不一定程序使用的是SQLServer数据库,所以通用性不高,这里就不介绍了)。也就是将会话数据存储到单独的内存缓冲区中,再由单独一台机器上运行的Windows服务来控制这个缓冲区。

状态服务全称是“ASP.NET State Service ”(aspnet_state.exe)。它由Web.config文件中的stateConnectionString属性来配置。该属性指定了服务所在的服务器,以及要监视的端口。

<sessionState mode="StateServer"      stateConnectionString="tcpip=127.0.0.1:42424"     cookieless="false" timeout="20" />

在这个例子中,状态服务在当前机器的42424端口(默认端口)运行。要在服务器上改变端口和开启远程服务器的该功能,可编辑HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters注册表项中的Port值和AllowRemoteConnection修改成1。

 显然,使用状态服务的优点在于进程隔离,并可在多站点中共享。 使用这种模式,会话状态的存储将不依赖于iis进程的失败或者重启,然而,一旦状态服务中止,所有会话数据都会丢失(这个问题redis不会存在,重新了数据不会丢失)。

这里提供一段bat文件帮助修改注册表,可以复制保存为.bat文件执行

reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters" /v "AllowRemoteConnection" /t REG_DWORD  /d 1 /f

reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\aspnet_state\Parameters" /v "Port" /t REG_DWORD  /d 42424 /f

net stop aspnet_state
net start aspnet_state

pause

Einführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis

完成这些配置以后还是不能实现共享,虽然站点间的SessionId是一致的,但只有一个站点能够读取的到值,而其它站点读取不到。下面给出解决方案,在Global文件里面添加下面代码

public override void Init()
 {
      base.Init();

      foreach (string moduleName in this.Modules)
      {
           string appName = "APPNAME";
           IHttpModule module = this.Modules[moduleName];
           SessionStateModule ssm = module as SessionStateModule;
           if (ssm != null)
           {
                FieldInfo storeInfo = typeof(SessionStateModule).GetField("_store", BindingFlags.Instance | BindingFlags.NonPublic);
                FieldInfo configMode = typeof(SessionStateModule).GetField("s_configMode", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static);

                SessionStateMode mode = (SessionStateMode)configMode.GetValue(ssm);
                if (mode == SessionStateMode.StateServer)
                {
                    SessionStateStoreProviderBase store = (SessionStateStoreProviderBase)storeInfo.GetValue(ssm);
                    if (store == null)//In IIS7 Integrated mode, module.Init() is called later
                    {
                        FieldInfo runtimeInfo = typeof(HttpRuntime).GetField("_theRuntime", BindingFlags.Static | BindingFlags.NonPublic);
                        HttpRuntime theRuntime = (HttpRuntime)runtimeInfo.GetValue(null);
                        FieldInfo appNameInfo = typeof(HttpRuntime).GetField("_appDomainAppId", BindingFlags.Instance | BindingFlags.NonPublic);
                        appNameInfo.SetValue(theRuntime, appName);
                    }
                    else
                    {
                       Type storeType = store.GetType();
                       if (storeType.Name.Equals("OutOfProcSessionStateStore"))
                       {
                           FieldInfo uribaseInfo = storeType.GetField("s_uribase", BindingFlags.Static | BindingFlags.NonPublic);
                           uribaseInfo.SetValue(storeType, appName);
                           object obj = null;
                           uribaseInfo.GetValue(obj);
                        }
                    }
                }
                break;
            }
        }
  }

二.redis实现session共享

下面我们将使用redis来实现共享,首先要弄清楚session的几个关键点,过期时间,SessionId,一个SessionId里面会存在多组key/value数据。基于这个特性我将采用Hash结构来存储,看看代码实现。用到了上一篇提供的RedisBase帮助类。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.SessionState;
using ServiceStack.Redis;
using Com.Redis;

namespace ResidSessionDemo.RedisDemo
{
    public class RedisSession
    {
        private HttpContext context;

        public RedisSession(HttpContext context, bool IsReadOnly, int Timeout)
        {
            this.context = context;
            this.IsReadOnly = IsReadOnly;
            this.Timeout = Timeout;
            //更新缓存过期时间
            RedisBase.Hash_SetExpire(SessionID, DateTime.Now.AddMinutes(Timeout));
        }

        /// <summary>
        /// SessionId标识符
        /// </summary>
        public static string SessionName = "Redis_SessionId";

        //
        // 摘要:
        //     获取会话状态集合中的项数。
        //
        // 返回结果:
        //     集合中的项数。
        public int Count
        {
            get
            {
                return RedisBase.Hash_GetCount(SessionID);
            }
        }

        //
        // 摘要:
        //     获取一个值,该值指示会话是否为只读。
        //
        // 返回结果:
        //     如果会话为只读,则为 true;否则为 false。
        public bool IsReadOnly { get; set; }

        //
        // 摘要:
        //     获取会话的唯一标识符。
        //
        // 返回结果:
        //     唯一会话标识符。
        public string SessionID
        {
            get
            {
                return GetSessionID();
            }
        }

        //
        // 摘要:
        //     获取并设置在会话状态提供程序终止会话之前各请求之间所允许的时间(以分钟为单位)。
        //
        // 返回结果:
        //     超时期限(以分钟为单位)。
        public int Timeout { get; set; }

        /// <summary>
        /// 获取SessionID
        /// </summary>
        /// <param name="key">SessionId标识符</param>
        /// <returns>HttpCookie值</returns>
        private string GetSessionID()
        {
            HttpCookie cookie = context.Request.Cookies.Get(SessionName);
            if (cookie == null || string.IsNullOrEmpty(cookie.Value))
            {
                string newSessionID = Guid.NewGuid().ToString();
                HttpCookie newCookie = new HttpCookie(SessionName, newSessionID);
                newCookie.HttpOnly = IsReadOnly;
                newCookie.Expires = DateTime.Now.AddMinutes(Timeout);
                context.Response.Cookies.Add(newCookie);
                return "Session_"+newSessionID;
            }
            else
            {
                return "Session_"+cookie.Value;
            }
        }

        //
        // 摘要:
        //     按名称获取或设置会话值。
        //
        // 参数:
        //   name:
        //     会话值的键名。
        //
        // 返回结果:
        //     具有指定名称的会话状态值;如果该项不存在,则为 null。
        public object this[string name]
        {
            get
            {
                return RedisBase.Hash_Get<object>(SessionID, name);
            }
            set
            {
                RedisBase.Hash_Set<object>(SessionID, name, value);
            }
        }

        // 摘要:
        //     判断会话中是否存在指定key
        //
        // 参数:
        //   name:
        //     键值
        //
        public bool IsExistKey(string name)
        {
            return RedisBase.Hash_Exist<object>(SessionID, name);
        }

        //
        // 摘要:
        //     向会话状态集合添加一个新项。
        //
        // 参数:
        //   name:
        //     要添加到会话状态集合的项的名称。
        //
        //   value:
        //     要添加到会话状态集合的项的值。
        public void Add(string name, object value)
        {
            RedisBase.Hash_Set<object>(SessionID, name, value);
        }
        //
        // 摘要:
        //     从会话状态集合中移除所有的键和值。
        public void Clear()
        {
            RedisBase.Hash_Remove(SessionID);
        }

        //
        // 摘要:
        //     删除会话状态集合中的项。
        //
        // 参数:
        //   name:
        //     要从会话状态集合中删除的项的名称。
        public void Remove(string name)
        {
            RedisBase.Hash_Remove(SessionID,name);
        }
        //
        // 摘要:
        //     从会话状态集合中移除所有的键和值。
        public void RemoveAll()
        {
            Clear();
        }
    }
}

下面是实现类似在cs文件中能直接使用Session["UserId"]的方式,我的MyPage类继承Page实现了自己的逻辑主要做了两件事  1:初始化RedisSession  2:实现统一登录认证,OnPreInit方法里面判断用户是否登录,如果没有登录了则跳转到登陆界面

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;

namespace ResidSessionDemo.RedisDemo
{
    /// <summary>
    /// 自定义Page 实现以下功能
    /// 1.初始化RedisSession
    /// 2.实现页面登录验证,继承此类,则可以实现所有页面的登录验证
    /// </summary>
    public class MyPage:Page
    {
        private RedisSession redisSession;

        /// <summary>
        /// RedisSession
        /// </summary>
        public RedisSession RedisSession
        {
            get
            {
                if (redisSession == null)
                {
                    redisSession = new RedisSession(Context, true, 20);
                }
                return redisSession;
            }
        }

        protected override void OnPreInit(EventArgs e)
        {
            base.OnPreInit(e);
            //判断用户是否已经登录,如果未登录,则跳转到登录界面
            if (!RedisSession.IsExistKey("UserCode"))
            {
                Response.Redirect("Login.aspx");
            }
        }
    }
}

我们来看看Default.aspx.cs是如何使用RedisSession的,至此我们实现了和Asp.netSession一模一样的功能和使用方式。

RedisSession.Remove("UserCode");

相比StateServer,RedisSession具有以下优点

1、redis服务器重启不会丢失数据  2.可以使用redis的读写分离个集群功能更加高效读写数据  

测试效果,使用nginx和iis部署两个站点做负载均衡,iis1地址127.0.0.1:8002 iis2地址127.0.0.1:9000  nginx代理服务地址127.0.0.1:8003,不懂如何配置的可以去阅读我的nginx+iis实现负载均衡这篇文章。我们来看一下测试结果。

访问127.0.0.1:8003 需要进行登录   用户名为admin  密码为123

Einführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis

登录成功以后,重点关注端口号信息

Einführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis

刷新页面,重点关注端口号信息

Einführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis

可以尝试直接访问iis1地址127.0.0.1:8002 iis2地址127.0.0.1:9000 这两个站点,你会发现都不需要登录了。至此我们的redis实现session功能算是大功告成了。

问题拓展

使用redis实现session告一段落,下面留个问题讨论一下方案。微信开发提供了很多接口,参考下面截图,可以看到获取access_token接口每日最多调用2000次,现在大公司提供的很多接口针对不对级别的用户接口访问次数限制都是不一样的,至于做这个限制的原因应该是防止恶意攻击和流量限制之类的。

那么我的问题是怎么实现这个接口调用次数限制功能。大家可以发挥想象力参与讨论哦,或许你也会碰到这个问题。

Einführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis

先说下我知道的两种方案:

1、使用流量整形中的令牌桶算法,大小固定的令牌桶可自行以恒定的速率源源不断地产生令牌。如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小。

Um es einfach auszudrücken: Beispielsweise hat die obige access_token-Schnittstelle eine Häufigkeit von 2000 Mal am Tag, also 1 Mal/Minute. Die Kapazität unseres Token-Buckets beträgt 2000 und kann mit dem einfachsten Schlüssel/Wert von Redis gespeichert werden. Der Schlüssel ist die Benutzer-ID und der Wert gibt an, wie oft der ganzzahlige Speicher verwendet werden kann client.Incr(key) für 1 Minute, um die Anzahl der Male zu erreichen, wenn ein Benutzer auf diese Schnittstelle zugreift, wird der entsprechende client.Decr(key) verwendet, um die Anzahl der Verwendungen zu reduzieren.

Aber hier gibt es ein Leistungsproblem. Angenommen, es gibt 100.000 Benutzer, um diesen Selbstinkrementierungsvorgang zu implementieren Client separat? .Incr(key)? Dies war nicht klar durchdacht.

2. Beurteilen Sie zunächst die Gesamtzahl der direkten Benutzerbesuche und führen Sie dann eine automatische Erhöhung durch, wenn die Bedingungen erfüllt sind

Einführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis

Für weitere Redis-Kenntnisse: Bitte folgen Sie der Spalte Redis Gate Tutorial.

Das obige ist der detaillierte Inhalt vonEinführung in die Methode zur Implementierung der Sitzungsfreigabe in Redis. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:cnblogs.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen