基于MongoDB打造.Net的分布式Session子系统

Taobao有她自己的分布式session框架,.net阵营也不能落后了,在下做了个基于MongoDB的支持最多26台MongoDB的分布式Session框架。

先看看配置文件:

<?xml version="1.0" encoding="utf-8" ?>
<MongoDBSession>
  <DbName>SessionDB</DbName>
  <IdentityMap Identity="A">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="B">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="C">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="D">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="E">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="F">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="G">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="H">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="I">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="J">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="K">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="L">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="M">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="N">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="O">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="P">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="Q">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="R">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="S">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="T">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="U">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="V">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="W">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="X">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="Y">mongodb://localhost</IdentityMap>
  <IdentityMap Identity="Z">mongodb://localhost</IdentityMap>
</MongoDBSession>

从Identity A一直到Z,默认分成了26个Map,具体的C#应用代码:

protected void btnTest_Click(object sender, EventArgs e)
        {
            Session["A"] = DateTime.Now;
            Session["B"] = 1111111111111;
            Session["C"] = "fffffffffffffff";
        }

        protected void btnGetSession_Click(object sender, EventArgs e)
        {
            Response.Write(Session["A"].ToString());
            Response.Write("<br />");
            Response.Write(Session["B"].ToString());
            Response.Write("<br />");
            Response.Write(Session["C"].ToString());
        }
        protected void btnAbandon_Click(object sender, EventArgs e)
        {
            Session.Abandon();
        }

呵呵,就是普通的Session用法。

这个要配置web.config:

<system.web>
    <sessionState mode="Custom" customProvider="A2DSessionProvider" sessionIDManagerType="A2DFramework.SessionService.MongoDBSessionIDManager">
      <providers>
        <add name="A2DSessionProvider" type="A2DFramework.SessionService.MongoDBSessionStateStore"/>
      </providers>
    </sessionState>
  </system.web>

这里会牵扯出2个类:

  1. A2DFramework.SessionService.MongoDBSessionIDManager
  2. A2DFramework.SessionService.MongoDBSessionStateStore

 MongoDBSessionIDManager

  • 自定义生成的cookie值(也就是SessionID),在这个sample中,会生成如“E.asadfalkasdfjal”这样的SessionID,其中前缀E代表这个Session的信息会映射到哪台MongoDB上。
  • 关键代码
  • public class MongoDBSessionIDManager : SessionIDManager
        {
            private Random rnd = new Random();
            private object oLock = new object();
    
            public override string CreateSessionID(System.Web.HttpContext context)
            {
                int index = 0;
                lock(this.oLock)
                {
                    index = rnd.Next(SessionConfiguration.SessionServerIdentities.Length);
                }
                string sessionId = string.Format("{0}.{1}", SessionConfiguration.SessionServerIdentities[index], base.CreateSessionID(context));
                return sessionId;
            }
    
            public override string Encode(string id)
            {
                return DESEncryptor.Encode(id, SessionConfiguration.DESKey);
            }
            public override string Decode(string id)
            {
                return DESEncryptor.Decode(id, SessionConfiguration.DESKey);
            }
    
            public override bool Validate(string id)
            {
                string prefix;
                string realId;
    
                if (!Helper.ParseSessionID(id, out prefix, out realId))
                    return false;
    
                return base.Validate(realId);
            }
        }

     

 MongoDBSessionStateStore

  • 自定义Session过程中最核心的一个类,代码如下(较多):
  • public sealed class MongoDBSessionStateStore : SessionStateStoreProviderBase
        {
            private SessionStateSection pConfig;
            private string pApplicationName;
    
            public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
            {
                base.Initialize(name, config);
    
                pApplicationName =System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
                System.Configuration.Configuration cfg = WebConfigurationManager.OpenWebConfiguration(pApplicationName);
                pConfig =(SessionStateSection)cfg.GetSection("system.web/sessionState");
            }
    
            public override SessionStateStoreData CreateNewStoreData(System.Web.HttpContext context, int timeout)
            {
                return new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), timeout);
            }
    
            public override void CreateUninitializedItem(System.Web.HttpContext context, string id, int timeout)
            {
                //insert to db
                MongoDBSessionEntity session = new MongoDBSessionEntity();
                session.ApplicationName = this.pApplicationName;
                session.SessionId = id;
                session.Created = DateTime.Now;
                session.Expires = DateTime.Now.AddMinutes(pConfig.Timeout.Minutes);
                session.LockDate = DateTime.Now;
                session.LockId = 0;
                session.Timeout = timeout;
                session.Locked = false;
                session.Flags = (int)SessionStateActions.InitializeItem;
    
                MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);
                collection.Save(session);
            }
    
            public override void Dispose()
            {
            }
    
            public override void EndRequest(System.Web.HttpContext context)
            {
            }
    
            public override SessionStateStoreData GetItem(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
            {
                return GetSessionStoreItem(false, context, id, out locked, out lockAge, out lockId, out actions);
            }
    
            public override SessionStateStoreData GetItemExclusive(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
            {
                return GetSessionStoreItem(true, context, id, out locked, out lockAge, out lockId, out actions);
            }
    
            public override void InitializeRequest(System.Web.HttpContext context)
            {
            }
    
            public override void ReleaseItemExclusive(System.Web.HttpContext context, string id, object lockId)
            {
                //update locked=0, expired=, where lockId=?
                MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);
    
                var query = Query.And(  Query.EQ("LockId", int.Parse(lockId.ToString())),
                                        Query.EQ("_id", id), 
                                        Query.EQ("ApplicationName", pApplicationName));
                var update = Update.Set("Locked", false)
                                    .Set("Expires", DateTime.Now.AddMinutes(pConfig.Timeout.Minutes));
    
                collection.Update(query, update);
            }
    
            public override void RemoveItem(System.Web.HttpContext context, string id, object lockId, SessionStateStoreData item)
            {
                //delete where sessionId=? and lockId=? and applicationname=?
                MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);
    
                var query = Query.And(Query.EQ("LockId", int.Parse(lockId.ToString())),
                                        Query.EQ("_id", id),
                                        Query.EQ("ApplicationName", pApplicationName));
                collection.Remove(query);
            }
    
            public override void ResetItemTimeout(System.Web.HttpContext context, string id)
            {
                //update expire date
                MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);
    
                var query = Query.And(Query.EQ("_id", id),
                                        Query.EQ("ApplicationName", pApplicationName));
                var update = Update.Set("Expires", DateTime.Now.AddMinutes(pConfig.Timeout.Minutes));
                collection.Update(query, update);
            }
    
            public override void SetAndReleaseItemExclusive(System.Web.HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
            {
                MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);
                if (newItem)
                {
                    //delete expired items
                    var query = Query.And(Query.EQ("_id", id),
                                        Query.EQ("ApplicationName", pApplicationName),
                                        Query.LT("Expires", DateTime.Now));
    
                    collection.Remove(query);
    
                    //insert new item
                    MongoDBSessionEntity session = new MongoDBSessionEntity();
                    session.ApplicationName = this.pApplicationName;
                    session.SessionId = id;
                    session.Created = DateTime.Now;
                    session.Expires = DateTime.Now.AddMinutes(pConfig.Timeout.Minutes);
                    session.LockDate = DateTime.Now;
                    session.LockId = 0;
                    session.Timeout = item.Timeout;
                    session.Locked = false;
                    session.Flags = (int)SessionStateActions.None;
                    session.SessionItems = Helper.Serialize((SessionStateItemCollection)item.Items);
    
                    collection.Save(session);
                }
                else
                {
                    //update item
                    var query = Query.And(Query.EQ("_id", id),
                                        Query.EQ("ApplicationName", pApplicationName),
                                        Query.EQ("LockId", int.Parse(lockId.ToString())));
                    MongoDBSessionEntity entity= collection.FindOne(query);
                    entity.Expires = DateTime.Now.AddMinutes(item.Timeout);
                    entity.SessionItems = Helper.Serialize((SessionStateItemCollection)item.Items);
                    entity.Locked = false;
                    collection.Save(entity);
                }
            }
    
            public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
            {
                return false;
            }
    
    
    
    
    
    
    
    
    
    
            private SessionStateStoreData GetSessionStoreItem(bool lockRecord, System.Web.HttpContext context, 
                                                                string id,
                                                                out bool locked,
                                                                out TimeSpan lockAge,
                                                                out object lockId,
                                                                out SessionStateActions actions)
            {
                SessionStateStoreData item = null;  
                lockAge = TimeSpan.Zero;
                lockId = null;
                locked = false;
                actions = 0;
    
                bool foundRecord = false;
                bool deleteData = false;
    
                MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);
    
                if (lockRecord)
                { 
                    //update db, set locked=1, lockdate=now
                    var query1 = Query.And(Query.EQ("_id", id),
                                        Query.EQ("ApplicationName", pApplicationName),
                                        Query.EQ("Locked", MongoDB.Bson.BsonValue.Create(false)),
                                        Query.GT("Expires", DateTime.UtcNow));
    
                    long count = collection.Find(query1).Count();
                    if (count == 0)
                    {
                        locked = true;
                    }
                    else
                    {
                        var update = Update.Set("Locked", true).Set("LockDate", DateTime.Now);
                        collection.Update(query1, update);
                        locked = false;
                    }
                }
                //get item by id
                var query2 = Query.And(Query.EQ("_id", id),
                                        Query.EQ("ApplicationName", pApplicationName));
                MongoDBSessionEntity entity=collection.FindOne(query2);
                if (entity != null)
                {
                    if (entity.Expires < DateTime.Now)
                    {
                        locked = false;
                        deleteData = true;
                    }
                    else
                    {
                        foundRecord = true;
                    }
                }
    
                //delete item if session expired
                if (deleteData)
                {
                    var query3 = Query.And(Query.EQ("_id", id),
                                        Query.EQ("ApplicationName", pApplicationName));
                    collection.Remove(query3);
                }
    
                if (!foundRecord)
                    locked = false;
    
                if (foundRecord && !locked)
                {
                    if (lockId == null)
                        lockId = 0;
                    lockId = (int)lockId + 1;
    
                    var query4 = Query.And(Query.EQ("_id", id),
                                        Query.EQ("ApplicationName", pApplicationName));
                    var update4 = Update.Set("LockId", (int)lockId)
                                            .Set("Flags", (int)SessionStateActions.None);
                    collection.Update(query4, update4);
    
                    if (actions == SessionStateActions.InitializeItem)
                        item = CreateNewStoreData(context, pConfig.Timeout.Minutes);
                    else
                        item = Helper.Deserialize(context, entity.SessionItems, entity.Timeout);
                }
                return item;
            }
        }

     

由于很多方法会用到MongoCollection,因此写了个static公用函数,如下:

public static MongoCollection<MongoDBSessionEntity> GetMongoDBCollection(string sessionId)
        {
            IPartitionResolver resolver = new MongoDBSessionPartitionResolver();
            string mongoDbConnectionString = resolver.ResolvePartition(sessionId);

            MongoClient client = new MongoClient(mongoDbConnectionString);
            MongoServer srv = client.GetServer();
            MongoDatabase db = srv.GetDatabase(SessionConfiguration.MongoDBName);
            if (!db.CollectionExists(SessionConfiguration.MongoDBCollectionName))
                db.CreateCollection(SessionConfiguration.MongoDBCollectionName);

            MongoCollection<MongoDBSessionEntity> collection = db.GetCollection<MongoDBSessionEntity>(SessionConfiguration.MongoDBCollectionName);

            return collection;
        }

 

 运行效果:

 

 点击Set Session后:

点击Get Session后:

点击Abandon后:

 

 

 源代码已经更新到A2D Framework中了。

 

 

posted @ 2013-08-28 23:46  McKay  阅读(9529)  评论(30编辑  收藏  举报