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/