1.pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.yun.base</groupId> <artifactId>pom</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>mq</artifactId>
<properties>
<spring.version>4.3.10.RELEASE</spring.version>
<logback-ext-spring.version>0.1.4</logback-ext-spring.version>
<slf4j.version>1.7.25</slf4j.version>
<logback.version>1.2.3</logback.version>
<javax.mail.version>1.6.0</javax.mail.version>
<rocketmq.version>3.2.6</rocketmq.version>
<commons-lang.version>2.6</commons-lang.version>
</properties>
<dependencies> <dependency> <groupId>com.yun.base</groupId> <artifactId>comm</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>com.alibaba.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>${rocketmq.version}</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>${commons-lang.version}</version> </dependency> </dependencies> </project>
2.生产者接口
package com.yun.base.mq.service; import com.alibaba.rocketmq.client.producer.SendCallback; import com.yun.base.mq.client.entity.MQEntity; /** * mq消息生产者 * @author Administrator * */ public interface IProducer { /** * 同步发送MQ * @param topic * @param entity */ public void send(String topic, MQEntity entity); /** * 发送MQ,提供回调函数,超时时间默认3s * @param topic * @param entity * @param sendCallback */ public void send( String topic, MQEntity entity, SendCallback sendCallback ); /** * 单向发送MQ,不等待服务器回应且没有回调函数触发,适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。 * @param topic * @param entity */ public void sendOneway(String topic, MQEntity entity); }
3.生产者实现
package com.yun.base.mq.service.impl; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import com.alibaba.rocketmq.client.producer.DefaultMQProducer; import com.alibaba.rocketmq.client.producer.SendCallback; import com.alibaba.rocketmq.common.message.Message; import com.yun.base.mq.client.entity.MQEntity; import com.yun.base.mq.service.IProducer; import com.yun.base.mq.util.SerializableUtil; public class RocketMqProducerImpl implements IProducer, InitializingBean{ private static Logger logger = LoggerFactory.getLogger(RocketMqProducerImpl.class); private String namesrvAddr; private String producerGroup; private Boolean retryAnotherBrokerWhenNotStoreOK; private DefaultMQProducer producer; /** * spring 容器初始化所有属性后调用此方法 */ public void afterPropertiesSet() throws Exception { producer = new DefaultMQProducer(); producer.setProducerGroup( this.producerGroup ); producer.setNamesrvAddr( this.namesrvAddr ); producer.setRetryAnotherBrokerWhenNotStoreOK( this.retryAnotherBrokerWhenNotStoreOK ); // producer.setVipChannelEnabled(false); /* * Producer对象在使用之前必须要调用start初始化,初始化一次即可<br> * 注意:切记不可以在每次发送消息时,都调用start方法 */ producer.start(); logger.info( "[{}:{}] start successd!",producerGroup,namesrvAddr ); } /** * 销毁 */ public void destroy() throws Exception { if (producer != null) { logger.info("producer: [{}:{}] end ",producerGroup,namesrvAddr); producer.shutdown(); } } public void send(String topic, MQEntity entity) { String keys = UUID.randomUUID().toString(); entity.setMqKey(keys); String tags = entity.getClass().getName(); logger.info("业务:{},tags:{},keys:{},entity:{}",topic, tags, keys, entity); Message msg = new Message(topic, tags, keys, SerializableUtil.toByte(entity)); try { producer.send(msg); } catch (Exception e) { logger.error(keys.concat(":发送消息失败"), e); throw new RuntimeException("发送消息失败",e); } } public void send(String topic, MQEntity entity, SendCallback sendCallback) { String keys = UUID.randomUUID().toString(); entity.setMqKey(keys); String tags = entity.getClass().getName(); logger.info("业务:{},tags:{},keys:{},entity:{}",topic, tags, keys, entity); Message msg = new Message(topic, tags, keys, SerializableUtil.toByte(entity)); try { producer.send(msg, sendCallback); } catch (Exception e) { logger.error(keys.concat(":发送消息失败"), e); throw new RuntimeException("发送消息失败",e); } } public void sendOneway(String topic, MQEntity entity) { String keys = UUID.randomUUID().toString(); entity.setMqKey(keys); String tags = entity.getClass().getName(); logger.info("业务:{},tags:{},keys:{},entity:{}",topic, tags, keys, entity); Message msg = new Message(topic, tags, keys, SerializableUtil.toByte(entity)); try { producer.sendOneway(msg); } catch (Exception e) { logger.error(keys.concat(":发送消息失败"), e); throw new RuntimeException("发送消息失败",e); } } public void setNamesrvAddr(String namesrvAddr) { this.namesrvAddr = namesrvAddr; } public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } public void setProducer(DefaultMQProducer producer) { this.producer = producer; } public void setRetryAnotherBrokerWhenNotStoreOK( Boolean retryAnotherBrokerWhenNotStoreOK) { this.retryAnotherBrokerWhenNotStoreOK = retryAnotherBrokerWhenNotStoreOK; } }
4.消费端接口
package com.yun.base.mq.service; import com.alibaba.rocketmq.common.message.MessageExt; public interface IConsumer { /** * 消费端解析消息 * @param msg */ void handlerMessage( MessageExt msg ); }
4.消费端实例化
package com.yun.base.mq.service.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer; import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import com.alibaba.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import com.alibaba.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import com.alibaba.rocketmq.client.consumer.listener.MessageListenerConcurrently; import com.alibaba.rocketmq.client.consumer.listener.MessageListenerOrderly; import com.alibaba.rocketmq.client.exception.MQClientException; import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere; import com.alibaba.rocketmq.common.message.MessageExt; import com.alibaba.rocketmq.common.protocol.heartbeat.MessageModel; public class RocketMqConsumerImpl implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(RocketMqConsumerImpl.class); private DefaultMQPushConsumer consumer; private String namesrvAddr; private String consumerGroup; private String messageModel; private String messageListener; private Map<String, AbstractConsumer> handlermap = new HashMap<String, AbstractConsumer>(); private void initializingMessageSelector() throws InterruptedException, MQClientException { consumer = new DefaultMQPushConsumer(); if( this.consumerGroup != null && this.consumerGroup.trim().length() > 0 ) { consumer.setConsumerGroup( this.consumerGroup ); logger.debug( "set consumer group " + this.consumerGroup ); } consumer.setNamesrvAddr( this.namesrvAddr ); consumer.setConsumeMessageBatchMaxSize( 1 ); logger.debug( "set consumer name server address " + this.namesrvAddr ); logger.debug( "set consumer message batch max size " + 1 ); if( "BROADCASTING".equals( messageModel ) ) { consumer.setMessageModel( MessageModel.BROADCASTING ); logger.debug( "set consumer message model BROADCASTING" ); } else if( "CLUSTERING".equals( messageModel ) ) { consumer.setMessageModel( MessageModel.CLUSTERING ); logger.debug( "set consumer message model CLUSTERING" ); } else { logger.debug( "set consumer message model should be BROADCASTING or CLUSTERING" ); throw new RuntimeException( "set consumer message model should be BROADCASTING or CLUSTERING" ); } /** * 订阅指定topic下所有消息<br> * 注意:一个consumer对象可以订阅多个topic */ if( handlermap != null && !handlermap.isEmpty() ) { for( String topic : handlermap.keySet() ) { consumer.subscribe( topic, "*" ); logger.debug( "consumer subscribe topic " + topic + " *" ); } } else { logger.debug( "you should provide at least one message handler." ); throw new RuntimeException( "you should provide at least one message handler." ); } /** * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费<br> * 如果非第一次启动,那么按照上次消费的位置继续消费 */ consumer.setConsumeFromWhere( ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET ); if( "CONCURRENTLY".equals( messageListener ) ) { consumer.registerMessageListener( new MessageListenerConcurrently() { /** * 默认msgs里只有一条消息,可以通过设置consumeMessageBatchMaxSize参数来批量接收消息 */ public ConsumeConcurrentlyStatus consumeMessage( final List<MessageExt> msgs, final ConsumeConcurrentlyContext context ) { try { if( msgs != null && !msgs.isEmpty() ) { for( MessageExt msg : msgs ) { logger.debug( String.format( "start consum message: message:id:%s topic:%s tags:%s ", msg.getMsgId(), msg.getTopic(), msg.getTags()) ); AbstractConsumer handler = handlermap.get( msg.getTopic() ); if( handler != null ) { handler.handlerMessage( msg ); } logger.debug( String.format( "consume message success! message:id:%s topic:%s tags:%s ", msg.getMsgId(), msg.getTopic(), msg.getTags()) ); } } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } catch( Exception e ) { logger.error( "consume message error!", e ); return ConsumeConcurrentlyStatus.RECONSUME_LATER; } } } ); } else if( "ORDERLY".equals( messageListener ) ) { consumer.registerMessageListener( new MessageListenerOrderly() { /** * 默认msgs里只有一条消息,可以通过设置consumeMessageBatchMaxSize参数来批量接收消息 */ public ConsumeOrderlyStatus consumeMessage( final List<MessageExt> msgs, final ConsumeOrderlyContext context ) { try { if( msgs != null && !msgs.isEmpty() ) { for( MessageExt msg : msgs ) { logger.debug( String.format( "start consum message: message:id:%s topic:%s tags:%s message:%s", msg.getMsgId(), msg.getTopic(), msg.getTags(), new String( msg.getBody() ) ) ); AbstractConsumer handler = handlermap.get( msg.getTopic() ); if( handler != null ) { handler.handlerMessage( msg ); } logger.debug( String.format( "consume message success! message:id:%s topic:%s tags:%s message:%s", msg.getMsgId(), msg.getTopic(), msg.getTags(), new String( msg.getBody() ) ) ); } } return ConsumeOrderlyStatus.SUCCESS; } catch( Exception e ) { logger.error( "consume message error!", e ); return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; } } } ); } /** * Consumer对象在使用之前必须要调用start初始化,初始化一次即可<br> */ consumer.start(); logger.debug( "consumer start successd!" ); } public void afterPropertiesSet() throws Exception { initializingMessageSelector(); } public void destroy() throws Exception { if (consumer != null) { consumer.shutdown(); logger.debug( "consumer shutdown!" ); } } public void setNamesrvAddr( String namesrvAddr ) { this.namesrvAddr = namesrvAddr; } public void setConsumerGroup( String consumerGroup ) { this.consumerGroup = consumerGroup; } public void setMessageModel( String messageModel ) { this.messageModel = messageModel; } public void setMessageListener( String messageListener ) { this.messageListener = messageListener; } public void setHandlermap( Map<String, AbstractConsumer> handlermap ) { this.handlermap = handlermap; } }
5.消费端抽象类
package com.yun.base.mq.service.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.rocketmq.common.message.MessageExt; import com.yun.base.mq.client.entity.MQEntity; import com.yun.base.mq.service.IConsumer; import com.yun.base.mq.util.SerializableUtil; public abstract class AbstractConsumer implements IConsumer { protected Logger logger = LoggerFactory.getLogger(AbstractConsumer.class); private String classTypeName; public void handlerMessage(MessageExt msg) { try { MQEntity entity = doStart(msg); execute(entity); doEnd(entity); } catch (Exception e) { logger.error("处理mq消息异常。",e); } } /** * 解析mq消息前置处理 * @param msg * @param entity * @throws ClassNotFoundException */ protected MQEntity doStart(MessageExt msg) throws ClassNotFoundException { Class<? extends MQEntity> clazz = (Class<? extends MQEntity>) Class.forName(classTypeName); return SerializableUtil.parse(msg.getBody(), clazz); } /** * 解析mq消息后置处理 * @param entity */ protected void doEnd(MQEntity entity) { } /** * 解析mq消息 MessageExt * @param entity */ public abstract void execute(MQEntity entity); public void setClassTypeName(String classTypeName) { this.classTypeName = classTypeName; } }
6.序列化工具
package com.yun.base.mq.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; /** * java的序列化和反序列化 * * @author Administrator */ public class SerializableUtil { @SuppressWarnings("unchecked") public static <T> T parse(byte[] rec, Class<T> classType) { ByteArrayInputStream arrayInputStream = null; ObjectInputStream objectInputStream = null; try { arrayInputStream = new ByteArrayInputStream(rec); objectInputStream = new ObjectInputStream(arrayInputStream); T t = (T) objectInputStream.readObject(); return t; } catch (Exception e) { e.printStackTrace(); }finally{ closeQuietly(arrayInputStream); closeQuietly(objectInputStream); } return null; } public static byte[] toByte(Object obj) { ByteArrayOutputStream arrayOutputStream = null; ObjectOutputStream objectOutputStream = null; try { arrayOutputStream = new ByteArrayOutputStream(); objectOutputStream = new ObjectOutputStream(arrayOutputStream); objectOutputStream.writeObject(obj); objectOutputStream.flush(); byte[] rtn = arrayOutputStream.toByteArray(); return rtn; } catch (IOException e) { e.printStackTrace(); } finally { closeQuietly(objectOutputStream); closeQuietly(arrayOutputStream); } return null; } public static void closeQuietly(InputStream in){ if(in!=null){ try { in.close(); } catch (Exception e) { } } } public static void closeQuietly(OutputStream out){ if(out!=null){ try { out.close(); } catch (Exception e) { } } } }
7.MQEntity
package com.yun.base.mq.client.entity; import java.io.Serializable; import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder; /** * @author Administrator * */ public class MQEntity implements Serializable{ private static final long serialVersionUID = -2551495105702956945L; private Map<String, Object> extObj = new LinkedHashMap<String, Object>(); private String mqId ; private String mqKey; /** * 添加附加字段 * @param key * @param value */ public void addExt(String key , Object value){ extObj.put(key, value); } /** * 获取附加字段 * @param key */ public void getExt(String key ){ extObj.get(key); } public String getMqId() { return mqId; } public void setMqId(String mqId) { this.mqId = mqId; } public String getMqKey() { return mqKey; } public void setMqKey(String mqKey) { this.mqKey = mqKey; } @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } @Override public boolean equals(Object obj) { return EqualsBuilder.reflectionEquals(this, obj); } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } }
8.rocketmq.producer.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 每个模块只需要配置一个 producer --> <bean id="producer" class="com.yun.base.mq.service.impl.RocketMqProducerImpl" init-method="afterPropertiesSet" destroy-method="destroy"> <property name="producerGroup" value="LOANSYS"></property> <property name="namesrvAddr" value="${rocketmq.namesrv}"></property> <property name="retryAnotherBrokerWhenNotStoreOK" value="${rocketmq.retryAnotherBrokerWhenNotStoreOK}"></property> </bean> </beans>
9.rocketmq.consumer.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 每个模块只需要配置一个 consumer --> <bean id="consumer" class="com.yun.base.mq.service.impl.RocketMqConsumerImpl" init-method="afterPropertiesSet" destroy-method="destroy"> <property name="namesrvAddr" value="${rocketmq.namesrv}" /> <property name="consumerGroup" value="LOANSYS" /> <!-- 消费方式:BROADCASTING 广播消费,CLUSTERING 集群消费 --> <property name="messageModel" value="BROADCASTING" /> <!-- CONCURRENTLY 无序消费 ORDERLY 有序消费 --> <property name="messageListener" value="CONCURRENTLY" /> <property name="handlermap"> <map> <entry key="loanRequest"> <bean class="com.yun.base.mq.test.LoanRequestConsumer"> <property name="classTypeName" value="com.yun.base.mq.test.LoanRequest"></property> </bean> </entry> </map> </property> </bean> </beans>
10.消费端实现
package com.yun.base.mq.test; import com.yun.base.mq.client.entity.MQEntity; import com.yun.base.mq.service.impl.AbstractConsumer; public class LoanRequestConsumer extends AbstractConsumer { @Override public void execute(MQEntity entity) { System.out.println("LoanRequestConsumer 消费消息"); System.out.println(entity.toString()); } }
11. 测试
package com.yun.base.mq.test; import java.math.BigDecimal; import java.util.Date; import com.yun.base.mq.client.entity.MQEntity; /** * 贷款申请 * @author Administrator */ public class LoanRequest extends MQEntity{ private static final long serialVersionUID = 5339647958993664240L; /** 贷款申请编号,前段生存的唯一编号 */ private String applyNo; /** 额度编号 */ private String creditNo; /**贷款人员工号 */ private String employeeNo; /** 贷款金额 */ private BigDecimal payamt; /**还款方式 * 0:多次还本、分次付息 1:分次还本、分次付息 2:等额本金 **/ private String repayMode; /**还款期限 1个月、3个月、6个月、9个月、12个月、15个月、18个月、24个月; * 1,3, 6 * */ private String oprTerm; /**用途*/ private String lendPurpose; //申请日期 private Date applyDate; //贷款卡号 private String loanCardNo; //开户行 private String bankName; //联行行号 private String bankCode ; public String getBankName() { return bankName; } public void setBankName(String bankName) { this.bankName = bankName; } public String getBankCode() { return bankCode; } public void setBankCode(String bankCode) { this.bankCode = bankCode; } public String getEmployeeNo() { return employeeNo; } public void setEmployeeNo(String employeeNo) { this.employeeNo = employeeNo; } public BigDecimal getPayamt() { return payamt; } public void setPayamt(BigDecimal payamt) { this.payamt = payamt; } public String getRepayMode() { return repayMode; } public void setRepayMode(String repayMode) { this.repayMode = repayMode; } public String getOprTerm() { return oprTerm; } public void setOprTerm(String oprTerm) { this.oprTerm = oprTerm; } public String getLendPurpose() { return lendPurpose; } public void setLendPurpose(String lendPurpose) { this.lendPurpose = lendPurpose; } public String getLoanCardNo() { return loanCardNo; } public void setLoanCardNo(String loanCardNo) { this.loanCardNo = loanCardNo; } public String getApplyNo() { return applyNo; } public void setApplyNo(String applyNo) { this.applyNo = applyNo; } public String getCreditNo() { return creditNo; } public void setCreditNo(String creditNo) { this.creditNo = creditNo; } public Date getApplyDate() { return applyDate; } public void setApplyDate(Date applyDate) { this.applyDate = applyDate; } }
package com.yun.base.mq.test; import java.math.BigDecimal; import java.util.Date; import org.junit.Before; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.yun.base.mq.service.IProducer; public class ProducerTest { private IProducer producer = null; @Before public void befor() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath*:applicationContext.xml"); producer = (IProducer) context.getBean("producer"); } @Test public void testSendMq() { LoanRequest loanRequest= new LoanRequest(); loanRequest.setApplyNo("loan00005432231"); loanRequest.setCreditNo("credit0000001"); loanRequest.setEmployeeNo("test1"); loanRequest.setLendPurpose("test1"); loanRequest.setLoanCardNo("362226197403220017"); loanRequest.setBankCode(null); loanRequest.setBankName(null); loanRequest.setOprTerm("12"); loanRequest.setPayamt(new BigDecimal(100000)); loanRequest.setRepayMode("0"); //0 1 2 loanRequest.setApplyDate(new Date()); producer.send("loanRequest", loanRequest); } }
今天集成的时候发现重启项目的时候会重新消费之前已经消费过的消息,是由于版本不一致导致的,我的服务器安装的是4.2.0,客户端却是用的3.2.6所以会出现这种情况,需要保证安装的版本是 3.2.6,客户端 rocketmq-client 也是 3.2.6 才行。