简体   繁体   English

如何处理/取消同一用户asp.net的并发请求

[英]How to handle / cancel concurrent request by same user asp.net

I'm writing an asp.net application. 我正在编写一个asp.net应用程序。 When a user view page XXX.aspx a lot of calls is done to a back-end application which provide data and business logic. 当用户查看页面XXX.aspx时,会对提供数据和业务逻辑的后端应用程序进行大量调用。 I have an API from that back-end application which i use to call methods to execute business logic and get data. 我有一个来自该后端应用程序的API,可用于调用方法以执行业务逻辑并获取数据。

My problem is as follows: If a user hits repeatedly F5 (or even just holds it down) then several web request will be executed at the same time. 我的问题如下:如果用户反复按下F5(甚至只是按住F5),那么将同时执行多个Web请求。 This again forces me to open several connections to the back-end application. 这再次迫使我打开与后端应用程序的多个连接。 To open connection is a expensive so I have implemented a caching mechanism so that a user session get's a connection and sticks to that connection until it's returned to an connection pool after about 15 seconds. 打开连接非常昂贵,因此我实现了一种缓存机制,以便用户会话获得连接并坚持使用该连接,直到15秒钟后将其返回到连接池。 If I activate the caching mechanism the connection towards the back-end app crashes when I hit F5 fast. 如果激活缓存机制,则在我快速按下F5时,与后端应用程序的连接会崩溃。 This happens because all of the request's are processed at the same time and therefore try to use the same connection simultaneously. 发生这种情况是因为所有请求都在同一时间处理,因此尝试同时使用同一连接。 This is not "legal". 这不是“合法的”。 Fare enough :) I have added a sleep function so that a connection will only be used by a single request at any give time. 够了:)我添加了一个睡眠功能,以便在任何给定时间仅单个请求可以使用连接。 But again this is slow and if I hit the F5 20 times i will have to wait the about 15-20 seconds before the "last" response is shown. 但这又很慢,如果我击中F5 20次,我将必须等待大约15-20秒,然后才能显示“最后”响应。 I don't want to process all these request. 我不想处理所有这些请求。 I have tried holding down F5 at other asp.net applications on the web and I notice that some have this problem, other not. 我曾尝试在Web上的其他asp.net应用程序上按住F5键,但我注意到有些出现此问题,而其他则没有。

This must be a quite common problem, but I cannot find any good information about this. 这肯定是一个非常普遍的问题,但是我找不到关于此的任何好的信息。 Are there any settings in asp.net that will cancel all request prior to the latest or do I have to implement my own system for this? asp.net中是否有任何设置可以取消所有最新请求,还是我必须为此实现自己的系统? Are there any best practices around this kind of situation? 围绕这种情况是否有最佳实践?

To provide a more common valid example: Let's say that a page request did 5 selects towards a sql server an a user hits F5 20 times super fast. 提供一个更常见的有效示例:假设一个页面请求对一个sql服务器执行了5次选择,而用户击中F5的速度是20倍。 To execute 5*20 selects is what I want to avoid, probably 10 of the selects will be executed since it takes some time to hit F5 like this, but once there is a build-up of request only the last should be executed. 我要避免执行5 * 20的选择,可能会执行10个选择,因为这样需要花费一些时间才能击中F5,但是一旦出现请求积累,则只应执行最后一个。

Thanks in advance! 提前致谢!

Update/additional info: 更新/其他信息:

  • The content "cannot" be cached. 内容“无法”被缓存。 Also by default, any caching is/should be done in back-end system making distributed cache possible and also making cache available to other applications running on top of back-end system. 同样默认情况下,任何缓存都应该/应该在后端系统中完成,从而使分布式缓存成为可能,并使缓存对后端系统之上运行的其他应用程序可用。
  • Each session gets it's own connection to back-end. 每个会话都有自己的后端连接。
  • The page is "fast", loading at 750-1000ms. 该页面“快速”,加载时间为750-1000ms。 (But I'm a lot faster at pushing F5...) (但是我在推动F5方面要快得多...)

If no better solution appear I will create a version number in session object or similar. 如果没有更好的解决方案出现,我将在会话对象或类似对象中创建一个版本号。 Before doing any call towards the back-end there will be a compare with current request version number against the version number in session. 在对后端进行任何呼叫之前,将当前会话的版本号与会话中的版本号进行比较。 Only request that match the latest version number will be executed. 仅执行与最新版本号匹配的请求。 For my ajax pages this check will be skipped so that concurrent ajax calls are possible. 对于我的ajax页面,此检查将被跳过,因此可以进行并发ajax调用。 It might also be that I will resort to some kind of very short lived caching in asp.net, but it opens a whole new world of issues. 也可能是我将在asp.net中求助于某种非常短暂的缓存,但它打开了一个全新的问题世界。 This is after all a problem that will occur seldom and it's furthermore impossible to prohibit completely due to the fact that I'm designing the asp.net solution to be more or less stateless. 毕竟这是很少会出现的问题,而且由于我将asp.net解决方案设计为或多或少是无状态的,因此不可能完全禁止它。 But if there is 2 or even more web servers there will still be benefit in the "version" system. 但是,如果有2个甚至更多的Web服务器,“版本”系统仍然会受益。

Thanks so far for good suggestions and interesting opinions! 到目前为止,感谢您的良好建议和有趣的意见!

Given the restrictions you're imposing, I would do something like this to serialize backend access for a given user: 鉴于您施加的限制,我将执行以下操作来序列化给定用户的后端访问:

Connection AcquireConnection (string user)
{
      lock (user) {
          Cache cache = HttpRuntime.Cache;
          string key = user + "@@@ConnectionInUse";
          if (cache [key] != null) {
               Monitor.Wait (user, true); // TODO: check return value!
          }
          cache [key] = key;
          return OpenConnection ();
      }
}

void ReleaseConnection (string user)
{
     lock (user) {
         Cache cache = HttpRuntime.Cache;
         string key = user + "@@@ConnectionInUse";
         cache.Remove (key);
         Monitor.Pulse (user);
     }
}

Then in the code of your page: 然后在页面代码中:

   // This will block until there's no other connection in use for 'user'
   Connection cnc = AcquireConnection (user);
   try {
       // Do your thing here
   } finally {
         // This will wake up the next request in Monitor.Wait()
         ReleaseConnection (user);
   }

I'm not sure if this will work, but I just thought: try to use Response.IsClientConnected before your expensive operation. 我不确定这是否行得通,但我只是想:在进行昂贵的操作之前,请尝试使用Response.IsClientConnected

Just wondering if, refreshing that page, ASP.NET knows previous page doesnt need to be completed. 只是想知道,刷新该页面后,ASP.NET是否知道不需要完成上一页。 That doesn't solve your immediate problem, but poses as a small workaround. 那并不能解决您眼前的问题,但是可以作为一个小解决方法。

您是否不仅可以将数据库查询的结果缓存在后端应用程序中(对于已经缓存连接的同一时间),并且是否有相同的请求返回而无需重新查询数据库就返回缓存的结果?

I think you need to use lazy initialization values for calls to your back and services. 我认为您需要使用延迟初始化值来调用后台和服务。 If any client requests information A , then you block the client and in separate background thread call back-end services. 如果有任何客户端请求信息A ,则您将阻止该客户端,并在单独的后台线程调用后端服务中。 All subsequent requests will be blocked by the same lazy value. 所有后续请求都将被相同的惰性值阻止。 When information is available, lazy value is stored in cache and all subsequent requests will have it. 当信息可用时,惰性值存储在缓存中,所有后续请求都将拥有它。

This has got nothing to do with Database. 这与数据库无关。 You need to take care of the behaviour where in user will press F5 rigorously. 您需要注意用户将严格按F5的行为。

One thing you can do is create a session key that keeps track of the client request. 您可以做的一件事是创建一个会话密钥,以跟踪客户端请求。 No further requests from the same client is served for the page. 该页面不会再收到来自同一客户端的其他请求。 Once the process is completed you release that key and wait to accept new requests. 该过程完成后,您释放该密钥并等待接受新的请求。

But what if he tries from another window - in that case that request needs to be served since it is not made rigorously. 但是,如果他从另一个窗口尝试尝试该怎么办?在这种情况下,由于请求没有经过严格的处理,因此需要处理。

I think it is not possible to completely fix the problem. 我认为不可能完全解决问题。 And my be this needs to be taken care at the IIS level and not by ASP.Net. 我要在IIS级别而不是ASP.Net上注意这一点。

It sounds like you're almost there - but I would do more to discourage the use of F5 . 听起来好像快要到了,但是我会做更多的事情来阻止F5的使用。

As you are already tracking users through the use of session to limit their requests to one connection you should try encouraging them to be a bit more patient. 由于您已经在通过会话的使用来跟踪用户,以将他们的请求限制为一个连接,因此您应该尝试鼓励他们更加耐心。 There are two ways you could do this: 有两种方法可以执行此操作:

1 Break New Requests 1中断请求

Add a session variable such as InProgress that you set to true as you start performing your select commands, and set to false when you've finished. 添加一个会话变量如InProgress你设置为true ,当你开始进行你的选择命令,并设置为false ,当你完成。

You can then check this value before starting the actions - if it's already InProgress , bail out, inform the user they've hit F5 to quickly, and please wait to try again. 然后,您可以在开始操作之前检查该值-如果它已经处于InProgress ,请纾困,并通知用户他们已快速按下F5 ,请等待重试。

Obviously you should warn the user at the start that this is a long running process, and the repercussions of their actions - although we all know users don't read warnings. 显然,您应该从一开始就警告用户这是一个长期运行的过程,并且会对他们的行为产生影响-尽管我们都知道用户不会阅读警告。

2 Provide more feedback to the users 2向用户提供更多反馈

You need to look at why your users are pressing F5 so much - usually it's frustration due to a lack of response from the web - people are becoming used to the AJAXy way of doing things - an "in progress" type animation informing them that stuff is happening, please wait with a "Please don't hit F5" type message usually works. 您需要查看用户为何如此频繁地按F5键 -通常是由于缺乏网络响应而感到沮丧-人们逐渐习惯了AJAXy的工作方式-一种“进行中”类型的动画通知他们这些东西正在发生,请等待“请不要按F5”类型的消息,通常可以正常工作。 You'd need to look into using something like an UpdatePanel or some other JS library to help there. 您需要研究使用诸如UpdatePanel之类的东西或其他一些JS库来提供帮助。

On pages where I have a lot of dynamic content I still dump the output into cache for at least 15 seconds so that way if repeated refresh requests hit I don't have to hit the backend every time. 在具有大量动态内容的页面上,我仍然将输出至少转储到缓存中至少15秒钟,这样,如果重复的刷新请求命中,则不必每次都到达后端。 Even if your backend caches it for you, the 15 second buffer means that you only get dinged at most 4 times a minute for content. 即使您的后端为您缓存了该缓存,它的15秒缓冲区也意味着您每分钟最多只能沉迷4次内容。

The next thing you do is implement a blocking scheme, which could be as simple as Session["IsLoading"] = true and not making a new backend connection unless that value is nonexistent or false. 接下来要做的是实现一个阻塞方案,该方案可以像Session["IsLoading"] = true一样简单,除非该值不存在或为false,否则不进行新的后端连接。 If it is in a loading state it returns a simple "leave-me-alone-I'm-working" message that also causes the page to auto refresh after 2 or 3 seconds. 如果处于加载状态,它将返回一条简单的“让我独自工作”的消息,该消息还会导致页面在2或3秒后自动刷新。

I solved this issue late yesterday evening by using a somehow similar approach as given by Gonzalo but with some extra feature that end "obsolute" requests. 我昨天晚上用一个与Gonzalo相似的方法解决了这个问题,但是它有一些额外的功能可以终止“绝对”请求。 Now I'm able to hold down F5 for as long as I want and still get a valid response within 1 sec after releasing F5 :) :) And so far I have not seen the "Please do not refresh so fast!" 现在,我可以一直按住F5并释放F5后1秒钟内仍然得到有效的响应:) :)到目前为止,我还没有看到“请不要刷新得这么快!”。 message on the client side. 客户端上的消息。

I created this class 我创建了这个课程

public class RequestTracker
{   
    private Hashtable hash;
    private Hashtable hashSync;

    public RequestTracker() {
        hash = new Hashtable();
        hashSync = Hashtable.Synchronized(hash);
    }

    public int UpdateRequestId() {
        if (CwGlobal.SessionIdExists)
        {
            int newRequestId = hashSync.ContainsKey(CwGlobal.SessionId) ? (int)hashSync[CwGlobal.SessionId] + 1 : 1;
            hashSync[CwGlobal.SessionId] = newRequestId;
            return newRequestId;
        }
        return 0;
    }
    public int CurrentRequestId() {
        if(CwGlobal.SessionIdExists && hashSync.ContainsKey(CwGlobal.SessionId))
            return (int)hashSync[CwGlobal.SessionId];
        return 0;
    }
}

And use it like this and together with the added lock() it works flawless 并像这样使用它,并与添加的lock()一起完美地工作

public void ValidateRequest()
{
    if(!this.UseRequestTracker || requestTracker == null)
        return;

    if (RequestId < requestTracker.CurrentRequestId())
    {
        httpContext.Response.Write("Please do not refresh so fast!");//TODO add automatic refresh to this page
        httpContext.Response.End();
    }
}
public string Foo()
{
    ValidateRequest();

    string ret;
    using (var apiConn = apiPool.getConn())
    {
        lock (apiConn)
        {
            ret = (string)apiConn.Call("bar");
        }
    }
    return ret;
}

There are some more code to it, like adding the RequestTracker in Global.asax and also making sure that RequestId is set when page load's etc. 还有更多代码,例如在Global.asax中添加RequestTracker,并确保在页面加载时设置RequestId等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM