背景

我司在很久之前,一位很久之前的同事写过一个文档转图片的服务,具体业务如下:

  1. 用户在客户端上传文档,可以是ppt,word,pdf 等格式,用户上传完成可以在客户端预览上传的文档,预览的时候采用的是图片形式(不要和我说用别的方式预览,现在已经来不及了)
  2. 当用户把文档上传到云端之后(阿里云),把文档相关的信息记录在数据库,然后等待转码完成
  3. 服务器有一个转码服务(其实就是一个windows service)不停的在轮训待转码的数据,如果有待转码的数据,则从数据库取出来,然后根据文档的网络地址下载到本地进行转码(转成多张图片)
  4. 当文档转码完毕,把转码出来的图片上传到云端,并把云端图片的信息记录到数据库
  5. 客户端有预览需求的时候,根据数据库来判断有没有转码成功,如果成功,则获取数据来显示。

文档预览的整体过程如以上所说,老的转码服务现在什么问题呢?

  1. 由于一个文档同时只能被一个线程进行转码操作,所以老的服务采用了把待转码数据划分管道的思想,一共有六个管道,映射到数据库大体就是 Id=》管道ID 这个样子。
  2. 一个控制台程序,根据配置文件信息,读取某一个管道待转码的文档,然后单线程进行转码操作
  3. 一共有六个管道,所以服务器上起了六个cmd的黑窗口……
  4. 有的时候个别文档由于格式问题或者其他问题 转码过程中会卡住,具体的表现为:停止了转码操作。
  5. 如果程序卡住了,需要运维人员重新启动转码cmd窗口(这种维护比较蛋疼)

后来机缘巧合,这个程序的维护落到的菜菜头上,维护了一周左右,大约重启了10多次,终于忍受不了了,重新搞一个吧。仔细分析过后,刨除实际文档转码的核心操作之外,整个转码流程其实还有很多注意点

  1. 需要保证转码服务不被卡住,如果和以前一样就没有必要重新设计了
  2. 尽量避免开多个进程的方式,其实在这个业务场景下,多个进程和多个线程作用是一致的。
  3. 每个文档只能被转码一次,如果一个文档被转码多次,不仅浪费了服务器资源,而且还有可能会有数据不一致的情况发生
  4. 转码失败的文档需要有一定次数的重试,因为一次失败不代表第二次失败,所以一定要给失败的文档再次被操作的机会
  5. 因为程序不停的把文档转码成本地图片,所以需要保证这些文件在转码完成在服务器上删除,不然的话,时间长了会生成很多无用的文件

说了这么多,其实需要注意的点还是很多的。以整个的转码流程来说,本质上是一个任务池的生产和消费问题,任务池中的任务就是待转码的文档,生产者不停的把待转码文档丢进任务池,消费者不停的把任务池中文档转码完成。

线程池

这很显然和线程池很类似,菜菜之前就写过一个线程池的文章,有兴趣的同学可以去翻翻历史。今天我们就以这个线程池来解决这个转码问题。线程池的本质是初始化一定数目的线程,不停的执行任务。

 //线程池定义 
    public class LXThreadPool:IDisposable
    {
        bool PoolEnable = true//线程池是否可用 
        List<Thread> ThreadContainer = null//线程的容器
        ConcurrentQueue<ActionData> JobContainer = null//任务的容器
        int _maxJobNumber; //线程池最大job容量

        ConcurrentDictionary<string, DateTime> JobIdList = new ConcurrentDictionary<string, DateTime>(); //job的副本,用于排除某个job 是否在运行中


        public LXThreadPool(