很长时间没有分享过学习心得了,看了下发布记录,最后一篇文章的时间都在2020-12-10年了,今天抽时间整理下一个很早就想整理的技术分享。顺便说句题外话,因为我一直没时间整理,再加上开发的小伙伴对Mq的理解不够,我开掉了好几个处理这个事情的开发小伙伴,所以我希望这篇文章能对大家带来一点帮助。
Mq(消息队列)做为一个消峰工具而常被使用,我们常用的Mq主要分为以下四种:
今天主要是聊聊RabbitMq,业务场景上选择RabbitMq的原因有很多,今天就不细说了。今天主要是说下如何动态创建队列,并实现动态监听的的方法。
做为一个CRM-SAAS平台,每天平台会进入大量的客户信息,那么我们需要用高效的方式把这些数据及时的发给销售,那么这里需要考虑以下几个问题:
基于上述考虑,我们选择RabbitMq来实现这个方案,既然是不同的队列消费不同的数据,那么第一步就是考虑如何动态创建队列,因为这里还要设定一个人为可控,也就是人员可以管理,所以比然后伴随着队列的删除和重建。
@Bean
public Queue syncCdrQueue(){
return new Queue(CrmMqConstant.SYNC_CDR_TO_CRM_Q,true,false,false);
}
Channel channelForm = connectionFactory().createConnection().createChannel(false);
channelForm.queueDeclare(nameForm, true, false, false, null);
rabbitAdmin.declareQueue(queue);
rabbitAdmin.declareExchange(fanoutExchange);
rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(fanoutExchange));
从创建队列的灵活度来说,肯定是依次减弱的:
private final String QUEUE_NAME="crm.websocket."+ IPUtils.getLocalhostIp();
@Bean
public Queue queue(){
return new Queue(QUEUE_NAME,true,false,false);
}
//监听
@RabbitListener(queues = "#{queue.name}")
@Bean
public List mqMsgQueues() throws IOException {
List queueNames = new ArrayList();
List
public void createMqQueue(String queueName,String exName,String rk,String type){
Properties properties = rabbitAdmin.getQueueProperties(queueName);
if(properties==null) {
Queue queue = new Queue(queueName, true, false, false, null);
if(BuiltinExchangeType.DIRECT.getType().equals(type)) {
DirectExchange directExchange = new DirectExchange(exName);
rabbitAdmin.declareQueue(queue);
rabbitAdmin.declareExchange(directExchange);
rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(directExchange).with(rk));
}else if(BuiltinExchangeType.FANOUT.getType().equals(type)){
FanoutExchange fanoutExchange = new FanoutExchange(exName);
rabbitAdmin.declareQueue(queue);
rabbitAdmin.declareExchange(fanoutExchange);
rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(fanoutExchange));
}else{
TopicExchange topicExchange = new TopicExchange(exName);
rabbitAdmin.declareQueue(queue);
rabbitAdmin.declareExchange(topicExchange);
rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(topicExchange).with(rk));
}
}
}
我们知道如何动态创建队列之后,接下来我们想办法解决动态消费监听得事情就行:
RabbitMq得抽象监听类是:AbstractMessageListenerContainer,他下面有三个实现类,这里就使用SimpleMessageListenerContainer类来进行简单得说明。
初始化队列,存储在静态缓存,用不同得bean来加载监听:
private List
那么这种方式呢确实能动态监听不同得队列和消费,但是因为利用得是Bean得初始化得方式,所以每次变更需要加载得队列内容就得重新加载Bean,也就是需要重启服务。
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(rabbitConfig.connectionFactory());
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
container.setMaxConcurrentConsumers(8);
container.setConcurrentConsumers(5);
container.setPrefetchCount(10);
// 查询有多少分配引擎,每个分配引擎一个队列
List engineList = autoAssignEngineService.getAllAutoAssignEngine();
if (engineList != null && engineList.size() > 0) {
for(AutoAssignEngine engine : engineList) {
mqService.addNewListener(engine.getOrgId(),engine.getSemAdType(),"1",container);
mqService.addNewListener(engine.getOrgId(),engine.getSemAdType(),"2",container);
}
}
return container;
}
public Boolean addNewListener(String orgId,String semType,String userLevel,SimpleMessageListenerContainer container ){
String queueNames=queueName + "." + orgId+"."+semType+"."+userLevel;
container.addQueueNames(queueNames);
container.setMessageListener(new ChannelAwareMessageListener() {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
}
});
return true;
}
问题1:这里再接收消息(onMessage方法内)得时候不要用方法传参,会出现并发问题。
解决方式1:
String receiveQueueName = message.getMessageProperties().getConsumerQueue();
队列名称解析获取,本人使用。
解决方式2:
使用final变量重新接收传参,不过这个有待测试,不一定又用。
问题2:这不是还是在Bean初始化得时候加载得嘛,如果想要在服务启动之后再增加监听如何处理
我们知道如何创建队列和监听之后就开始解决问题2。
需求:变更现有队列。
转化需求为:删除现有队列和监听,新建新得队列并增加监听
问题:推送和消费不再统一服务。
解决方式:暴露接口,利用http请求实现同步。
代码实现:
public Boolean updateListener(String orgId,String semType,String oldOrg){
logger.info("================================消费端开始处理");
String newFirstQueueName = queueName+"."+orgId+"."+semType+"."+1;
String newFirstRk = routingKey+"."+orgId+"."+semType+"."+1;
String newSecondQueueName = queueName+"."+orgId+"."+semType+"."+2;
String newSecondRk = routingKey+"."+orgId+"."+semType+"."+2;
createMqQueue(newFirstQueueName,topicExchange,newFirstRk, BuiltinExchangeType.TOPIC.getType());
createMqQueue(newSecondQueueName,topicExchange,newSecondRk, BuiltinExchangeType.TOPIC.getType());
logger.info("================================创建队列");
SimpleMessageListenerContainer container = SpringCtxUtils.getBean(SimpleMessageListenerContainer.class);
String oneQueueNames=queueName + "." + orgId+"."+semType+"."+1;
String twoQueueNames=queueName + "." + orgId+"."+semType+"."+2;
if(!"NO".equals(oldOrg)) {
String oneOldQueueNames = queueName + "." + oldOrg + "." + semType + "." + 1;
String twoOldQueueNames = queueName + "." + oldOrg + "." + semType + "." + 2;
container.removeQueueNames(oneOldQueueNames);
container.removeQueueNames(twoOldQueueNames);
logger.info("================================删除监听成功");
}
container.addQueueNames(oneQueueNames);
container.addQueueNames(twoQueueNames);
logger.info("================================添加监听成功");
container.setMessageListener(new ChannelAwareMessageListener() {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
}
});
return true;
}
public void createMqQueue(String queueName,String exName,String rk,String type){
Properties properties = rabbitAdmin.getQueueProperties(queueName);
if(properties==null) {
Queue queue = new Queue(queueName, true, false, false, null);
if(BuiltinExchangeType.DIRECT.getType().equals(type)) {
DirectExchange directExchange = new DirectExchange(exName);
rabbitAdmin.declareQueue(queue);
rabbitAdmin.declareExchange(directExchange);
rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(directExchange).with(rk));
}else if(BuiltinExchangeType.FANOUT.getType().equals(type)){
FanoutExchange fanoutExchange = new FanoutExchange(exName);
rabbitAdmin.declareQueue(queue);
rabbitAdmin.declareExchange(fanoutExchange);
rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(fanoutExchange));
}else{
TopicExchange topicExchange = new TopicExchange(exName);
rabbitAdmin.declareQueue(queue);
rabbitAdmin.declareExchange(topicExchange);
rabbitAdmin.declareBinding(BindingBuilder.bind(queue).to(topicExchange).with(rk));
}
}
}
暴露接口:
@GetMapping("/add-listener/{orgId}/{semType}/{oldOrg}")
public ComResponse addListener(@PathVariable("orgId") String orgId,@PathVariable("semType") String semType,@PathVariable("oldOrg") String oldOrg){
mqService.updateListener(orgId,semType,oldOrg);
return ComResponse.successResponse();
}
注意:执行顺序,创建新队列,删除监听,添加监听
//添加新得监听
String requestUrl = consumerUrl+"/"+newOrg+"/"+semType+"/"+oldOrgId;
String result = restTemplateService.getWithNoParams(requestUrl,String.class);
log.info("请求结束:{}",result);
if(!"NO".equals(oldOrgId)) {
String firstQueueName = queue + "." + oldOrgId + "." + semType + "." + 1;
String secondQueueName = queue + "." + oldOrgId + "." + semType + "." + 2;
mqService.deleteMqQueue(firstQueueName);
mqService.deleteMqQueue(secondQueueName);
log.info("删除队列结束");
}
//新增新的的队列
String newFirstQueueName = queue+"."+newOrg+"."+semType+"."+1;
String newFirstRk = routingKey+"."+newOrg+"."+semType+"."+1;
String newSecondQueueName = queue+"."+newOrg+"."+semType+"."+2;
String newSecondRk = routingKey+"."+newOrg+"."+semType+"."+2;
mqService.createMqQueue(newFirstQueueName,topicExchange,newFirstRk, BuiltinExchangeType.TOPIC.getType());
mqService.createMqQueue(newSecondQueueName,topicExchange,newSecondRk, BuiltinExchangeType.TOPIC.getType());
log.info("添加队列结束");
注意:执行顺序:变更监听,删除队列,添加新得队列
到这里基本上就实现了动态创建队列和动态监听。大家如果有什么不太明白得可以留言,抽时间整理得,所以写得比较草,大家凑合着看把。