反应式服务中的线程本地状态可用性
任何体系结构决策都需要权衡。 如果您决定采用反应式,也没有什么不同,例如,一方面使用反应式流实现几乎可以立即获得更好的资源利用率,但另一方面会使调试更加困难。 引入反应式库也对您的域产生巨大影响,您的域将不再仅在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
反应式服务中的线程本地状态可用性相关推荐
- react 线程_React式服务中的线程本地状态可用性
react 线程 任何架构决策都需要权衡. 如果您决定采用React式,也没有什么不同,例如,一方面使用React式流实现几乎可以立即获得更好的资源利用率,但另一方面会使调试更加困难. 引入React ...
- c语言 多个线程对同一变量执行memcpy_你可曾听过网络编程中应用线程本地存储?...
壹:你可曾听过线程本地存储? 1. 什么是线程本地存储? 线程本地存储:thread local storage(简称TLS).也叫线程特有存储:thread specific storage(简称T ...
- 聊聊Linux中的线程本地存储(1)——什么是TLS
从本篇开始进入另一个话题:线程本地存储(Thread Local Storage),在介绍这个概念前先说说变量和多线程的相关知识. 多线程下的变量模型 在单线程模型下,变量定义有两个维度,那就是在何处 ...
- Java中的线程本地存储
开发人员中鲜为人知的功能之一是线程本地存储. 这个想法很简单,并且在需要数据的情况下很有用. 如果我们有两个线程,则它们引用相同的全局变量,但我们希望它们具有彼此独立初始化的单独值. 大多数主要的编程 ...
- java中关于线程的状态属性_深入理解Java多线程与并发框(第①篇)——线程的状态...
![](http://img.blog.itpub.net/blog/2020/03/20/5d189a73e1147f37.png?x-oss-process=style/bb) **1. 新建状态 ...
- web服务中使用线程池减少时间的方法
感谢:蚂蚁学Python 这是B站Up主蚂蚁学Python那里学到的笔记 安装并启动flask # 安装并启动flask import flask# 安装python3 -m pip install ...
- 【Android 异步操作】手写 Handler ( Message 消息 | ThreadLocal 线程本地变量 | Looper 中的消息队列 MessageQueue )
文章目录 一.Message 消息 二.ThreadLocal 线程本地变量 三.Looper 中的消息队列 MessageQueue 一.Message 消息 模仿 Android 中的 Messa ...
- 并发基础篇(四): java中线程的状态深入浅出
一.线程的五种状态 线程的生命周期可以大致分为5种,但这种说法是比较旧的一种说法,有点过时了,或者更确切的来说,这是操作系统的说法,而不是java的说法.但对下面所说的六种状态的理解有所帮助,所以 ...
- Java中一个线程只有六个状态。至于阻塞、可运行、挂起状态都是人们为了便于理解,自己加上去的。...
java中,线程的状态使用一个枚举类型来描述的.这个枚举一共有6个值: NEW(新建).RUNNABLE(运行).BLOCKED(锁池).TIMED_WAITING(定时等待).WAITING(等待) ...
最新文章
- java标志清理_JVM内存管理之GC算法精解(五分钟让你彻底明白标记/清除算法)...
- linux 脚本$字符,一文看懂shell脚本中$0 $1 $# $@ $* $? $$ 的各种符号意义
- Bash脚本教程之脚本入门
- 计算机主机核心通常包括,计算机一级考试题库选择题
- 记一次git amend事故处理方案
- python常用模块之time和datetime
- 如何通过Keras来掌握深度学习
- C# 数组比较--取得两个集合的交集,差集,并集的方法
- c#服务器上的文件怎么打印机,如何通过使用C#窗口服务通过打印机打印数据打印文本文件...
- 所谓的日常 #10 - 勤王室馬騰舉義 報父仇曹操興師
- 微积分手机版 pk 清华大学微积分教程
- 分享一款超强看图软件。极速看图。这些看图软件比系统自带的强大多了!
- 【iOS开发】—— 调用相机、相册
- 抽取word中的背景图
- 泡泡堂、QQ堂游戏通信架构分析
- 今年双旦期间简直人品爆棚,晒晒我抽中的趣享付趣号卡
- 机器学习中的优化算法介绍
- PTA实验题:6-7 自定义的学生类 (10 分)
- 单链表、循环链表、双向循环链表总结
- 老司机 iOS 周报 #50 | 2019-01-02