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"/& gt; </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; }
查看本欄目
點擊Get Session後:
點擊Abandon後:
源代碼已經更新到A2D Framework中了: https://sourceforge.net/projects/a2dframework/