- 概述
- 集群部署架构
- 线上集群指标
- 集群使用流程
- 基本规则
- 客户端配置
- 主要参数
- Java客户端配置与代码实现
一、概述
- ActiveMQ集群为贝壳找房(前身为链家网)所有业务线产品提供可靠稳定高速消息中间件服务,帮助系统间实现异步通信和模块解耦。长期时间稳定为SE、链家网和搜索团队提供服务。
- 笔者所在2018年初,线上持续维护的ActiveMQ集群有两个,均采用一主三从方式部署。
- MQ集群当时为贝壳大多数团队服务,使用方持续增长。当时对于客户端使用的限制较少,集群的连接数,可用队列缓存都是有限的,所以建议大家在线上使用时,务必谨慎规范!
二、集群部署架构
部署说明
- 集群为“一主三从”架构,LevelDB用于持久化消息的缓存服务。
- 客户端使用Failover机制连接集群。若集群主节点宕机,ZK将选择出新的主节点,持久化消息不会丢失,非持久化消息丢失。
三、线上集群指标
- 单集群允许总连接数小于1000。
- 单集群,消息在非持久化状态下性能为,生产者30000 QPS,消费者12000 QPS。
- 单集群,消息在持久化状态下性能为,生产者5000 QPS,消费者4000 QPS。
- 单个消息,消息体过大时,对发送速率有明显影响。
四、集群使用流程
1 发送邮件给MQ管理员,注明如下信息:
- 业务使用场景,生产者和消费者系统说明
- 是否需要持久化消息
- 发送方/接收方模式(点对点,主题订阅)
- 发送方QPS均值、峰值预估
- 发送方,接收方的连接数均值、峰值预估
2 MQ管理员会评估需求,确认现有资源能否满足。如果可以满足,将提供线上集群连接配置以及线下测试环境集群配置。
3 业务方根据自身系统特点,选择相应开发库(比如Java、PHP已有成熟库和使用方案)。在线下联调测试。
4 告知MQ管理员业务上线时间,MQ管理员负责监控线上服务是否正常运行。
5 在AMQ业务接入方登记页,填写自己的服务信息。
五、基本规则
1.客户端配置
- MQ允许持久化消息,和非持久化消息。持久化消息会在集群所有节点持久化存储,集群保证不丢消息,但对性能影响较大。消息量不大时,或者消息非常需要可靠传达时,可用持久化方式。
- 消息体内容在2KB以内时,性能较好。建议控制消息体长度,消息体长度超过1MB时,谨慎使用MQ。
- 与任何通信协议相同,生产者/消费者和MQ建立连接和释放连接时会消耗较多资源。确保自己的客户端每次subscribe时,尽量处理完queue所有消息再退出!一定要释放连接!
- MQ可工作在两种方式:点对点、主题订阅模型。请根据自己的业务特点选择合适的模式。
2.主要参数
六、Java客户端配置与代码实现
- spring与activemq 的maven依赖
org.apache.activemqactivemq-core5.7.0org.apache.activemqactivemq-pool5.12.0org.apache.xbeanxbean-spring3.16org.springframeworkspring-jms4.2.1.RELEASE
- 属性配置文件 config-activemq.properties
#######activemq#######实际项目中属性值分多环境配置,为方便说明下面列出#activemq.broker.url=${activemq.broker.url}activemq.broker.url=failover:(tcp://172.30.xx.xx:61616, tcp://172.30.xx.xx:61616, tcp://172.30.xx.xx:61616) ?initialReconnectDelay=1000 &maxReconnectDelay=10000 &jms.prefetchPolicy.all=1000&timeout=3000feed.queue.subject=FEED.QUEUE.DEV1feed.topic.subject=FEED.TOPIC.DEVmobile.queue.subject=CONSUMER.MESSAGE.QUEUE.FOLIOqueue.interlink_customer_prod=interlink_customer_prodqueue.interlink_house_prod=interlink_house_prodqueue.check_customer_prod=check_customer_prodqueue.redstar_task=HONGXING.TASK.SCHEDULE.DOCKINGqueue.callRecord_customer_prod=callRecord_customer_prod
- mq总(概括性)配置文件spring-activemq.xml
<?xml version="1.0" encoding="UTF-8"?>
- 生产者基本配置文件 spring-producer-basic.xml
<?xml version="1.0" encoding="UTF-8"?>
- 生产者实例配置spring-producer-callrecord.xml
<?xml version="1.0" encoding="UTF-8"?>
其他生产者配置,如spring-producer-redstar.xml,类似spring-producer-callrecord.xml配置,不再啰嗦列举。
- 生产者java代码实例CallRecordMessageProducer.java
package com.lianjia.customer.message.producer;import com.alibaba.fastjson.JSONObject;import com.lianjia.blacklist.api.dto.PhoneHasMarkedDTO;import com.lianjia.common.datasource.DataRegion;import com.lianjia.common.log.RlaLogUtil;import com.lianjia.customer.log.CustomerRlaLogCode;import com.lianjia.customer.service.constant.Constants;import org.apache.commons.collections.CollectionUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.jms.core.JmsTemplate;import org.springframework.stereotype.Service;import javax.annotation.Resource;import javax.jms.Destination;import java.util.*;@Servicepublic class CallRecordMessageProducer {protected Logger logger = LoggerFactory.getLogger(this.getClass());@ResourceJmsTemplate callRecordJmsTemplate;@ResourceDestination callRecordProductDestination;public void sendMessageForDealCallRecord(final String regionStr, final Long brokerUid, final Map phoneHasMarkedDTOMap, final Collection phones,final Boolean isEncrypt) {try {if(CollectionUtils.isNotEmpty(phones)){Constants.THREAD_POOL_MQ.execute(new Runnable() {@Overridepublic void run() {long beginTime = System.currentTimeMillis();JSONObject jo = new JSONObject();jo.put("brokerUid", brokerUid);jo.put("phones", phones);jo.put("regionStr", regionStr);jo.put("phoneHasMarkedDTOMap", phoneHasMarkedDTOMap);jo.put("isEncrypt", isEncrypt);//jo.put("businessId", businessId);callRecordJmsTemplate.convertAndSend(callRecordProductDestination, jo.toJSONString());logger.info("==========sendCallRecordMessage:{},threadId:{},brokerUid:{},phones:{},time:{}",jo, Thread.currentThread().getId(), regionStr, brokerUid, phones, System.currentTimeMillis()- beginTime);}});}} catch (Exception e) {RlaLogUtil.log(logger, e, CustomerRlaLogCode.errorMessageSendCheck, brokerUid, phones);}}}
- 消费者基本配置文件 spring-consumer-basic.xml
<?xml version="1.0" encoding="UTF-8"?>
- 消费者实例配置 spring-consumer-callrecord.xml
<?xml version="1.0" encoding="UTF-8"?>
其他消费者配置,如spring-consumer-resblock.xml参考上面
- 消费者实例java代码实现 CallRecordMessageConsumer.java
package com.lianjia.customer.message.consumer;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import com.lianjia.common.datasource.DataRegion;import com.lianjia.common.datasource.impl.CustomerRegion;import com.lianjia.common.datasource.transaction.DataRegionsTransactional;import com.lianjia.common.log.RlaLogUtil;import com.lianjia.customer.log.CustomerRlaLogCode;import com.lianjia.customer.service.facade.DataFacade;import com.lianjia.customer.service.facade.LogicFacade;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.jms.support.converter.MessageConverter;import org.springframework.stereotype.Service;import javax.annotation.Resource;import javax.jms.Message;import javax.jms.MessageListener;import java.util.*;@Servicepublic class CallRecordMessageConsumer implements MessageListener { protected Logger logger = LoggerFactory.getLogger(this.getClass()); @Resource MessageConverter jmsSimpleMessageConverter; @Resource private LogicFacade logicFacade; @Resource private DataFacade dataFacade; @DataRegionsTransactional public void onMessage(Message message) { try { String objMessage = (String) this.jmsSimpleMessageConverter.fromMessage(message); RlaLogUtil.log(logger, CustomerRlaLogCode.infoCallRecordMessageAccepted, objMessage); JSONObject jo = JSON.parseObject(objMessage); String regionStr = jo.getString("regionStr"); Long brokerUid = jo.getLong("brokerUid"); Boolean isEncrypt = jo.getBoolean("isEncrypt"); Object phoneHasMarkedDTOMapObject = jo.get("phoneHasMarkedDTOMap"); JSONArray phoneArr = jo.getJSONArray("phones"); if (phoneArr == null || phoneArr.size() == 0) { logger.warn("phones is empty: {}", phoneArr); return; } Collection phones = new ArrayList(); for (int i = 0; i < phoneArr.size(); i++) { phones.add((String) phoneArr.get(i)); } DataRegion region = CustomerRegion.getByRegionName(regionStr); //同步黑名单 //TODO 代码省略 //同步客源潜客 //TODO 代码省略 } catch (Exception e) { RlaLogUtil.log(logger, e, CustomerRlaLogCode.errorCallRecordMessageAccepted); try { Thread.sleep(1000); } catch (InterruptedException e1) { RlaLogUtil.log(logger, e1, CustomerRlaLogCode.errorInternalServer); } throw new RuntimeException(e); } }}
按上面配置,生产、消费mq消息demo已经可用了。
欢迎大家关注我,后面我会分享更多精彩的文章给大家。