如何利用限流解決遊戲陪玩app開發中的重複提交問題?

雲豹科技程式設計師發表於2021-12-03

問題

在遊戲陪玩app開發中,經常遇到使用者重複提交的問題。

對於解決問題的辦法,你可能會說,前端提交後鎖住按鈕不就可以了嘛!確實能夠解決一部分問題。為什麼是一部分呢?因為呼叫者可能繞過前端介面,直接訪問後端服務。那你也可能會說,在遊戲陪玩app開發的服務端加上判斷不就可以了嘛!

好的,我們先來看一下怎麼判斷:

我們假設使用者的身份是用手機號來區分的,那麼服務端判斷是否重複提交的時候,可以用一條SQL查一下:

select count(*) from table where mobile = 'xxx'

如果查詢出的數量大於0,我們就認為已經報名過,從而中斷程式的執行,返回錯誤。

還有很多的重複提交的場景,我們也都可以通過在服務端增加類似重複判斷的方式來解決。但是每次都要編寫這些大體類似的業務判斷邏輯,遊戲陪玩app開發不是經常說: Don’t repeat yourself 嗎?

再者增加判斷也不能完美解決問題,為什麼呢?因為遊戲陪玩app開發的後端服務一般是多執行緒處理的,甚至可能是分散式的,只是寫個判斷的邏輯還不夠,還要處理資料一致的問題,這個就有點技術含量了。

有什麼通用的簡單辦法嗎?

方案

問題歸納

這裡先來看下游戲陪玩app開發的服務端可能收到重複請求的場景,歸納如下:

  • 前端把關不嚴,使用者 “提交中” 時沒有禁用提交按鈕,導致使用者多次點選,向服務端發起多次請求。
  • 呼叫者直接訪問服務,因程式錯誤(如死迴圈)或者攻擊行為(如重放攻擊),導致對同一業務多次發起服務請求。
  • 程式重試,可能會在前端或者後端的程式碼中使用重試邏輯,發生某些異常或者超時的時候自動重試,導致一次業務多次請求服務。
  • 多執行緒或分散式環境下,加了重複判斷,但是因為資料一致性問題導致判斷失效,業務被重複處理。

前兩種場景比較好理解,這裡不做過多說明。重點說明一下後兩種是如何發生的。

先來看重試導致的重複提交,遊戲陪玩app開發的客戶端第一次請求後沒有正常收到返回,判斷超時後,再次發起第二次業務請求,此時服務端執行了兩次相同的業務處理。

在這裡插入圖片描述

再來看多執行緒環境下的重複提交,執行緒1訪問資料庫查詢資料,然後判斷沒有提交過,線上程1寫入資料前,執行緒2也來訪問資料庫查詢資料,然後判斷也沒有提交過,於是執行緒1和執行緒2都向資料庫寫入相同的資料。

在這裡插入圖片描述

限流方案

現在到重點了,限流為何能夠應用到遊戲陪玩app開發中解決重複提交的問題?

重複提交滿足限流的基本要素

關於限流,這裡定義如下幾個基本要素(個人總結):

  • 限流有一個針對的目標,比如限制IP、限制使用者等。
  • 限流有一個時間週期,比如1秒之內、1分鐘之內等。
  • 限流有一個對應時間週期的閾值,比如每秒10次、每分鐘100次等。

再回到重複提交上,我們可以分析得出:

  • 重複提交可以通過某些資料進行識別,這個就可以看作是限流目標。
  • 重複提交天然的存在一個時間緯度,可以對應到限流的時間週期上。
  • 重複提交即提交一次之後繼續提交,可以使用限流的閾值進行控制,並固定閾值為1。

限流用於重複提交的限制

不過你也許已經發現,這裡有一個隱含的假設:所有第一次業務提交都得到了正確的處理。所以限流計數1才能代表已經提交過一次。這在遊戲陪玩app開發實際執行中很難保證,因為限流計數和業務處理往往不在一個事務中,限流計數一般更靠前一些,所以限流計數可能沒問題,但是業務處理並沒有成功,比如超時、斷網、當機等基礎設施問題,甚至是業務條件不滿足等業務邏輯問題。那麼限流又要被一棍子打死了嗎?

在遇到比較棘手的問題的時候,我經常想之前是否出現過呢?

在網路論壇比較流行的年代,發帖或者回帖後,都會先進入到一個數秒的倒數計時跳轉頁面,倒數計時結束後再跳轉到正常的頁面。

這不就是一種限流並有效防止了重複提交的方式嘛!這個設計給到的一個啟示就是:遊戲陪玩app開發的系統可以在很短的一個時間之內,通過限流這種低成本的方式,限制使用者的重複操作,正常使用者可能不會感覺到或者只有輕微的影響,但卻很大程度上能夠避免重複提交帶來的資料問題,也可以遮蔽某些惡意行為。

基於這個認識,我們再來看下前文提到的幾個重複請求場景:

  • 前端把關不嚴,導致使用者多次點選,向服務端發起多次請求。

服務端可以對某一個使用者的提交使用短時間跨度的限流,比如5秒1次,正常使用者填寫1個表單耗費的時間應在5秒以上,假如使用者在5s內又提交了,則前端可以根據服務端返回的錯誤碼提示使用者,並跳轉到提交結果查詢頁面,使用者可以看到自己的提交結果。如果第一次提交真的沒有處理成功,則使用者可以再重新填寫提交表單,因為這時距離第一次提交超過了5s,因此使用者不會被限流。因為絕大部分提交都應該是正常的,所以這種概率比較小,但是也給了補救的機會,使用者可能會抱怨幾句。

這裡也可能出現遊戲陪玩app開發的服務端處理過慢,查詢結果的時候查不到的問題,解決這個問題或許可以在服務端設定一個儘可能短的超時時間,在前端多查詢幾次,其出現的概率一般不高,而且也可以通過技術手段降低。

  • 直接訪問介面時,因程式錯誤或者攻擊行為,導致同一業務多次發起服務請求。

服務端可以對同一個訪問者的提交使用短時間跨度的限流,比如5秒1次,如果觸發限流,同時給予一個限流懲罰,30秒內都不能提交,還可以對這個限流懲罰時間採用指數遞增的方式。這樣可以儘量降低外部程式異常行為對服務的影響,同時呼叫方正常處理後又能自動恢復正常。

在遊戲陪玩app開發的某些介面中可能會定義時間戳、驗證碼、SessionId之類的引數,也可以把它們加到限流目標中,用以準確識別重複提交。

  • 程式重試導致重複提交,發生某些異常或者超時的時候自動重試,導致一次業務多次請求服務。

這可能是個設計問題。應該避免在中間服務發起提交行為的重試操作,因為很多的業務處理可能都不是冪等的,中間服務的重試行為因為訪問者看不到,所以很可能被忽略掉,從而導致資料問題。如果需要重試,應該僅在遊戲陪玩app開發的業務的發起處進行重試,發起者應該清楚重試邏輯可能導致的問題,並儘量降低影響。

可以在最上層服務引入限流處理,選擇合適的限流目標,限流時間跨度和限流閾值,內部服務一般認為相對可靠,沒必要引入限流。

  • 多執行緒或分散式環境下,加了重複判斷,但是因為資料一致性問題導致判斷失效,業務被重複處理。

通過選擇合適的限流目標,使用分散式一致性的限流演算法,比如使用Redis,也可以實現提交操作在某個時間範圍內只能被執行一次,從而讓重複判斷的結果有效,避免業務重複處理。

通過對遊戲陪玩app開發的這幾種重複提交場景的分析,可以看到:限流並不能完美的避免重複提交,但是它可以提供一種通用的機制很大程度的降低重複提交,而且這種機制的成本可以很低,相比每個方法中硬編碼重複資料的判斷、查詢資料庫、使用分散式鎖等帶來的成本可能都要低不少。當然為了處理的更好,還可能需要前後端的一些其它配合。

其實也可以在遊戲陪玩app開發得到業務處理中增加對重複資料的判斷,因為前邊已經被限流攔截了一道,重複執行的機會可以大為減少,重複資料判斷的邏輯帶來的影響也將很低。特別是一些關鍵業務中,重複提交導致的麻煩可能比較大,不過這時候可能要解決資料庫的查詢效能、分散式的資料一致問題。兩害相權取其輕。

實現

分析清楚了限流對於遊戲陪玩app開發中限制重複提交的意義,就可以在合適的場景來應用它。

比如存在一個前後端分離的系統,使用者都通過一個前端介面來處理業務,使用者同時一般只能操作一個介面,為了儘可能避免重複提交問題,我們在後端API中增加對使用者提交行為的限流操作,對於每個獨立的使用者限制5秒之內只能提交1次。

這裡還是使用 FireflySoft.RateLimit 來做限流,後端API基於ASP.NET Core WebAPI實現。

安裝 Nuget 包

使用包管理器控制檯:

Install-Package FireflySoft.RateLimit.AspNetCore

或者使用 .NET CLI:

dotnet add package FireflySoft.RateLimit.AspNetCore

或者直接新增到專案檔案中:

<ItemGroup><PackageReference Include="FireflySoft.RateLimit.AspNetCore" Version="2.*" /></ItemGroup>

編寫限流規則

在Startup.cs中註冊限流服務並使用限流中介軟體。

public void ConfigureServices(IServiceCollection services){
    ...
    services.AddRateLimit(new InProcessFixedWindowAlgorithm(
        new[] {
            new FixedWindowRule()
            {
                Id = "1",
                ExtractTarget = context =>
                {
                    // 限流的目標:使用者Id,這裡假設它是從HTTP Header中傳遞過來的
                    return (context as HttpContext).Request.GetTypedHeaders().Get<string>("userId");
                },
                CheckRuleMatching = context =>
                {
                  	// 在這裡判斷當前請求是否 “提交行為”,提交行為才進行限流處理
                    var path = (context as HttpContext).Request.Path.Value;
                    if(path == "/Comapny/Add"
                      ||path == "/Comapny/Update"
                      ||path == "/Goods/Purchase"
                      ||path == "/Goods/ChangePrice"
                      ||path == "/Order/Pay"
                      ||path == "/Order/Cancel"){
                        return true;
                    }
                    return false;
                },
                Name = "使用者提交行為限流",
                LimitNumber = 1, // 限流閾值
                StatWindow = TimeSpan.FromSeconds(5), //限流的時間視窗,這裡是5秒
                StartTimeType = StartTimeType.FromNaturalPeriodBeign            }
        })
    );
    ...}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
    ...
    app.UseRateLimit();
    ...}

只需要上邊這些簡單的程式碼就可以用來限制重複提交了。可以跑起來試試。

不過如果你要在分散式環境下使用,還需要準備一個Redis,將 InProcessFixedWindowAlgorithm 換成 RedisFixedWindowAlgorithm ,除了多傳遞一個Redis連線物件,其它的程式碼都是一樣的。

好了,這就是這篇文章的主要內容了。對於用限流解決遊戲陪玩app開發中重複提交的問題,你有什麼想說的呢?

本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理
原文連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2845670/,如需轉載,請註明出處,否則將追究法律責任。

相關文章