對接不同消息隊列抽象提取

背景:

公司對外輸出服務 希望對消息隊列抽象 在不修改業務代碼的情況下 替換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對比圖

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