前传:
接到一个新任务,就是pdf文件上传后要转成图片
思路很简单
伪代码:
//获取上传文件
File file=new File(“路径”);
//引用pdf工具类把pdf转成图片
Img img=PDFUtil.toImg(file);
后来…需求改了….
要传一个压缩包…是多个文件,也简单
//获取上传文件
FileRar file=new File(“路径”);
//解压获取所有文件对象
List<File> files= RarUtil.getFiles(Rarfile);
for (File file : files) {
//引用pdf工具类把pdf转成图片
Img img=PDFUtil.toImg(file);
}
加几行代码也过了
在后来……用户吼道~文件上传太慢,检查一下…文件上传一个压缩包有几百个文件,而且文件都较大,处理速度慢,而且异常后要从来…用户抓狂了~
业务: 这个问题一定要解决
PM: 这个一定能解决
程序猿: 香蕉在哪里~
一般web的文件上传业务和逻辑基本都放一起,因为这样做简单省事,当然在不考虑文件大小和处理速度的时候~
这里大量的文件和冗长的处理时间以及异常确实不太合适,这个时候,就需要后台处理了,程序猿吃着香蕉开始做方案
方案:
前提需求:这部分肯定得后台做与文件上传分离,然后就是由什么来触发这个文件的处理,异常如何处理,处理时间如何保障
方案一
做定时任务,在表里加个字段,一天执行一次或多次(这样做会显得非常白痴,延时太高,而且不好定时间,要同时考虑到执行时间段的问题[ps:全世界人都觉得晚上12点服务器最轻松,所以所有定时任务都是晚上12点……])
~~~不用看了,这个马上毙掉
方案二:
后台启用线程(也可以另外写一个单独的程序,不过太麻烦了,成本上划不来),间隔(1分钟或者10分钟)读取数据库,执行任务
这样确实延时小了,上传与处理也分离开了,虽然有增加数据库查询的负担,不过可以容忍(每分钟一次的查询所消耗的资源还是非常小的)
总的来说,这个方案不错,不过对于有完美主义倾向的处女座来说..(处女座程序猿吼道:什么一分钟一次查询,这不行,数据库连接断了怎么办,晚上没人穿文件还得继续查?不行,不科学~)
所以方案二也被毙掉了
方案三
后台起一个现成,前台用户上传文件后马上触发事件,执行修改.
有几个关键点:
Web程序如何触发和控制这个任务线程,每次都是new一个线程,然后在.run()
再就是springmvc是多线程的,如果用户量很大,会不会演变成1000个用户起1000个线程,如果真是这样,线程中如果不小心在new几个对象或者集合……内存怎么办,还有线程并发问题,共享问题(其实没那么多问题,这只是猜测,具体请深入了解spring的对象管理机制)
是不是使用线程池就不会有问题了?
线程池可以减少new线程的成本,但是线程的数量问题依旧不能得到解决(虽然现在的用户量肯定暴不了内存)
理想情况:
所有用户共享一个单独的任务处理线程,有任务就处理,没任务就阻塞
好吧,前面全是废话
现在要实现功能:
1. springmvc可以使用的后台线程
2. 该线程是基于任务的,阻塞的
So…很简单..所以代码也很简单
首先定义一个基类:
具体的说来,这个方法很简单,是一个处理任务的基类,实现一个线程,判断队列中是否有数据,没有数据就阻塞,有数据则调用实现类的process()方法处理任务
(要集成到spring中,所以前面的注解必须要写清楚, prototype代表这个类可以有多个实例先这样配置,用不用得到以后在推论)
BlockingQueue 这是一个阻塞队列,读取和写入的方式请参考< http://blog.csdn.net/cyp900509/article/details/40895695 >
@Component
@Scope("prototype")
public abstractclass BaseTask<T> implements Runnable {
protected BlockingQueue<T> queue;// 任务队列
public BlockingQueue<T> getQueue() {
return queue;
}
public void setQueue(BlockingQueue<T> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (;;) {
Thread.sleep(1000);
Tt = queue.take();
process(t);
}
}catch(Exception e) {
e.printStackTrace();
}
}
/**
* 业务处理逻辑
*
* @param t
*/
protected abstract void process(T t);
}
这个就是将来要传入 BaseTask<T>中的 T ,额看不懂请参考…算了,别参考了,还是换个行业吧…
public class TaskBean1 {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
任务处理类
这个就是具体执行任务的类,线程接到任务后,真正处理的逻辑在这
public class Task1 extends BaseTask<TaskBean1> {
@Override
protected void process(TaskBean1 t) {
System.out.println(t.getName());
}
}
Ok,父类有了,线程有了,实现也有了,那调用试试
public classMain {
private static BlockingQueue<TaskBean1> queue =new ArrayBlockingQueue<TaskBean1>(10);
public static void main(String[] args) {
//这个是java自带的线程池
ExecutorService service = Executors.newCachedThreadPool();
//先实例化一个任务对象
Task1t1 = newTask1();
//把队列和任务对象绑定在一起
//这里多说一句,最好应该是在构造函数中直接传进去,但是springmvc中对这个支持不是很好,所以先粗暴一把,以后在想办法
t1.setQueue(queue);
//启动这个线程
service.submit(t1);
try {
//线程有了,接下来不能让线程闲着啊,拼命建任务啊
//任务一
TaskBean1 b1 = new TaskBean1();
b1.setName("gogogo~");
//任务二
TaskBean1 b2 = new TaskBean1();
b2.setName("tototo~");
//任务??
TaskBean1 b3 = new TaskBean1();
b3.setName("ououou~");
//任务??
TaskBean1 b4 = new TaskBean1();
b4.setName("aoaoao~");
//任务有了,丢到一起,之后的事就让任务线程去忙吧,吼吼
queue.put(b1);
queue.put(b2);
queue.put(b3);
queue.put(b4);
}catch(InterruptedException e1) {
e1.printStackTrace();
}
try {
Thread.sleep(5000);
}catch(Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
就这样一个简单的基于任务的线程就搞定了,为了证明可用性…接下来处理pdf吧~~
第一步 把任务处理逻辑起来
1. 继承BaseTask<T>类,并且确定<T>的类型
2. 实现process()方法
@Component
public class ImgProcessTask extends BaseTask<StdFiles> {
@Override
protected void process(StdFiles t) {
try {
// 获取文件路径
Filefile = newFile(“”);
doBufferedImage(file); //生成预览图
} catch (Exception e) {
log.error("文件转图片失败,文件信息:{}", t.toString());
}
}
第二步: 在mvc中加入这个任务机制
//引入实现的类(必须是spring托管的,否则会是null)
@Autowired
private ImgProcessTask it;
//初始化任务队列(全球共享)
public static BlockingQueue<StdFiles> IMGPROCESS_QUEUE =new ArrayBlockingQueue<StdFiles>(1000);
// 初始化线程池
ExecutorService service = Executors.newCachedThreadPool();
// PostConstruct 是告诉spring初始化类后要调用这个方法
@PostConstruct
public void init(){
System.out.println("~~~~~~~~~~~~~~~~~init~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
it.setQueue(IMGPROCESS_QUEUE);;
service.submit(it);
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
第三部:
在web的server中把任务加到队列里面去,
pabulic void rarProcess(StdFiles t) {
//实例文件类
StdFilesfmodel=………
//文件转图片处理
boolean imgret=common.IMGPROCESS_QUEUE.offer(fmodel); // offer方法可以判断任务是否加载到队列中去
if (!imgret) {
logger.warn("文件加入处理列表失败,文件信息:{}",fmodel.toString());
}
}
//最后启动试试~~
总结:
做外包也好做啥也好,都无所谓,努力做最好的自己就好.不用总是把自己伪装成成受害者~额,虽然我做的也不是很好…共勉吧~