<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 防抖技術 防抖(Debouncing)是一種編程技術,用于控制事件處理函數的執行頻率。在用戶與界面交互頻繁的場景中,比如連續滾動、連續輸入等,如果每次交互都觸發事件處理函數,可能會導致性能問題或不必要的數據庫操作。 防抖技術通過設定一個延遲時間,在這段時間內,即使觸發了多次事件,事件處理函數也只會在延遲時間結束后執行一次。如果在這個延遲時間內再次觸發事件,那么之前的延遲會被重置,重新開始計算延遲時間。這樣,只有最后一次事件觸發后,延遲時間結束后,事件處理函數才會執行。 > 防抖技術常用于以下場景 - 搜索框輸入:用戶連續輸入時,只有輸入停止一段時間后才觸發搜索請求。 - 窗口調整大小:用戶調整窗口大小時,只有調整結束后才執行相關操作。 - 滾動事件:用戶滾動頁面時,只有滾動停止一段時間后才進行數據處理。 ### 解決方案 在Web系統的交互設計中,表單提交是一個核心功能,但若不加以適當控制,用戶誤操作或網絡的不穩定性都可能導致同一請求被重復發送,從而產生冗余數據。為了應對這一挑戰,我們可以從兩個層面進行優化: 1. **前端防抖**:通過在用戶界面上實現按鈕的加載狀態(loading state),可以有效防止用戶因手抖而重復點擊,從而避免前端生成多個請求。 2. **后端防抖**:對于由網絡波動引起的請求重發問題,前端的控制措施顯得不夠充分。因此,后端需要引入防抖邏輯,通過識別請求的唯一性(例如使用請求ID或時間戳),確保即便在網絡不穩定的情況下,同一請求也不會被重復處理。 防抖策略是確保Web系統穩定性和數據一致性的關鍵。前端的防抖措施提升了用戶體驗,而后端的防抖措施則保障了數據的準確性和系統的健壯性。兩者結合,可以構建一個更加穩定和用戶友好的Web應用環境。通過這種雙重保障,我們可以有效地減少因誤操作或網絡問題導致的重復請求,維護系統的高效運行。 ## 防抖場景 在Web系統中,并非所有接口都需要防抖,但以下類型的接口通常可以從防抖機制中獲益: #### 表單輸入場景 * **搜索框輸入**:用戶在搜索框中輸入時,可能會觸發實時搜索或自動完成功能。防抖可以減少因快速輸入導致的頻繁請求。 * **表單輸入**:尤其是那些包含多個字段或需要進行復雜驗證的表單,防抖可以避免用戶因誤操作而重復提交。 #### 按鈕點擊場景 按鈕點擊類接口,如提交表單或保存設置,用戶在操作過程中可能會因各種原因頻繁點擊按鈕,這不僅可能影響用戶體驗,還可能導致不必要的服務器請求,增加系統負擔。 為了防止用戶因急促操作而導致的頻繁請求。通過設置一個短暫的等待時間,只有在用戶停止點擊達到預設的時間閾值后,才會觸發實際的請求發送。這種方法不僅減少了服務器的負擔,也避免了因重復請求而可能產生的數據錯誤或沖突。 #### 滾動加載場景 在滾動加載類接口中,如下拉刷新、上拉加載等,用戶的操作往往伴隨著連續的滾動動作。為了提升系統效率并避免因頻繁觸發而導致的性能問題。通過設定一個合理的時間間隔,只有在用戶滾動動作停止一段時間后,系統才會執行請求發送,從而實現智能的請求管理。 ## 如何防抖 ### 使用共享緩存 ![](https://img.kancloud.cn/fc/33/fc333f9aa9dd48d07bb801444391593c_772x1496.png) *圖片來源:https://developer.aliyun.com/article/1541251* ### 使用分布式鎖 ![](https://img.kancloud.cn/e7/b4/e7b4bb7d216b6612c5ff1734cd00a70c_720x1486.png) *圖片來源:https://developer.aliyun.com/article/1541251* 常見的分布式組件有Redis、Zookeeper等,但結合實際業務來看,一般都會選擇Redis,因為Redis一般都是Web系統必備的組件,不需要額外搭建。 ## 代碼實現 模仿一個用戶添加接口 目前數據庫表中沒有對`mobile`字段做UK唯一索引限制,這就會導致每調用一次`userAdd`就會創建一個用戶,即使`mobile`相同。 `demo_user` 表結構 ```sql CREATE TABLE `demo_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL, `mobile` char(13) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` ### 分布式鎖 RedisLock.php ``` <?php /** * @desc RedisLock.php 描述信息 * @author Tinywan(ShaoBo Wan) * @date 2024/6/23 8:31 */ declare(strict_types=1); namespace app\common\service; use support\Redis; class RedisLock { // 分布式并發鎖 const DISTRIBUTED_CONCURRENT_LOCK = 'DISTRIBUTED_CONCURRENT_LOCK:'; /** * @desc: 獲取鎖 * @param string $lock_name * @param int $acquire_time * @param int $lock_timeout * @return bool|string * @author Tinywan(ShaoBo Wan) */ public static function getLockWithTimeout(string $lock_name, int $acquire_time = 3, int $lock_timeout = 20) { $identifier = md5($_SERVER['REQUEST_TIME'] . mt_rand(1, 10000000)); $lock_name = self::DISTRIBUTED_CONCURRENT_LOCK . $lock_name; $lock_timeout = intval(ceil($lock_timeout)); $end_time = time() + $acquire_time; while (time() < $end_time) { $script = <<<luascript local result = redis.call('setnx',KEYS[1],ARGV[1]); if result == 1 then return redis.call('expire',KEYS[1],ARGV[2]) elseif redis.call('ttl',KEYS[1]) == -1 then return redis.call('expire',KEYS[1],ARGV[2]) -- 續租(renew) else return 0 end luascript; $result = Redis::eval($script, 1, $lock_name, $identifier, $lock_timeout); if ($result === 1) { return $identifier; } usleep(100000); } return false; } /** * @desc: 釋放鎖 * @param string $lock_name * @param string $identifier * @return bool * @author Tinywan(ShaoBo Wan) */ public static function releaseLock(string $lock_name, string $identifier): bool { $lock_name = self::DISTRIBUTED_CONCURRENT_LOCK . $lock_name; while (true) { $script = <<<luascript if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]); else return 0 end luascript; $result = Redis::eval($script, 1, $lock_name, $identifier); if ($result == 1) { return true; } break; } return false; } } ``` ### 業務代碼實現 ``` <?php /** * @desc Demo.php 描述信息 * @author Tinywan(ShaoBo Wan) * @date 2024/6/23 20:14 */ declare(strict_types=1); namespace app\controller; use app\common\service\RedisLock; use support\Request; use support\Response; use Tinywan\ExceptionHandler\Exception\BadRequestHttpException; use Tinywan\ExceptionHandler\Exception\ServerErrorHttpException; class DemoController { /** * @desc 用戶添加 * @param Request $request * @return Response * @throws BadRequestHttpException * @throws ServerErrorHttpException * @author Tinywan(ShaoBo Wan) */ public function userAdd(Request $request): Response { $param = $request->post(); /** 鎖名稱 */ $lockName = (string) $param['mobile']; /** 嘗試獲取搶占鎖標識 */ $lockIdentifier = RedisLock::getLockWithTimeout($lockName); /** 沒有拿到鎖說明已經有了請求了 */ if (false === $lockIdentifier) { throw new BadRequestHttpException('您的操作太快啦!請不要連續點擊提交'); } try { /** 進行業務處理 */ \think\facade\Db::table('demo_user')->insert($param); /** 進行業務處理 */ } catch (\Throwable $throwable) { /** 釋放鎖 */ RedisLock::releaseLock($lockName, $lockIdentifier); throw new ServerErrorHttpException('系統異常:' . $throwable->getMessage()); } /** 釋放鎖 */ RedisLock::releaseLock($lockName, $lockIdentifier); return json(['code' => 200, 'msg' => 'success']); } } ``` RedisLock的核心思路就是搶鎖,當一次請求搶到鎖之后,對鎖加一個過期時間,在這個時間段內重復的請求是無法獲得這個鎖。 ### 驗證分布式鎖 > 正確提交 ![](https://img.kancloud.cn/ee/42/ee4269d57c8674d5a7129e415db12653_915x479.png) > 后端異常提交 ![](https://img.kancloud.cn/af/d4/afd488a521b59b97aaa0f4402a0aad78_990x535.png) > 后端未響應之前提交 ![](https://img.kancloud.cn/68/e7/68e792ec1288562ca788353b9d88a04e_955x528.png) > 相同時間段內重復,鎖釋放剩余時間 ![](https://img.kancloud.cn/73/5c/735c15cb47a114db5d7854b753d9e324_879x211.png)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看