反应式服务中的线程本地状态可用性
任何體系結構決策都需要權衡。 如果您決定采用反應式,也沒有什么不同,例如,一方面使用反應式流實現幾乎可以立即獲得更好的資源利用率,但另一方面會使調試更加困難。 引入反應式庫也對您的域產生巨大影響,您的域將不再僅在Payment , Order或Customer方面說話,反應式術語將破解Flux<Payment> , Flux<Order> , Mono<Customer> (或Observable<Payment> , Flowable<Order> , Single<Customer>或您選擇的庫提供的任何Reactive Streams發布者)。 這種折衷很快就變得很明顯,但是您可能會猜想并非所有的折衷都會如此明顯– 泄漏抽象定律保證了這一點。
反應性庫使更改線程上下文變得輕而易舉。 您可以輕松地訂閱一個調度程序,然后在另一個調度程序上執行一部分操作員鏈,最后跳到完全不同的調度程序上。 只要不涉及線程局部狀態,這種從一個線程到另一個線程的跳轉就可以工作-盡管它支持服務的關鍵部分(例如安全性,事務),但通常不會每天處理該狀態, 多租戶)。 當您的技術堆棧中隱藏良好的部分取決于線程局部狀態時,更改線程上下文會導致棘手的錯誤定位。
讓我通過一個簡單的示例演示該問題:
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final String SESSION_ID = "session-id";@GetMapping("documents/{id}") Mono<String> getDocument(@PathVariable("id") String documentId) {MDC.put(SESSION_ID, UUID.randomUUID().toString());LOG.info("Requested document[id={}]", documentId);return Mono.just("Lorem ipsum").map(doc -> {LOG.debug("Sanitizing document[id={}]", documentId);return doc.trim();}); }使用MDC.put(SESSION_ID, UUID.randomUUID().toString())我們將會話session-id放入基礎日志庫的映射診斷上下文中 ,以便稍后進行登錄。
讓我們以自動為我們記錄session-id的方式配置記錄模式:
logging.pattern.console=[%-28thread] [%-36mdc{session-id}] - %-5level - %msg%n當我們通過請求( curl localhost:8080/documents/42 )訪問公開的服務時,我們將看到session-id出現在日志條目中:
[reactor-http-server-epoll-10] [00c4b05f-a6ee-4a7d-9f92-d9d53dbbb9d0] - INFO - Requested document[id=42] [reactor-http-server-epoll-10] [00c4b05f-a6ee-4a7d-9f92-d9d53dbbb9d0] - DEBUG - Sanitizing document[id=42]如果在將session-id放入MDC之后切換執行上下文(例如,通過預訂不同的調度程序),情況將發生變化:
@GetMapping("documents/{id}") Mono<String> getDocument(@PathVariable("id") String documentId) {MDC.put(SESSION_ID, UUID.randomUUID().toString());LOG.info("Requested document[id={}]", documentId);return Mono.just("Lorem ipsum").map(doc -> {LOG.debug("Sanitizing document[id={}]", documentId);return doc.trim();}).subscribeOn(Schedulers.elastic()); // don't use schedulers with unbounded thread pool in production }執行上下文更改后,我們將注意到該調度程序調度的操作員記錄的日志條目中缺少session-id :
[reactor-http-server-epoll-10] [c2ceae03-593e-4fb3-bbfa-bc4970322e44] - INFO - Requested document[id=42] [elastic-2 ] [ ] - DEBUG - Sanitizing document[id=42]您可能會猜到,我們正在使用的日志記錄庫內部深處隱藏著一些ThreadLocal 。
一些Reactive Streams實現提供了允許將上下文數據提供給操作員的機制(例如Project Reactor提供訂戶上下文 ):
@GetMapping("documents/{id}") Mono<String> getDocument4(@PathVariable("id") String documentId) {String sessionId = UUID.randomUUID().toString();MDC.put(SESSION_ID, sessionId);LOG.info("Requested document[id={}]", documentId);return Mono.just("Lorem ipsum").zipWith(Mono.subscriberContext()).map(docAndCtxTuple -> {try(MDC.MDCCloseable mdc = MDC.putCloseable(SESSION_ID, docAndCtxTuple.getT2().get(SESSION_ID))) {LOG.debug("Sanitizing document[id={}]", documentId);return docAndCtxTuple.getT1().trim();}}).subscriberContext(Context.of(SESSION_ID, sessionId)).subscribeOn(Schedulers.elastic()); // don't use schedulers with unbounded thread pool in production }當然,使數據可用只是故事的一部分。 一旦我們提供了session-id ( subscriberContext(Context.of(SESSION_ID, sessionId)) ),我們不僅必須檢索它,還必須將其附加到線程上下文中,并且由于調度程序可以自由地記住自己,所以請自己清理一下。重用線程。
提出的實現會帶回session-id :
[reactor-http-server-epoll-10] [24351524-f105-4746-8e06-b165036d02e6] - INFO - Requested document[id=42] [elastic-2 ] [24351524-f105-4746-8e06-b165036d02e6] - DEBUG - Sanitizing document[id=42]但是,使它起作用的代碼太復雜,太具有侵入性,以致于在大多數代碼庫中都不會張開雙臂來歡迎它,尤其是當它最終散布在整個代碼庫中時。
我很樂意通過為該問題提供一個簡單的解決方案來結束本博文,但我尚未偶然發現這樣的問題(現在,我們需要使用這樣更復雜,更具侵入性的解決方案,同時還要嘗試解決這種復雜性從以業務為中心的軟件部分到基礎設施部分,如果可能,還可以直接到庫本身)。
翻譯自: https://www.javacodegeeks.com/2018/09/thread-local-state-availability-in-reactive-services.html
總結
以上是生活随笔為你收集整理的反应式服务中的线程本地状态可用性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 打造桌面音响院只差最后一步?惠威618超
- 下一篇: jvm破坏双亲委派_破坏JVM