使用数据库实现本地阻塞队列

文章描述了一种在华为内容管理组中使用本地队列替代MQ进行任务处理的场景,因为MQ的队列限制。展示了如何设计和实现抽象的任务队列接口,包括异步阻塞队列的实现,以及具体的审核任务队列案例。此外,还提供了MySQL数据库脚本,用于存储和管理任务队列的相关配置和历史记录。
摘要由CSDN通过智能技术生成

本人在华为做开发时学习到的技术,当时是在内容管理组做内容审核,发现他们使用本地队列,当时我问行方项目经理为啥不用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);

如图

项目结构

仓库地址

https://gitee.com/jadenJunLanLiu/study3.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值