完全替换ASP.Net的会话

48

ASP.Net会话对于传统的WebForms应用程序来说似乎很完美,但对于现代AJAX和MVC应用程序来说,它们做了一些严重的问题。

具体而言,访问ASP.Net提供程序只有三种方式:

  1. 锁定读取和写入(默认)- 从AcquireRequestState触发到ReleaseRequestState触发时,会锁定会话。如果浏览器同时发生3个请求,它们将在服务器上排队。这是MVC 2中唯一的选项,但MVC 3允许...

  2. 非锁定只读 - 会话未被锁定,但无法保存。然而,这似乎不可靠,因为某些读取操作似乎会再次锁定会话。

  3. 禁用会话 - 任何尝试读取或写入会话的操作都会引发异常。

然而,在现代MVC应用程序中,我有很多同时发生的AJAX事件 - 我不希望它们在服务器上排队,但我确实希望它们能够写入会话。

我想要的是第四种模式:脏读,最后写入胜出 我认为(如果有错误请指出),唯一的方法是完全替换ASP.Net的会话。我可以编写自己的提供程序,但ASP仍将使用其支持的三种模式之一调用它。是否有任何方法使ASP.Net支持乐观并发?
这让我用一个新类替换所有对会话的调用,该类基本上执行相同的操作,但不锁定 - 这很繁琐。
我希望尽可能保留当前会话的大部分内容(最重要的是各种日志中的会话ID),同时代码替换的量最小。有没有办法做到这一点?理想情况下,我希望HttpContext.Current.Session指向我的新类,但不锁定任何请求。
是否已经有人做过这样的事情?看起来很奇怪,考虑到所有AJAXey MVC应用程序都是ASP的新问题。

@HasanKhan - 我认为(我想确定一下)SetAndReleaseItemExclusive(保存值的方法)只有在应用了锁定时才会在ReleaseRequestState事件期间被调用。如果有某种欺骗方式,我想从已经做过的人那里了解;我不可能是需要乐观并发会话的第一个人,我宁愿不要重复造轮子。 - Keith
3
“我不希望他们在服务器上排队,但我确实希望他们能够写入会话。” 不,你不应该这样做。如果他们需要写入会话,他们需要轮流写入以避免不一致的写入。尽可能多地使用选项3来获取请求,以尽量减少阻塞,并就此结束。 - Ben
3
@Ben - 我不在乎不一致的写入。正如我在问题中所说,我想要最后写入的数据覆盖之前的数据。对于我的应用程序(以及可能还有很多其他应用程序),会话保存用户选项(页面大小、当前排序字段、购物篮等)或缓存值以进行优化——对于我的应用程序,最后写入的模型是我想要的。我相信有很多应用程序的当前锁定模型是完美的,但我不可能是唯一一个寻求会话中乐观并发性的开发人员。 - Keith
@Keith,我的建议是尽可能多地获取选项3的请求。然后再看看你是否还有问题。 - Ben
2
@Aristos 就像我一开始所说的那样,独占锁对许多情况都是理想的。我并不是在争论乐观并发应该成为默认选项,我只是想问是否有其他人已经这样做了或者找到了更好的实现方式。 - Keith
显示剩余5条评论
1个回答

11

首先我要说的是,MS asp.net在“处理页面时使用会话”核心中锁定了会话,这个锁定操作在asp.net 4版本的"webengine4.dll"动态链接库中。

从微软的角度来看,asp.net以正确的方式锁定了会话,因为asp.net无法知道我们在会话中保存的信息类型,所以可以实现“最后修改者获胜”的正确性。

同样正确的是锁定会话的整个页面,因为这样可以给您的程序提供非常重要的同步机制。从现在的经验来看,我告诉您,大多数情况下都需要它。在我用我的会话替换了ms会话之后,在我所有的操作中都需要进行全局锁定,否则就会出现双重插入、双重操作等问题。

我给出一个例子来说明我在这里发现的问题。会话数据保存在SessionSateItemCollection中,这是一个键列表。当会话读取或写入此集合时,它们都是一起进行的。那么让我们看看这种情况。

我们在会话中有三个变量,“VAR1”,“VAR2”,“VAR3”

第一页。
getsession (实际上获取所有的数据并将它们放在列表中)
session[VAR1] = "data1";
savesession()
结果是VAR1=data1,VAR2=(var2的最后一个数据),VAR3=(var3的最后一个数据)

第二页。
getsession (实际上获取所有的数据并将它们放在列表中)
session[VAR2] = "data2";
savesession()
结果是VAR1=(var1的最后一个数据),VAR2=data2,VAR3=(var3的最后一个数据)

第三页。
getsession (实际上获取所有的数据并将它们放在列表中)
session[VAR3] = "data3";
savesession()
结果是 VAR1=(var1的最后一个数据), VAR2=(var2的最后一个数据) VAR3="data3"

请注意,每个页面都有数据的副本,因为它们从会话介质(例如,它们是从数据库中读取的)中读取了最近的数据。

如果我们让这3个页面在没有锁定的情况下运行,实际上我们并没有脏读,也不是“最后一次写入胜出”——我们所拥有的是“最后一次写入破坏其他写入”,因为如果你再想一想,当 VAR1 发生改变时,为什么 VAR2 和 VAR3 保持不变?如果你在其他地方更改了 VAR2,会怎样呢?

正因为这个原因,我们不能让这个系统没有锁定就运行。

现在假设有20个变量……完全混乱。

可能的解决方案

由于asp.net具有多池的多线程功能,保持跨池、跨计算机的相同数据的唯一方法是使用共同的数据库,或者像asp.net状态服务这样对所有进程都通用的程序。

我选择使用共同的数据库,因为这比创建一个专门用于此目的的程序更容易一些。

现在,如果我们将一个cookie用户的会话数据连接起来,并使用完全自定义的数据库字段来控制数据,我们就可以知道要锁定什么、不锁定什么、如何保存或更改、如何比较和保持最后一次写入胜出的数据,从而使其正常工作,并完全禁用asp.net会话。尽管asp.net会话是用于所有需要的通用会话保留器,但不适用于像这种特殊情况。

asp.net是否可以在未来版本中使此工作?是的,通过增加一个称为“dirty”的额外字段,并在会话保存数据时使用“dirty”字段合并数据等方式,asp.net可以实现此目标。但是目前我所发现的情况是无法让它正常工作。

实际上,SessionStateItem在其属性上有一个脏标志,但未将其合并到保存数据的末尾。如果有人能为现有的ms asp.net会话状态保持器找到解决方案,那就太好了,我写下了我现在所发现的,但这并不意味着肯定没有其他方法 - 我只是没找到而已。

希望这一切都能有所帮助:)

自定义SessionStateModule

在这个答案中https://dev59.com/W3A65IYBdhLWcg3wyh57#3660837,James在编写自定义模块之后说:我仍然无法相信ASP.Net Session的自定义实现会锁定整个请求的会话。


1
非常棒的指点(之后会点赞),但是它忽略了一个事实,即在多服务器环境中,即使ASP会话被“锁定”,也可能存在乐观的情况。如果我需要独占锁定,则需要确保无论是在单个服务器上使用“InProc”还是在大型服务器集群上使用都能够正常工作。如果我无法绝对依赖独占锁定,则必须将所有内容视为可能被并发调用覆盖或丢弃。 - Keith
@Keith,ASP.NET只储存在数据库的一个字段——仅一个二进制数据字段。这个二进制数据字段包含了所有密钥。这不能在乐观并发控制下进行,因为如果删除了其中一个数据,则会删除所有其他数据,或者需要一种合并方法,或者需要在每次密钥更改时都进行锁定。然而,在每次密钥更改时进行锁定将使速度变慢,因为我们正在讨论的是数据库类型锁定。 - Aristos
是的,我明白了 - 这不仅仅是针对一个键值对的最后写入胜利,而是整个会话。这并不改变独占锁仍然不能_保证_独占锁的事实。每当我使用会话时,我都必须为乐观情况编写代码,那么为什么我不能将其设置为默认值呢? - Keith
@Keith 在那篇帖子之后,我制作了完全自定义的会话保持器。它花费我的时间比我寻找解决方案来让 MS 工作的时间还要少。缺点是它是完全定制的解决方案,而不像 ASP.NET 那样通用。其他所有的都非常酷,实际上我按照你说的方式进行了实现。 - Aristos
我的应用程序是一个MVC应用程序,其中包含一些遗留的WebForms部分。我已经在Global.asax级别上插入了我的解决方案,因为它还必须考虑自定义身份验证(我们支持SAML2 SSO,而ASP不支持)。 - Keith
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接