对接不同消息队列抽象提取

背景:

公司对外输出服务 希望对消息队列抽象 在不修改业务代码的情况下 替换MQ 例如 kafka 替换 阿里云 RocketMQ

MQ分类整理


kafka :Apache 自有协议 性能 十万级 消费消息 只支持 pull
RocketMQ :Ali 自有协议 性能 十万级 消费消息 支持 pull/push
rabbitmq 或者 activeMQ:支持AMQP协议 性能 万级 消费消息 支持 pull/push
Redis:Redis的 MQ 实现是使用 lists (队列)数据类型 使用 lpush 入队和 brpop 阻塞出队列的方式 非典型简单的消息队列

MQ 发送消息抽象:

所有的MQ发送就是调用 可以直接抽象

public class OnsSendServiceImpl implements MQSendService {


    private Producer onsProducer;
    private String name;
    private String groupId;
    private String topic;
    private String tag;
    /**
     * 发送mq消息
     *
     * @param msgId   消息id
     * @param message 默认统一使用json
     */
    @Override
    public void sendMqMessage(String msgId, String message) {
        log.info("send ons ,topic:{},tags:{},model:{},key:{}", topic, message, msgId);
        try {
            Message msg = new Message(topic, tag, msgId, message.getBytes(Charset.forName("utf-8")));
            SendResult sendResult = onsProducer.send(msg);
            log.info("ons消息发送成功。topic:{},,messageId:{},resultMessageId:{},key:{}", topic, msg.getMsgID(),
                    sendResult.getMessageId(), msgId);
        } catch (Exception e) {
            log.error("ons 信息发送失败,topic:" + topic + ",key:" + msgId + ",e=", e);
        }

    }

##(重点来了) MQ 消费消息抽象:
参考spring-boot-data-kafka的 @KafkaListener 我们可以抽象一个 类似 @MqConsumer 注解在对应的消费方法上
扫描注解得到方法代理:MqListener 然后封装组合统一使用pull的方式生成 Consumer

public class MqListener {

    private Method method;

    private Object bean;

    private MqConsumer mqConsumer;

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MqConsumer {
    /**
     * 作为Listener的名字
     *
     * @return
     */
    String name();

    String topic();

    String tag() default "*";

    String servers();

    String secretKey() default "";

    String accessKey() default "";
}
private synchronized void initMqConsumer(MqKafkaListener mqKafkaListener) {
        if (kafkaConsumerMap.get(mqKafkaListener.getKafkaConsumer().name()) != null) {
            logger.warn(mqKafkaListener.getKafkaConsumer().name() + "kafkaConsumer 已经注册过,忽略");
            return;
        }
        String group = this.placeHolderResolver.resolveStringValue(mqKafkaListener.getKafkaConsumer().group());
        String topic = this.placeHolderResolver.resolveStringValue(mqKafkaListener.getKafkaConsumer().topic());


        String consumerName = mqKafkaListener.getKafkaConsumer().name();

        KafkaConsumer<String, String> kafkaConsumer = kafkaConsumerMap.get(consumerName);
        if (null == kafkaConsumer) {
            Properties props = new Properties();
            props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, mqKafkaListener.getKafkaConsumer().servers());
            props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 25000);
            props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 30);
            props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization" +
                    ".StringDeserializer");
            props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization" +
                    ".StringDeserializer");
            props.put(ConsumerConfig.GROUP_ID_CONFIG, group);
            kafkaConsumer = new KafkaConsumer<>(props);
            List<String> subscribedTopics = new ArrayList<>();
            subscribedTopics.add(topic);
            kafkaConsumer.subscribe(subscribedTopics);
            kafkaConsumerMap.put(consumerName, kafkaConsumer);

            try {
                KafkaConsumer<String, String> finalKafkaConsumer = kafkaConsumer;
                threadPool.submit(() -> {
                    ConsumerRecords<String, String> records = finalKafkaConsumer.poll(1000);
                    for (ConsumerRecord<String, String> record : records) {
                        Optional<String> kafkaMessage = Optional.ofNullable(record.value());
                        if (kafkaMessage.isPresent()) {
                            String message = kafkaMessage.get();
                            logger.info("接收到kafka的消息:{}", message);
                            try {
                                mqKafkaListener.getMethod().invoke(mqKafkaListener.getBean(), message);
                            } catch (Exception e) {
                                logger.error("调用应用系统失败,看到此异常时,应该系统应该自行处理异常,不应该抛出,请修改!:{}", e);
                            }
                        }
                    }
                });
            } catch (Exception e) {
                logger.error("调用应用系统失败,看到此异常时,应该系统应该自行处理异常,不应该抛出,请修改!:{}", e);
            }
        }


    }

MQ消费pull/push示例代码:
RocketMQ Push 方式

Properties properties = new Properties();
        // 您在控制台创建的 Group ID
       properties.put(PropertyKeyConst.GROUP_ID, "XXX");
        // AccessKeyId 阿里云身份验证,在阿里云服务器管理控制台创建
       properties.put(PropertyKeyConst.AccessKey, "XXX");
        // AccesskeySecret 阿里云身份验证,在阿里云服务器管理控制台创建
       properties.put(PropertyKeyConst.SecretKey, "XXX");
        // 设置 TCP 接入域名,进入控制台的实例管理页面的获取接入点信息区域查看
       properties.put(PropertyKeyConst.NAMESRV_ADDR, "XXX");
          // 集群订阅方式 (默认)
          // properties.put(PropertyKeyConst.MessageModel, PropertyValueConst.CLUSTERING);
          // 广播订阅方式
          // properties.put(PropertyKeyConst.MessageModel, PropertyValueConst.BROADCASTING);

       Consumer consumer = ONSFactory.createConsumer(properties);
       consumer.subscribe("TopicTestMQ", "TagA||TagB", new MessageListener() { //订阅多个 Tag
           public Action consume(Message message, ConsumeContext context) {
               System.out.println("Receive: " + message);
               return Action.CommitMessage;
           }
       });

        //订阅另外一个 Topic,如需取消订阅该 Topic,请删除该部分的订阅代码,重新启动消费端即可
        consumer.subscribe("TopicTestMQ-Other", "*", new MessageListener() { //订阅全部 Tag
           public Action consume(Message message, ConsumeContext context) {
               System.out.println("Receive: " + message);
               return Action.CommitMessage;
           }
       });

       consumer.start();
       System.out.println("Consumer Started");

RocketMQ Pull 方式

 Properties properties = new Properties();
        properties.setProperty(PropertyKeyConst.GROUP_ID, "GID-xxxxx");
        // AccessKeyId 阿里云身份验证,在阿里云服务器管理控制台创建
        properties.put(PropertyKeyConst.AccessKey, "xxxxxxx");
        // AccessKeySecret 阿里云身份验证,在阿里云服务器管理控制台创建
        properties.put(PropertyKeyConst.SecretKey, "xxxxxxx");
        // 设置 TCP 接入域名,进入控制台的实例管理页面的获取接入点信息区域查看
        properties.put(PropertyKeyConst.NAMESRV_ADDR, "xxxxx");
        PullConsumer consumer = ONSFactory.createPullConsumer(properties);
        // 启动 Consumer
        consumer.start();
        // 获取 topic-xxx 下的所有分区
        Set<TopicPartition> topicPartitions = consumer.topicPartitions("topic-xxx");
        // 指定需要拉取消息的分区
        consumer.assign(topicPartitions);

        while (true) {
            // 拉取消息,超时时间为 3000 ms
            List<Message> messages = consumer.poll(3000);
            System.out.printf("Received message: %s %n", messages);
        }

网上找的MQ对比图

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章