從springcloud-gateway了解同步和異步,webflux webMvc、共享變量

webMVC和webFlux

這是spring framework提供的兩種不同的Web編程模型

在這里插入圖片描述

應用場景:

  • 用 WebMvc:
    項目依賴 Servlet 生態、需要簡單同步代碼,或使用阻塞式數據庫(如 MySQL + JDBC)。

  • 用 WebFlux:
    需要高并發(如每秒萬級請求)、低資源消耗,或已使用響應式技術棧(如 MongoDB Reactive、Kafka)。

springcloud -gateway

在 Spring Cloud Gateway 中,請求處理默認是基于 異步非阻塞 模型的,這是由其底層使用的 Reactor Netty 和 WebFlux 框架決定的。

你說是gateway是異步的,但是一個請求不還是要等待下游服務器的響應嗎?這不還是同步嗎?
nonono~
[EventLoop線程] 接收請求 → 發起網絡調用 → 立即釋放 → 處理其他請求

[IO完成回調] → 收到響應 → 繼續處理
實際上:

  • 同一個線程可以交替處理多個請求的不同階段
  • 等待IO時不占用線程(線程可以去處理其他請求)
  • 基于回調/事件機制,IO完成后會通知系統

在這里插入圖片描述

gateway是基于WebFlux的,理由也很合理:
webMVC 最高只能有200并發,而已經到微服務級別的應用,很明顯并發量肯定不會少,網關層面肯定不能用同步去做,異步是最好最確切的選擇。

ContextHolder在同步/異步模式中的實現

contextHolder 個人理解,就是一個全局變量,但是這"全局"是‘一個線程’的范圍

專業的說: 基于 線程/上下文隔離 的臨時數據存儲,生命周期與請求或線程綁定

必要性:

  • 解耦業務代碼:
    避免在方法參數中層層傳遞上下文(如用戶身份、跟蹤ID)。

  • 在攔截器、Service 層、日志工具等地方都能訪問統一上下文。

原生Servlet–request.setAttribute

  • Servlet規范本身并未直接定義線程級別的共享變量接口,只定義了會話范圍的HttpSession
  • 但是可以通過ServletRequest獲取,因為在Servlet規范中,每個請求都是由容器分配的獨立線程處理,ServletRequest 和 ServletResponse 對象天然與線程綁定

在這里插入圖片描述

tomcat 容器內部 則通過ThreadLocal關聯了請求和線程。如果開發者使用ThreadLocal,需要自行清理(存在風險)

WebMvc–ThreadLocal

不同于原生Servlet,這個ThreadLocal不是Tomcat容器給的,而是Springmvc框架本身提供的。

此時: Tomcat 不直接使用 ThreadLocal 存儲 Spring MVC 的請求信息,它只是提供了線程模型(每個請求一個線程),而 Spring MVC 在此基礎上利用 ThreadLocal 存儲請求相關的上下文。

如果自定義使用:

  1. 定義ThreadLocal工具類
public class MyThreadLocalContext {private static final ThreadLocal<String> currentTenantId = new ThreadLocal<>();public static void setTenantId(String tenantId) {currentTenantId.set(tenantId);}public static String getTenantId() {return currentTenantId.get();}public static void clear() {currentTenantId.remove(); // 防止內存泄漏}
}
  1. 在 Filter/Interceptor 中設置和清理

ThreadLocal的隱式線程綁定

在工具類中你會發現,其實set/get過程并沒有關聯任何的線程ID

這是因為,ThreadLocal內部實現了自動關聯線程ID
在這里插入圖片描述

WebFlux

ThreadLocal已經無法處理異步請求的問題了,因為異步請求一般都存在線程的切換

springcloud-gateway提供了幾種機制來處理這個問題

1. Reactor Context

2. TransmittableThreadLocal

簡稱 (TTL) 是阿里開源的一個線程本地變量工具,它是對 Java 標準 ThreadLocal 的增強,主要解決了線程池等異步執行場景下的線程變量傳遞問題。

使用:

  1. 定義TransmittableThreadLocal工具類
package com.ruoyi.common.core.context;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.core.utils.StringUtils;/*** 獲取當前線程變量中的 用戶id、用戶名稱、Token等信息 * 注意: 必須在網關通過請求頭的方法傳入,同時在HeaderInterceptor攔截器設置值。 否則這里無法獲取** @author ruoyi*/
public class SecurityContextHolder
{/*** 既然springboot中默認用的是同步請求,必須等待響應完成,為什么這里用異步的threadlocal去存儲?** 1.  防御性編程,為未來的異步拓展預留空間** 2.  即時外部請求是重構的,框架內部可能使用線程池處理一些異步任務* 比如日志記錄:* @GetMapping("/user/info")*     public UserInfo getUserInfo() {*         // 同步設置用戶上下文*         SecurityContextHolder.setUserId("123");**         // 同步操作*         UserInfo info = userService.getInfo();**         // 異步記錄日志(很常見的需求)*         logAsync("User info accessed"); // 內部使用線程池,內部會用contextHolder獲取到用戶信息,**         return info;*     }* 3. springcloud-gateway中請求默認是異步非阻塞的,原因是它是webflux編程*/private static final TransmittableThreadLocal<Map<String, Object>> THREAD_LOCAL = new TransmittableThreadLocal<>();public static void set(String key, Object value){Map<String, Object> map = getLocalMap();map.put(key, value == null ? StringUtils.EMPTY : value);}public static String get(String key){Map<String, Object> map = getLocalMap();return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY));}public static <T> T get(String key, Class<T> clazz){Map<String, Object> map = getLocalMap();return StringUtils.cast(map.getOrDefault(key, null));}public static Map<String, Object> getLocalMap(){Map<String, Object> map = THREAD_LOCAL.get();if (map == null){map = new ConcurrentHashMap<String, Object>();THREAD_LOCAL.set(map);}return map;}public static void setLocalMap(Map<String, Object> threadLocalMap){THREAD_LOCAL.set(threadLocalMap);}public static Long getUserId(){return Convert.toLong(get(SecurityConstants.DETAILS_USER_ID), 0L);}public static void setUserId(String account){set(SecurityConstants.DETAILS_USER_ID, account);}public static String getUserName(){return get(SecurityConstants.DETAILS_USERNAME);}public static void setUserName(String username){set(SecurityConstants.DETAILS_USERNAME, username);}public static String getUserKey(){return get(SecurityConstants.USER_KEY);}public static void setUserKey(String userKey){set(SecurityConstants.USER_KEY, userKey);}public static String getPermission(){return get(SecurityConstants.ROLE_PERMISSION);}public static void setPermission(String permissions){set(SecurityConstants.ROLE_PERMISSION, permissions);}public static void remove(){THREAD_LOCAL.remove();}
}
  1. 在攔截器中添加和刪除
/*** 自定義請求頭攔截器,將Header數據封裝到線程變量中方便獲取* 注意:此攔截器會同時驗證當前用戶有效期自動刷新有效期** @author ruoyi*/
public class HeaderInterceptor implements AsyncHandlerInterceptor
{@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{if (!(handler instanceof HandlerMethod)){return true;}SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID));SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME));SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY));String token = SecurityUtils.getToken();if (StringUtils.isNotEmpty(token)){LoginUser loginUser = AuthUtil.getLoginUser(token);if (StringUtils.isNotNull(loginUser)){AuthUtil.verifyLoginUserExpire(loginUser);SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);}}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception{SecurityContextHolder.remove();}
}

TTL底層依賴ThreadLocal,但是它的核心是 跨線程傳遞 ThreadLocal 值
具體怎么做到的:父子線程之間的值傳遞

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/89187.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/89187.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/89187.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何在 Pytest 中調用其他用例返回的接口參數?

回答重點在 Pytest 中&#xff0c;我們可以通過使用共享夾具&#xff08;fixtures&#xff09;來調用和復用其他用例返回的接口參數。在 Pytest 中&#xff0c;fixtures 提供了一種靈活且有組織的方式來共享測試數據或對象。具體步驟如下&#xff1a;1&#xff09;首先&#xf…

倒計時熔斷機制的出價邏輯

一、業務背景傳統競價機制中&#xff0c;“倒計時結束”是系統決定成交者的關鍵邏輯&#xff0c;但在實際中&#xff0c;最后3秒突然被搶價的情況極為常見&#xff0c;出現以下問題&#xff1a;用戶投訴平臺機制不公平&#xff1b;用戶出價但未成交&#xff0c;產生爭議訂單&am…

未來手機會自動充電嗎

未來手機實現?全自動充電&#xff08;無需人為干預&#xff09;?是技術發展的明確趨勢&#xff0c;目前已有部分技術落地&#xff0c;但要達到“隨時隨地無感補電”&#xff0c;仍需突破以下關鍵領域&#xff1a;一、已實現的技術&#xff08;當下可用的“半自動”充電&#…

MySQL高級篇(二):深入理解數據庫事務與MySQL鎖機制

引言在現代數據庫系統中&#xff0c;事務和鎖機制是確保數據一致性和完整性的兩大核心技術。無論是金融交易系統、電商平臺還是企業級應用&#xff0c;都離不開這些基礎功能的支持。本文將全面剖析數據庫事務的四大特性&#xff0c;深入探討MySQL中的各種鎖機制&#xff0c;幫助…

XML 指南

XML 指南 引言 XML(可擴展標記語言)是一種用于存儲和傳輸數據的標記語言,它具有高度的可擴展性和靈活性。在互聯網和軟件開發領域,XML被廣泛應用于數據交換、配置文件、文檔存儲等場景。本文將為您詳細介紹XML的基本概念、語法規則、應用場景以及開發技巧,幫助您全面了解…

Flink Watermark原理與實戰

一、引言Flink 作為一款強大的流處理框架&#xff0c;在其中扮演著關鍵角色。今天&#xff0c;咱們來聊聊 Flink 中一個極為重要的概念 —— Watermark&#xff08;水位線&#xff09;&#xff0c;它是處理亂序數據和準確計算的關鍵。接下來我們直入主題&#xff0c;首先來看看…

Rust Web 全棧開發(五):使用 sqlx 連接 MySQL 數據庫

Rust Web 全棧開發&#xff08;五&#xff09;&#xff1a;使用 sqlx 連接 MySQL 數據庫Rust Web 全棧開發&#xff08;五&#xff09;&#xff1a;使用 sqlx 連接 MySQL 數據庫項目創建數據庫準備連接請求功能實現Rust Web 全棧開發&#xff08;五&#xff09;&#xff1a;使用…

【zynq7020】PS的“Hello World”

目錄 基本過程 新建Vivado工程 ZYNQ IP核設置 使用SDK進行軟件開發 基于Vivado2017 Vivado工程建立 SDK調試 固化程序 注&#xff1a;Vivado 2019.1 及之前&#xff1a;默認使用 SDK Vivado 2019.2-2020.1&#xff1a;逐步過渡&#xff0c;支持 SDK 與 Vitis 并存 Vi…

希爾排序和選擇排序及計數排序的簡單介紹

希爾排序法又稱縮小增量法。希爾排序法的基本思想是&#xff1a;先選定一個整數gap&#xff0c;把待排序文件中所有數據分成幾個組&#xff0c;所有距離為gap的數據分在同一組內&#xff0c;并對每一組內的數據進行排序。然后gap減減&#xff0c;重復上述分組和排序的工作。當到…

Solid Edge多項目并行,浮動許可如何高效調度?

在制造企業的數字化設計體系中&#xff0c;Solid Edge 作為主流 CAD 工具&#xff0c;因其靈活的建模能力、同步技術和強大的裝配設計功能&#xff0c;廣泛應用于機械設備、零部件制造等行業的研發場景。隨著企業設計任務復雜化&#xff0c;多項目并行成為常態&#xff0c;Soli…

Flink cdc 使用總結

Flink 與 Flink CDC 版本兼容對照表Flink 版本支持的 Flink CDC 版本關鍵說明Flink 1.11.xFlink CDC 1.2.x早期版本&#xff0c;需注意 Flink 1.11.0 的 Bug&#xff08;如 Upsert 寫入問題&#xff09;&#xff0c;建議使用 1.11.1 及以上。Flink 1.12.xFlink CDC 2.0.x&#…

企業培訓筆記:axios 發送 ajax 請求

文章目錄axios 簡介一&#xff0c;Vue工程中安裝axios二&#xff0c;編寫app.vue三&#xff0c;編寫HomeView.vue四&#xff0c;Idea打開后臺項目五&#xff0c;創建HelloController六&#xff0c;配置web訪問端口七&#xff0c;運行項目&#xff0c;查看效果&#xff08;一&am…

Maven下載與配置對Java項目的理解

目錄 一、背景 二、JAVA項目與Maven的關系 2.1標準java項目 2.2 maven 2.2.1 下載maven 1、下載 2、配置環境 2.2.2 setting.xml 1、配置settings.xml 2、IDEA配置maven 一、背景 在java項目中&#xff0c;新手小白很有可能看不懂整體的目錄結構&#xff0c;以及每個…

Mars3d的走廊只能在一個平面的無法折疊的解決方案

問題場景&#xff1a;1. Mars3d的CorridorEntity只能在一個平面修改高度值&#xff0c;無法根據坐標點位制作有高度值的走廊效果&#xff0c;想要做大蜀山盤山走廊的效果實現不了。解決方案&#xff1a;1.使用原生cesium實現對應的走廊的截面形狀、走廊的坐標點&#xff0c;包括…

LeetCode 每日一題 2025/7/7-2025/7/13

記錄了初步解題思路 以及本地實現代碼&#xff1b;并不一定為最優 也希望大家能一起探討 一起進步 目錄7/7 1353. 最多可以參加的會議數目7/8 1751. 最多可以參加的會議數目 II7/9 3439. 重新安排會議得到最多空余時間 I7/10 3440. 重新安排會議得到最多空余時間 II7/11 3169. …

Bash常見條件語句和循環語句

以下是 Bash 中常用的條件語句和循環語句分類及語法說明&#xff0c;附帶典型用例&#xff1a;一、條件語句 1. if 語句 作用&#xff1a;根據條件執行不同代碼塊 語法&#xff1a; if [ 條件 ]; then# 條件為真時執行 elif [ 其他條件 ]; then# 其他條件為真時執行 else# 所有…

uni-app 選擇國家區號

uni-app選擇國家區號組件 hy-countryPicker 我們在做登錄注冊功能的時候&#xff0c;可能會遇到選擇區號來使用不同國家手機號來登錄或者注冊的功能。這里我就介紹下我這個uni-app中使用的選擇區號的組件&#xff0c;包含不同國家國旗圖標。 效果圖 別的不說&#xff0c;先來…

客戶端主機宕機,服務端如何處理 TCP 連接?詳解

文章目錄一、客戶端主機宕機后迅速重啟1、服務端有數據發送2、服務端開啟「保活」機制3、服務端既沒有數據發送&#xff0c;也沒有開啟「保活」機制二、客戶端主機宕機后一直沒有重啟1、服務端有數據發送2、服務端開啟「保活」機制3、服務端既沒有數據發送&#xff0c;也沒有開…

《大數據技術原理與應用》實驗報告五 熟悉 Hive 的基本操作

目 錄 一、實驗目的 二、實驗環境 三、數據集 四、實驗內容與完成情況 4.1 創建一個內部表 stocks&#xff0c;字段分隔符為英文逗號&#xff0c;表結構下所示。 4.2 創建一個外部分區表 dividends&#xff08;分區字段為 exchange 和symbol&#xff09;&#xff0c;字段…

【橘子分布式】Thrift RPC(編程篇)

一、簡介 之前我們研究了一下thrift的一些知識&#xff0c;我們知道他是一個rpc框架&#xff0c;他作為rpc自然是提供了客戶端到服務端的訪問以及兩端數據傳輸的消息序列化&#xff0c;消息的協議解析和傳輸&#xff0c;所以我們今天就來了解一下他是如何實現這些功能&#xff…