quartz结合多线程处理后台业务

  最近项目中有播放视频的需求,技术选型采用UMS播放器,免费版只能播放FLV格式的视频文件,因此需要对用户上传的视频进行格式转换,转换工具为FormatFactory,功能还是比较强大的。但是面临的一个问题,视频转换是非常耗时的,上传完直接转换是没法接受的,于是决定采用quartz,以任务调度的方式,在后台进行转换,具体步骤如下:

  1.定义一个任务队列,将待转换的视频文件信息放到队列中。采用单例模式,并且考虑到线程安全问题,采用线程安全的Vector作为队列容器:

  

复制代码
/**
 * 格式转换任务队列
 * 队列中放的是ResourceInfo类型对象
 * @author Administrator
 *
 */
public class TransformTaskQueue {

private static TransformTaskQueue instance = null;

//实际存放转换对象信息的队列,采用线程安全的Vercor容器
public static Vector<ResourceInfo> taskQueue = new Vector<ResourceInfo>();

public static TransformTaskQueue getInstance() {
if (instance == null) {
instance
= new TransformTaskQueue();
}
return instance;
}

/**
* 向队列中添加对象
*
@param info
*/
public static void add(ResourceInfo info) {
taskQueue.add(info);
}

/**
* 从队列中删除对象
*
@param info
*/
public static void remove(ResourceInfo info) {
if(taskQueue.size()>0 && taskQueue.contains(info)){
taskQueue.remove(info);
}
}

}

复制代码

  2.用户上传视频文件之后,后台进行判断,如果不是flv格式,则将文件转换所需信息封装到ResuorceInfo对象,将该对象放入待转换队列:

复制代码
// 如果源视频文件存在,则进行相应的转换,转换为FLV文件
        if (new File(TransConfig.VIDEO_SOURCE_ROOT + path + fileName).exists()) {

ResourceInfo info = new ResourceInfo();
info.setResourceId(resourceId);
info.setPath(path);
info.setFileName(fileName);
info.setStatus(
0);
// 添加到转换队列
TransformTaskQueue.add(info);

} else {
System.out.println(
"源文件不存在!");
}

复制代码

  3.执行单个具体文件转换的操作类代码如下:

复制代码
/**
 * 执行具体转换操作的类,
 * 采用多线程技术,继承了runnable接口
 * @author Administrator
 *
 */
public class TransformExecutor implements Runnable,Serializable{

private static final long serialVersionUID = 1L;

private ResourceInfo info = null ;

public TransformExecutor(ResourceInfo info){
this.info = info;
}

@Override
public void run() {

String resourceId = info.getResourceId();
String path
= info.getPath();
String fileName
= info.getFileName();

String videoFilename = TransConfig.VIDEO_SOURCE_ROOT + path
+ fileName;
String flvFilename
= path
+ FileUtil.getFilePrefix(fileName) + ".flv";

// 转换成功,修改数据库中的is_transed字段为1
if (Video2FLVTransfer.transform(videoFilename, flvFilename) == 1) {
CRUDUtil.update(resourceId,
1);
}
// 转换失败,修改数据库中的is_transed字段为2
else {
CRUDUtil.update(resourceId,
2);
}

// 将resourceInfo从转换队列中去除
TransformTaskQueue.remove(info);

}

}

复制代码

  4.下面是开启多线程转换的操作类,采用线程池技术,因为转换视频文件格式工作量比较大,因此规定每次最多开启3个线程:

复制代码
/**
 * 转换执行器服务类
 * @author Administrator
 *
 */
public class TransExecutorService {

private final ExecutorService pool;

private static TransExecutorService instance;
//线程池大小,即每次最多允许开启几个线程执行转换操作
private static final int THREAD_SIZE = 3;

public static TransExecutorService getInstance() {
if (instance == null) {
instance
= new TransExecutorService();
}
return instance;
}

private TransExecutorService() {
// pool = Executors.newCachedThreadPool();
pool = Executors.newFixedThreadPool(THREAD_SIZE);
}

/**
* 开启新线程,执行转换操作
*
@param info
*/
public void execute(ResourceInfo info) {
try {
pool.submit(
new TransformExecutor(info));
}
catch (Exception e) {
e.printStackTrace();
}
}

public synchronized void shutdown() {
pool.shutdown();
}
}

复制代码

  5.调度任务实现类,即每次执行调度,执行的操作

复制代码
/**
 * 调度任务具体执行类
 * @author Administrator
 *
 */
public class TransformJob implements Job {

@Override
public void execute(JobExecutionContext ctx) throws JobExecutionException {

//获取当前待转换视频文件队列
Vector<ResourceInfo> infos = TransformTaskQueue.getInstance().taskQueue;
System.out.println(
"size:"+infos.size());

//如果任务队列中存在待转换对象,则进行转换
if (infos.size() > 0) {
for (int i=0;i<infos.size();i++) {
//status为0,表示不是正在转换中的
if (infos.get(i).getStatus() == 0) {
infos.get(i).setStatus(
1);
//新开线程,执行转换操作
TransExecutorService.getInstance().execute(infos.get(i));
}
}
}
}

}

复制代码

  6.任务调度管理类,规定了调度执行的一些规则,其中定时表达式请自行网上搜索,这里采用的是每10秒执行一次。

复制代码
/**
 * 格式转换任务调度管理类
 * 
 * @author Administrator
 * 
 */
public class SchedulManager {

private static SchedulManager instance = new SchedulManager();
private Scheduler scheduler;
private volatile boolean start = false;

private SchedulManager() {
try {
SchedulerFactory factory
= new StdSchedulerFactory();
scheduler
= factory.getScheduler();
}
catch (SchedulerException e) {
e.printStackTrace();
}
}

public static SchedulManager getInstance() {
return instance;
}

/**
* 开始执行,将加载调度配置并启动每个调度。
*
* @注意: 一般在程序启动时调用该方法。
*/
public void execute() {
try {
// 加载调度配置,并启动每个调度。
scheduleJobs();
scheduler.start();
}
catch (Exception e) {
e.printStackTrace();
}
}

/**
* 加载调度配置并启动每个调度
*
* @注意: TODO
*/
@SuppressWarnings(
"static-access")
private void scheduleJobs() {

Vector<ResourceInfo> infos = TransformTaskQueue.getInstance().taskQueue;
System.out.println(
"size:" + infos.size());
if (infos.size() > 0) {
start();
}
start
= true;
}

/**
* 根据ResourceInfo启动一个调度
*
* @注意: 内部方法,外部不能调用
*
@param ResourceInfo
* 资源信息
*/
private void start() {

try {
// String id = info.getResourceId();
// 构造方法中 第一个是任务名称 ,第二个是任务组名,第三个是任务执行的类
JobDetail jobDetail = new JobDetail("video_trans_id",
Scheduler.DEFAULT_GROUP, TransformJob.
class);
String cronExpr
= "0/10 * * * * ?";
String triggerName
= "video_trans_trigger";
Trigger trigger
= new CronTrigger(triggerName,
Scheduler.DEFAULT_GROUP, cronExpr);

scheduler.scheduleJob(jobDetail, trigger);

} catch (Exception e) {
System.out.println(
"出错");
e.printStackTrace();
}
}

public void init() {

SchedulManager sm = SchedulManager.getInstance();
// sm.start(info);
// sm.scheduleJobs();
sm.start();
try {
sm.scheduler.start();
}
catch (SchedulerException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}

复制代码

  7.通过上述6个步骤,已经可以通过quartz以任务调度的形式来进行格式转换了,接下来的问题,是写一个listener类,以实现在服务器启动的时候,任务调度自动启动。

首先需要在web.xml中加入如下配置:

    <listener> 
        <listener-class>com.yunda.web.EventTransformStartupListener</listener-class> 
    </listener>

之后就是实现配置文件中的实现监听功能的类,非常简单,就是调用SchedulManager中的init()方法即可,代码如下:

复制代码
public class EventTransformStartupListener implements ServletContextListener {

@Override
public void contextDestroyed(ServletContextEvent arg0) {
}

@Override
public void contextInitialized(ServletContextEvent arg0) {

System.out.println("init...");

SchedulManager sm = SchedulManager.getInstance();
//sm.start(info);
sm.init();
}
}

复制代码

  至此,后台进行格式转换的功能全部完成,通过做这个功能,发现quartz采用的任务调度机制,跟linux的crontab差不多,也是采用定时扫描的方法来完成,连定时表达式的规则都长的差不多。先写这么多吧,就是学习了quartz和多线程的简单用法,留个笔记,以便日后深究^_^

posted @ 2018-10-10 15:43  星朝  阅读(1841)  评论(0)    收藏  举报