后端处理高并发状态的多次重复请求

  相信做Web的,都有可能遇到有多次重复请求发送到后端的情况。而这些重复请求,可能大都是由于在网络较差的情况下,用户多次连续点击。最后导致后端面临处理大量重复请求的境地。阻止这些情况发生的方法有很多。

  比如在前端,可以设置当用户点击按钮之后,禁用按钮直到有结果返回。如果是用ajax发送请求,那么在发送请求之前,可以调用XMLHttpRequest的abort()函数,abort 函数是清除上一个XMLHttpRequest 重置为 readyState 为 0 的状态,并且取消所有未决的网络活动,等等。

  在后端,可以用消息队列,或者缓存,过滤掉相同的请求,也可以设置请求时间间隔。在一个请求执行完一段时间之后才可以执行下一个相同的请求,就当于不休息不给干活。也可以每次都执行你发送的最后一个请求,多次请求只执行最后一次。

  以上是比较常见的一些方法。然后我遇到的问题,用这些方法却不能很好的解决。

问题: 目前在做一个基于Lucene的搜索系统,在用户第一次点击搜索的时候,会先调用建索引的接口,把用户相关的信息写到索引里。然后再根据查询条件去查找索引并返回结果。但是由于键索引消耗的时间和资源有点多(包括调用获取数据的API接口),经常会建1分钟的索引。用户在这段时间,会多次点击搜索。于是在后台,就会发现7,8个重复的建索引的请求。同时多个用户如此点击,导致获取数据的API接口的cpu直接爆满,建索引的速度也相当的慢。都是由于7,8个相当于并行处理的请求。然后我希望这些重复的请求只执行一个,并且以最快的速度返回给前端。

解决:我只想用后端的方式解决,那么很显然,只执行第一个请求,后面的都忽略。一开始设置了一层缓存:

   //设置一层缓存,来阻止大量的相同的访问。
            string cachekey = GetMemoryKey(appName, authorId);
            DateTime result;
            if(!_memoryCache.TryGetValue(cachekey,out result))
            {
                result = DateTime.Now;
                _memoryCache.Set(cachekey, result,new MemoryCacheEntryOptions()
                                 .SetAbsoluteExpiration(TimeSpan.FromMinutes(7))
                                 .SetSlidingExpiration(TimeSpan.FromMinutes(6))
                                 .RegisterPostEvictionCallback((key, value, reason, state) =>
                {
                    Console.WriteLine("缓存失效");
                    
                }));
    
    
                 //执行建索引的代码
            }
                

第一个请求进来,会为它建立缓存,后面的请求进来会先查找缓存中是否有相同的请求。如果有,那就不执行,那么问题来了,后面的请求不执行检索,那它应该立即返回吗?如果立即返回的话,那么前端会认为索引已经建好了,就开始调用搜索的接口,最后搜索的结果自然是空的。也不能不返回,那样就会报超时的错误。

后来看到网上的一句话: 对于高并发或者分布式的场景 重复的请求最好是不要阻塞 通过判断锁状态直接返回处理状态就好

意思就是,后面请求应该是去看它要执行的代码是否正在被执行,如果正在被执行,就返回索引正在维护,如果执行完了就返回索引已经建好,它本身不回去执行。所以我设计了一下,让后面的请求都搁置,知道索引建完,然后再返回。当然这里最好的办法应该是:返回索引正在维护,并且对维护索引的代码上锁。

修改之后的代码:

   //设置一层缓存,来阻止大量的相同的访问。
            string cachekey = GetMemoryKey(appName, authorId);
            DateTime result;
            if(!_memoryCache.TryGetValue(cachekey,out result))
            {
                result = DateTime.Now;
                _memoryCache.Set(cachekey, result,new MemoryCacheEntryOptions()
                                 .SetAbsoluteExpiration(TimeSpan.FromMinutes(7))
                                 .SetSlidingExpiration(TimeSpan.FromMinutes(6))
                                 .RegisterPostEvictionCallback((key, value, reason, state) =>
                {
                    Console.WriteLine("缓存失效");
                    
                }));

              
                //建索引代码
               
                Console.WriteLine("索引建好了");
                //如果索引成功建完
                string buildKey = GetMemoryKey2(appName, authorId);
                _memoryCache.Set(buildKey, "ok", new MemoryCacheEntryOptions()
                                 .SetAbsoluteExpiration(TimeSpan.FromDays(0.8))
                                 .SetSlidingExpiration(TimeSpan.FromDays(0.8))
                                 .RegisterPostEvictionCallback((key, value, reason, state) =>
                                 {
                    Console.WriteLine("建索引缓存失效");

                                 }));
            }
            else
            {
                var result2 = "ok";
                string buildKey = GetMemoryKey2(appName, authorId);
                Console.WriteLine(buildKey);
                while(true)
                {
                  
                    if(_memoryCache.TryGetValue(buildKey,out result2))
                    {
                        Console.WriteLine("索引建好了");
                        break;
                    }
                }
            }

我用了一个无限循环,暴力等待索引建好。这样很多请求过来,只有一个请求在执行,并且等第一个请求执行完之后,全部返回同样的结果。这样,这种并发的情况就可以处理好了。

cpu也没有报警。

原文地址:https://www.cnblogs.com/dacc123/p/8277507.html