新增自定义发送mq接口,礼物mq重构
This commit is contained in:
parent
72f9cf4255
commit
3b5dfb54cc
@ -7,7 +7,11 @@ import org.springframework.context.annotation.ComponentScan;
|
||||
* @author pengliang on 2023/3/20
|
||||
*/
|
||||
@Slf4j
|
||||
@ComponentScan("com.red.circle.mq.rocket.business.producer")
|
||||
@ComponentScan({
|
||||
"com.red.circle.mq.rocket.business.producer",
|
||||
"com.red.circle.mq.rocket.business.config",
|
||||
"com.red.circle.mq.rocket.business.service"
|
||||
})
|
||||
public class CommonBusinessRocketMqAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,75 @@
|
||||
package com.red.circle.mq.rocket.business.config;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* MQ 消息发送线程池配置.
|
||||
*
|
||||
* @author Leo
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class MqThreadPoolConfig {
|
||||
|
||||
@Bean(name = "mqCallbackExecutor", destroyMethod = "shutdown")
|
||||
public ThreadPoolExecutor mqCallbackExecutor() {
|
||||
|
||||
int corePoolSize = 50; // 核心线程
|
||||
int maximumPoolSize = 300; // 最大线程
|
||||
long keepAliveTime = 60L;
|
||||
int queueCapacity = 10000; // 增大队列
|
||||
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(
|
||||
corePoolSize,
|
||||
maximumPoolSize,
|
||||
keepAliveTime,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(queueCapacity),
|
||||
new MqCallbackThreadFactory(),
|
||||
new MqCallbackRejectedHandler()
|
||||
);
|
||||
|
||||
executor.allowCoreThreadTimeOut(false);
|
||||
|
||||
log.info("MQ 回调线程池初始化完成 - 核心线程:{}, 最大线程:{}, 队列容量:{}",
|
||||
corePoolSize, maximumPoolSize, queueCapacity);
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
private static class MqCallbackThreadFactory implements ThreadFactory {
|
||||
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
private final String namePrefix = "mq-callback-";
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread thread = new Thread(r, namePrefix + threadNumber.getAndIncrement());
|
||||
thread.setDaemon(false);
|
||||
thread.setPriority(Thread.NORM_PRIORITY);
|
||||
return thread;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MqCallbackRejectedHandler implements RejectedExecutionHandler {
|
||||
|
||||
@Override
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
|
||||
log.error("MQ 回调线程池队列已满!当前线程数:{}, 队列大小:{}, 任务被拒绝",
|
||||
executor.getPoolSize(),
|
||||
executor.getQueue().size());
|
||||
|
||||
if (!executor.isShutdown()) {
|
||||
r.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,9 @@ package com.red.circle.mq.rocket.business.producer;
|
||||
|
||||
import com.red.circle.component.mq.MessageEvent;
|
||||
import com.red.circle.component.mq.service.FixedTopic;
|
||||
import com.red.circle.component.mq.service.SenderService;
|
||||
import com.red.circle.mq.business.model.event.gift.GiveAwayGiftBatchEvent;
|
||||
import com.red.circle.mq.business.model.event.gift.OfflineProcessGiftEvent;
|
||||
import com.red.circle.mq.rocket.business.service.MessageSenderService;
|
||||
import com.red.circle.mq.rocket.business.streams.GiveGiftSink;
|
||||
import com.red.circle.mq.rocket.business.streams.OfflineProcessGiftSink;
|
||||
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
|
||||
@ -20,7 +20,7 @@ import org.springframework.stereotype.Component;
|
||||
@RequiredArgsConstructor
|
||||
public class GiftMqMessage {
|
||||
|
||||
private final SenderService senderService;
|
||||
private final MessageSenderService senderService;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,155 @@
|
||||
package com.red.circle.mq.rocket.business.service;
|
||||
|
||||
import com.red.circle.component.mq.props.RocketMQProperties;
|
||||
import com.red.circle.component.mq.service.*;
|
||||
import com.red.circle.tool.core.collection.CollectionUtils;
|
||||
import com.red.circle.tool.core.text.StringUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.client.apis.ClientException;
|
||||
import org.apache.rocketmq.client.apis.ClientServiceProvider;
|
||||
import org.apache.rocketmq.client.apis.message.Message;
|
||||
import org.apache.rocketmq.client.apis.message.MessageBuilder;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 安全的 RocketMQ Producer.
|
||||
*
|
||||
* @author Leo
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ManagedRocketMQProducer implements Producer {
|
||||
|
||||
private RocketMQProperties rocketMQProperties;
|
||||
private final Map<String, RocketMQProducerMapping> producerMapping = new ConcurrentHashMap<>();
|
||||
private final ThreadPoolExecutor mqCallbackExecutor;
|
||||
|
||||
public ManagedRocketMQProducer(
|
||||
RocketMQProperties rocketMQProperties,
|
||||
@Qualifier("mqCallbackExecutor") ThreadPoolExecutor mqCallbackExecutor) {
|
||||
this.rocketMQProperties = rocketMQProperties;
|
||||
this.mqCallbackExecutor = mqCallbackExecutor;
|
||||
}
|
||||
|
||||
private Message convertMessage(ProducerMessage producerMessage) {
|
||||
if (Objects.isNull(producerMessage)) {
|
||||
throw new IllegalArgumentException("producerMessage is null.");
|
||||
}
|
||||
|
||||
RocketMQProducerMapping mapping = getRocketMQProducerMapping(producerMessage.getTopic());
|
||||
MessageBuilder builder = mapping.getProvider()
|
||||
.newMessageBuilder()
|
||||
.setTopic(producerMessage.getTopic())
|
||||
.setKeys(producerMessage.getKeys())
|
||||
.setBody(producerMessage.getBody());
|
||||
|
||||
if (Objects.nonNull(producerMessage.getDeliveryTimestamp())) {
|
||||
builder.setDeliveryTimestamp(producerMessage.getDeliveryTimestamp());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(producerMessage.getTag())) {
|
||||
builder.setTag(producerMessage.getTag());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(producerMessage.getMessageGroup())) {
|
||||
builder.setMessageGroup(producerMessage.getMessageGroup());
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private RocketMQProducerMapping getRocketMQProducerMapping(String topic) {
|
||||
RocketMQProducerMapping mapping = producerMapping.get(topic);
|
||||
if (Objects.isNull(mapping)) {
|
||||
throw new IllegalArgumentException("Not Topic RocketMQProducerMapping.");
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
List<String> failedTopics = new ArrayList<>();
|
||||
|
||||
rocketMQProperties.getTopics().forEach((topic, value) -> {
|
||||
try {
|
||||
ClientServiceProvider provider = ClientServiceProvider.loadService();
|
||||
org.apache.rocketmq.client.apis.producer.Producer producer = provider
|
||||
.newProducerBuilder()
|
||||
.setClientConfiguration(rocketMQProperties.newClientConfiguration())
|
||||
.setTopics(new String[]{topic})
|
||||
.build();
|
||||
producerMapping.put(topic, new RocketMQProducerMapping(provider, producer));
|
||||
log.info("MQ Producer 初始化成功: topic={}", topic);
|
||||
} catch (Throwable e) {
|
||||
log.error("MQ Producer 初始化失败: topic={}", topic, e);
|
||||
failedTopics.add(topic);
|
||||
}
|
||||
});
|
||||
|
||||
if (!failedTopics.isEmpty()) {
|
||||
throw new RuntimeException("MQ Producer 初始化失败的 topic: " + failedTopics);
|
||||
}
|
||||
}
|
||||
|
||||
public String send(ProducerMessage message) {
|
||||
org.apache.rocketmq.client.apis.producer.Producer producer =
|
||||
getRocketMQProducerMapping(message.getTopic()).getProducer();
|
||||
|
||||
if (Objects.isNull(producer)) {
|
||||
throw new IllegalArgumentException("Topic=" + message.getTopic() + " config error.");
|
||||
}
|
||||
|
||||
try {
|
||||
return producer.send(convertMessage(message)).getMessageId().toString();
|
||||
} catch (ClientException e) {
|
||||
log.error("MQ 同步发送失败, topic:{}, tag:{}",
|
||||
message.getTopic(), message.getTag(), e);
|
||||
throw new RuntimeException("MQ send failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void sendAsync(ProducerMessage message, SendCallback callback) {
|
||||
org.apache.rocketmq.client.apis.producer.Producer producer =
|
||||
getRocketMQProducerMapping(message.getTopic()).getProducer();
|
||||
|
||||
if (Objects.isNull(producer)) {
|
||||
throw new IllegalArgumentException("Topic=" + message.getTopic() + " config error.");
|
||||
}
|
||||
|
||||
CompletableFuture<org.apache.rocketmq.client.apis.producer.SendReceipt> future =
|
||||
producer.sendAsync(convertMessage(message));
|
||||
|
||||
future.whenCompleteAsync((result, throwable) -> {
|
||||
if (throwable != null) {
|
||||
callback.onFail(throwable);
|
||||
} else if (Objects.isNull(result.getMessageId())) {
|
||||
callback.onFail(new RuntimeException("message Id is null."));
|
||||
} else {
|
||||
callback.onSuccess(result.getMessageId().toString(), message);
|
||||
}
|
||||
}, mqCallbackExecutor);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
if (CollectionUtils.isNotEmpty(producerMapping)) {
|
||||
producerMapping.values().forEach(mapping -> {
|
||||
mapping.close();
|
||||
log.info("producer [{}] close.", mapping.getProducer());
|
||||
});
|
||||
producerMapping.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
package com.red.circle.mq.rocket.business.service;
|
||||
|
||||
import com.red.circle.component.mq.MessageEvent;
|
||||
import com.red.circle.component.mq.config.RocketMqFailMessage;
|
||||
import com.red.circle.component.mq.config.RocketMqSenderFailService;
|
||||
import com.red.circle.component.mq.service.FixedTopic;
|
||||
import com.red.circle.component.mq.service.ProducerMessage;
|
||||
import com.red.circle.component.mq.service.SendCallback;
|
||||
import com.red.circle.tool.core.date.DateUtils;
|
||||
import com.red.circle.tool.core.text.StringUtils;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 安全的 MQ 发送服务.
|
||||
*
|
||||
* @author Leo
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MessageSenderService {
|
||||
|
||||
private final ManagedRocketMQProducer managedRocketMQProducer;
|
||||
private final RocketMqSenderFailService rocketMqSenderFailService;
|
||||
|
||||
public MessageSenderService(
|
||||
ManagedRocketMQProducer managedRocketMQProducer,
|
||||
RocketMqSenderFailService rocketMqSenderFailService) {
|
||||
this.managedRocketMQProducer = managedRocketMQProducer;
|
||||
this.rocketMqSenderFailService = rocketMqSenderFailService;
|
||||
}
|
||||
|
||||
public void sendEventMessage(final MessageEvent event) {
|
||||
this.sendEventMessage(event, new SendCallback() {
|
||||
@Override
|
||||
public void onSuccess(String messageId, ProducerMessage message) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(Throwable throwable) {
|
||||
log.error("SafeSenderService.sendEventMessage onFail: {},{}",
|
||||
throwable.getMessage(), event.toJson());
|
||||
rocketMqSenderFailService.add(RocketMqFailMessage.builder()
|
||||
.id(event.getConsumeId())
|
||||
.topic(event.getTopic())
|
||||
.tag(event.getTag())
|
||||
.errorMsg(throwable.getMessage())
|
||||
.body(event.getBody())
|
||||
.startDeliverTime(event.getStartDeliverTime())
|
||||
.process(Boolean.FALSE)
|
||||
.createTime(DateUtils.now())
|
||||
.updateTime(DateUtils.now())
|
||||
.expiredTime(DateUtils.nowPlusDays(7))
|
||||
.build());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void sendEventMessage(MessageEvent event, SendCallback sendCallback) {
|
||||
checkEvent(event);
|
||||
checkSetDefaultTopic(event);
|
||||
|
||||
ProducerMessage message = convertMessage(event);
|
||||
managedRocketMQProducer.sendAsync(message, sendCallback);
|
||||
}
|
||||
|
||||
private void checkSetDefaultTopic(MessageEvent event) {
|
||||
if (!StringUtils.isNotBlank(event.getTopic())) {
|
||||
if (event.checkStartDeliverTimeMessage()) {
|
||||
event.setTopic(FixedTopic.RC_DEFAULT_APP_DELAYED.getTopic());
|
||||
} else if (event.checkOrderMessage()) {
|
||||
event.setTopic(FixedTopic.RC_DEFAULT_APP_ORDER.getTopic());
|
||||
} else {
|
||||
event.setTopic(FixedTopic.RC_DEFAULT_APP_ORDINARY.getTopic());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ProducerMessage convertMessage(MessageEvent event) {
|
||||
return ProducerMessage.builder()
|
||||
.topic(event.getTopic())
|
||||
.tag(event.getTag())
|
||||
.messageGroup(event.getMessageGroup())
|
||||
.keys(new String[]{event.messageKey()})
|
||||
.body(event.toBytes(StandardCharsets.UTF_8))
|
||||
.deliveryTimestamp(event.checkStartDeliverTimeMessage() ? event.getStartDeliverTime() : null)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void checkEvent(MessageEvent event) {
|
||||
if (Objects.isNull(event)) {
|
||||
throw new IllegalArgumentException("SafeSenderService.sendObject event is null.");
|
||||
}
|
||||
if (StringUtils.isBlank(event.getTag())) {
|
||||
throw new IllegalArgumentException("SafeSenderService.sendObject tag isBlank.");
|
||||
}
|
||||
if (StringUtils.isBlank(event.getBody())) {
|
||||
throw new IllegalArgumentException("SafeSenderService.sendObject body isBlank.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user