1. 程式人生 > >C# 基於StackExchange.Redis.dll利用Redis實現分散式Session

C# 基於StackExchange.Redis.dll利用Redis實現分散式Session

最近在研發一款O2O產品,考慮到分散式架構的需要,以前那一套.NET的Session管理方式已經不合用了。遂研究了一下Redis,發現基於這種Key-Value的記憶體資料庫很適合來做分散式Session。本示例將基於StackExchange.Redis.dll進行實現,序列化使用的是Newtonsoft.json.dll。

為了避免APP等客戶端重複登入導致同一個使用者出現重複的Session,將以ServerID(可以是資料庫主鍵)為服務端令牌,並生成一個新的GUID作為其客戶端令牌,客戶端令牌將返回給客戶端。
兩個令牌都將儲存在Redis中,形成2個鍵值對,在服務端令牌中儲存了其對應的客戶端令牌ID。當同一使用者再次登入時,將首先檢查是否有基於其ServerID的服務端令牌存在,如果存在,則直接取其值(客戶端令牌)返回給客戶端,以此避免重複的Session浪費。


Web.config:

<!--Redis資料庫編號(範圍:0~15)-->
<add key="SessionRedisDBIndex" value="0"/>
<!--主Redis連線串-->
<add key="SessionRedisMaster" value="pass:192.168.1.236:6379"/>
<!--Session有效時長,單位:分鐘-->
<add key="SessionTimeOut" value="120"/>
Session基類:
public abstract class BaseSession
    {
        DateTime mExpiresTime = DateTime.Now;

        [JsonConverter(typeof(TimestampConverter))]
        public DateTime ExpiresTime
        {
            get { return mExpiresTime; }
        }

        /// <summary>
        /// 設定Session過期時間
        /// </summary>
        /// <param name="aExpiresTime"></param>
        internal void SetExpiresTime(DateTime aExpiresTime)
        {
            mExpiresTime = aExpiresTime;
        }

        [JsonIgnore]
        /// <summary>
        /// 獲取服務端ID
        /// </summary>
        public abstract string ServerID { get; }
    }
 public static class SessionService
    {
        /// <summary>
        /// Session有效時長,預設120分鐘
        /// </summary>
        static int TimeOutMinutes=120;

        /// <summary>
        /// Redis快取訪問物件
        /// </summary>
        static ConnectionMultiplexer mRedisSession;

        /// <summary>
        /// Session管理
        /// </summary>
        static SessionService()
        {
            try
            {
                TimeOutMinutes = int.Parse(ConfigurationManager.AppSettings["SessionTimeOut"]);
                string[] redisMaster = ConfigurationManager.AppSettings["SessionRedisMaster"].Split(':');
                ConfigurationOptions fRedisConfig = new ConfigurationOptions()
                {
                    Password = redisMaster[0],
                    EndPoints ={ { redisMaster[1], int.Parse(redisMaster[2]) } },
                    KeepAlive = 180,
                    DefaultDatabase = int.Parse(ConfigurationManager.AppSettings["SessionRedisDBIndex"])
                };
                mRedisSession = ConnectionMultiplexer.Connect(fRedisConfig);
            }
            catch (Exception ex)
            {
                Lib.LocalLog.Append("Session Error(SessionService):" + ex.Message, "", "SessionError", "SessionError");
                throw ex;
            }
        }

        /// <summary>
        /// 新增Session
        /// </summary>
        /// <typeparam name="T">Session物件型別</typeparam>
        /// <param name="aSession">要存為Session的物件</param>
        /// <param name="aPrefix">字首</param>
        /// <returns>令牌</returns>
        public static string Add<T>(T aSession,string aPrefix="") where T:BaseSession
        {
            try
            {
                IDatabase client = mRedisSession.GetDatabase();
                aSession.SetExpiresTime(DateTime.Now.AddMinutes(TimeOutMinutes));
                string fExistToken = client.StringGet(aSession.ServerID);//獲取服務端唯一碼當前對應的客戶端令牌
                if (fExistToken != null)
                {//存在客戶端令牌
                    //更新客戶端令牌
                    if (client.StringSet(fExistToken,
                        JsonConvert.SerializeObject(aSession), 
                        aSession.ExpiresTime.Subtract(DateTime.Now)))
                    {
                        return fExistToken;
                    }
                }
                else
                {
                    string token = aPrefix + Guid.NewGuid().ToString("N");
                    if(client.StringSet(token,
                        JsonConvert.SerializeObject(aSession),
                        aSession.ExpiresTime.Subtract(DateTime.Now)) &&//新增Session
                       client.StringSet(aSession.ServerID,
                        token,
                        aSession.ExpiresTime.AddSeconds(-5).Subtract(DateTime.Now)))//繫結服務端唯一碼與客戶端令牌(將比Session早5秒失效)
                    {
                        return token;
                    }
                }
            }
            catch (Exception ex)
            {
                Lib.LocalLog.Append("Session Error(Add<T>):" + ex.Message, "", "SessionError", "SessionError");
            }
            return null;
        }

        /// <summary>
        /// 獲取Session
        /// </summary>
        /// <typeparam name="T">Session物件型別</typeparam>
        /// <param name="aToken">令牌</param>
        /// <returns>令牌對應的Session物件</returns>
        public static T Get<T>(string aToken) where T : BaseSession
        {
            try
            {
                IDatabase client = mRedisSession.GetDatabase();
                T fEntity = null;
                string fSessionValue = client.StringGet(aToken);
                if (!string.IsNullOrEmpty(fSessionValue))
                {
                    fEntity = JsonConvert.DeserializeObject<T>(fSessionValue);
                    CheckExpireTime<T>(aToken, fEntity, client);
                }
                return fEntity;
            }
            catch (Exception ex)
            {
                Lib.LocalLog.Append("Session Error(Get<T>):" + ex.Message, "", "SessionError", "SessionError");
            }
            return default(T);
        }

        /// <summary>
        /// 獲取Session 字串值
        /// </summary>
        /// <param name="aToken">令牌</param>
        /// <returns>令牌對應的Session 字串值</returns>
        public static string GetValue<T>(string aToken) where T : BaseSession
        {
            try
            {
                IDatabase client = mRedisSession.GetDatabase();
                string fSessionValue = client.StringGet(aToken);
                if (fSessionValue != null)
                {
                    CheckExpireTime<T>(aToken, JsonConvert.DeserializeObject<T>(fSessionValue), client);
                }
                return fSessionValue;
            }
            catch (Exception ex)
            {
                Lib.LocalLog.Append("Session Error(GetValue<T>):" + ex.Message, "", "SessionError", "SessionError");
            }
            return null;
        }

        /// <summary>
        /// 刪除Session
        /// </summary>
        /// <param name="aToken">令牌</param>
        public static void Remove<T>(string aToken) where T : BaseSession
        {
            try
            {
                IDatabase client = mRedisSession.GetDatabase();
                T fEntity = null;
                string fSessionValue = client.StringGet(aToken);
                if (!string.IsNullOrEmpty(fSessionValue))
                {
                    fEntity = JsonConvert.DeserializeObject<T>(fSessionValue);
                    client.KeyDelete(fEntity.ServerID);
                    client.KeyDelete(aToken);
                }
            }
            catch (Exception ex)
            {
                Lib.LocalLog.Append("Session Error(Remove):" + ex.Message, "", "SessionError", "SessionError");
            }
        }

        /// <summary>
        /// 令牌有效時間檢查
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="aToken">令牌</param>
        /// <param name="aSession"></param>
        static void CheckExpireTime<T>(string aToken, T aSession, IDatabase client) where T : BaseSession
        {
            if (aSession.ExpiresTime.Subtract(DateTime.Now).TotalMinutes < 10)
            {//離Session過期時間小於10分鐘,延長Session有效期
                try
                {
                    aSession.SetExpiresTime(DateTime.Now.AddMinutes(TimeOutMinutes));
                    client.StringSet(aToken, JsonConvert.SerializeObject(aSession), aSession.ExpiresTime.Subtract(DateTime.Now));
                    client.KeyExpire(aSession.ServerID, aSession.ExpiresTime.AddSeconds(-5).Subtract(DateTime.Now));
                }
                catch (Exception ex)
                {
                    Lib.LocalLog.Append("Session Error(6):" + ex.Message, "", "SessionError", "SessionError");
                }
            }
        }
    }