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个类:
- A2DFramework.SessionService.MongoDBSessionIDManager
- 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);
- }
- }
#p#
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;
- }
#p#
运行效果:
点击Set Session后:
点击Get Session后:
点击Abandon后:
源代码已经更新到A2D Framework中了。