需求
最近接到公司的这么一个需求,在不使用消息中间件的情况下,开发一个消息发送的框架来异步处理各种服务,以提高系统的吞吐量,具体有以下几点要求:
1.支持数据的可持久化,公司最看重这个
2.服务高可用,公司使用了ngnix做负载均衡,有2台服务器
3.服务队列支持暂停和恢复
4.不侵入其他业务逻辑,也就是要做到低耦合。
接到这个任务后,回去琢磨了一下,觉得应该先设计UML图和数据库。
初次使用EA软件,画了一个简单的图例。
图解:
1.CustomJob是我设计的任务类的接口,用户自定义的任务类需要继承这个接口。
2.CustomJobFactory是这个任务类的生成类,实现了ApplicationContextAware接口,方便使用spring的高级容器applicationContext.
3.QueueFactory是用来生成单例队列。
4.JobConsume是消费者,我使用单例,处理过程选择多线程,这样与spring更容易结合。
5. QueueApi是提供给其他用户的接口。
示例代码
CustomJob
package com.example.queue;
/**
* @author 13781
* @version 1.0
* @created 23-9��-2018 12:42:11
*/
public interface CustomJob {
void execute(CustomJobDetail jobDetail);
}
CustomJobFactory
package com.example.queue;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringUtils;
/**
* @author 13781
* @version 1.0
* @created 22-9��-2018 21:25:07
*/
public class CustomJobFactory implements ApplicationContextAware {
private ApplicationContext applicationContext;
public CustomJobFactory(){
}
public void finalize() throws Throwable {
super.finalize();
}
public void setApplicationContext(ApplicationContext applicationContext){
this.applicationContext=applicationContext;
}
/**
*
* @param bundle
*/
protected Object createJobInstance(CustomBundle bundle)throws InstantiationException,IllegalAccessException
,ClassNotFoundException{
Object obj=null;
if(bundle.getByteCLass()!=null){
obj=bundle.getByteCLass().newInstance();
}else if(!StringUtils.isEmpty(bundle.getPathClass())){
obj=Class.forName(bundle.getPathClass()).newInstance();
}else {
throw new InstantiationException("未找到任务类");
}
return obj;
}
/**
*
* @param customBundle
*/
public CustomJob newJob(Class<? extends CustomJob> clazz,CustomBundle customBundle)throws InstantiationException,IllegalAccessException
,ClassNotFoundException{
customBundle.setByteCLass(clazz);
return newJob(customBundle);
}
public CustomJob newJob(CustomBundle customBundle)throws InstantiationException,IllegalAccessException
,ClassNotFoundException{
Object obj=this.createJobInstance(customBundle);
this.applicationContext.getAutowireCapableBeanFactory().autowireBean(obj);
return (CustomJob)obj;
}
}//end CustomJobFactory
QueueFactory
package com.example.queue.core;
import com.example.queue.WrapperJob;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author 13781
* @version 1.0
* @created 23-9��-2018 12:42:16
*/
public class QueueFactory {
private static BlockingQueue<WrapperJob> blockQueue=new LinkedBlockingQueue<WrapperJob>();
public QueueFactory(){
}
public void finalize() throws Throwable {
}
public static BlockingQueue getSingleBlockQueue(){
return blockQueue;
}
}//end QueueFactory
JobConsume
package com.example.queue.consume;
import com.example.queue.WrapperJob;
import com.example.queue.core.QueueFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 13781
* @version 1.0
* @created 23-9��-2018 12:42:14
*/
@Component
public class JobComsume extends Thread{
private final Logger log = LoggerFactory.getLogger(this.getClass());
private static final int cousumeSum=3;
private volatile boolean isPause;
private ReentrantLock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
// private ExecutorService service=Executors.newFixedThreadPool(cousumeSum);
public JobComsume(){
}
@Override
public void run(){
while (true){
lock.lock();
try{
while(isPause){
condition.await();
}
}catch (Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
try{
Thread.sleep(100);
BlockingQueue<WrapperJob> blockingQueue=QueueFactory.getSingleBlockQueue();
List<Runnable> list=new ArrayList<Runnable>();
for (int i = 0; i <cousumeSum; i++) {
list.add(blockingQueue.take());
}
ExecutorService service=Executors.newFixedThreadPool(cousumeSum);
for (Runnable job:list ) {
service.submit(job);
}
service.shutdown();
}catch (java.lang.InterruptedException e){
e.printStackTrace();
}
}
}
public void pauseJob(){
lock.lock();
try{
isPause=true;
log.info("消费线程将暂停。。。。。。。");
}catch (Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void resumeJob(){
lock.lock();
try{
isPause=false;
condition.signalAll();
log.info("消费线程将恢复运行。。。。。。。");
}catch (Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}//end JobComsume
QueueApi
package com.example.queue.api;
import com.example.queue.*;
import com.example.queue.core.QueueFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author 13781
* @version 1.0
* @created 23-9��-2018 12:42:15
*/
@Component
public class QueueApi {
@Autowired
private CustomWrapperJobFactory jobFactory;
/**
*
* @param byteClazz
* @param customJobDetail
*/
public void newJob(Class byteClazz, CustomJobDetail customJobDetail)throws InstantiationException,IllegalAccessException
,ClassNotFoundException,InterruptedException{
CustomBundle customBundle=new CustomBundle(customJobDetail);
customBundle.setByteCLass(byteClazz);
CustomJob customJob=jobFactory.newJob(customBundle);
WrapperJob wrapperJob=new WrapperJob(customJob);
wrapperJob.setJobDetail(customJobDetail);
QueueFactory.getSingleBlockQueue().put(wrapperJob);
}
}//end QueueApi
测试类
package queue;
import com.example.queue.CustomJob;
import com.example.queue.CustomJobDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JobDemo implements CustomJob {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public void execute(CustomJobDetail jobDetail) {
log.info(jobDetail.getJobName()+"。。。。。");
}
}
package queue;
import com.example.queue.CustomJobDetail;
import com.example.queue.api.QueueApi;
import com.example.queue.consume.JobComsume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:springmvc.xml"})
public class QueueTest {
@Autowired
QueueApi api;
@Autowired
JobComsume consume;
@Test
public void test0()throws Exception{
//创建100个任务并添加到队列
for (int i = 0; i < 100; i++) {
CustomJobDetail customJobDetail=new CustomJobDetail();
customJobDetail.setJobName("任务【"+i+"】");
api.newJob(JobDemo.class,customJobDetail);
}
//运行消费者
consume.start();
Thread.sleep(10);
//暂停消费者
new Thread(new Runnable() {
public void run() {
consume.pauseJob();
}
}).start();
Thread.sleep(6000);
//恢复消费者线程
new Thread(new Runnable() {
public void run() {
consume.resumeJob();
}
}).start();
Thread.sleep(10);
//再次暂停消费者
new Thread(new Runnable() {
public void run() {
consume.pauseJob();
}
}).start();
Thread.sleep(100*1000L);
}
}
测试结果
信息: Refreshing org.springframework.context.support.GenericApplicationContext@77b52d12: startup date [Mon Sep 24 12:13:45 CST 2018]; root of context hierarchy
九月 24, 2018 12:13:45 下午 org.springframework.web.servlet.handler.SimpleUrlHandlerMapping registerHandler
信息: Mapped URL path [/**] onto handler 'org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#0'
九月 24, 2018 12:13:46 下午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache
信息: Looking for @ControllerAdvice: org.springframework.context.support.GenericApplicationContext@77b52d12: startup date [Mon Sep 24 12:13:45 CST 2018]; root of context hierarchy
九月 24, 2018 12:13:46 下午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache
信息: Looking for @ControllerAdvice: org.springframework.context.support.GenericApplicationContext@77b52d12: startup date [Mon Sep 24 12:13:45 CST 2018]; root of context hierarchy
12:13:46.144 [Thread-3] INFO com.example.queue.consume.JobComsume - 消费线程将暂停。。。。。。。
12:13:46.235 [pool-1-thread-1] INFO queue.JobDemo - 任务【0】。。。。。
12:13:46.235 [pool-1-thread-2] INFO queue.JobDemo - 任务【1】。。。。。
12:13:46.235 [pool-1-thread-3] INFO queue.JobDemo - 任务【2】。。。。。
12:13:52.144 [Thread-4] INFO com.example.queue.consume.JobComsume - 消费线程将恢复运行。。。。。。。
12:13:52.154 [Thread-5] INFO com.example.queue.consume.JobComsume - 消费线程将暂停。。。。。。。
12:13:52.248 [pool-2-thread-1] INFO queue.JobDemo - 任务【3】。。。。。
12:13:52.248 [pool-2-thread-2] INFO queue.JobDemo - 任务【4】。。。。。
12:13:52.248 [pool-2-thread-3] INFO queue.JobDemo - 任务【5】。。。。。
总结:job类中你可以注入任意多的service来处理你的逻辑,可以与spring容器结合,将代码简单的扩充后可以实现1、3和4的要求。至于第二点集群,可以取巧为加载数据的时候采用分片加载的方式,这样各个集群节点就不会重复加载数据。
源码下载地址:自定义队列服务功能源码