【问题标题】:Caching data in ASPNET MVC SinglePageApplication在 ASP NET MVC 单页应用程序中缓存数据
【发布时间】:2018-01-23 19:25:19
【问题描述】:

我正在寻找有关我的问题的解决方案或最佳实践。因为它更像是一个架构挑战,所以我没有源代码......

我的问题是如何处理与我的要求有关的 ASP.NET 会话。还是不符合我的要求?

情况:我在 ASP.NET MVC 之上构建了一个 SinglePageApplication。 MVC-app 提供服务器端接口来处理后端。 MVC 应用程序调用不同后端服务器的服务来请求数据。这些数据可能很多,应该缓存在 MVC-app 中。

据我所知,ASP.NET 会话似乎不是 SPA 的最佳解决方案,因为它的并发行为......?!如果有任何请求需要对会话进行写访问(在我的情况下发生这种情况,因为必须缓存数据),则不可能有并发的 ajax 请求。

示例:至少在我的应用程序中,典型情况是,当用户访问某个屏幕时,会执行一些 ajax 请求。例如。第一个请求触发从后端服务器加载数据并需要一些时间(可能大约 30 秒......)。它还需要对会话进行写访问,因为它应该为这个(!)用户缓存数据。其他请求现在必须等待,即使它们只需要对会话进行读取访问并且不处理要加载的数据。一个请求等待另一个请求的行为应该只发生在某些情况下。

我现在可以使用 System.Runtime.Caching 实现不同的机制来为每个用户缓存数据,但这是推荐的还是最佳的解决方案?

处理这种情况的最佳模式是什么?

有什么最佳做法吗?

编辑2:

一个重要的点是执行并发 ajax 调用时会话的行为。我准备了一个小例子:一个执行 ajax 调用的应用程序 - 有些将会话用作 ReadOnly - 其他的则需要 ReadWrite。操作有超时问题更明显...

这些是我的控制器:

[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class SessionReadOnlyController : Controller
{
    public ActionResult Perform()
    {
        Session["x"] = 5;

        Thread.Sleep(1500);
        return Json(new { success = true }, JsonRequestBehavior.AllowGet);
    }
}

[SessionState(System.Web.SessionState.SessionStateBehavior.Required)]
public class SessionReadWriteController : Controller
{
    public ActionResult Perform()
    {
        Session["x"] = 5;

        Thread.Sleep(1500);
        return Json(new { success = true }, JsonRequestBehavior.AllowGet);
    }
}

这是执行 ajax 调用的 javascript 代码。所有调用都会立即执行:

jQuery(function ()
{
    $('body').append('<p>start....1</p>');
    $.ajax({
        url: '/SessionReadWrite/Perform?id=1',
        success: function () { $('body').append('<p>success1</p>'); }
    });

    $('body').append('<p>start....2</p>');
    $.ajax({
        url: '/SessionReadWrite/Perform?id=2',
        success: function () { $('body').append('<p>success2</p>'); }
    });

    $('body').append('<p>start....3</p>');
    $.ajax({
        url: '/SessionReadOnly/Perform?id=3',
        success: function () { $('body').append('<p>success3</p>'); }
    });

    $('body').append('<p>start....4</p>');
    $.ajax({
        url: '/SessionReadOnly/Perform?id=4',
        success: function () { $('body').append('<p>success4</p>'); }
    });

    $('body').append('<p>start....5</p>');
    $.ajax({
        url: '/SessionReadOnly/Perform?id=5',
        success: function () { $('body').append('<p>success5</p>'); }
    });
});

在我的案例中的结果(取自 internet-explorer 的开发者工具):

  • 呼叫 1 在 1.51 秒后响应
  • 呼叫 2 在 3.03 秒后响应
  • 呼叫 3 在 4.56 秒后响应
  • 呼叫 4 在 4.56 秒后响应
  • 呼叫 5 在 4.56 秒后响应

据我了解,ReadWrite-Session-actions 甚至对于 ReadOnly-Session-actions 都锁定了会话...?! (或者我做错了什么?)

来自msdn (http://msdn.microsoft.com/de-de/library/ms178581%28v=vs.100%29.aspx),上一章:

[...] 但是,如果对同一个会话发出两个并发请求(通过使用相同的 SessionID 值),则第一个请求将获得对会话信息的独占访问权。第二个请求仅在第一个请求完成后执行。 [...] 但是,会话数据的只读请求可能仍需要等待会话数据的读写请求设置的锁定才能清除。

关于我的情况:如果我想在 SinglePageApplication 中缓存数据(内存中可以,因为该应用程序仅由少数用户使用),我需要与会话不同的概念......因此我的问题是最佳做法?

编辑:改进的情况描述...

Edit2:添加了一个示例...

【问题讨论】:

  • 如果你不展示一些代码或以更好的方式解释你在做什么,没有人会帮助你。问题无法理解。
  • 希望描述更好...
  • 现在描述完美了。您必须进行一些自定义编码。我已经解释了如何使用伪代码来做到这一点。制作完整的工作样本太长了。

标签: ajax asp.net-mvc session single-page-application


【解决方案1】:

现在,终于,问题很清楚了。由于 ASP.NET 会锁定来自不同线程的同一用户的会话访问权限,因此 Session 将不允许您解决问题。

所以,你需要自己实现一些线程安全的东西。正如您在问题中所说,没有多少人,内存消耗不是问题,您可以使用自定义静态类,其生命周期从创建(首次访问)到结束或应用程序(例如服务器重启,或应用程序池回收)。

您必须决定数据是为用户存储还是为会话存储。如果它是为用户存储的,它将“存活”会话超时。如果它是为会话存储的,由会话 ID 标识,您可以在会话结束时处理 global.asax 中的会话结束事件。

您需要两级锁定:

  1. 用于访问特定用户的数据和锁(或创建新用户并返回)。
  2. 其他用于用户的锁和数据(就是上面描述的)

对于 1,您可以使用 ConcurrentDictionary 之类的东西。在这个字典中,为用户保留用户 id + 相关的锁和数据。每当您需要为用户读取或写入数据时,请从此字典中获取它。该第一级或锁保证访问用户的锁和数据而不受其他线程的干扰。您可以为此使用GetOrAddmethod。

对于 2,您可以使用具有这样成员的对象,这将是提供用户 id 时从字典返回的对象:

private object _data; // to store the cached data

public void StoreData(object data)
{
    // - If there is a lock return inmediately: 
    //   someone is already writing the data
    // - If there is no lock, create the lock, store the data in _data, 
    //   and release the lock
}

public object GetData(int userId)
{
    // - Wait for the lock
    // - Return data from _data
}

有很多方法可以锁定对_data 的访问。我会使用事件等待句柄:see here for a full explanation。但是还有更多有效的选项。

有了这个实现,

  • 任何试图读取或写入的线程都将从字典 1 中获取用户数据。对于GetOrAdd,此锁将非常短。访问字典的第一个将创建对象 (2)。下一个线程将恢复相同的对象 (2)。
  • 如果线程试图读取数据,它将使用对象(2)GetData方法,该方法将锁定直到有数据可用(实际上它可以读取空数据,并触发StoreData并递归调用GetData 以保证数据的创建)。 *如果线程正在尝试写入数据,它将使用对象 (2) StoreData 方法来创建锁,并在写入时释放它

要做好实现,您需要接收一个返回数据的函数,以便数据的创建也受到锁的保护,即使用这样的方法,而不是StoreData

public void CreateStoreData(Func<object> methodThatCreatesData)
{
    // - If there is a lock return inmediately: 
    //   someone is already writing the data
    // - If there is no lock, create the lock, call methodThatCreatesData,
    //   write the data, and release the lock
}

你的代码必须这样做:

DataCreator dc = new DataCreator(); // prepare the class that generates the data
var userData = UsersData.GetOrAdd(...) // get the dictionary entry for the user
userData.CreateStoreData(dc.DataCreatorMethod);

锁定不是一项简单的任务,要做的一件非常非常重要的事情是保证任何已建立的锁定总是被释放。您可以使用 try-finally 模式来执行此操作。

【讨论】:

  • 我提供了一个例子——我希望——指出了情况。从浏览器的角度来看 Ajax 调用没有问题。
  • 感谢您提供详细信息。我担心... ;) 事件 session-start 和 session-end 是否安全,因为它们总是被触发? (仅在我知道进程内时使用)
猜你喜欢
  • 2013-04-08
  • 2012-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-28
  • 2012-07-26
  • 2010-09-25
相关资源
最近更新 更多