当前位置:网站首页>小型项目如何使用异步任务管理器实现不同业务间的解耦
小型项目如何使用异步任务管理器实现不同业务间的解耦
2022-08-09 14:53:00 【Java升级之路】
前言
大家好,我是希留。
在有些业务场景中,系统对于响应时间有一定的要求,而一个方法里面同步执行的业务逻辑太多势必会影响响应速度,带来不好的用户体验。比如登录时记录登录用户的访问记录、注册时发送邮件、短信通知等等场景,不需要等待处理结果之后再进行下一步操作,这时候就可以使用异步线程进行处理,这样主线程不会因为这些耗时的操作而阻塞,保证主线程的流程可以正常进行。
异步任务可以通过多线程也可以通过消息队列来实现,目的都是为了实现不同业务之间的解耦,提高业务系统的响应速度。但是相对于小型系统采用多线程的方式相对更便捷,所以,这篇文章就记录一下我是如何使用多线程实现异步任务管理器来记录访问日志的。
一、异步任务管理器是什么?
顾名思义,就是用来对异步任务进行统一的管理,并提供了一种访问其唯一对象的方式,这样做得好处就是,在内存中有且仅有一个实例,减少了内存的开销,尤其对于频繁的创建和销毁实例,用这种方式来频繁执行多个异步任务性能是相对比较好的。
二、实现步骤
1.自定义线程池
执行异步任务时,需要将执行的任务放入到线程池中,所以需配置好我们的线程池。并创建一个调度线程池执行器,用来执行异步任务。代码如下(示例):
@Configuration
public class ThreadPoolConfig {
/** * 核心线程池大小 **/
private int corePoolSize = 50;
/** * 最大可创建的线程数 **/
private int maxPoolSize = 200;
/** * 队列最大长度 **/
private int queueCapacity = 1000;
/** * 线程池维护线程所允许的空闲时间 **/
private int keepAliveSeconds = 300;
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPoolSize);
executor.setCorePoolSize(corePoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
// 线程池对拒绝任务(无线程可用)的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
/** * 执行周期性或定时任务 */
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
return new ScheduledThreadPoolExecutor(corePoolSize, new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
// 打印线程异常信息
ThreadsUtils.printException(r, t);
}
};
}
/** * 打印线程异常信息 */
public static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone()) {
future.get();
}
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (t != null) {
logger.error(t.getMessage(), t);
}
}
2. 新建异步任务管理器类
代码如下(示例):
public class AsyncManager {
/** * 操作延迟10毫秒 */
private final int OPERATE_DELAY_TIME = 10;
/** * 异步操作任务调度线程池 */
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
/** * 饿汉式单例模式 */
private AsyncManager(){
}
private static AsyncManager me = new AsyncManager();
public static AsyncManager me() {
return me;
}
/** * 执行任务 * @param task 任务 */
public void execute(TimerTask task) {
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
3. 新建异步工厂类
设计这个类主要是用来产生 TimerTask 的,代码如下(示例):
@Slf4j
public class AsyncFactory {
/** * 记录登录信息 * @param username 用户名 * @param status 状态 * @param message 消息 * @param args 列表 * @return 任务task */
public static TimerTask recordLoginLog(final String username, final String status, final String message,final Object... args) {
// 客户端操作系统、浏览器等信息
final UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
// 请求的IP地址
final String ip = ServletUtil.getClientIP(ServletUtils.getRequest());
return new TimerTask() {
@Override
public void run() {
String address = AddressUtils.getRealAddressByIp(ip);
// 获取客户端操作系统
String os = userAgent.getOs().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
// 封装对象
XlLoginLog loginLog = new XlLoginLog();
loginLog.setUserCode(username);
loginLog.setIpaddr(ip);
loginLog.setLoginLocation(address);
loginLog.setBrowser(browser);
loginLog.setOs(os);
loginLog.setMsg(message);
loginLog.setLoginTime(new Date());
// 日志状态
if (Constants.LOGIN_FAIL.equals(status)) {
loginLog.setStatus(Integer.valueOf(Constants.FAIL));
} else {
loginLog.setStatus(Integer.valueOf(Constants.SUCCESS));
}
// 插入数据
SpringUtils.getBean(IXlLoginLogService.class).create(loginLog);
}
};
}
}
4. 调用
例如:在登录的方法中链式调用,与同步方式不同,开发者不用考虑当进行登录操作是否进行日志操作,在异步的方式中,业务的操作与日志的操作分开来。执行流程:AsyncManager.me()获取一个AsyncManager对象,执行execute方法,执行任务,传入的是一个task对象。实现了Runnable接口,是一个任务,由线程Thread去执行。
recordLoginLog方法返回的是TimerTask定时任务类,将用户登录信息记录到日志中作为一个定时任务,交给定时任务调度线程池scheduledExecutorService,scheduledExecutorService通过在异步任务管理器类中,用getBean()从IOC容器中获取。
5. 实现效果
进行登录操作时,会异步进行日志的记录。
总结
好了,以上就是这篇文章的全部内容了,感谢大家的阅读。
若觉得本文对您有帮助的话,还不忘点赞评论关注,支持一波哟~
边栏推荐
- Simple analysis of regularization principle (L1 / L2 regularization)
- NetCore 5.0连接MySql
- Use tensorboard remotely on the server
- 编译器不同,模式不同,对结果的影响
- Inverted order at the beginning of the C language 】 【 string (type I like Beijing. Output Beijing. Like I)
- 二叉排序树的左旋与右旋
- 排序方法(希尔、快速、堆)
- 突然想分析下房贷利率及利息计算
- 浅谈ArraryList的浅克隆和深克隆
- The recycle bin has been showed no problem to empty the icon
猜你喜欢
随机推荐
Inverted order at the beginning of the C language 】 【 string (type I like Beijing. Output Beijing. Like I)
What are the misunderstandings about the programmatic trading system interface?
DMPE-PEG-Mal Maleimide-PEG-DMPE dimyristoylphosphatidylethanolamine-polyethylene glycol-maleimide
How to flexibly use the advantages of the quantitative trading interface to complement each other?
How to use and execute quantitative programmatic trading?
大咖说·对话生态|当Confluent遇见云:实时流动的数据更有价值
[MySql] implement multi-table query - one-to-one, one-to-many
单向链表几个比较重要的函数(包括插入、删除、反转等)
How to achieve long-term benefits through the Tongdaxin quantitative trading interface?
Regular Expressions for Shell Programming
OpenCV - Matrix Operations Part 3
用户如何正确去认识程序化交易?
一些需要思考的物理问题
docker安装单机版redis、集群版redis
数字图像处理的基本原理和常用方法
docker安装seata(指定配置文件、数据库、容器数据卷等)
ASP.Net Core实战——使用Swagger
通用的双向循环列表的几个比较重要的函数操作
xshell7连接工具下载
[Mysql]--Transaction, transaction isolation level, dirty read, non-repeatable read, phantom read analysis








