- 浏览: 150589 次
- 性别:
- 来自: 沈阳
文章分类
最新评论
-
lxxxxl:
非常感谢,谢谢
使用 .Net Memory Profiler 诊断 .NET 应用内存泄漏(方法与实践) -
bhc1978:
怎么还搞了个密码啊
使用 .NET 调用有自定义 Handler 验证的 XFire Web 服务 -
wrj913:
对我有指导意义
gen_server tasting 之超简单名称服务 -
wrj913:
非常感谢 !!!对我很有用 !
gen_server tasting 之超简单名称服务 -
meng0819:
这个东西确实不错,可惜貌似没有试用版本,有些封闭。
使用 PerformaSure 监控 Apusic
前两周做的一个 Web 应用系统项目中,遇到了一个由于跨页面状态传递机制设计不合理,造成内存泄露的小问题 。有这里做以记录,欢迎大家一同探讨,同时在本文的后面探讨了解决方案,并详细探讨了一个自定义 Session 实现并提供了完整代码 。
闭话少絮,描述问题请先看图。
上面的序列图中描述的一个这样特点的业务:
- 对于客户端用户来讲,该业务由二个页面组成。首先在用户页面1中填写表单并提交到服务器,然后 Web 应用会根据页面1提交的内容返回业页面2供用户填写,在完成表单后由用户提交,此时应用显示成功页完成该业务。
- 对于服务端的 Web 应用来讲,页面1后台代码有3步操作,页面2后台代码涉及4步操作。对于页面1来讲,首先从 Oracle 加裁数据,然后由业务代码完成处理,最后将处理后的“中间”结果(相对整个业务)保存在 Session 中。对于页面2来讲,在用户提交后,首先从 Session 中取出上面步骤保存的临时“中间”结果,然后由业务代码完成相关处理,接下来会持久化一些数据到 Oracle,最后是清除 Session 中此业务所涉及的数据。
- 可以看到,该业务涉及到一次页面状态传递,并通过 Session 来保持。
- 上面图中红色所标记的调用是这里的重点,首先状态是由1.3方法存入 Session 的,然后在3.1方法中被使用,并最终由3.4方法将状态数据从 Session 中清除,释放内存(这里所释放的是 Heap 中的对象引用)。
相信明眼人已经看出来了,这个设计由于一些原因将一项业务分解到二个页中完成,就必然涉及到一次状态传递,因此一旦用户在完成页面1但又不提交页面2(即二步操作被切断,业务终止),则由页面1保存的状态数据就不会被释放(即方法3.4不会“如期”执行)。由于这里的方案是采用了 Session 作为状态数据容器,所以这些无用的对象最终会在 Session 过期后由后台守护线程所清除。但是,这里又有了另外的一个问题,也是我真正所要说的,Session 中对象过期是有时间的,一般都在几十分钟,往往默认都在20、30分钟,有的可能更长。那么结合到上述 Web 应用的结果的,只要在这几十分钟的 Session 有效期内、只访问到该业务页面1的并发用户压力足够大、同时保存到 Session 中的状态数据(由方法1.3存入)占用的内存足够大(往往都不小),就会使内存溢出。结果就是性能逐步下降,最终导致 core dump 的发生。
接下来讨论一下可行的解决方案。实际上替代的方案真的不少,可以大致罗列一下:
- 通过页面来传递状态数据。包括最简单的使用查询字符串推送状态数据,这种方式很常见。其次,可以像 ASP.NET 中常见的 ViewState 所使用的在页面的表单中添加隐藏域的方式来推送状态数据,这种方式相对安全得多,而且往往这些隐藏域中的状态数据会经过加密。缺点是如果业务对安全性要求较高的话,一般不会使用从客户端提交回的状态数据,而会更加倾向于从服务端重新获得。
- 使用独立的专门用于存放状态数据的缓存服务器,memcache 也许是首选。缺点是快速验证的成本较大,如果想在已经上线的生产系统中快速修复这类问题,恐怕不太容易获得资源。
- 第三种是我个人比较喜欢的方式,觉得它比较容易快速验证、解决问题,成本也相对最小。实际上述问题本质上就是需要一种过期时间短同时生命周期也较短的容器对象,这类容器对象应该足够轻量且构造方便。
下面的代码所描述的就是这样一个容器对象,Java 平台的兄弟们看个意思吧。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace com.lzy.javaeye { public interface ISessionId<T> { T Value { get; set; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace com.lzy.javaeye { public interface ISessionEntry<T> { ISessionId<T> SessionId { get; set; } DateTime LastAccessTime { get; set; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace com.lzy.javaeye { public interface ISession<T, U> where T : ISessionEntry<U> { IList<T> Entries(); void SetData(string key, object val, ISessionId<U> sessionId); object GetData(string key, ISessionId<U> sessionId); T this[ISessionId<U> sessionId] { get; } void Register(ref T newEntry); bool Unregister(ISessionId<U> sessionId); bool IsOnline(ISessionId<U> sessionId); void PrepareForDispose(); bool UpdateLastAccessTime(ISessionId<U> sessionId); SessionIdExpiresPolicy SessionIdExpiredPolicy { get; set; } event SessionEntryTimeoutDelegate<T, U> EntryTimeout; } public delegate void SessionEntryTimeoutDelegate<T, U>(T sessionEntry) where T : ISessionEntry<U>; }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace com.lzy.javaeye { [Serializable] public class SessionLongId : ISessionId<long> { public SessionLongId() { this.Value = default(long); } #region ISessionId<long> Members public long Value { get; set; } #endregion public override bool Equals(object obj) { if (obj.GetType().Equals(this.GetType())) return this.Value == ((SessionLongId)obj).Value; else return obj.Equals(this); } public override int GetHashCode() { int i = this.Value.GetHashCode(); return i; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace com.lzy.javaeye { public class SessionEntry<T> : ISessionEntry<T> { public SessionEntry(ISessionId<T> sessionId) { this.SessionId = sessionId; } #region ISessionEntry<T> Members public ISessionId<T> SessionId { get; set; } public DateTime LastAccessTime { get; set; } #endregion } }
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace com.lzy.javaeye { public class Session<T> : ISession<T, long> where T : ISessionEntry<long> { // Timeout event. public event SessionEntryTimeoutDelegate<T, long> EntryTimeout = null; // Storage collections. private IList<T> activeEntries = new List<T>(); private IList<T> expiredEntries = new List<T>(); private Dictionary<ISessionId<long>, Hashtable> data = new Dictionary<ISessionId<long>, Hashtable>(); // 'FollowSessionEntry' policy is safe, but 'Never' is simple. SessionIdExpiresPolicy sessionIdExpiredPolicy = SessionIdExpiresPolicy.Never; // Threading private Thread cleaner = null; private ReaderWriterLockSlim slimLock = new ReaderWriterLockSlim(); private volatile bool running = true; private T GetEntryById(ISessionId<long> sessionId, out int posInActiveEntries) { T outputEntry = default(T); posInActiveEntries = -1; for (int idx = 0; idx < this.activeEntries.Count; idx++) { if (this.activeEntries[idx].SessionId.Equals(sessionId)) { outputEntry = this.activeEntries[idx]; posInActiveEntries = idx; break; } } return outputEntry; } public Session(int sessionTimeoutInMinutes) { this.cleaner = new Thread(new ParameterizedThreadStart(this.ClearExpired)); this.cleaner.IsBackground = true; this.cleaner.Start(sessionTimeoutInMinutes); } public T this[ISessionId<long> sessionId] { get { this.slimLock.EnterReadLock(); T outputEntry = default(T); int posInActiveEntries = -1; try { outputEntry = this.GetEntryById(sessionId, out posInActiveEntries); } finally { slimLock.ExitReadLock(); } if (posInActiveEntries == -1) throw new SessionIdExpiresException<ISessionId<long>, long>(sessionId); return outputEntry; } } public void Register(ref T newEntry) { this.slimLock.EnterWriteLock(); try { newEntry.LastAccessTime = DateTime.Now; // Support 'Never' session Id expires policy. if (newEntry.SessionId.Value == default(long)) newEntry.SessionId.Value = newEntry.LastAccessTime.ToBinary(); this.activeEntries.Add(newEntry); } finally { this.slimLock.ExitWriteLock(); } } public bool Unregister(ISessionId<long> sessionId) { this.slimLock.EnterWriteLock(); bool result = false; try { int posInActiveEntries = -1; this.GetEntryById(sessionId, out posInActiveEntries); if (posInActiveEntries != -1) { this.data.Remove(sessionId); this.activeEntries.RemoveAt(posInActiveEntries); result = true; } } finally { this.slimLock.ExitWriteLock(); } return result; } public bool UpdateLastAccessTime(ISessionId<long> sessionId) { this.slimLock.EnterWriteLock(); bool result = false; try { int posInActiveEntries = -1; T entry = this.GetEntryById(sessionId, out posInActiveEntries); if (posInActiveEntries != -1) { entry.LastAccessTime = DateTime.Now; result = true; } } finally { this.slimLock.ExitWriteLock(); } return result; } public bool IsOnline(ISessionId<long> sessionId) { this.slimLock.EnterReadLock(); bool result = false; try { int posInActiveEntries = -1; this.GetEntryById(sessionId, out posInActiveEntries); if (posInActiveEntries != -1) result = true; } finally { this.slimLock.ExitReadLock(); } return result; } public IList<T> Entries() { this.slimLock.EnterReadLock(); try { return this.activeEntries; } finally { this.slimLock.ExitReadLock(); } } public void SetData(string key, object val, ISessionId<long> sessionId) { if (!this.IsOnline(sessionId)) { if (sessionIdExpiredPolicy == SessionIdExpiresPolicy.FollowSessionEntry) { throw new SessionIdExpiresException<ISessionId<long>, long>(sessionId); } else if (sessionIdExpiredPolicy == SessionIdExpiresPolicy.Never) { T entry = (T)(ISessionEntry<long>)(new SessionEntry<long>(sessionId)); this.Register(ref entry); } else { throw new NotSupportedException(); } } this.slimLock.EnterWriteLock(); try { Hashtable ht = null; if (this.data.ContainsKey(sessionId)) { ht = data[sessionId]; // Overwrite value if key exists. Actions like the ASP.NET session. if (ht.ContainsKey(key)) ht[key] = val; else ht.Add(key, val); this.data[sessionId] = ht; } else { ht = new Hashtable(); ht.Add(key, val); this.data.Add(sessionId, ht); } } finally { this.slimLock.ExitWriteLock(); } } public object GetData(string key, ISessionId<long> sessionId) { this.slimLock.EnterReadLock(); object result = null; try { if (this.data.ContainsKey(sessionId)) result = data[sessionId][key]; } finally { this.slimLock.ExitReadLock(); } return result; } public SessionIdExpiresPolicy SessionIdExpiredPolicy { get; set; } public void PrepareForDispose() { // Setup flag. this.running = false; // Wake up cleaner. if (this.cleaner.ThreadState == ThreadState.WaitSleepJoin) this.cleaner.Interrupt(); // Wait for the thread to stop for (int i = 0; i < 100; i++) { if ((this.cleaner == null) || (this.cleaner.ThreadState == ThreadState.Stopped)) { System.Diagnostics.Debug.WriteLine( "Cleaner has stopped after " + i * 100 + " milliseconds"); break; } Thread.Sleep(100); } // Prepare objects for GC. this.activeEntries.Clear(); this.activeEntries = null; this.expiredEntries.Clear(); this.expiredEntries = null; this.data.Clear(); this.data = null; } void ClearExpired(object sessionTimeout) { while (this.running) { this.slimLock.EnterUpgradeableReadLock(); try { // Process all active entries. for (int i = 0; i < this.activeEntries.Count; i++) { TimeSpan span = DateTime.Now - this.activeEntries[i].LastAccessTime; if (span.TotalMinutes >= Convert.ToDouble(sessionTimeout)) this.expiredEntries.Add(this.activeEntries[i]); } // Remove timeout entries. if (this.expiredEntries.Count > 0) { this.slimLock.EnterWriteLock(); try { foreach (T entry in this.expiredEntries) { System.Diagnostics.Debug.WriteLine(string.Format("Session {0} expired.", entry.SessionId.Value)); // Will slow down the thread. if (this.EntryTimeout != null) this.EntryTimeout(entry); this.data.Remove(entry.SessionId); this.activeEntries.Remove(entry); } this.expiredEntries.Clear(); } finally { this.slimLock.ExitWriteLock(); } } } finally { this.slimLock.ExitUpgradeableReadLock(); } // Sleep for 1 minute. (larger values will speed up the session) Thread.Sleep((int)TimeSpan.FromMinutes(1).TotalMilliseconds); } } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace com.lzy.javaeye { public enum SessionIdExpiresPolicy { FollowSessionEntry, Never // Unsafe option. } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Security.Permissions; using System.Runtime.Serialization; using System.Runtime.Remoting; namespace com.lzy.javaeye { [Serializable] public class SessionIdExpiresException<T, U> : RemotingException where T : ISessionId<U> { protected SessionIdExpiresException(SerializationInfo info, StreamingContext context) : base(info, context) { this.SessionId = (T) info.GetValue("_SessionId", typeof(T)); } public SessionIdExpiresException(T sessionId) : base(string.Format("Session {0} expired.", sessionId.Value)) { if (sessionId.Value.Equals(default(U))) throw new ArgumentException(string.Format("Session Id value '{0}' is invalid.", sessionId.Value)); this.SessionId = sessionId; } public T SessionId { get; private set; } [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("_SessionId", this.SessionId, typeof(T)); } } }
上面的代码实现了以下5个方面,分别包括功能契约接口和实现:
- SessionLongId(ISessionId)表示 Session 中存放对象的标识。
- SessionEntry(ISessionEntry)表示 Session 中存放的对象。
- Session(ISession)表示 Session 对象。
- SessionIdExpiresException 表示了某个 Session Id 过期异常。
- SessionIdExpiresPolicy 表示了 Session 存放的对象过期时对其处理策略。
代码本身已经很简单了,相信仔细看看理解起来是没问题的,需要注意的是需要把放入 Session 中的对象继承自 SessionEntry (或自定义实现的 ISessionEntry 类),就这点来说有些侵入的稍深了些,就看怎么看待了。
写到这里必须要感谢 stefanprodan,Session provider for .NET Remoting and WCF ,原型代码和思路原自他。
先到这里吧,准备休息迎接2009年了,也正好以此贴纪念不平凡的2008年(感觉实际还是平凡度过的,呵呵)。预祝大家元旦快乐,在2009年都能心想事成,一切顺利。
// 2009.03.07 13:30 添加 ////
作者:lzy.je
出处:http://lzy.iteye.com
本文版权归作者所有,只允许以摘要和完整全文两种形式转载,不允许对文字进行裁剪。未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
评论
对任何web系统都有这样的情况
设计的人的问题。
这样设计不就不是强耦合了吗?
C#代码 public class SessionData<T> { public SessionData() { this.CreateTime = DateTime.Now; } public long SessionID { get; set; } public string Name { get; set; } public T Data { get; set; } public TimeSpan CacheTime { get; set; } internal DateTime CreateTime { get; set; } } public interface IDataContainer { SessionData<T> GetData<T>(long sessionId,string name); void SetData<T>(SessionData<T> data); } public class SessionData<T>
{
public SessionData()
{
this.CreateTime = DateTime.Now;
}
public long SessionID { get; set; }
public string Name { get; set; }
public T Data { get; set; }
public TimeSpan CacheTime { get; set; }
internal DateTime CreateTime { get; set; }
}
public interface IDataContainer
{
SessionData<T> GetData<T>(long sessionId,string name);
void SetData<T>(SessionData<T> data);
}
具体怎么实现这个容器还是不用Session的的好,自己实现一个呗,按销毁时间排序,过期的销毁。
我觉得上边的代码和你说的这些是一回事吧?
呵呵,怎么有点乱呢。
session传递业务数据
这个设计本身就是有问题的
请您详细讲讲。
这个设计本身就是有问题的
引用需要注意的是需要吧放入 Session 中的对象继承自 SessionEntry (或自定义实现的 ISessionEntry 类),不过这点来说有些侵入的稍深了些,就看怎么看待了。
不喜欢继承方式,烂用继承是代码的坏味道,造成强耦合。
同意你的看法,这里有个度的把握问题,存在的就是合理的。
个人觉得,像 Java、C# 等这些只支持单继承体系的语言,更应小心这种把继承作为支撑顶层结构的设计风格。
相关推荐
当心环境中的电磁波.pdf
【大班平安教案当心用电】的教案设计主要围绕幼儿的平安教育,旨在教授孩子们基本的用电常识,增强他们的自我爱护意识。活动的目标是让孩子们初步理解用电的基础知识,并学会在生活中应用这些知识来保障自身安全。 ...
这表示市场可能处于超买状态,提示投资者当心可能的回调。 7. **底部条件1**: 如果Q指标小于15,底部1线显示30,否则显示0。这表示市场可能处于超卖状态,可能存在买入机会。 8. **底部条件2**: 如果K指标小于10...
对于民营企业而言,引进外企高管时,也需关注这些潜在的授权陷阱,提供适应性的指导和支持,以避免不必要的损失。同时,建立科学的人才评估体系,如E9人才管理标准,能帮助企业更好地识别和应对这些问题,确保人才...
当心购房踩.doc
文章目录避免内部类中的内存泄漏步骤1:内部类引用其外部类步骤2:构造函数获取封闭的类引用步骤3:声明一种新方法内存泄漏的解剖 避免内部类中的内存泄漏 使用内部类时要当心垃圾收集 如果您已了解静态类和内部类,...
然而,泳池中却暗藏着一些潜在的风险,这些风险不仅包括物理性的伤害,还有可能引发各种疾病。以下是一些关于如何健康安全地游泳,以及预防泳池疾病的知识点。 首先,物理伤害不容忽视。案例中小王的经历警示我们,...
《当心机器人抢了你饭碗》这篇文稿探讨了机器人技术在各个领域的快速发展及其对就业市场的影响。随着机器人的智商提升,它们在程序性工作和智能性工作上的能力日益增强,逐步渗透到各行各业,包括生产线、服务业、...
沙锤的形状和把手设计可能导致儿童误吞或吸入沙锤,从而引起窒息。因此,家长在选购此类玩具时,应确保玩具的尺寸适宜,避免可能被儿童误吞的部件。 另外,风靡一时的“网红气球”也是一个关注焦点。这种气球通常由...
在C++编程中,`std::vector`是一个非常常用的动态数组容器,它可以自动管理内存,提供了灵活的元素插入和删除功能。在处理二维`vector`时,尤其需要注意其特性和操作,以免出现像"vector subscript out of range...
春节购物当心过期商品.doc
安全标志的设计需要遵守一定的规范,以确保它们能够有效地传递安全信息。例如: * 禁止标志的基本形式:外径 d1=0.025L,内径 d2=0.800d1,斜杠宽 c=0.080d1,斜杠与水平线的夹角 a=45°,L 为观察距离。 * ...
警告标识用于提醒潜在危险,如“当心触电”;指令标识指示必须遵循的行为,如“必须戴安全帽”;提示标识提供信息或指示方向,如“安全出口”。这些标识通过特定的色彩、图案和形式,刺激人们的视觉和心理,起到警示...
微波炉作为现代厨房的常用电器,为人们的生活带来极大的便利,然而,如果不正确使用,可能会对健康造成潜在的危害。微波炉的工作原理是通过发射微波,使食物内部的水分子振动产生热量,达到加热食物的目的。然而,...
- 当心碰头、当心爆炸、当心电缆、当心吊物、当心拉断、当心电离辐射标志:提示避免头部碰撞、爆炸物、电缆触电、吊装物品砸伤、辐射伤害等。 - 当心泄露、当心落水、当心突出标志:提醒注意液体泄漏、水域安全和...
HTML页面修改注册表解密 -------------------------------------------------------------------------------- No. File Name Title Size 1 ActiveX技术综述.htm ActiveX技术综述 10K 2 IE恶意修改...
当心被无线路由器“出卖”.pdf
本教案旨在通过一系列的活动,帮助幼儿园大班的孩子们了解和认识生活中常见的安全标志,增强他们的安全意识,并教授他们在遇到潜在危险时的基本自救方法。活动内容丰富,形式多样,旨在提升孩子们的语言表达能力、...
标题和描述都提到了在车内开空调的潜在风险,尤其是长时间使用空调且车窗紧闭的情况下。这个话题涉及的是汽车空调使用安全与车内空气质量的问题。下面我们将深入探讨这个知识点。 汽车空调系统的主要功能是调节车内...