5.四种订阅模式、延时消息

四种订阅模式

独占Exclusive

在独占模式下,只允许一个消费者附加到订阅上。如果多个消费者使用同一个订阅来订阅一个主题,就会发生错误。在下图中,只有消费者A-0被允许消费消息。

此模式也是默认模式

Exclusive subscriptions

Demo

subscriptionName相同,同时执行exclusive/comsumer1和exclusive/comsumer2就会报错

package com.project.pulsar.delayMsgAndSubscriptionsModel;

import com.project.pulsar.conf.PulsarConf;
import org.apache.pulsar.client.admin.Namespaces;
import org.apache.pulsar.client.admin.PulsarAdmin;
import org.apache.pulsar.client.admin.Tenants;
import org.apache.pulsar.client.api.*;
import org.apache.pulsar.common.policies.data.DelayedDeliveryPolicies;
import org.apache.pulsar.common.policies.data.TenantInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Set;

/**
 * 独占模式
 */
@RestController
public class ExclusiveMessageController {
    @Autowired
    PulsarConf pulsarConf;

    String tenantName = "delay";//调用接口创建的租户,创建方法见com.project.pulsar.persistent.PersistentController.java
    String namespace = "mySpace";//调用接口创建的命名空间,创建方法见com.project.pulsar.persistent.PersistentController.java

    /**
     * 生产消息
     *
     * @param msg
     * @throws PulsarClientException
     */
    @GetMapping("/exclusive/sendMsg")
    public MessageId sendMsg(String msg) throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        //持久化         租户   命名空间   主题
        Producer<byte[]> producer1 = pulsarFactory.newProducer()
                .topic(topic)
                .create();

        return producer1.send(msg.getBytes());
    }


    /**
     * 手动执行获取消息
     *
     * @throws PulsarClientException
     */
    @GetMapping("/exclusive/comsumer")
    public void comsumerByArtificial() throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        //持久化         租户   命名空间   主题
        Consumer<byte[]> consumer = pulsarFactory.newConsumer()
                .topic(topic)
                .subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
                .subscriptionType(SubscriptionType.Exclusive)//指定模式
                .subscribe();
        Message<byte[]> receive = consumer.receive();
        System.out.println(new String(receive.getData()));
        consumer.acknowledge(receive);//确认消息被消费
        consumer.close();
    }

    /**
     * 手动执行获取消息
     *
     * @throws PulsarClientException
     */
    @GetMapping("/exclusive/comsumer2")
    public void comsumerByArtificial2() throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        //持久化         租户   命名空间   主题
        Consumer<byte[]> consumer = pulsarFactory.newConsumer()
                .topic(topic)
                .subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
                .subscriptionType(SubscriptionType.Exclusive)//指定模式
                .subscribe();
        Message<byte[]> receive = consumer.receive();
        System.out.println(new String(receive.getData()));
        consumer.acknowledge(receive);//确认消息被消费
        consumer.close();
    }
}

代码下载

代码见此delayMsgAndSubscriptionsModel包下ExclusiveMessageController.java

灾备Failover

在故障转移模式中,多个消费者可以附加到同一个订阅。为非分区主题或分区主题的每个分区挑选一个主消费者并接收消息。当主消费者断开连接时,所有(未确认的和后续的)消息都被传递给排在后面的消费者。对于分区主题,经纪人将按优先级和消费者名称的词汇顺序对消费者进行排序。对于非分区主题,经纪人将按照消费者订阅非分区主题的顺序挑选消费者。在下图中,消费者B-0是主消费者,而如果消费者B-0断开连接,消费者B-1将是排在后面的消费者接收消息。

Failover subscriptions

Demo

package com.project.pulsar.delayMsgAndSubscriptionsModel;

import com.project.pulsar.conf.PulsarConf;
import org.apache.pulsar.client.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 灾备模式
 */
@RestController
public class FailoverMessageController {
    @Autowired
    PulsarConf pulsarConf;

    String tenantName = "delay";//调用接口创建的租户,创建方法见com.project.pulsar.persistent.PersistentController.java
    String namespace = "mySpace";//调用接口创建的命名空间,创建方法见com.project.pulsar.persistent.PersistentController.java

    /**
     * 生产消息
     *
     * @param msg
     * @throws PulsarClientException
     */
    @GetMapping("/failover/sendMsg")
    public MessageId sendMsg(String msg) throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        //持久化         租户   命名空间   主题
        Producer<byte[]> producer1 = pulsarFactory.newProducer()
                .topic(topic)
                .create();

        return producer1.send(msg.getBytes());
    }


    /**
     * 手动执行获取消息
     *
     * @throws PulsarClientException
     */
    @GetMapping("/failover/comsumer")
    public void comsumerByArtificial() throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        //持久化         租户   命名空间   主题
        Consumer<byte[]> consumer = pulsarFactory.newConsumer()
                .topic(topic)
                .subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
                .subscriptionType(SubscriptionType.Failover)//指定模式
                .subscribe();
        Message<byte[]> receive = consumer.receive();
        System.out.println(new String(receive.getData()));
        consumer.acknowledge(receive);//确认消息被消费
        consumer.close();
    }

    /**
     * 手动执行获取消息
     *
     * @throws PulsarClientException
     */
    @GetMapping("/failover/comsumer2")
    public void comsumerByArtificial2() throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        //持久化         租户   命名空间   主题
        Consumer<byte[]> consumer = pulsarFactory.newConsumer()
                .topic(topic)
                .subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
                .subscriptionType(SubscriptionType.Failover)//指定模式
                .subscribe();
        Message<byte[]> receive = consumer.receive();
        System.out.println(new String(receive.getData()));
        consumer.acknowledge(receive);//确认消息被消费
        consumer.close();
    }
}

代码下载

代码见此delayMsgAndSubscriptionsModel包下FailoverMessageController.java

共享Shared

在共享或循环模式下,多个使用者可以附加到同一订阅。消息在消费者之间以循环分发的方式传递,任何给定的消息只传递给一个消费者。当消费者断开连接时,发送给它但未确认的所有消息将重新安排发送给其余消费者。

在下图中,Consumer-C-1和Consumer-C-2可以订阅该主题,但Consumer-C-3和其他人也可以订阅。

Shared subscriptions

Demo

package com.project.pulsar.delayMsgAndSubscriptionsModel;

import com.project.pulsar.conf.PulsarConf;
import org.apache.pulsar.client.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 共享模式
 */
@RestController
public class ShareMessageController {
    @Autowired
    PulsarConf pulsarConf;

    String tenantName = "delay";//调用接口创建的租户,创建方法见com.project.pulsar.persistent.PersistentController.java
    String namespace = "mySpace";//调用接口创建的命名空间,创建方法见com.project.pulsar.persistent.PersistentController.java

    /**
     * 生产消息
     *
     * @param msg
     * @throws PulsarClientException
     */
    @GetMapping("/share/sendMsg")
    public MessageId sendMsg(String msg) throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        //持久化         租户   命名空间   主题
        Producer<byte[]> producer1 = pulsarFactory.newProducer()
                .topic(topic)
                .create();

        return producer1.send(msg.getBytes());
    }


    /**
     * 手动执行获取消息
     *
     * @throws PulsarClientException
     */
    @GetMapping("/share/comsumer")
    public void comsumerByArtificial() throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        //持久化         租户   命名空间   主题
        Consumer<byte[]> consumer = pulsarFactory.newConsumer()
                .topic(topic)
                .subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
                .subscriptionType(SubscriptionType.Shared)//指定模式
                .subscribe();
        Message<byte[]> receive = consumer.receive();
        System.out.println(new String(receive.getData()));
        consumer.acknowledge(receive);//确认消息被消费
        consumer.close();
    }

    /**
     * 手动执行获取消息
     *
     * @throws PulsarClientException
     */
    @GetMapping("/share/comsumer2")
    public void comsumerByArtificial2() throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        //持久化         租户   命名空间   主题
        Consumer<byte[]> consumer = pulsarFactory.newConsumer()
                .topic(topic)
                .subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
                .subscriptionType(SubscriptionType.Shared)//指定模式
                .subscribe();
        Message<byte[]> receive = consumer.receive();
        System.out.println(new String(receive.getData()));
        consumer.acknowledge(receive);//确认消息被消费
        consumer.close();
    }
}

代码下载

代码见此delayMsgAndSubscriptionsModel包下ShareMessageController.java

按照Key共享Key_Shared

在Key_Shared模式下,多个消费者可以附加到同一个订阅。消息在消费者之间分布传递,具有相同密钥或相同订购密钥的消息只传递给一个消费者。无论消息被重新传递多少次,它都会被传递给同一个消费者。当一个消费者连接或断开连接时,将导致所服务的消费者对消息的某些键进行改变。

当您使用Key_Shared模式时,Key_Shared模式的局限性,应注意:您需要指定键或orderingKey的消息。你不能使用与Key_Shared模式的累积确认。你的生产者应禁用批处理或使用一个基于批处理生成器。

Key_Shared subscriptions

Demo

package com.project.pulsar.delayMsgAndSubscriptionsModel;

import com.project.pulsar.conf.PulsarConf;
import org.apache.pulsar.client.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 共享模式
 */
@RestController
public class KeyShareMessageController {
    @Autowired
    PulsarConf pulsarConf;

    String tenantName = "delay";//调用接口创建的租户,创建方法见com.project.pulsar.persistent.PersistentController.java
    String namespace = "mySpace";//调用接口创建的命名空间,创建方法见com.project.pulsar.persistent.PersistentController.java

    /**
     * 生产消息
     *
     * @param msg
     * @throws PulsarClientException
     */
    @GetMapping("/keyShare/sendMsg")
    public MessageId sendMsg(String msg) throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        //持久化         租户   命名空间   主题
        Producer<byte[]> producer1 = pulsarFactory.newProducer()
                .topic(topic)
                .create();

        return producer1.send(msg.getBytes());
    }



    @Bean
    public void comsumerByListener1() throws PulsarClientException {
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        MessageListener myMessageListener = (consumer, msg) -> {
            try {
                System.out.println("comsumerByListener1: " + new String(msg.getData()));
                consumer.acknowledge(msg);
            } catch (Exception e) {
                consumer.negativeAcknowledge(msg);
            }
        };
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        pulsarFactory.newConsumer()
                .topic(topic)
                .subscriptionName("my-subscriptionByListener")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
                .subscriptionType(SubscriptionType.Key_Shared)
                .messageListener(myMessageListener)
                .subscribe();
    }

    @Bean
    public void comsumerByListener2() throws PulsarClientException {
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-topic";
        MessageListener myMessageListener = (consumer, msg) -> {
            try {
                System.out.println("comsumerByListener2: " + new String(msg.getData()));
                consumer.acknowledge(msg);
            } catch (Exception e) {
                consumer.negativeAcknowledge(msg);
            }
        };
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        pulsarFactory.newConsumer()
                .topic(topic)
                .subscriptionName("my-subscriptionByListener")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
                .subscriptionType(SubscriptionType.Key_Shared)
                .messageListener(myMessageListener)
                .subscribe();
    }
}

代码下载

代码见此delayMsgAndSubscriptionsModel包下KeyShareMessageController.java

延时消息

仅对Share和KeyShare模式有效,其他模式均为立即发送

延时消息有两种模式:

  1. 指定时长后发送

    deliverAfter(3L, TimeUnit.MINUTES)

  2. 指定时间发送

    deliverAt(LocalDateTime.of(2021, 9, 20, 16, 20).toInstant(ZoneOffset.of("+8")).toEpochMilli() )

Demo

package com.project.pulsar.delayMsgAndSubscriptionsModel;

import com.project.pulsar.conf.PulsarConf;
import org.apache.pulsar.client.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * 延时消息
 */
@RestController
public class DelayMessageController {
    @Autowired
    PulsarConf pulsarConf;

    String tenantName = "delay";//调用接口创建的租户,创建方法见com.project.pulsar.persistent.PersistentController.java
    String namespace = "mySpace";//调用接口创建的命名空间,创建方法见com.project.pulsar.persistent.PersistentController.java

    /**
     * 生产消息
     *
     * @param msg
     * @throws PulsarClientException
     */
    @GetMapping("/delay/sendMsg")
    public void sendMsg(String msg) throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-delay-topic";
        //持久化         租户   命名空间   主题
        Producer<byte[]> producer = pulsarFactory.newProducer()
                .topic(topic)
                .create();
        producer.newMessage().deliverAfter(2L, TimeUnit.MINUTES).key("key-1").value("message-1-1".getBytes()).send();
        //        //    MessageId send = producer1.newMessage().deliverAfter(3L, TimeUnit.MINUTES).value("Hello Pulsar!".getBytes()).send();//指定消息在X时分秒后执行
//        MessageId send =producer1.newMessage().deliverAt(
//                LocalDateTime.of(2021, 9, 20, 16, 20).toInstant(ZoneOffset.of("+8")).toEpochMilli()
//        ).value(msg.getBytes()).send();//指定消息在2021/09/20 15:52分发送,如发送时日期已过,会立刻发送
    }


    /**
     * 手动执行获取消息
     *
     * @throws PulsarClientException
     */
    @GetMapping("/delay/comsumer")
    public void comsumerByArtificial() throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-delay-topic";
        //持久化         租户   命名空间   主题
        Consumer<byte[]> consumer = pulsarFactory.newConsumer()
                .topic(topic)
                .subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
                .subscriptionType(SubscriptionType.Key_Shared)//指定模式
                .subscribe();
        Message<byte[]> receive = consumer.receive();
        System.out.println(new String(receive.getData()));
        consumer.acknowledge(receive);//确认消息被消费
        consumer.close();
    }

    /**
     * 手动执行获取消息
     *
     * @throws PulsarClientException
     */
    @GetMapping("/delay/comsumer2")
    public void comsumerByArtificial2() throws PulsarClientException {
        PulsarClient pulsarFactory = pulsarConf.pulsarFactory();
        String topic = "persistent://" + tenantName + "/" + namespace + "/my-delay-topic";
        //持久化         租户   命名空间   主题
        Consumer<byte[]> consumer = pulsarFactory.newConsumer()
                .topic(topic)
                .subscriptionName("my-subscription")//必须名称一致,才可触发下一行,否则就是两个不同的消费者
                .subscriptionType(SubscriptionType.Key_Shared)//指定模式
                .subscribe();
        Message<byte[]> receive = consumer.receive();
        System.out.println(new String(receive.getData()));
        consumer.acknowledge(receive);//确认消息被消费
        consumer.close();
    }
}

代码下载

代码见此delayMsgAndSubscriptionsModel包下DelayMessageController.java

Pulsar还有很多特性,写不过来了,以后用到再总结,有问题就官网、Github、谷歌、百度

1.所写技术都是我工作中用到的
2.所写技术都是自己从项目中提取的
3.所有配置搭建流程都经过2到3遍的测试
4.因都是工作中使用的技术,所以不确定是否有转载的,如果有,请及时通知
原文地址:https://www.cnblogs.com/rb2010/p/15314695.html