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

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

先看看配置文件:

 
 
 
 
  1. <?xml version="1.0" encoding="utf-8" ?> 

  2. <MongoDBSession> 
  3.   <DbName>SessionDB</DbName> 
  4.   <IdentityMap Identity="A">mongodb://localhost</IdentityMap> 
  5.   <IdentityMap Identity="B">mongodb://localhost</IdentityMap> 
  6.   <IdentityMap Identity="C">mongodb://localhost</IdentityMap> 
  7.   <IdentityMap Identity="D">mongodb://localhost</IdentityMap> 
  8.   <IdentityMap Identity="E">mongodb://localhost</IdentityMap> 
  9.   <IdentityMap Identity="F">mongodb://localhost</IdentityMap> 
  10.   <IdentityMap Identity="G">mongodb://localhost</IdentityMap> 
  11.   <IdentityMap Identity="H">mongodb://localhost</IdentityMap> 
  12.   <IdentityMap Identity="I">mongodb://localhost</IdentityMap> 
  13.   <IdentityMap Identity="J">mongodb://localhost</IdentityMap> 
  14.   <IdentityMap Identity="K">mongodb://localhost</IdentityMap> 

     

  15.   <IdentityMap Identity="L">mongodb://localhost</IdentityMap> 
  16.   <IdentityMap Identity="M">mongodb://localhost</IdentityMap> 
  17.   <IdentityMap Identity="N">mongodb://localhost</IdentityMap> 
  18.   <IdentityMap Identity="O">mongodb://localhost</IdentityMap> 
  19.   <IdentityMap Identity="P">mongodb://localhost</IdentityMap> 
  20.   <IdentityMap Identity="Q">mongodb://localhost</IdentityMap> 
  21.   <IdentityMap Identity="R">mongodb://localhost</IdentityMap> 
  22.   <IdentityMap Identity="S">mongodb://localhost</IdentityMap> 
  23.   <IdentityMap Identity="T">mongodb://localhost</IdentityMap> 
  24.   <IdentityMap Identity="U">mongodb://localhost</IdentityMap> 
  25.   <IdentityMap Identity="V">mongodb://localhost</IdentityMap> 
  26.   <IdentityMap Identity="W">mongodb://localhost</IdentityMap> 
  27.   <IdentityMap Identity="X">mongodb://localhost</IdentityMap> 
  28.   <IdentityMap Identity="Y">mongodb://localhost</IdentityMap> 
  29.   <IdentityMap Identity="Z">mongodb://localhost</IdentityMap> 
  30. </MongoDBSession> 

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

 
 
 
 
  1. protected void btnTest_Click(object sender, EventArgs e)  
  2.         {  
  3.             Session["A"] = DateTime.Now;  
  4.             Session["B"] = 1111111111111;  
  5.             Session["C"] = "fffffffffffffff";  
  6.         }  
  7.  
  8.         protected void btnGetSession_Click(object sender, EventArgs e)  
  9.         {  
  10.             Response.Write(Session["A"].ToString());  
  11.             Response.Write("<br />");  
  12.             Response.Write(Session["B"].ToString());  
  13.             Response.Write("<br />");  
  14.             Response.Write(Session["C"].ToString());  
  15.         }  
  16.         protected void btnAbandon_Click(object sender, EventArgs e)  
  17.         {  
  18.             Session.Abandon();  
  19.         } 

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

这个要配置web.config:

 
 
 
 
  1. <system.web> 
  2.     <sessionState mode="Custom" customProvider="A2DSessionProvider" sessionIDManagerType="A2DFramework.SessionService.MongoDBSessionIDManager"> 
  3.       <providers> 
  4.         <add name="A2DSessionProvider" type="A2DFramework.SessionService.MongoDBSessionStateStore"/> 
  5.       </providers> 
  6.     </sessionState> 
  7.   </system.web> 

这里会牵扯出2个类:

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

 MongoDBSessionIDManager

  • 自定义生成的cookie值(也就是SessionID),在这个sample中,会生成如“E.asadfalkasdfjal”这样的SessionID,其中前缀E代表这个Session的信息会映射到哪台MongoDB上。
  • 关键代码
 
 
 
 
  1. public class MongoDBSessionIDManager : SessionIDManager  
  2.     {  
  3.         private Random rnd = new Random();  
  4.         private object oLock = new object();  
  5.  
  6.         public override string CreateSessionID(System.Web.HttpContext context)  
  7.         {  
  8.             int index = 0;  
  9.             lock(this.oLock)  
  10.             {  
  11.                 index = rnd.Next(SessionConfiguration.SessionServerIdentities.Length);  
  12.             }  
  13.             string sessionId = string.Format("{0}.{1}", SessionConfiguration.SessionServerIdentities[index], base.CreateSessionID(context));  
  14.             return sessionId;  
  15.         }  
  16.  
  17.         public override string Encode(string id)  
  18.         {  
  19.             return DESEncryptor.Encode(id, SessionConfiguration.DESKey);  
  20.         }  
  21.         public override string Decode(string id)  
  22.         {  
  23.             return DESEncryptor.Decode(id, SessionConfiguration.DESKey);  
  24.         }  
  25.  
  26.         public override bool Validate(string id)  
  27.         {  
  28.             string prefix;  
  29.             string realId;  
  30.  
  31.             if (!Helper.ParseSessionID(id, out prefix, out realId))  
  32.                 return false;  
  33.  
  34.             return base.Validate(realId);  
  35.         }  
  36.     } 

#p#

MongoDBSessionStateStore

  • 自定义Session过程中最核心的一个类,代码如下(较多):
 
 
 
 
  1. public sealed class MongoDBSessionStateStore : SessionStateStoreProviderBase  
  2.     {  
  3.         private SessionStateSection pConfig;  
  4.         private string pApplicationName;  
  5.  
  6.         public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)  
  7.         {  
  8.             base.Initialize(name, config);  
  9.  
  10.             pApplicationName =System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;  
  11.             System.Configuration.Configuration cfg = WebConfigurationManager.OpenWebConfiguration(pApplicationName);  
  12.             pConfig =(SessionStateSection)cfg.GetSection("system.web/sessionState");  
  13.         }  
  14.  
  15.         public override SessionStateStoreData CreateNewStoreData(System.Web.HttpContext context, int timeout)  
  16.         {  
  17.             return new SessionStateStoreData(new SessionStateItemCollection(), SessionStateUtility.GetSessionStaticObjects(context), timeout);  
  18.         }  
  19.  
  20.         public override void CreateUninitializedItem(System.Web.HttpContext context, string id, int timeout)  
  21.         {  
  22.             //insert to db  
  23.             MongoDBSessionEntity session = new MongoDBSessionEntity();  
  24.             session.ApplicationName = this.pApplicationName;  
  25.             session.SessionId = id;  
  26.             session.Created = DateTime.Now;  
  27.             session.Expires = DateTime.Now.AddMinutes(pConfig.Timeout.Minutes);  
  28.             session.LockDate = DateTime.Now;  
  29.             session.LockId = 0;  
  30.             session.Timeout = timeout;  
  31.             session.Locked = false;  
  32.             session.Flags = (int)SessionStateActions.InitializeItem;  
  33.  
  34.             MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);  
  35.             collection.Save(session);  
  36.         }  
  37.  
  38.         public override void Dispose()  
  39.         {  
  40.         }  
  41.  
  42.         public override void EndRequest(System.Web.HttpContext context)  
  43.         {  
  44.         }  
  45.  
  46.         public override SessionStateStoreData GetItem(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)  
  47.         {  
  48.             return GetSessionStoreItem(false, context, id, out locked, out lockAge, out lockId, out actions);  
  49.         }  
  50.  
  51.         public override SessionStateStoreData GetItemExclusive(System.Web.HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)  
  52.         {  
  53.             return GetSessionStoreItem(true, context, id, out locked, out lockAge, out lockId, out actions);  
  54.         }  
  55.  
  56.         public override void InitializeRequest(System.Web.HttpContext context)  
  57.         {  
  58.         }  
  59.  
  60.         public override void ReleaseItemExclusive(System.Web.HttpContext context, string id, object lockId)  
  61.         {  
  62.             //update locked=0, expired=, where lockId=?  
  63.             MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);  
  64.  
  65.             var query = Query.And(  Query.EQ("LockId"int.Parse(lockId.ToString())),  
  66.                                     Query.EQ("_id", id),   
  67.                                     Query.EQ("ApplicationName", pApplicationName));  
  68.             var update = Update.Set("Locked"false)  
  69.                                 .Set("Expires", DateTime.Now.AddMinutes(pConfig.Timeout.Minutes));  
  70.  
  71.             collection.Update(query, update);  
  72.         }  
  73.  
  74.         public override void RemoveItem(System.Web.HttpContext context, string id, object lockId, SessionStateStoreData item)  
  75.         {  
  76.             //delete where sessionId=? and lockId=? and applicationname=?  
  77.             MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);  
  78.  
  79.             var query = Query.And(Query.EQ("LockId"int.Parse(lockId.ToString())),  
  80.                                     Query.EQ("_id", id),  
  81.                                     Query.EQ("ApplicationName", pApplicationName));  
  82.             collection.Remove(query);  
  83.         }  
  84.  
  85.         public override void ResetItemTimeout(System.Web.HttpContext context, string id)  
  86.         {  
  87.             //update expire date  
  88.             MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);  
  89.  
  90.             var query = Query.And(Query.EQ("_id", id),  
  91.                                     Query.EQ("ApplicationName", pApplicationName));  
  92.             var update = Update.Set("Expires", DateTime.Now.AddMinutes(pConfig.Timeout.Minutes));  
  93.             collection.Update(query, update);  
  94.         }  
  95.  
  96.         public override void SetAndReleaseItemExclusive(System.Web.HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)  
  97.         {  
  98.             MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);  
  99.             if (newItem)  
  100.             {  
  101.                 //delete expired items  
  102.                 var query = Query.And(Query.EQ("_id", id),  
  103.                                     Query.EQ("ApplicationName", pApplicationName),  
  104.                                     Query.LT("Expires", DateTime.Now));  
  105.  
  106.                 collection.Remove(query);  
  107.  
  108.                 //insert new item  
  109.                 MongoDBSessionEntity session = new MongoDBSessionEntity();  
  110.                 session.ApplicationName = this.pApplicationName;  
  111.                 session.SessionId = id;  
  112.                 session.Created = DateTime.Now;  
  113.                 session.Expires = DateTime.Now.AddMinutes(pConfig.Timeout.Minutes);  
  114.                 session.LockDate = DateTime.Now;  
  115.                 session.LockId = 0;  
  116.                 session.Timeout = item.Timeout;  
  117.                 session.Locked = false;  
  118.                 session.Flags = (int)SessionStateActions.None;  
  119.                 session.SessionItems = Helper.Serialize((SessionStateItemCollection)item.Items);  
  120.  
  121.                 collection.Save(session);  
  122.             }  
  123.             else 
  124.             {  
  125.                 //update item  
  126.                 var query = Query.And(Query.EQ("_id", id),  
  127.                                     Query.EQ("ApplicationName", pApplicationName),  
  128.                                     Query.EQ("LockId"int.Parse(lockId.ToString())));  
  129.                 MongoDBSessionEntity entity= collection.FindOne(query);  
  130.                 entity.Expires = DateTime.Now.AddMinutes(item.Timeout);  
  131.                 entity.SessionItems = Helper.Serialize((SessionStateItemCollection)item.Items);  
  132.                 entity.Locked = false;  
  133.                 collection.Save(entity);  
  134.             }  
  135.         }  
  136.  
  137.         public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)  
  138.         {  
  139.             return false;  
  140.         }  
  141.  
  142.  
  143.  
  144.  
  145.  
  146.  
  147.  
  148.  
  149.  
  150.  
  151.         private SessionStateStoreData GetSessionStoreItem(bool lockRecord, System.Web.HttpContext context,   
  152.                                                             string id,  
  153.                                                             out bool locked,  
  154.                                                             out TimeSpan lockAge,  
  155.                                                             out object lockId,  
  156.                                                             out SessionStateActions actions)  
  157.         {  
  158.             SessionStateStoreData item = null;    
  159.             lockAge = TimeSpan.Zero;  
  160.             lockId = null;  
  161.             locked = false;  
  162.             actions = 0;  
  163.  
  164.             bool foundRecord = false;  
  165.             bool deleteData = false;  
  166.  
  167.             MongoCollection<MongoDBSessionEntity> collection = Helper.GetMongoDBCollection(id);  
  168.  
  169.             if (lockRecord)  
  170.             {   
  171.                 //update db, set locked=1, lockdate=now  
  172.                 var query1 = Query.And(Query.EQ("_id", id),  
  173.                                     Query.EQ("ApplicationName", pApplicationName),  
  174.                                     Query.EQ("Locked", MongoDB.Bson.BsonValue.Create(false)),  
  175.                                     Query.GT("Expires", DateTime.UtcNow));  
  176.  
  177.                 long count = collection.Find(query1).Count();  
  178.                 if (count == 0)  
  179.                 {  
  180.                     locked = true;  
  181.                 }  
  182.                 else 
  183.                 {  
  184.                     var update = Update.Set("Locked"true).Set("LockDate", DateTime.Now);  
  185.                     collection.Update(query1, update);  
  186.                     locked = false;  
  187.                 }  
  188.             }  
  189.             //get item by id  
  190.             var query2 = Query.And(Query.EQ("_id", id),  
  191.                                     Query.EQ("ApplicationName", pApplicationName));  
  192.             MongoDBSessionEntity entity=collection.FindOne(query2);  
  193.             if (entity != null)  
  194.             {  
  195.                 if (entity.Expires < DateTime.Now)  
  196.                 {  
  197.                     locked = false;  
  198.                     deleteData = true;  
  199.                 }  
  200.                 else 
  201.                 {  
  202.                     foundRecord = true;  
  203.                 }  
  204.             }  
  205.  
  206.             //delete item if session expired  
  207.             if (deleteData)  
  208.             {  
  209.                 var query3 = Query.And(Query.EQ("_id", id),  
  210.                                     Query.EQ("ApplicationName", pApplicationName));  
  211.                 collection.Remove(query3);  
  212.             }  
  213.  
  214.             if (!foundRecord)  
  215.                 locked = false;  
  216.  
  217.             if (foundRecord && !locked)  
  218.             {  
  219.                 if (lockId == null)  
  220.                     lockId = 0;  
  221.                 lockId = (int)lockId + 1;  
  222.  
  223.                 var query4 = Query.And(Query.EQ("_id", id),  
  224.                                     Query.EQ("ApplicationName", pApplicationName));  
  225.                 var update4 = Update.Set("LockId", (int)lockId)  
  226.                                         .Set("Flags", (int)SessionStateActions.None);  
  227.                 collection.Update(query4, update4);  
  228.  
  229.                 if (actions == SessionStateActions.InitializeItem)  
  230.                     item = CreateNewStoreData(context, pConfig.Timeout.Minutes);  
  231.                 else 
  232.                     item = Helper.Deserialize(context, entity.SessionItems, entity.Timeout);  
  233.             }  
  234.             return item;  
  235.         }  
  236.     } 

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

 
 
 
 
  1. public static MongoCollection<MongoDBSessionEntity> GetMongoDBCollection(string sessionId)  
  2.         {  
  3.             IPartitionResolver resolver = new MongoDBSessionPartitionResolver();  
  4.             string mongoDbConnectionString = resolver.ResolvePartition(sessionId);  
  5.  
  6.             MongoClient client = new MongoClient(mongoDbConnectionString);  
  7.             MongoServer srv = client.GetServer();  
  8.             MongoDatabase db = srv.GetDatabase(SessionConfiguration.MongoDBName);  
  9.             if (!db.CollectionExists(SessionConfiguration.MongoDBCollectionName))  
  10.                 db.CreateCollection(SessionConfiguration.MongoDBCollectionName);  
  11.  
  12.             MongoCollection<MongoDBSessionEntity> collection = db.GetCollection<MongoDBSessionEntity>(SessionConfiguration.MongoDBCollectionName);  
  13.  
  14.             return collection;  
  15.         } 

#p#

运行效果:

 

 点击Set Session后:

点击Get Session后:

点击Abandon后:

 

 

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

原文链接:http://www.cnblogs.com/aarond/p/MongoSession.html

THE END