1. 程式人生 > >後端處理高並發狀態的多次重復請求

後端處理高並發狀態的多次重復請求

由於 build 無限循環 目前 檢索 沒有 調用 body 通過

  相信做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也沒有報警。

後端處理高並發狀態的多次重復請求