1. 程式人生 > >quartz結合多線程處理後臺業務

quartz結合多線程處理後臺業務

文件中 start amp 代碼 str rri 網上 inf nta

  最近項目中有播放視頻的需求,技術選型采用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和多線程的簡單用法,留個筆記,以便日後深究^_^

quartz結合多線程處理後臺業務