好用易用方便的Java基于Springboot的异步处理框架代码使用及说明
1. 功能和使用场景总结
该代码是一个基于Redis的异步任务处理框架,主要功能是将任务异步化处理,通过多线程从Redis队列中获取任务并执行,支持任务失败重试、异常恢复和多线程并发处理。
使用场景:适用于需要异步处理的业务场景(如日志上报、消息推送、数据异步更新等),尤其适合任务处理耗时较长或需要避免阻塞主流程的场景。
2. 核心模块拆分与解释
(1)日志配置模块(log4j.properties)
功能:配置日志输出方式,包括控制台输出和文件输出,同时设置第三方框架的日志级别(如Spring、Hibernate等只输出ERROR级别日志,减少冗余)。
核心配置:
- 日志输出到控制台(`log2Console`)和文件(`log2File`),文件按大小滚动(最大4096KB,保留50个备份)。
- 日志格式包含时间、类名、行号、日志级别和消息内容,方便问题追踪。
(2)异步任务实体类(AsyncData)
功能:封装异步任务的基本信息,相当于任务的"快递包裹"。
核心属性:
- `businessFlag`:业务标记(类似快递单上的"易碎品"标识,区分不同任务类型)。
- `data`:任务数据(包裹里的物品,即要处理的具体内容)。
- `failCount`:失败次数(记录包裹投递失败的次数,超过上限则放弃)。
- `beginTime`:任务创建时间(包裹生成时间)。
(3)异步处理接口(AsyncInterface)
功能:定义任务处理的标准方法,相当于"快递处理指南"。
核心方法:`handle(String businessFlag, Object data)`,返回`boolean`表示处理成功/失败(类似指南中规定"签收成功"或"拒收"的判断标准)。
使用方式:用户需实现该接口,编写具体的任务处理逻辑(如消息推送的具体代码)。
(4)工具类(LashenAsyncUtils)
功能:框架的"控制面板",负责初始化配置、管理任务队列和启动线程。
核心功能:
- 配置管理:设置线程数、重试次数、Redis键名等(类似调节快递站的"最大快递员数量"、"最多派送次数")。
- 任务添加:`addAsyncData()`方法将任务放入Redis队列(类似市民把快递放进快递柜)。
- 线程启动:`startAsyncTaskServiceThread()`方法启动处理线程(安排快递员开始工作),同时处理异常退出的线程恢复(快递员突然离岗后,安排其他人接手未完成的快递)。
(5)异步处理线程(AsyncHandleThread)
功能:实际执行任务的"快递员",负责从Redis队列取任务、处理任务、失败重试和异常恢复。
核心流程:
1. 异常恢复(restoreHandle):启动时检查是否有未完成的任务(快递员上班先看是否有昨天没送完的快递),如有则优先处理。
2. 正常处理(doHandle):循环从Redis队列取任务(不断从快递柜取件),处理完成后删除备份;失败则重试(派送失败就再试几次)。
3. 等待机制(waitForRedisData):队列空时休眠等待(快递柜空了就休息一会儿,定期查看),避免无效循环。
3. 抽象概念类比说明
Redis队列:相当于"快递柜",`addAsyncData`方法是存快递,线程的`fetchAsyncData`是取快递。
线程池(多线程):多个"快递员"同时工作,提高处理效率。
失败重试:类似快递派送失败后,再试几次(最多`maxRetryCount`次)。
异常恢复:快递员突然离职,新快递员接手他未完成的快递(从Redis备份中获取)。
等待机制:快递柜空了,快递员不会一直盯着,而是隔一段时间看一次(`sleepTime`),节省精力。
通过以上模块协作,框架实现了任务的异步化、高可用(失败重试+异常恢复)和可配置(线程数、重试次数等),适合作为通用的异步任务处理组件嵌入业务系统。
代码结构图

异步数据类AsyncData
package com.lashen.async.entity;
import java.io.Serializable;
/*
* @Description: 异步数据结构
* @Author: sam
* @Date: 2023-05-16 18:04:24
*/
public class AsyncData implements Serializable {
//业务标记;由用户自行随意定义,用于区分不同的异步数据
private String businessFlag;
//开始时间
private Long beginTime;
//需要异步处理的数据
private Object data;
//失败次数
private Integer failCount = 0;
public AsyncData(String businessFlag,Object data){
setBusinessFlag(businessFlag);
setBeginTime(System.currentTimeMillis());
setData(data);
}
public String getBusinessFlag() {
return businessFlag;
}
public void setBusinessFlag(String businessFlag) {
this.businessFlag = businessFlag;
}
public Long getBeginTime() {
return beginTime;
}
public void setBeginTime(Long beginTime) {
this.beginTime = beginTime;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Integer getFailCount() {
return failCount;
}
public void setFailCount(Integer failCount) {
this.failCount = failCount;
}
}
接口类AsyncInterface
package com.lashen.async.interfaces;
/*
* @Description: 异步处理接口
* @Author: sam
* @Date: 2023-05-16 16:13:49
*/
public interface AsyncInterface {
/**
* @description: 异步处理接口实现函数
* @param {String} businessFlag 业务标记,由用户自行随意定义,用于区分不同的数据处理
* @param {Object} data 要异步处理的数据
* @return {boolean} true表示处理成功,false表示处理失败(注意:如果处理失败,会进行重试,最大重试次数由配置文件中的ASYNC_MAX_RETRY_COUNT决定,超过最大重试次数这条数据会被抛弃,默认为3次)
* @author: sam
* @Date: 2023-05-16 16:21:35
*/
public abstract boolean handle(String businessFlag,Object data);
}
异步处理线程类AsyncHandleThread
package com.lashen.async.service.async.thread;
import org.apache.log4j.Logger;
import org.springframework.data.redis.core.RedisTemplate;
import com.lashen.async.entity.AsyncData;
import com.lashen.async.interfaces.AsyncInterface;
import com.lashen.async.utils.LashenAsyncUtils;
/*
* @Description: 异步处理线程
* @Author: sam
* @Date: 2023-05-15 13:40:13
*/
public class AsyncHandleThread extends Thread{
private static final Logger logger = Logger.getLogger(AsyncHandleThread.class);
private RedisTemplate redisTemplate;
private AsyncInterface asyncInterface ;
//最大重试次数
private int maxRetryCount;
//是否只是恢复处理
private boolean isRestore = false;
public AsyncHandleThread(RedisTemplate redisTemplate,String name,AsyncInterface asyncInterface,boolean isRestore){
this.setName(name);
this.redisTemplate = redisTemplate;
this.asyncInterface = asyncInterface;
this.maxRetryCount = LashenAsyncUtils.getAsyncMaxRetryCount();
this.isRestore = isRestore;
}
//线程run
public void run(){
//1. 异常恢复处理
restoreHandle();
//2. 是否是异常恢复线程
if(isRestore == true){
//如果只是恢复处理线程,则异常恢复后,直接退出。
logger.info("thread="+this.getName()+",exception thread data restore finished,exit current thread.");
return ;
}
//3. 正常处理
doHandle();
}
//异常恢复处理
private void restoreHandle(){
try{
//从备份hash中获取数据
AsyncData asyncData =(AsyncData)redisTemplate.opsForHash().get(LashenAsyncUtils.getAsyncRedisListKeyBackup(), this.getName());
if(asyncData == null){
return ;
}
//进行数据处理
handleData(asyncData);
//从备份中删除
redisTemplate.opsForHash().delete(LashenAsyncUtils.getAsyncRedisListKeyBackup(), this.getName());
}catch(Exception e){
logger.error("",e);
}
}
//正常处理
private void doHandle(){
try{
while(true){
//1. 等待redis list中出现数据
waitForRedisData();
//2. 从redis list中取数据
AsyncData asyncData = fetchAsyncData();
if(asyncData == null){
//设置为没有数据标记
LashenAsyncUtils.setRedisListExistData(false);
logger.info("thread="+this.getName()+",redis list no data.");
continue;
}
//3. 处理数据
handleData(asyncData);
}//end while
}catch(Exception e){
logger.error("",e);
}
}
//处理数据
private void handleData(AsyncData asyncData){
try{
logger.info("thread="+this.getName()+",handle data begin-------------------->>>");
if(asyncInterface.handle(asyncData.getBusinessFlag(), asyncData.getData()) == false){//处理失败
logger.info("thread="+this.getName()+",handle data fail,go to retry--------------------<<<");
retryData(asyncData);
}else{//处理成功
logger.info("thread="+this.getName()+",handle data ok--------------------<<<");
}
//从备份中删除
redisTemplate.opsForHash().delete(LashenAsyncUtils.getAsyncRedisListKeyBackup(), this.getName());
}catch(Exception ee){
logger.error("thread="+this.getName()+",handle data exception:",ee);
retryData(asyncData);
}
}
//从redis list中取数据
private AsyncData fetchAsyncData(){
try{
//从list中取出
AsyncData asyncData = (AsyncData)redisTemplate.opsForList().rightPop(LashenAsyncUtils.getAsyncRedisListKey());
if(asyncData == null){
//删除备份
redisTemplate.opsForHash().delete(LashenAsyncUtils.getAsyncRedisListKeyBackup(), this.getName());
return null;
}
//保存到hash中
redisTemplate.opsForHash().put(LashenAsyncUtils.getAsyncRedisListKeyBackup(), this.getName(), asyncData);
return asyncData;
}catch(Exception e){
logger.error("",e);
}
return null;
}
//重试处理
private void retryData(AsyncData asyncData){
try{
//超过最大重试次数就抛弃。
if(asyncData.getFailCount()>=maxRetryCount){
return ;
}
//把本次处理失败的追加到待处理中,再次进行处理
asyncData.setFailCount(asyncData.getFailCount()+1);
redisTemplate.opsForList().leftPush(LashenAsyncUtils.getAsyncRedisListKey(), asyncData);
}catch(Exception e){
logger.error("",e);
}
}
//等待redis list中出现数据
private void waitForRedisData(){
long st = System.currentTimeMillis();
long stReset = st;
while(true){
if(LashenAsyncUtils.getRedisListExistData() == true){
return ;
}
//等待几十毫秒
sleepTime();
long ms = System.currentTimeMillis() - st;
if(ms>(LashenAsyncUtils.getAsyncMaxLivingTime() * 1000l)){
st = System.currentTimeMillis();
logger.info("thread="+this.getName()+",idle...");
}
//判断是否有设置异常重置(大于0表示设置,其他:表示未设置)
if(LashenAsyncUtils.getAsyncMaxExceptionResetTime()>0){
ms = System.currentTimeMillis() - stReset;
if(ms>(LashenAsyncUtils.getAsyncMaxExceptionResetTime() * 1000l)){
stReset = System.currentTimeMillis();
//设置为有新添加的数据。
LashenAsyncUtils.setRedisListExistData(true);
}
}
}//end while
}
//等待几十毫秒
private void sleepTime(){
try{
//wait 50 ms
Thread.sleep(LashenAsyncUtils.getAsyncMaxActionTime());
}catch(Exception e){
logger.error("",e);
}
}
}
异步处理工具类LashenAsyncUtils
package com.lashen.async.utils;
import java.util.Set;
import org.apache.log4j.Logger;
import org.springframework.data.redis.core.RedisTemplate;
import com.lashen.async.entity.AsyncData;
import com.lashen.async.interfaces.AsyncInterface;
import com.lashen.async.service.async.thread.AsyncHandleThread;
/*
* @Description: sinocontact异步处理通用框架工具类
* @Author: sam
* @Date: 2023-05-15 14:38:37
*/
public class LashenAsyncUtils {
private static final Logger logger = Logger.getLogger(LashenAsyncUtils.class);
private static final String REDIS_LIST_KEY="---REDIS_LIST_KEY---";
private static final String REDIS_LIST_KEY_BACKUP="---REDIS_LIST_KEY_BACKUP---";
//REDIS_LIST_KEY中是否存在数据
private static boolean redisListExistData = true;
//最大重试次数
private static int asyncMaxRetryCount = 3;
//异步线程数
private static int asyncMaxThreadCount = 1;
//线程活着状态打印间隔时间(秒) 默认是30秒
private static int asyncMaxLivingTime = 30;
//线程最大响应时间(毫秒ms),默认50毫秒 0.05秒
private static long asyncMaxActionTime = 50l;
//最大异常重置时间(秒),默认为-1;当此值小于等于0时,表示永远不自动重置 redisListExistData = true;否则,超过此值(秒),即自动重置redisListExistData为true。
private static int asyncMaxExceptionResetTime = -1;
//最大异常重置时间(秒)
public static int getAsyncMaxExceptionResetTime(){
return asyncMaxExceptionResetTime;
}
//最大重试次数
public static int getAsyncMaxRetryCount(){
return asyncMaxRetryCount;
}
//异步线程数
public static int getAsyncMaxThreadCount(){
return asyncMaxThreadCount;
}
public static int getAsyncMaxLivingTime(){
return asyncMaxLivingTime;
}
//线程活着状态打印间隔时间(秒) 默认是30秒
public static void SetAsyncMaxLivingTime(int maxLivingTime){
asyncMaxLivingTime = maxLivingTime;
}
public static long getAsyncMaxActionTime(){
return asyncMaxActionTime;
}
public static void setAsyncMaxActionTime(long maxActionTime){
asyncMaxActionTime = maxActionTime;
}
//最大异常重置时间(秒),默认为-1;当此值小于等于0时,表示永远不自动重置 redisListExistData = true;否则,超过此值(秒),即自动重置redisListExistData为true。
public static void setAsyncMaxExceptionResetTime(int maxExceptionResetTime){
asyncMaxExceptionResetTime = maxExceptionResetTime;
}
//设置REDIS_LIST_KEY中是否存在数据
public synchronized static void setRedisListExistData(boolean existData){
LashenAsyncUtils.redisListExistData = existData;
}
//得到REDIS_LIST_KEY中是否存在数据
public synchronized static boolean getRedisListExistData(){
return LashenAsyncUtils.redisListExistData;
}
//redis 中 list的key名称
public static String getAsyncRedisListKey(){
return REDIS_LIST_KEY;
}
//redis 中hash的key名称
public static String getAsyncRedisListKeyBackup(){
return REDIS_LIST_KEY_BACKUP;
}
/**
* @description: 添加一个要处理的数据
* @param {RedisTemplate}: springboot中的redis操作对象:RedisTemplate
* @param {businessFlag} :业务标记;由用户自行随意定义,用于区分不同的异步数据
* @param {Object} data: 要进行异步处理的数据。特别注意:data需要实现 implements Serializable ,否则无法添加成功。
* @return {boolean} true表示添加成功,false表示添加失败
* @author: sam
* @Date: 2023-05-15 14:59:45
*/
public static boolean addAsyncData(RedisTemplate redisTemplate,String businessFlag,Object data){
try{
//1. 构建 要保存的数据结构
AsyncData asyncData = new AsyncData(businessFlag,data);
//2. 数据保存到redis list 中
redisTemplate.opsForList().leftPush(getAsyncRedisListKey(), asyncData);
//3. 设置为有数据标记
setRedisListExistData(true);
return true;
}catch(Exception e){
logger.error("",e);
}
return false;
}
/**
* @description: 启动异步处理线程
* @param {RedisTemplate} redisTemplate : redis操作对象
* @param {AsyncInterface} asyncInterface: 异步处理接口实现
* @return {*}
* @author: sam
* @Date: 2023-05-16 17:54:23
*/
public static void startAsyncTaskServiceThread(RedisTemplate redisTemplate,AsyncInterface asyncInterface){
LashenAsyncUtils.asyncMaxThreadCount = 1;
startAsyncTaskServiceThread(redisTemplate, asyncInterface);
}
/**
* @description: 启动异步处理线程
* @param {RedisTemplate} redisTemplate : redis操作对象
* @param {AsyncInterface} asyncInterface: 异步处理接口实现
* @param {int} asyncMaxThreadCount : 启动异步线程数
* @return {*}
* @author: sam
* @Date: 2023-05-16 17:54:23
*/
public static void startAsyncTaskServiceThread(RedisTemplate redisTemplate,AsyncInterface asyncInterface,int asyncMaxThreadCount){
LashenAsyncUtils.asyncMaxRetryCount = 3;
startAsyncTaskServiceThread(redisTemplate, asyncInterface,asyncMaxThreadCount,3);
}
/**
* @description: 启动异步处理线程
* @param {RedisTemplate} redisTemplate : redis操作对象
* @param {AsyncInterface} asyncInterface: 异步处理接口实现
* @param {int} asyncMaxThreadCount : 启动异步线程数
* @param {int} asyncMaxRetryCount: 处理出错重试次数
* @return {*}
* @author: sam
* @Date: 2023-05-16 17:54:23
*/
public static void startAsyncTaskServiceThread(RedisTemplate redisTemplate,AsyncInterface asyncInterface,int asyncMaxThreadCount,int asyncMaxRetryCount){
try{
LashenAsyncUtils.asyncMaxThreadCount = asyncMaxThreadCount;
LashenAsyncUtils.asyncMaxRetryCount = asyncMaxRetryCount;
//配置的处理最大线程数
//上次退出时的有数据未处理完成的线程数
Set set = redisTemplate.opsForHash().keys(LashenAsyncUtils.getAsyncRedisListKeyBackup());
int cnt = asyncMaxThreadCount - set.size();
if(cnt>=0){//需要线程比异常的线程多的情况
for(Object hashkey:set){//用异常线程的名称
AsyncHandleThread baseThread = new AsyncHandleThread(redisTemplate,hashkey.toString(),asyncInterface,false);
baseThread.start();
}//end for
for(int i=0;i<cnt;i++){
String threadName="async_thread_"+System.currentTimeMillis()+"_"+(i+1);
AsyncHandleThread baseThread = new AsyncHandleThread(redisTemplate,threadName,asyncInterface,false);
baseThread.start();
}//end for
}else{//异常的线程比需要的线程多的情况
int i=0;
for(Object hashkey:set){//全部都用异常线程
boolean isRestore = false;
if(i>=asyncMaxThreadCount){//如果当前配置的线程数,比上次异常退出时的线程数少,则超过当前配置数的线程作为异常恢复处理线程,异常恢复处理完成后就会自动退出。
isRestore = true;
}
AsyncHandleThread baseThread = new AsyncHandleThread(redisTemplate,hashkey.toString(),asyncInterface,isRestore);
baseThread.start();
i++;
}//end for
}
}catch(Exception e){
logger.error("",e);
}
}
}
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lashen</groupId>
<artifactId>async</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>async</name>
<description>lashen async workframe</description>
<properties>
<webVersion>2.5</webVersion>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
</dependencies>
<build>
<finalName>async</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
二:使用例子
特别注意:本异步处理框架需要使用springboot redis的RedisTemplate。
- 实现异步处理接口
通过实现AsyncInterface接口来处理异步数据。
String businessFlag 表示异步数据业务标记,由用户在添加异步数据时自由定义,主要用来区分异步数据类型。
import com.lashen.async.interfaces.AsyncInterface;
public class AsyncHandleImpl implements AsyncInterface{
public boolean handle(String businessFlag, Object data) {
ResultData resultData = (ResultData)data;
System.out.println("处理数据:"+resultData.getData().toString());
return true;
}
}
- 启动异步处理线程
通过
LashenAsyncUtils.startAsyncTaskServiceThread(RedisTemplate, AsyncInterface);
启动异步处理线程。
import com.lashen.async.utils. LashenAsyncUtils;
@Component
public class AsyncInit implements ApplicationRunner{
@Autowired
private RedisTemplate redisTemplate;
public void run(ApplicationArguments args){
//异步处理线程数
int asyncMaxThreadCount = 1;
//出错重试次数
int asyncMaxRetryCount = 3;
System.out.println("启动异步处理------------>>>");
//LashenAsyncUtils.startAsyncTaskServiceThread(redisTemplate,new AsyncHandleImpl());
//LashenAsyncUtils.startAsyncTaskServiceThread(redisTemplate, new AsyncHandleImpl(), asyncMaxThreadCount);
LashenAsyncUtils.startAsyncTaskServiceThread(redisTemplate, new AsyncHandleImpl(), asyncMaxThreadCount,asyncMaxRetryCount);
System.out.println("启动异步处理完成------------<<<");
}
}
- 添加异步处理数据
通过LashenAsyncUtils.addAsyncData(RedisTemplate,String businessFlag, Object data);添加异步处理数据。
@Autowired
private RedisTemplate redisTemplate;
@RequestMapping("add")
@ResponseBody
public String add(){
String businessFlag = "--sys--";
ResultData data = ResultData.success("sino--------ok");
LashenAsyncUtils.addAsyncData(redisTemplate,businessFlag,data);
return "ok";
}
1431

被折叠的 条评论
为什么被折叠?



