【用户会话信息在异步事件/线程池的传递】
用户会话信息在异步事件/线程池的传递
@author:shengfq
@date:2024-07-29
@version:1.0
背景:
同事写的一个代码功能,是在一个主线程中通过如下代码进行异步任务的执行,结果遇到了问题.
1.ThreadPool.execute(Runnable)启动一个子线程执行异步任务
2.applicationContext.publishEvent(applicationEvent)启动一个异步事件执行异步任务
问题:
由于异步任务线程内也需要用户的身份会话信息UserDetail对象,他将该对象作为payload直接从主线程通过方法传递进去(硬编码),导致业务方法带了个UserDetail的参数,而且进入到子线程后,通过DetailHelper.setUserDetail(UserDetail)这种方式将会话数据放入Context后,执行service方法却意外清理了UserDetail对象.为什么会出现这种情况? 用户会话信息在这种场景下如何传递比较合适?
@Async(ThreadPoolName)
@EventListener(PublishEvent event){UserDetail userDetail=event.getPayload();DetailHelper.setUserDetail(userDetail);...DetailHelper.getUserDetail();//是null
}
实现原理:
通过定制化线程池对象ThreadPoolExecutor的submit(),进行包装,将Context的用户信息通过方法传参给子线程的ContextHolder.
代码如下:
1. ITask ICallback 这是执行异步任务的抽象,每个任务一个类一个回调
public interface ITask {/**执行异步任务*/void executeTask();
}public interface ICallback {/**是否执行成功*/void callback(boolean result);
}
2. EssContextHolder 线程变量上下文
package org.hzero.samples.core.context;/*** 线程变量上下文*/
public class EssContextHolder {private EssContextHolder() {}/*** sid*/private final static ThreadLocal<String> SID = new ThreadLocal<>();/*** token*/private final static ThreadLocal<String> TOKEN = new ThreadLocal<>();/*** 联盟code*/private final static ThreadLocal<String> UNION_CODE = new ThreadLocal<>();/*** 联盟unionId*/private final static ThreadLocal<String> UNION_ID = new ThreadLocal<>();/*** 设置SID** @param sid*/public static void setSID(String sid) {EssContextHolder.SID.set(sid);}/*** 获取SID*/public static String getSID() {return EssContextHolder.SID.get();}/*** 设置TOKEN** @param token*/public static void setToken(String token) {EssContextHolder.TOKEN.set(token);}/*** 获取TOKEN*/public static String getToken() {return EssContextHolder.TOKEN.get();}/*** 设置unionCode*/public static void setUnionCode(String unionCode) {EssContextHolder.UNION_CODE.set(unionCode);}/*** 获取unionCode*/public static String getUnionCode() {return EssContextHolder.UNION_CODE.get();}/*** 设置unionId*/public static void setUnionId(String unionId) {EssContextHolder.UNION_ID.set(unionId);}/*** 获取联盟unionId*/public static String getUnionId() {return EssContextHolder.UNION_ID.get();}
}
3. ThreadPoolExecutorMdcWrapper 线程池的个性化定制类
package org.hzero.samples.core.context;import org.slf4j.MDC;import java.util.concurrent.*;/*** @ClassName ThreadPoolExecutorMdcWrapper* @Description 线程池的个性化定制类* @Author shengfq* @Date 2021/5/28 0028 上午 10:53* @Version*/
public class ThreadPoolExecutorMdcWrapper extends ThreadPoolExecutor {public ThreadPoolExecutorMdcWrapper(AsyncTaskThreadPoolConfig config, ThreadFactory threadFactory, RejectedExecutionHandler handler ) {this(config.getCorePoolSize(), config.getMaxPoolSize(), config.getKeepAliveSecond(), TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(config.getQueueCapacity()),threadFactory,handler);}public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);}public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);}public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}@Overridepublic void execute(Runnable task) {String sid = EssContextHolder.getSID();String token = EssContextHolder.getToken();String unionId = EssContextHolder.getUnionId();super.execute(ThreadMdcUtil.wrap(sid,token,unionId,task, MDC.getCopyOfContextMap()));}@Overridepublic <T> Future<T> submit(Runnable task, T result) {String sid = EssContextHolder.getSID();String token = EssContextHolder.getToken();String unionId = EssContextHolder.getUnionId();return super.submit(ThreadMdcUtil.wrap(sid,token,unionId,task, MDC.getCopyOfContextMap()), result);}@Overridepublic <T> Future<T> submit(Callable<T> task) {String sid = EssContextHolder.getSID();String token = EssContextHolder.getToken();String unionId = EssContextHolder.getUnionId();return super.submit(ThreadMdcUtil.wrap(sid,token,unionId,task, MDC.getCopyOfContextMap()));}@Overridepublic Future<?> submit(Runnable task) {String sid = EssContextHolder.getSID();String token = EssContextHolder.getToken();String unionId = EssContextHolder.getUnionId();return super.submit(ThreadMdcUtil.wrap(sid,token,unionId,task, MDC.getCopyOfContextMap()));}
}
4. ThreadMdcUtil.java Runnable和Callable的wrap包装,增加环境变量的绑定
package org.hzero.samples.core.context;import org.slf4j.MDC;import java.util.Map;
import java.util.concurrent.Callable;/*** @ClassName ThreadMdcUtil* @Description * @Author shengfq* @Date 2021/5/28 0028 上午 10:54* @Version*/
public class ThreadMdcUtil {public static <T> Callable<T> wrap(final String sid,final String token,final String unionId,final Callable<T> callable, final Map<String, String> context) {return () -> {EssContextHolder.setSID(sid);EssContextHolder.setToken(token);EssContextHolder.setUnionId(unionId);if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}try {return callable.call();} finally {MDC.clear();}};}public static Runnable wrap(final String sid,final String token,final String unionId,final Runnable runnable, final Map<String, String> context) {return () -> {EssContextHolder.setSID(sid);EssContextHolder.setToken(token);EssContextHolder.setUnionId(unionId);if (context == null) {MDC.clear();} else {MDC.setContextMap(context);}try {runnable.run();} finally {MDC.clear();}};}
}
5. ExecuteTaskUtils 异步任务执行器(对调用者开放)
package org.hzero.samples.core.context;import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;import java.util.Map;/*** @ClassName ExecuteTaskUtils* @Description 执行异步线程工具类* @Author shengfq* @Date 2021/5/28 0028 上午 8:23* @Version*/
@Slf4j
public class ExecuteTaskUtils {private static ExecuteTaskService executeTaskService;/*** 获取单例对象** @return*/private static synchronized ExecuteTaskService getInstance() {if (executeTaskService == null) {executeTaskService = new ExecuteTaskService();}return executeTaskService;}/*** 提交异步任务** @return*/public static void submitTask(ITask task) {submitTask(task, null);}/*** 提交异步任务,执行结束后回调方法.** @param task* @param callback*/public static void submitTask(ITask task, ICallback callback) {Map<String, String> mdcMap = MDC.getCopyOfContextMap();String sid = EssContextHolder.getSID();String token = EssContextHolder.getToken();String unionId = EssContextHolder.getUnionId();getInstance().submitTask(() -> {try {// 设置if (mdcMap != null) {MDC.setContextMap(mdcMap);}EssContextHolder.setSID(sid);EssContextHolder.setToken(token);EssContextHolder.setUnionId(unionId);task.executeTask();if (callback != null) {callback.callback(true);}} catch (Exception e) {log.error("执行异步任务异常:", e);if (callback != null) {callback.callback(false);}}});}
}
6. ExecuteTaskService.java 对线程池对象ThreadPoolExecutor进行初始化,异步任务api封装
package org.hzero.samples.core.context;import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;@Slf4j
public class ExecuteTaskService {protected static ThreadPoolExecutor pools = null;/*** 提交任务请求* @param task*/public void submitTask(Runnable task) {pools.execute(task);}/*** 停止线程池*/public void shutdown() {pools.shutdown();}/*** 初始化线下池*/public ExecuteTaskService(){initPool(6,10);}/*** 初始化线下池*/public ExecuteTaskService(int corePoolSize, int maxPoolSize){initPool(corePoolSize,maxPoolSize);}private void initPool(int corePoolSize,int maxPoolSize){ThreadFactory guavaThreadFactory = new ThreadFactoryBuilder().setNameFormat("ess-task-pool-%d").build();pools = new ThreadPoolExecutorMdcWrapper(corePoolSize, maxPoolSize, 10L,TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10000),guavaThreadFactory, new ThreadPoolExecutor.CallerRunsPolicy());}
}
总结
这是我几年前实现的环境变量传递,在异步线程池执行异步任务时通过公共组装实现,而不用用户自己每次传递会话信息,同时也实现了线程池对象的定制化,对于异步事件@Async注解如果指定了线程池名字,原则上也是使用了我们自己初始化的ThreadPoolExecutorMdcWrapper对象.
相关文章:
【用户会话信息在异步事件/线程池的传递】
用户会话信息在异步事件/线程池的传递 author:shengfq date:2024-07-29 version:1.0 背景: 同事写的一个代码功能,是在一个主线程中通过如下代码进行异步任务的执行,结果遇到了问题. 1.ThreadPool.execute(Runnable)启动一个子线程执行异步任务 2.applicationContext.publis…...
Java8: BigDecimal
Java8:BigDecimal 转两位小数的百分数-CSDN博客 BigDecimal 先做除法 然后取绝对值 在Java 8中,如果你想要对一个BigDecimal值进行除法操作,并随后取其绝对值,你可以通过组合divide方法和abs方法来实现这一目的。不过,需要注意的…...

苹果推送iOS 18.1带来Apple Intelligence预览
🦉 AI新闻 🚀 苹果推送iOS 18.1带来Apple Intelligence预览 摘要:苹果向iPhone和iPad用户推送iOS 18.1和iPadOS 18.1开发者预览版Beta更新,带来“Apple Intelligence”预览。目前仅支持M1芯片或更高版本的设备。Apple Intellige…...

testRigor-基于人工智能驱动的无代码自动化测试平台
1、testRigor介绍 简单来说,testRigor是一款基于人工智能驱动的无代码自动化测试平台,它能够通过分析应用的行为模式,智能地生成测试用例,并自动执行这些测试,无需人工编写测试脚本。可以用于Web、移动、API和本机桌面…...

hadoop学习(一)
一.hadoop概述 1.1hadoop优势 1)高可靠性:Hadoop底层维护多个数据副本,即使Hadoop某个计算元素或存储出现故障,也不会导致数据的丢失。 2)高扩展性:在集群间分配任务数据,可方便扩展数以千计…...

Linux性能监控:sar的可视化方案
在当今的IT环境中,系统性能监控是确保应用程序稳定运行和快速响应问题的关键。Linux作为一种广泛使用的操作系统,拥有多种性能监控工具,其中sar(System Activity Reporter)因其全面性和灵活性被广泛采用。然而…...

如何录制电脑屏幕视频,5招让您成为电脑录制高手
在今天,屏幕录制成为每个电脑使用者都应掌握的基础技能。不论是教学分享、会议记录还是游戏直播,屏幕录制都能帮你捕捉那些重要的瞬间,将无形的信息转化为有形的视频。那么,如何录制电脑屏幕视频呢?今天,我…...

AI届的新宠:小语言模型(SLM)?
大语言模型(LLM)在过去几年产生了巨大影响,特别是随着OpenAI的ChatGPT的出现,各种大语言模型如雨后春笋般出现,国内如KimiChat、通义千问、文心一言和智谱清言等。 然而,大语言模型通常拥有庞大的参数&…...
PMP模拟题错题本
模拟题A 错题整理 项目经理为一个具有按时完成盈利项目历史记录的组织工作。然而,由于缺乏相关方的支持以及他们未能提供信息,这些项目都经历过问题。若要避免这些问题,项目经理在新项目开始时应该做什么? A. 在启动阶段识别关键…...
Laravel Dusk:点亮自动化测试的明灯
Laravel Dusk:点亮自动化测试的明灯 在Web开发中,确保应用程序的用户体验和功能正确性至关重要。Laravel Dusk是一个强大的浏览器自动化测试工具,它允许开发者模拟用户与应用程序的交互,从而进行端到端的测试。本文将深入探讨Lar…...

Git、Gitlab以及分支管理
分布式版本控制系统 一、Git概述 Git是一种分布式版本控制系统,用于跟踪和管理代码的变更。它由Linus torvalds创建的,最初被设计用于Linux内核的开发。Git 允许开发人员跟踪和管理代码的版本,并且可以在不同的开发人员之间进行协作。 Githu…...
TCP/IP 协议栈介绍
TCP/IP 协议栈介绍 1. 引言 TCP/IP(传输控制协议/互联网协议)是一组用于数据网络中通信的协议集合,它是互联网的基础。本文将详细介绍TCP/IP协议栈的各个层次、工作原理以及其在网络通信中的作用。 2. TCP/IP 协议栈的层次结构 TCP/IP协议…...
香橙派orangepi系统没有apt,也没有apt-get,也没有yum命令,找不到apt、apt-get、yum的Linux系统
以下是一个关于如何在 Orange Pi 上的 Arch Linux 系统中发现缺失包管理器的问题并解决的详细教程。 发现问题 确认系统类型: 使用以下命令检查当前的 Linux 发行版: uname -a cat /etc/os-release如果你看到类似于 “Arch Linux” 的信息,说…...

在invidia jetpack4.5.1上运行c++版yolov8(tensorRT)
心路历程(可略过) 为了能在arm64上跑通yolov8,我试过很多很多代码,太多对库版本的要求太高了; 比如说有一个是需要依赖onnx库的,(https://github.com/UNeedCryDear/yolov8-opencv-onnxruntime-…...
Vue3 接入 i18n 实现国际化多语言
在 Vue.js 3 中实现网页的国际化多语言,最常用的包是 vue-i18n。 第一步,安装一个 Vite 下使用 <i18n> 标签的插件:unplugin-vue-i18n npm install unplugin-vue-i18n # 或 yarn add unplugin-vue-i18n 安装完成后,调整 v…...
深度学习环境坑。
前面装好了之后装pytorch之后老显示gpufalse。 https://www.jb51.net/article/247762.htm 原因就是清华源的坑。 安装的时候不要用conda, 用pip命令 我cuda12.6,4070s cudnn-windows-x86_64-8.9.7.29_cuda12-archive.zip cuda_12.5.1_555.85_windows.…...

LLM——10个大型语言模型(LLM)常见面试题以及答案解析
今天我们来总结以下大型语言模型面试中常问的问题 1、哪种技术有助于减轻基于提示的学习中的偏见? A.微调 Fine-tuning B.数据增强 Data augmentation C.提示校准 Prompt calibration D.梯度裁剪 Gradient clipping 答案:C 提示校准包括调整提示,尽量减少产生…...
MongoDB - 聚合阶段 $count、$skip、$project
文章目录 1. $count 聚合阶段2. $skip 聚合阶段3. $project 聚合阶段1. 包含指定字段2. 排除_id字段3. 排除指定字段4. 不能同时指定包含字段和排除字段5. 排除嵌入式文档中的指定字段6. 包含嵌入式文档中的指定字段7. 添加新字段8. 重命名字段 1. $count 聚合阶段 计算匹配到…...
如何获取文件缩略图(C#和C++实现)
在C中,可以有以下两种办法 使用COM接口IThumbnailCache 文档链接:IThumbnailCache (thumbcache.h) - Win32 apps | Microsoft Learn 示例代码如下: VOID GetFileThumbnail(PCWSTR path) {HRESULT hr CoInitialize(nullptr);IShellItem* i…...
create-vue项目的README中文版
使用方法 要使用 create-vue 创建一个新的 Vue 项目,只需在终端中运行以下命令: npm create vuelatest[!注意] (latest 或 legacy) 不能省略,否则 npm 可能会解析到缓存中过时版本的包。 或者,如果你需要支持 IE11,你…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...

Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...