目前有个需求,需要在项目中使用Pulsar-java-api,正常使用需要新建client,新建消费者,并逐一维护他们的配置。为了简便,我们可以使用Java自定义注解来简化开发过程。
先列出正常建消费者的步骤
public class ConsumerDemo {
public static void main(String[] args) throws PulsarClientException {
PulsarClient client = PulsarClient.builder()
.serviceUrl("pulsar://10.0.0.27:30980")
.listenerThreads(200)
.build();
MessageListener myMessageListener = new MessageListener() {
@Override
public void received(Consumer consumer, Message msg) {
try {
System.out.println("收到消息: " + new String(msg.getData())+"当前条数"+adder.toString());
consumer.acknowledge(msg);
} catch (Exception e) {
consumer.negativeAcknowledge(msg);
}
}
};
Consumer consumer = client.newConsumer(Schema.STRING)
.topic("persistent://gpsTenant/positionNamespace/positionTopic")
.subscriptionName("devicePositionMsgTopicSubscription")
.subscriptionType(SubscriptionType.Shared)
.messageListener(myMessageListener)
.subscribe();
}
}
下面是使用自定义注解实现的方法
1、新建注解类
@Retention(RetentionPolicy.RUNTIME) // 生命周期是在项目运行过程中
@Target(ElementType.METHOD) // 作用域在方法上
public @interface PulsarConsumer {
String topic();
Class<?> clazz() default byte[].class;
Serialization serialization() default Serialization.STRING;
/**
* 订阅类型
*/
SubscriptionType[] subscriptionType() default {};
/**
* 消费者名称,为空pulsar会自动生成id
*/
String consumerName() default "";
/**
* 订阅名称,为空pulsar会自动生成id
* 在共享模式(Shared)中,subscriptionName会决定是不是同一个消费者租
* subscriptionName一样,消息会按照round-robin(轮询)机制或其他自定义策略分发给消费者组内的各个消费者。
* subscriptionName不一样,每个消费者组都会独立地接收Topic中的所有消息。
*/
String subscriptionName() default "";
/***
* 消息重试时间
*/
int negativeAckRedeliveryDelay() default -1;
int ackTimeout() default 0;
/**
* 发送到死信队列之前重试的最大次数
*/
int maxRedeliverCount() default -1;
/**
* 死信队列topic名称
*/
String deadLetterTopic() default "";
/**
* 项目启动时是否加载该消费者,默认加载
*/
boolean autoStart() default true;
/**
* 命名空间名称,默认加载配置文件
*/
String namespace() default "";
/**
* 消费消息的初始位置,默认为Latest最新的, Earliest最旧的
*/
SubscriptionInitialPosition initialPosition() default SubscriptionInitialPosition.Latest;
}
2、收集使用了注解的方法,利用spring的bean来筛选
/**
* 消费者收集器
*/
@Configuration
public class ConsumerCollector implements BeanPostProcessor {
private final UrlBuildService urlBuildService;
private final Map<String, ConsumerHolder> consumers = new ConcurrentHashMap<>();
private final Set<Class<?>> nonAnnotatedClasses = Sets.newHashSet();
public ConsumerCollector(UrlBuildService urlBuildService) {
this.urlBuildService = urlBuildService;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
final Class<?> beanClass = bean.getClass();
// 重复bean校验
if (this.nonAnnotatedClasses.contains(beanClass)) {
return bean;
}
// 检查是否有注解 @PulsarConsumer
consumers.putAll(Arrays.stream(beanClass.getDeclaredMethods())
.filter($ -> {
if (!$.isAnnotationPresent(PulsarConsumer.class)) {
this.nonAnnotatedClasses.add(beanClass);
LoggerBuilder.busLogger().trace("No @PulsarConsumer annotations found on bean type:{} ", bean.getClass());
return false;
}
return true;
})
.collect(Collectors.toMap(
method -> urlBuildService.buildConsumerName(beanClass, method),
method -> new ConsumerHolder(method.getAnnotation(PulsarConsumer.class), method, bean, SchemaUtils.getParameterType(method)))));
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
public Map<String, ConsumerHolder> getConsumers() {
return consumers;
}
public Optional<ConsumerHolder> getConsumer(String methodDescriptor) {
return Optional.ofNullable(consumers.get(methodDescriptor));
}
}
3、使用收集到的消费者,来进行构建。
pulsarClient这个bean在这里就忽略不写了。
@DependsOn保证这个bean构建时在pulsarClient和consumerCollector之后构建
@EventListener(ApplicationReadyEvent.class) 注解保证该方法在ApplicationReadyEvent事件触发后调用,
该事件在Spring 应用程序完成所有初始化工作并准备好开始接收请求后发布。
@Component
@DependsOn({"pulsarClient", "consumerCollector"})
public class ConsumerAggregator implements EmbeddedValueResolverAware {
// private Sinks.Many<GpsException> sink = Sinks.many().multicast().onBackpressureBuffer(Queues.SMALL_BUFFER_SIZE, false);
private final ConsumerCollector consumerCollector;
private final PulsarClient pulsarClient;
private final PulsarProperties pulsarProperties;
private final UrlBuildService urlBuildService;
private final ConsumerInterceptor consumerInterceptor;
private StringValueResolver stringValueResolver;
private List<Consumer> consumers;
public ConsumerAggregator(ConsumerCollector consumerCollector, PulsarClient pulsarClient,
PulsarProperties pulsarProperties, UrlBuildService urlBuildService,
ConsumerInterceptor consumerInterceptor) {
this.consumerCollector = consumerCollector;
this.pulsarClient = pulsarClient;
this.pulsarProperties = pulsarProperties;
this.urlBuildService = urlBuildService;
this.consumerInterceptor = consumerInterceptor;
}
@EventListener(ApplicationReadyEvent.class)
public void init() {
//@FIXME 这里是每个消费者的配置,需要再细化到消费者进行判断
if (pulsarProperties.isAutoStart()) {
consumers = consumerCollector.getConsumers().entrySet().stream()
.filter(holder -> holder.getValue().getAnnotation().autoStart())
.map(holder -> subscribe(holder.getKey(), holder.getValue()))
.collect(Collectors.toList());
}
}
private Consumer<?> subscribe(String generatedConsumerName, ConsumerHolder holder) {
try {
final String consumerName = stringValueResolver.resolveStringValue(holder.getAnnotation().consumerName());
final String subscriptionName = stringValueResolver.resolveStringValue(holder.getAnnotation().subscriptionName());
final String topicName = stringValueResolver.resolveStringValue(holder.getAnnotation().topic());
final String namespace = stringValueResolver.resolveStringValue(holder.getAnnotation().namespace());
final SubscriptionType subscriptionType = urlBuildService.getSubscriptionType(holder);
final ConsumerBuilder<?> consumerBuilder = pulsarClient
.newConsumer(SchemaUtils.getSchema(holder.getAnnotation().serialization(),
holder.getAnnotation().clazz()))
.consumerName(urlBuildService.buildPulsarConsumerName(consumerName, generatedConsumerName))
.subscriptionName(urlBuildService.buildPulsarSubscriptionName(subscriptionName, generatedConsumerName))
.topic(urlBuildService.buildTopicUrl(topicName, namespace))
.subscriptionType(subscriptionType)
.subscriptionInitialPosition(holder.getAnnotation().initialPosition())
.messageListener((consumer, msg) -> {
try {
final Method method = holder.getHandler();
method.setAccessible(true);
//可以将代理类注入到包装处理的
if (holder.isWrapped()) {
method.invoke(holder.getBean(), wrapMessage(msg));
} else {
method.invoke(holder.getBean(), msg.getValue());
}
consumer.acknowledge(msg);
} catch (Exception e) {
LoggerBuilder.busLogger().error(msg.getValue() + "-->消费失败", e);
consumer.negativeAcknowledge(msg);
// sink.tryEmitNext(new GpsException(PulsarError.PULSAR_CONSUMER_ERROR,e));
}
});
if (holder.getAnnotation().negativeAckRedeliveryDelay() > 0) {
consumerBuilder.negativeAckRedeliveryDelay(holder.getAnnotation().negativeAckRedeliveryDelay(), TimeUnit.SECONDS);
}
// 设置消费者确认消息超时时间
if (holder.getAnnotation().ackTimeout() > 0) {
consumerBuilder.ackTimeout(holder.getAnnotation().ackTimeout(), TimeUnit.SECONDS);
}
if (pulsarProperties.isAllowInterceptor()) {
consumerBuilder.intercept(consumerInterceptor);
}
urlBuildService.buildDeadLetterPolicy(
holder.getAnnotation().maxRedeliverCount(),
holder.getAnnotation().deadLetterTopic(),
consumerBuilder);
return consumerBuilder.subscribe();
} catch (PulsarClientException e) {
LoggerBuilder.busLogger().error(e.getLocalizedMessage());
throw new PulsarException(PulsarError.PULSAR_CONSUMER_INIT_ERROR, e.getMessage());
}
}
public <T> PulsarMessage<T> wrapMessage(Message<T> message) {
final PulsarMessage<T> pulsarMessage = new PulsarMessage<T>();
pulsarMessage.setValue(message.getValue());
pulsarMessage.setMessageId(message.getMessageId());
pulsarMessage.setSequenceId(message.getSequenceId());
pulsarMessage.setProperties(message.getProperties());
pulsarMessage.setTopicName(message.getTopicName());
pulsarMessage.setKey(message.getKey());
pulsarMessage.setEventTime(message.getEventTime());
pulsarMessage.setPublishTime(message.getPublishTime());
pulsarMessage.setProducerName(message.getProducerName());
return pulsarMessage;
}
public List<Consumer> getConsumers() {
return consumers;
}
// public Disposable onError(java.util.function.Consumer<? super GpsException> consumer) {
// return sink.asFlux().subscribe(consumer);
// }
@Override
public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
this.stringValueResolver = stringValueResolver;
}
}
到目前为止,我们基本工作已经做完了,里面有挺多细节代码没列出来。
思路就是新建注解,利用spring+反射来加载出来消费者们,并将他们初始化。
至此,我们可以愉快的使用注解来创建消费者了,需要注意的是:消费者的类需要被spring来维护
@PulsarConsumer(topic = "delayQueueTopic",
clazz = String.class,
serialization = Serialization.STRING,
subscriptionType = SubscriptionType.Shared, // 订阅模式,默认为独占模式
subscriptionName = "delayQueueTopicSubscription",
maxRedeliverCount = -1, // 最大重试次数
deadLetterTopic = "delayQueueTopic_DLT", // 死信topic名称
negativeAckRedeliveryDelay = 20 // 重试间隔时间 30s
)
// 如果消费失败,请抛出异常,这样消息会进入重试队列,之后可以重新消费,直到达到最大重试次数之后,进入死信队列。
public void received(String str) {
GpsLogger.busLogger().info("收到车队列消息,消息内容message={}", str);
}