基于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个类:
- 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); } }
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中了。