├── src └── main │ └── java │ └── com │ └── scienjus │ └── disque │ ├── consumer │ ├── annotation │ │ ├── GetJob.java │ │ └── Consumer.java │ ├── model │ │ └── ConsumerMethod.java │ ├── DisqueConsumer.java │ ├── worker │ │ └── ConsumerWorker.java │ └── factory │ │ └── SchedulerBeanFactory.java │ ├── producer │ ├── annotation │ │ ├── AddJob.java │ │ └── Producer.java │ ├── DisqueProducer.java │ └── worker │ │ └── ProducerWorker.java │ └── util │ └── SerializeUtil.java ├── .gitattributes ├── .gitignore ├── pom.xml └── README.md /src/main/java/com/scienjus/disque/consumer/annotation/GetJob.java: -------------------------------------------------------------------------------- 1 | package com.scienjus.disque.consumer.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author ScienJus 7 | * @date 2015/12/8. 8 | */ 9 | @Target(ElementType.METHOD) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Documented 12 | public @interface GetJob { 13 | 14 | String queue(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/scienjus/disque/producer/annotation/AddJob.java: -------------------------------------------------------------------------------- 1 | package com.scienjus.disque.producer.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author ScienJus 7 | * @date 2015/12/8. 8 | */ 9 | @Target(ElementType.METHOD) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Documented 12 | public @interface AddJob { 13 | 14 | String queue(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/scienjus/disque/consumer/annotation/Consumer.java: -------------------------------------------------------------------------------- 1 | package com.scienjus.disque.consumer.annotation; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * @author ScienJus 9 | * @date 2015/12/8. 10 | */ 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Documented 14 | @Component 15 | public @interface Consumer { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/scienjus/disque/producer/annotation/Producer.java: -------------------------------------------------------------------------------- 1 | package com.scienjus.disque.producer.annotation; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * @author ScienJus 9 | * @date 2015/12/8. 10 | */ 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Documented 14 | @Component 15 | public @interface Producer { 16 | } 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /src/main/java/com/scienjus/disque/producer/DisqueProducer.java: -------------------------------------------------------------------------------- 1 | package com.scienjus.disque.producer; 2 | 3 | import com.github.xetorthio.jedisque.Jedisque; 4 | import com.scienjus.disque.util.SerializeUtil; 5 | 6 | /** 7 | * @author XieEnlong 8 | * @date 2015/12/14. 9 | */ 10 | public class DisqueProducer { 11 | 12 | private Jedisque jedisque; 13 | 14 | public void setJedisque(Jedisque jedisque) { 15 | this.jedisque = jedisque; 16 | } 17 | 18 | public void addJob(String queue, Object job, long mstimeout) { 19 | jedisque.addJob(queue.getBytes(), SerializeUtil.serialize(job), mstimeout); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/scienjus/disque/consumer/model/ConsumerMethod.java: -------------------------------------------------------------------------------- 1 | package com.scienjus.disque.consumer.model; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | /** 6 | * @author ScienJus 7 | * @date 2015/12/8. 8 | */ 9 | public class ConsumerMethod { 10 | 11 | private String queue; 12 | 13 | private Method method; 14 | 15 | private Object bean; 16 | 17 | public ConsumerMethod(String queue, Method method, Object bean) { 18 | this.queue = queue; 19 | this.method = method; 20 | this.bean = bean; 21 | } 22 | 23 | public String getQueue() { 24 | return queue; 25 | } 26 | 27 | public Method getMethod() { 28 | return method; 29 | } 30 | 31 | public Object getBean() { 32 | return bean; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/scienjus/disque/consumer/DisqueConsumer.java: -------------------------------------------------------------------------------- 1 | package com.scienjus.disque.consumer; 2 | 3 | import com.github.xetorthio.jedisque.Jedisque; 4 | import com.github.xetorthio.jedisque.Job; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author XieEnlong 10 | * @date 2015/12/14. 11 | */ 12 | public class DisqueConsumer { 13 | 14 | private Jedisque jedisque; 15 | 16 | public void setJedisque(Jedisque jedisque) { 17 | this.jedisque = jedisque; 18 | } 19 | 20 | public Job getJob(String queue) { 21 | List jobs = jedisque.getJob(queue); 22 | if (jobs != null && !jobs.isEmpty()) { 23 | return jobs.get(0); 24 | } 25 | return null; 26 | } 27 | 28 | public void ackJob(Job job) { 29 | jedisque.ackjob(job.getId()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | *.iml 49 | target/ 50 | src/test/ 51 | -------------------------------------------------------------------------------- /src/main/java/com/scienjus/disque/producer/worker/ProducerWorker.java: -------------------------------------------------------------------------------- 1 | package com.scienjus.disque.producer.worker; 2 | 3 | import com.scienjus.disque.producer.DisqueProducer; 4 | import com.scienjus.disque.producer.annotation.AddJob; 5 | import org.aspectj.lang.ProceedingJoinPoint; 6 | import org.aspectj.lang.annotation.Around; 7 | import org.aspectj.lang.annotation.Aspect; 8 | 9 | /** 10 | * @author ScienJus 11 | * @date 2015/12/8. 12 | */ 13 | @Aspect 14 | public class ProducerWorker { 15 | 16 | private DisqueProducer producer; 17 | 18 | public void setProducer(DisqueProducer producer) { 19 | this.producer = producer; 20 | } 21 | 22 | @Around("@annotation(addJob)") 23 | public Object around(ProceedingJoinPoint point, AddJob addJob) { 24 | Object content = null; 25 | try { 26 | content = point.proceed(); 27 | String queue = addJob.queue(); 28 | producer.addJob(queue, content, 0); 29 | } catch (Throwable throwable) { 30 | throwable.printStackTrace(); 31 | } 32 | return content; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/scienjus/disque/util/SerializeUtil.java: -------------------------------------------------------------------------------- 1 | package com.scienjus.disque.util; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.ObjectInputStream; 6 | import java.io.ObjectOutputStream; 7 | 8 | /** 9 | * @author ScienJus 10 | * @date 2014/7/24. 11 | */ 12 | public class SerializeUtil { 13 | 14 | /** 15 | * 序列化 16 | * 17 | * @param object 18 | */ 19 | public static byte[] serialize(Object object) { 20 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 21 | ObjectOutputStream oos = new ObjectOutputStream(baos);) { 22 | oos.writeObject(object); 23 | return baos.toByteArray(); 24 | } catch (Exception e) { 25 | } 26 | return null; 27 | } 28 | 29 | /** 30 | * 反序列化 31 | * 32 | * @param bytes 33 | */ 34 | public static Object unserialize(byte[] bytes) { 35 | try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 36 | ObjectInputStream ois = new ObjectInputStream(bais);) { 37 | return ois.readObject(); 38 | } catch (Exception e) { 39 | } 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/scienjus/disque/consumer/worker/ConsumerWorker.java: -------------------------------------------------------------------------------- 1 | package com.scienjus.disque.consumer.worker; 2 | 3 | 4 | import com.github.xetorthio.jedisque.Job; 5 | import com.scienjus.disque.consumer.DisqueConsumer; 6 | import com.scienjus.disque.consumer.model.ConsumerMethod; 7 | import com.scienjus.disque.util.SerializeUtil; 8 | 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.lang.reflect.Method; 11 | 12 | /** 13 | * @author ScienJus 14 | * @date 2015/12/8. 15 | */ 16 | public class ConsumerWorker implements Runnable { 17 | 18 | private ConsumerMethod method; 19 | 20 | private DisqueConsumer consumer; 21 | 22 | public ConsumerWorker(ConsumerMethod method, DisqueConsumer consumer) { 23 | this.method = method; 24 | this.consumer = consumer; 25 | } 26 | 27 | @Override 28 | public void run() { 29 | Object bean = method.getBean(); 30 | Method jobMethod = method.getMethod(); 31 | String queue = method.getQueue(); 32 | //获取消息 33 | Job job; 34 | while ((job = consumer.getJob(queue)) != null) { 35 | try { 36 | System.out.println(job.getId()); 37 | boolean isSuccess; 38 | if (method.getMethod().getReturnType().isAssignableFrom(Boolean.TYPE)) { 39 | isSuccess = (boolean) jobMethod.invoke(bean, SerializeUtil.unserialize(job.getBodyAsBytes())); 40 | } else { 41 | jobMethod.invoke(bean, SerializeUtil.unserialize(job.getBodyAsBytes())); 42 | isSuccess = true; 43 | } 44 | if (isSuccess) { 45 | consumer.ackJob(job); 46 | } 47 | } catch (InvocationTargetException | IllegalAccessException e) { 48 | e.printStackTrace(); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.scienjus 6 | spring-disque 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | spring-disque 11 | http://maven.apache.org 12 | 13 | 14 | 4.1.8.RELEASE 15 | 0.0.4 16 | 1.8.7 17 | 2.2.1 18 | 19 | 20 | 21 | 22 | 23 | com.github.xetorthio 24 | jedisque 25 | ${jedisque.version} 26 | 27 | 28 | 29 | 30 | org.springframework 31 | spring-context 32 | ${spring.version} 33 | 34 | 35 | 36 | 37 | org.springframework 38 | spring-aop 39 | ${spring.version} 40 | 41 | 42 | 43 | org.aspectj 44 | aspectjweaver 45 | ${aspectj.version} 46 | 47 | 48 | 49 | 50 | ${project.artifactId} 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-compiler-plugin 55 | 56 | 1.7 57 | 1.7 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/java/com/scienjus/disque/consumer/factory/SchedulerBeanFactory.java: -------------------------------------------------------------------------------- 1 | package com.scienjus.disque.consumer.factory; 2 | 3 | import com.scienjus.disque.consumer.DisqueConsumer; 4 | import com.scienjus.disque.consumer.annotation.Consumer; 5 | import com.scienjus.disque.consumer.annotation.GetJob; 6 | import com.scienjus.disque.consumer.model.ConsumerMethod; 7 | import com.scienjus.disque.consumer.worker.ConsumerWorker; 8 | import org.springframework.beans.BeansException; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.context.ApplicationContextAware; 11 | 12 | import java.lang.reflect.Method; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.concurrent.Executors; 17 | import java.util.concurrent.ScheduledExecutorService; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | /** 21 | * @author ScienJus 22 | * @date 2015/12/8. 23 | */ 24 | public class SchedulerBeanFactory implements ApplicationContextAware { 25 | 26 | private ApplicationContext applicationContext; 27 | 28 | private DisqueConsumer consumer; 29 | 30 | private ScheduledExecutorService scheduled; 31 | 32 | public void setConsumer(DisqueConsumer consumer) { 33 | this.consumer = consumer; 34 | } 35 | 36 | @Override 37 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 38 | this.applicationContext = applicationContext; 39 | } 40 | 41 | private void init() { 42 | //获得所有消费者Bean 43 | Map beans = applicationContext.getBeansWithAnnotation(Consumer.class); 44 | //保存worker 45 | List workers = new ArrayList<>(); 46 | 47 | for (Map.Entry entry : beans.entrySet()) { 48 | String name = entry.getKey(); 49 | Object bean = entry.getValue(); 50 | Class clazz = applicationContext.getType(name); 51 | for (Method method : clazz.getMethods()) { 52 | GetJob getJob = method.getAnnotation(GetJob.class); 53 | if (getJob != null) { 54 | ConsumerMethod consumerMethod = new ConsumerMethod(getJob.queue(), method, bean); 55 | ConsumerWorker worker = new ConsumerWorker(consumerMethod, consumer); 56 | workers.add(worker); 57 | } 58 | } 59 | } 60 | //注册Scheduler 61 | scheduled = Executors.newScheduledThreadPool(workers.size()); 62 | for (ConsumerWorker worker : workers) { 63 | scheduled.scheduleWithFixedDelay(worker, 10, 2, TimeUnit.SECONDS); 64 | } 65 | } 66 | 67 | private void destroy() { 68 | scheduled.shutdown(); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Disque 2 | 3 | 基于 Spring 和 Jedis 的 Disque 封装,使用注解驱动 4 | 5 | ###关于 Disque 6 | 7 | > Disque 是一个内存储存的分布式任务队列实现, 它由 Redis 的作者 Salvatore Sanfilippo (@antirez)开发, 目前正处于预览版(alpha)阶段。 8 | 9 | 一些介绍: 10 | 11 | - 该项目的地址:[Disque, an in-memory, distributed job queue][1] 12 | - 该项目的中文介绍:[Disque 使用教程][2] 13 | - Java 的客户端实现(Jedis 的作者开发):[Jedisque][3] 14 | 15 | [1]: https://github.com/antirez/disque 16 | [2]: http://disquebook.com/ 17 | [3]: https://github.com/xetorthio/jedisque 18 | 19 | ### 使用方法 20 | 21 | **创建项目** 22 | 23 | 仓库: 24 | 25 | ``` 26 | 27 | scienjus-mvn-repo 28 | https://raw.github.com/ScienJus/maven/snapshot/ 29 | 30 | true 31 | always 32 | 33 | 34 | ``` 35 | 36 | 依赖: 37 | 38 | ``` 39 | 40 | com.scienjus 41 | spring-disque 42 | 1.0-SNAPSHOT 43 | 44 | ``` 45 | 46 | 所有依赖 Jar: 47 | 48 | ``` 49 | 50 | 4.1.8.RELEASE 51 | 0.0.4 52 | 1.8.7 53 | 2.2.1 54 | 55 | 56 | 57 | 58 | com.github.xetorthio 59 | jedisque 60 | ${jedisque.version} 61 | 62 | 63 | 64 | 65 | org.springframework 66 | spring-context-support 67 | ${spring.version} 68 | 69 | 70 | 71 | org.springframework 72 | spring-tx 73 | ${spring.version} 74 | 75 | 76 | 77 | org.quartz-scheduler 78 | quartz 79 | ${quartz.version} 80 | 81 | 82 | 83 | 84 | org.springframework 85 | spring-aop 86 | ${spring.version} 87 | 88 | 89 | 90 | org.aspectj 91 | aspectjweaver 92 | ${aspectj.version} 93 | 94 | 95 | ``` 96 | 97 | **配置 Spring Bean** 98 | 99 | 配置 Jedisque 客户端: 100 | 101 | ``` 102 | @Bean 103 | public Jedisque jedisque() { 104 | try { 105 | Jedisque jedisque = new Jedisque(new URI("disque://192.168.1.222:7711")); 106 | return jedisque; 107 | } catch (URISyntaxException e) { 108 | return null; 109 | } 110 | } 111 | ``` 112 | 113 | 配置消费者: 114 | 115 | ``` 116 | @Bean 117 | public DisqueConsumer consumer() { 118 | DisqueConsumer consumer = new DisqueConsumer(); 119 | consumer.setJedisque(jedisque()); 120 | return consumer; 121 | } 122 | ``` 123 | 124 | 配置生产者: 125 | 126 | ``` 127 | @Bean 128 | public DisqueProducer producer() { 129 | DisqueProducer producer = new DisqueProducer(); 130 | producer.setJedisque(jedisque()); 131 | return producer; 132 | } 133 | ``` 134 | 135 | 配置消费者定时扫描任务(仅当使用注解驱动的消费者时才需要配置): 136 | 137 | ``` 138 | @Bean(initMethod = "init", destroyMethod = "destroy") 139 | public SchedulerBeanFactory schedulerBeanFactory() { 140 | SchedulerBeanFactory schedulerBeanFactory = new SchedulerBeanFactory(); 141 | schedulerBeanFactory.setConsumer(consumer()); 142 | return schedulerBeanFactory; 143 | } 144 | ``` 145 | 146 | 注意一定要将`initMethod`设为`init`方法。 147 | 148 | 配置生产者自动推送任务(仅当使用注解驱动的生产者时才需要配置): 149 | 150 | ``` 151 | @Bean 152 | public ProducerWorker producerWorker() { 153 | ProducerWorker producerWorker = new ProducerWorker(); 154 | producerWorker.setProducer(producer()); 155 | return producerWorker; 156 | } 157 | ``` 158 | 159 | **创建生产者实例** 160 | 161 | 在类上使用`@Producer`注解,在方法上使用`@AddJob`注解,`retrun`需要发送的对象(需要配置`producerWorker`): 162 | 163 | ``` 164 | @Producer 165 | public class SayHelloProducer { 166 | 167 | @AddJob(queue = "say_hello") 168 | public String sayHello(String name) { 169 | return name; 170 | } 171 | } 172 | ``` 173 | 174 | **创建消费者实例** 175 | 176 | 在类上使用`@Consumer`注解,在方法上使用`@GetJob`注解(需要配置`schedulerBeanFactory`): 177 | 178 | ``` 179 | @Consumer 180 | public class SayHelloConsumer { 181 | 182 | @GetJob(queue = "say_hello") 183 | public boolean onSayHello(String name) { 184 | System.out.println("Hello ! " + name + " !"); 185 | return true; 186 | } 187 | } 188 | ``` 189 | 190 | **消费者的重试机制** 191 | 192 | 当`@GetJob`方法的返回值类型为`boolean`类型,并且执行的结果为`false`时,系统认定此任务执行失败。 193 | 194 | 当方法的返回值为`Void`或是执行的结果为`true`时,系统认定任务执行成功。 195 | 196 | 执行失败的任务将会在一段时间后重新投递,直到执行成功或超过任务的生存周期。 197 | 198 | ### 待办事项 199 | 200 | - [ ] 任务参数 201 | - [ ] 监控页面 202 | 203 | ### 联系方式 204 | 205 | 可以提 Issues 或是通过邮件联系我:`i@scienjus.com` 206 | --------------------------------------------------------------------------------