本人在华为做开发时学习到的技术,当时是在内容管理组做内容审核,发现他们使用本地队列,当时我问行方项目经理为啥不用mq,他的回答是“一个应用只能使用5个队列,我们的应用队列有很多,根本就申请不了,就只能用本地队列”。
设计接口
import com.hncu.ex.dbqueue.base.domain.QueueTask;
/**
* 任务队列定义
*/
public interface TaskQueue {
/**
* 推送消息
*
* @param queueTask 任务
* @return 是否成功
*/
boolean push(QueueTask queueTask);
/**
* 处理消息
*
* @param queueTask 任务
*/
void queueHandler(QueueTask queueTask);
}
抽象方法1
import cn.hutool.extra.spring.SpringUtil;
import com.hncu.ex.common.base.util.IpUtils;
import com.hncu.ex.dbqueue.base.TaskQueue;
import com.hncu.ex.dbqueue.base.entity.Q3QueueConfig;
import com.hncu.ex.dbqueue.base.service.IQ3QueueConfigService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.DependsOn;
/**
* 抽象任务队列,用于实现框架设计
*/
@Data
@Slf4j
@DependsOn({"springUtil", "queueConfigService"}) // 作为父类不生效,必须与@Component注解同时使用才能生效
public abstract class AbstractTaskQueue implements TaskQueue {
private String taskName;// 任务名称
private String localIp;// 当前运行服务器地址
private int taskLimit;// 每次读取数量
private boolean isStart;// 是否启动线程
private IQ3QueueConfigService q3QueueConfigService = SpringUtil.getBean("queueConfigService");
public AbstractTaskQueue() {
init();
}
// 初始化队列配置信息
protected void init() {
// 获取任务名称
this.taskName = getTaskQueueName();
this.localIp = IpUtils.getLocalIp();
this.taskLimit = 3;// 默认3条
this.isStart = true;// 是否启动线程
// 加载任务配置
Q3QueueConfig q3QueueConfig = q3QueueConfigService.queryByTaskName(taskName);
Integer taskLimit = q3QueueConfig.getTaskLimit();
if (taskLimit != null) {
this.taskLimit = taskLimit;
}
String isIniUse = q3QueueConfig.getIsIniUse();
if (StringUtils.isEmpty(isIniUse) || "1".equals(isIniUse)) {
this.isStart = true;
} else {
this.isStart = false;
}
log.info("队列[{}],配置初始化完毕", taskName);
}
/**
* 获取任务队列名称
*
* @return 队列名称
*/
protected abstract String getTaskQueueName();
}
抽象方法2
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.hncu.ex.common.base.id.IdGenerator;
import com.hncu.ex.dbqueue.base.domain.QueueTask;
import com.hncu.ex.dbqueue.base.entity.Q3QueueTask;
import com.hncu.ex.dbqueue.base.service.IQ3QueueTaskHistoryService;
import com.hncu.ex.dbqueue.base.service.IQ3QueueTaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.DependsOn;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.ReentrantLock;
/**
* 异步阻塞队列实现
*/
@Slf4j
@DependsOn({"springUtil", "queueTaskService", "queueTaskHistoryService"}) // 作为父类不生效,必须与@Component注解同时使用才能生效
public abstract class AsyncAbstractTaskQueue extends AbstractTaskQueue implements Runnable {
public static final int QUEUE_SIZE = 128;// 异步队列大小
private BlockingQueue<QueueTask> blockingQueue = new LinkedBlockingQueue<>(QUEUE_SIZE);
private ReentrantLock mainLock = new ReentrantLock(true);// 使用公平锁,谁先拿谁就先访问;这里就是一个普通对象
private Thread asyncThread;// 异步线程
private IdGenerator idGenerator = SpringUtil.getBean("snowFlakeService");
private IQ3QueueTaskService queueTaskService = SpringUtil.getBean("queueTaskService");
private IQ3QueueTaskHistoryService queueTaskHistoryService = SpringUtil.getBean("queueTaskHistoryService");
public AsyncAbstractTaskQueue() {
ThreadGroup group = new ThreadGroup("edu_group");
String threadName = "lq_thread_" + getTaskName();
this.asyncThread = new Thread(group, this, threadName);
// 直接启动
this.asyncThread.start();
log.info("队列[{}],启动线程成功", getTaskName());
}
@Override
public boolean push(QueueTask queueTask) {
if (StringUtils.isEmpty(queueTask.getObjectId())) {
return false;
}
// 保存一条任务
String taskId = idGenerator.nextId();
String serverIp = getLocalIp();
String curUser = "sys_designer";
String curDate = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_FORMAT);
Q3QueueTask q3QueueTask = new Q3QueueTask();
q3QueueTask.setTaskId(taskId);
q3QueueTask.setTaskName(queueTask.getTaskName());
q3QueueTask.setObjectId(queueTask.getObjectId());
q3QueueTask.setServerIp(serverIp);
q3QueueTask.setCreateUser(curUser);
q3QueueTask.setCreateDate(curDate);
queueTaskService.save(q3QueueTask);
// 激活异步任务处理
return activeTask();
}
// 激活异步任务处理
public boolean activeTask() {
// 线程唤醒
boolean alive = this.asyncThread.isAlive();
// WAITING true
Thread.State state = this.asyncThread.getState();
if (alive) {
log.info("队列[{}],添加任务成功,检查状态({}),线程唤醒", getTaskName(), state);
// 必须加锁,不然报java.lang.IllegalMonitorStateException
synchronized (mainLock) {
mainLock.notify();// 唤醒一个线程
}
return true;
} else {
log.info("队列[{}],添加任务失败,检查状态({}),线程停机", getTaskName(), state);
// 中断线程
this.asyncThread.interrupt();
return false;
}
}
@Override
public void run() {
log.info("队列[{}],线程run方法运行成功", getTaskName());
QueueTask queueTask = new QueueTask();
queueTask.setTaskName(getTaskName());
while (this.isStart()) {
// 队列处理
queueHandler(queueTask);
}
}
/**
* 异步调用
*
* @param queueTask 任务
*/
@Override
public void queueHandler(QueueTask queueTask) {
try {
// 1、调用前处理
prefixHandler();
List<String> taskIds = new ArrayList<>(QUEUE_SIZE);
// 2、客户端处理
while (!blockingQueue.isEmpty()) {
QueueTask oneTask = blockingQueue.poll();
// 异步任务处理器
asyncHandler(oneTask);
// 添加需要删除的任务
taskIds.add(oneTask.getTaskId());
}
// 3、调用后处理
suffixHandler(taskIds);
} catch (InterruptedException e) {
log.info("队列阻塞失败", e);
} finally {
}
}
/**
* 异步任务处理器
*
* @param queueTask 任务
*/
protected abstract void asyncHandler(QueueTask queueTask);
// 调用前处理
private void prefixHandler() throws InterruptedException {
log.info("队列[{}],读取内容", getTaskName());
Thread.State state = this.asyncThread.getState();
// 按照顺序排序查询队列任务表3条数据
List<Q3QueueTask> q3QueueTasks = queueTaskService.queryByPageSize(getTaskLimit());
if (CollectionUtil.isEmpty(q3QueueTasks)) {
log.info("队列[{}],读取内容为空,线程进入阻塞状态({})", getTaskName(), state);
// 必须加锁,不然报java.lang.IllegalMonitorStateException
synchronized (mainLock) {
mainLock.wait();// 阻塞一个线程
log.info("队列[{}],读取内容为空,线程阻塞状态->>>运行状态", getTaskName());
}
} else {
log.info("队列[{}],读取内容不为空,[{}]条任务读取成功", getTaskName(), q3QueueTasks.size());
for (Q3QueueTask q3QueueTask : q3QueueTasks) {
QueueTask queueTask = new QueueTask();
queueTask.setTaskId(q3QueueTask.getTaskId());
queueTask.setTaskName(q3QueueTask.getTaskName());
queueTask.setObjectId(q3QueueTask.getObjectId());
blockingQueue.offer(queueTask);// 进入阻塞队列
}
}
}
// 调用后处理
private void suffixHandler(List<String> taskIds) {
if (CollectionUtil.isEmpty(taskIds)) {
return;
}
// 1、将数据拷贝到历史表
boolean flag = queueTaskHistoryService.copySave(taskIds);
// 2、删除任务表中的任务
if (flag) {
boolean cnt = queueTaskService.removeByIds(taskIds);
log.info("队列[{}],删除任务表中的任务成功,[{}]条任务删除成功", getTaskName(), cnt);
} else {
log.info("队列[{}],删除任务表中的任务失败,[{}]条任务删除成功", getTaskName(), taskIds.size());
}
}
}
案例1
import com.hncu.ex.dbqueue.base.biz.AsyncAbstractTaskQueue;
import com.hncu.ex.dbqueue.base.domain.QueueTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.DependsOn;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* 异步队列:简单案例实现
*/
@Slf4j
@DependsOn({"springUtil", "queueConfigService"}) // 作为父类不生效,必须与@Component注解同时使用才能生效
public class SimpleTaskQueue extends AsyncAbstractTaskQueue {
public static final String TASK_QUEUE_NAME = "simpleTaskQueue";
protected Random random = new Random();
/**
* 获取任务队列名称
* TODO 需要开发覆盖该方法
*
* @return 队列名称
*/
@Override
protected String getTaskQueueName() {
return TASK_QUEUE_NAME;
}
/**
* 推送任务(子类可以直接访问)
*
* @param objectId 任务对象
* @return 是否成功
*/
public boolean pushTask(String objectId) {
QueueTask queueTask = new QueueTask();
queueTask.setTaskName(getTaskName());
queueTask.setObjectId(objectId);
// 可以写业务逻辑
return this.push(queueTask);
}
/**
* 异步任务处理器
* TODO 需要开发覆盖该方法
*
* @param queueTask 任务
*/
@Override
protected void asyncHandler(QueueTask queueTask) {
String objectId = queueTask.getObjectId();
log.info("查询业务数据-{}", objectId);
// 随机睡眠
try {
int sleepTime = random.nextInt(15);// 15秒
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
案例二
import com.hncu.ex.dbqueue.base.example.SimpleTaskQueue;
import com.hncu.ex.dbqueue.base.domain.QueueTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 审核队列
*/
@Slf4j
@Component
@DependsOn({"springUtil", "queueConfigService"})
public class AuditTaskQueue extends SimpleTaskQueue {
public static final String QUEUE_NAME = "auditTaskQueue";
@Override
protected String getTaskQueueName() {
return QUEUE_NAME;
}
@Override
protected void asyncHandler(QueueTask queueTask) {
String objectId = queueTask.getObjectId();
log.info("调用第三方内容审核服务接口-{}", objectId);
// 随机睡眠
try {
int sleepTime = this.random.nextInt(20);// 20
TimeUnit.SECONDS.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
审核服务接受请求,本地消峰,异步处理(这个是我在华为开发时调用方式)
mysql脚本
create database ex_async;
-- 任务队列配置表
drop table if exists q3_queue_config;
create table q3_queue_config (
task_name varchar(80) not null COMMENT '任务名称',
task_desc varchar(200) not null COMMENT '任务描述',
task_limit tinyint COMMENT '任务读取条数',
is_ini_use varchar(32) COMMENT '任务是否启用',
sort_no varchar(32) COMMENT '排序号',
create_user varchar(40) COMMENT '创建人',
create_date varchar(20) COMMENT '创建时间',
update_user varchar(40) COMMENT '修改人',
update_date varchar(20) COMMENT '修改时间',
primary key (task_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 队列任务表
drop table if exists q3_queue_task;
create table q3_queue_task (
task_id varchar(32) not null COMMENT '任务主键',
task_name varchar(80) COMMENT '任务名称',
task_content varchar(200) COMMENT '任务内容',
object_id varchar(32) COMMENT '任务对象',
server_ip varchar(32) COMMENT '任务处理服务器',
create_user varchar(40) COMMENT '创建人',
create_date varchar(20) COMMENT '创建时间',
primary key (task_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 队列任务历史表
drop table if exists q3_queue_task_history;
create table q3_queue_task_history (
task_id varchar(32) not null COMMENT '任务主键',
task_name varchar(80) COMMENT '任务名称',
task_content varchar(200) COMMENT '任务内容',
object_id varchar(32) COMMENT '任务对象',
server_ip varchar(32) COMMENT '任务处理服务器',
create_user varchar(40) COMMENT '创建人',
create_date varchar(20) COMMENT '创建时间',
history_user varchar(40) COMMENT '历史创建人',
history_date varchar(20) COMMENT '历史创建时间',
primary key (task_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
use ex_async;
delete from `q3_queue_config` where 1=1;
INSERT INTO `q3_queue_config` (`task_name`, `task_desc`, `task_limit`, `is_ini_use`, `sort_no`, `create_user`, `create_date`, `update_user`, `update_date`)
VALUES ('simpleTaskQueue', '简单队列实现', '3', '1', '1', 'sys_designer', '20230325', NULL, NULL);
INSERT INTO `q3_queue_config` (`task_name`, `task_desc`, `task_limit`, `is_ini_use`, `sort_no`, `create_user`, `create_date`, `update_user`, `update_date`)
VALUES ('auditTaskQueue', '审核队列', '3', '1', '1', 'sys_designer', '20230325', NULL, NULL);
如图
项目结构
仓库地址