-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[issue#4042] improve java functions API #4093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
c786346
issue:4042 improve java functions API
wolfstudy 5b593f3
fix code logic
wolfstudy 83d778e
change publish methods to use newOutputMessage
wolfstudy 2e455ad
add java doc for newOutputMessage
wolfstudy 5b0bfe9
update publish function examples
wolfstudy 22bc0f7
remove pulish() function
wolfstudy f046fc1
fix function example
wolfstudy 94ab855
fix a little
wolfstudy 656b921
fix integration tests
wolfstudy 037c28a
fix PublishFunctionWithMessageConf
wolfstudy 36e905b
fix error info
wolfstudy d475985
use typedMessageBuilderPublish instead of PublishFunctionWithMessageConf
wolfstudy 9c83892
fix a little
wolfstudy 6280e56
fix integration tests
wolfstudy 3c2e8b2
fix java unit test
wolfstudy 80d3ae4
fix integration test
wolfstudy 1b0dc30
Merge branch 'master' into xiaolong/localrun
wolfstudy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,10 @@ | |
| package org.apache.pulsar.functions.api; | ||
|
|
||
| import java.nio.ByteBuffer; | ||
|
|
||
| import org.apache.pulsar.client.api.PulsarClientException; | ||
| import org.apache.pulsar.client.api.Schema; | ||
| import org.apache.pulsar.client.api.TypedMessageBuilder; | ||
| import org.slf4j.Logger; | ||
|
|
||
| import java.util.Collection; | ||
|
|
@@ -232,6 +236,7 @@ public interface Context { | |
| * @param schemaOrSerdeClassName Either a builtin schema type (eg: "avro", "json", "protobuf") or the class name | ||
| * of the custom schema class | ||
| * @return A future that completes when the framework is done publishing the message | ||
| * @deprecated in favor of using {@link #newOutputMessage(String, Schema)} | ||
| */ | ||
| <O> CompletableFuture<Void> publish(String topicName, O object, String schemaOrSerdeClassName); | ||
|
|
||
|
|
@@ -241,28 +246,18 @@ public interface Context { | |
| * @param topicName The name of the topic for publishing | ||
| * @param object The object that needs to be published | ||
| * @return A future that completes when the framework is done publishing the message | ||
| * @deprecated in favor of using {@link #newOutputMessage(String, Schema)} | ||
| */ | ||
| <O> CompletableFuture<Void> publish(String topicName, O object); | ||
|
|
||
| /** | ||
| * Publish an object using serDe or schema class for serializing to the topic. | ||
| * | ||
| * @param topicName The name of the topic for publishing | ||
| * @param object The object that needs to be published | ||
| * @param schemaOrSerdeClassName Either a builtin schema type (eg: "avro", "json", "protobuf") or the class name | ||
| * of the custom schema class | ||
| * @param messageConf A map of configurations to set for the message that will be published | ||
| * The available options are: | ||
| * | ||
| * "key" - Parition Key | ||
| * "properties" - Map of properties | ||
| * "eventTime" | ||
| * "sequenceId" | ||
| * "replicationClusters" | ||
| * "disableReplication" | ||
| * New output message using schema for serializing to the topic | ||
| * | ||
| * @return A future that completes when the framework is done publishing the message | ||
| * @param topicName The name of the topic for output message | ||
| * @param schema provide a way to convert between serialized data and domain objects | ||
| * @param <O> | ||
| * @return the message builder instance | ||
| * @throws PulsarClientException | ||
| */ | ||
| <O> CompletableFuture<Void> publish(String topicName, O object, String schemaOrSerdeClassName, Map<String, Object> messageConf); | ||
|
|
||
| <O> TypedMessageBuilder<O> newOutputMessage(String topicName, Schema<O> schema) throws PulsarClientException; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @wolfstudy Can you add javadoc comment? |
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,14 +25,7 @@ | |
| import lombok.Getter; | ||
| import lombok.Setter; | ||
| import org.apache.commons.lang3.StringUtils; | ||
| import org.apache.pulsar.client.api.CompressionType; | ||
| import org.apache.pulsar.client.api.HashingScheme; | ||
| import org.apache.pulsar.client.api.MessageRoutingMode; | ||
| import org.apache.pulsar.client.api.Producer; | ||
| import org.apache.pulsar.client.api.PulsarClient; | ||
| import org.apache.pulsar.client.api.PulsarClientException; | ||
| import org.apache.pulsar.client.api.Schema; | ||
| import org.apache.pulsar.client.api.TypedMessageBuilder; | ||
| import org.apache.pulsar.client.api.*; | ||
| import org.apache.pulsar.client.impl.ProducerBuilderImpl; | ||
| import org.apache.pulsar.common.util.FutureUtil; | ||
| import org.apache.pulsar.functions.api.Context; | ||
|
|
@@ -52,12 +45,9 @@ | |
| import org.slf4j.Logger; | ||
|
|
||
| import java.nio.ByteBuffer; | ||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| import java.util.Optional; | ||
| import java.util.*; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.concurrent.ExecutionException; | ||
| import java.util.concurrent.TimeUnit; | ||
|
|
||
| import static com.google.common.base.Preconditions.checkState; | ||
|
|
@@ -94,11 +84,13 @@ class ContextImpl implements Context, SinkContext, SourceContext { | |
| private final Summary userMetricsSummary; | ||
|
|
||
| private final static String[] userMetricsLabelNames; | ||
|
|
||
| static { | ||
| // add label to indicate user metric | ||
| userMetricsLabelNames = Arrays.copyOf(ComponentStatsManager.metricsLabelNames, ComponentStatsManager.metricsLabelNames.length + 1); | ||
| userMetricsLabelNames[ComponentStatsManager.metricsLabelNames.length] = "metric"; | ||
| } | ||
|
|
||
| private final ComponentType componentType; | ||
|
|
||
| public ContextImpl(InstanceConfig config, Logger logger, PulsarClient client, | ||
|
|
@@ -259,7 +251,7 @@ public String getSecret(String secretName) { | |
| return null; | ||
| } | ||
| } | ||
|
|
||
| private void ensureStateEnabled() { | ||
| checkState(null != stateContext, "State is not enabled."); | ||
| } | ||
|
|
@@ -337,69 +329,26 @@ public <O> CompletableFuture<Void> publish(String topicName, O object) { | |
| @SuppressWarnings("unchecked") | ||
| @Override | ||
| public <O> CompletableFuture<Void> publish(String topicName, O object, String schemaOrSerdeClassName) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change all the |
||
| return publish(topicName, object, schemaOrSerdeClassName, null); | ||
| return publish(topicName, object, (Schema<O>) topicSchema.getSchema(topicName, object, schemaOrSerdeClassName, false)); | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| @Override | ||
| public <O> CompletableFuture<Void> publish(String topicName, O object, String schemaOrSerdeClassName, Map<String, Object> messageConf) { | ||
| return publish(topicName, object, (Schema<O>) topicSchema.getSchema(topicName, object, schemaOrSerdeClassName, false), messageConf); | ||
| public <O> TypedMessageBuilder<O> newOutputMessage(String topicName, Schema<O> schema) throws PulsarClientException { | ||
| MessageBuilderImpl<O> messageBuilder = new MessageBuilderImpl<>(); | ||
| TypedMessageBuilder<O> typedMessageBuilder = getProducer(topicName, schema).newMessage(); | ||
| messageBuilder.setUnderlyingBuilder(typedMessageBuilder); | ||
| return messageBuilder; | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| public <O> CompletableFuture<Void> publish(String topicName, O object, Schema<O> schema, Map<String, Object> messageConf) { | ||
| Producer<O> producer = (Producer<O>) publishProducers.get(topicName); | ||
|
|
||
| if (producer == null) { | ||
| try { | ||
| Producer<O> newProducer = ((ProducerBuilderImpl<O>) producerBuilder.clone()) | ||
| .schema(schema) | ||
| .blockIfQueueFull(true) | ||
| .enableBatching(true) | ||
| .batchingMaxPublishDelay(10, TimeUnit.MILLISECONDS) | ||
| .compressionType(CompressionType.LZ4) | ||
| .hashingScheme(HashingScheme.Murmur3_32Hash) // | ||
| .messageRoutingMode(MessageRoutingMode.CustomPartition) | ||
| .messageRouter(FunctionResultRouter.of()) | ||
| // set send timeout to be infinity to prevent potential deadlock with consumer | ||
| // that might happen when consumer is blocked due to unacked messages | ||
| .sendTimeout(0, TimeUnit.SECONDS) | ||
| .topic(topicName) | ||
| .properties(InstanceUtils.getProperties(componentType, | ||
| FunctionCommon.getFullyQualifiedName( | ||
| this.config.getFunctionDetails().getTenant(), | ||
| this.config.getFunctionDetails().getNamespace(), | ||
| this.config.getFunctionDetails().getName()), | ||
| this.config.getInstanceId())) | ||
| .create(); | ||
|
|
||
| Producer<O> existingProducer = (Producer<O>) publishProducers.putIfAbsent(topicName, newProducer); | ||
|
|
||
| if (existingProducer != null) { | ||
| // The value in the map was not updated after the concurrent put | ||
| newProducer.close(); | ||
| producer = existingProducer; | ||
| } else { | ||
| producer = newProducer; | ||
| } | ||
|
|
||
| } catch (PulsarClientException e) { | ||
| logger.error("Failed to create Producer while doing user publish", e); | ||
| return FutureUtil.failedFuture(e); | ||
| } | ||
| } | ||
|
|
||
| TypedMessageBuilder<O> messageBuilder = producer.newMessage(); | ||
| if (messageConf != null) { | ||
| messageBuilder.loadConf(messageConf); | ||
| public <O> CompletableFuture<Void> publish(String topicName, O object, Schema<O> schema) { | ||
| try { | ||
| return newOutputMessage(topicName, schema).value(object).sendAsync().thenApply(msgId -> null); | ||
| } catch (PulsarClientException e) { | ||
| logger.error("Failed to create Producer while doing user publish", e); | ||
| return FutureUtil.failedFuture(e); | ||
| } | ||
| CompletableFuture<Void> future = messageBuilder.value(object).sendAsync().thenApply(msgId -> null); | ||
| future.exceptionally(e -> { | ||
| this.statsManager.incrSysExceptions(e); | ||
| logger.error("Failed to publish to topic {} with error {}", topicName, e); | ||
| return null; | ||
| }); | ||
| return future; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -417,6 +366,46 @@ public void recordMetric(String metricName, double value) { | |
| } | ||
| } | ||
|
|
||
| private <O> Producer<O> getProducer(String topicName, Schema<O> schema) throws PulsarClientException { | ||
| Producer<O> producer = (Producer<O>) publishProducers.get(topicName); | ||
|
|
||
| if (producer == null) { | ||
|
|
||
| Producer<O> newProducer = ((ProducerBuilderImpl<O>) producerBuilder.clone()) | ||
| .schema(schema) | ||
| .blockIfQueueFull(true) | ||
| .enableBatching(true) | ||
| .batchingMaxPublishDelay(10, TimeUnit.MILLISECONDS) | ||
| .compressionType(CompressionType.LZ4) | ||
| .hashingScheme(HashingScheme.Murmur3_32Hash) // | ||
| .messageRoutingMode(MessageRoutingMode.CustomPartition) | ||
| .messageRouter(FunctionResultRouter.of()) | ||
| // set send timeout to be infinity to prevent potential deadlock with consumer | ||
| // that might happen when consumer is blocked due to unacked messages | ||
| .sendTimeout(0, TimeUnit.SECONDS) | ||
| .topic(topicName) | ||
| .properties(InstanceUtils.getProperties(componentType, | ||
| FunctionCommon.getFullyQualifiedName( | ||
| this.config.getFunctionDetails().getTenant(), | ||
| this.config.getFunctionDetails().getNamespace(), | ||
| this.config.getFunctionDetails().getName()), | ||
| this.config.getInstanceId())) | ||
| .create(); | ||
|
|
||
| Producer<O> existingProducer = (Producer<O>) publishProducers.putIfAbsent(topicName, newProducer); | ||
|
|
||
| if (existingProducer != null) { | ||
| // The value in the map was not updated after the concurrent put | ||
| newProducer.close(); | ||
| producer = existingProducer; | ||
| } else { | ||
| producer = newProducer; | ||
| } | ||
|
|
||
| } | ||
| return producer; | ||
| } | ||
|
|
||
| public Map<String, Double> getAndResetMetrics() { | ||
| Map<String, Double> retval = getMetrics(); | ||
| resetMetrics(); | ||
|
|
@@ -443,4 +432,105 @@ public Map<String, Double> getMetrics() { | |
| } | ||
| return metricsMap; | ||
| } | ||
|
|
||
| class MessageBuilderImpl<O> implements TypedMessageBuilder<O> { | ||
| private TypedMessageBuilder<O> underlyingBuilder; | ||
| @Override | ||
| public MessageId send() throws PulsarClientException { | ||
| try { | ||
| return sendAsync().get(); | ||
| } catch (ExecutionException e) { | ||
| Throwable t = e.getCause(); | ||
| if (t instanceof PulsarClientException) { | ||
| throw (PulsarClientException) t; | ||
| } else { | ||
| throw new PulsarClientException(t); | ||
| } | ||
| } catch (InterruptedException e) { | ||
| Thread.currentThread().interrupt(); | ||
| throw new PulsarClientException(e); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public CompletableFuture<MessageId> sendAsync() { | ||
| return underlyingBuilder.sendAsync() | ||
| .whenComplete((result, cause) -> { | ||
| if (null != cause) { | ||
| statsManager.incrSysExceptions(cause); | ||
| logger.error("Failed to publish to topic with error {}", cause); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| @Override | ||
| public TypedMessageBuilder<O> key(String key) { | ||
| underlyingBuilder.key(key); | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public TypedMessageBuilder<O> keyBytes(byte[] key) { | ||
| underlyingBuilder.keyBytes(key); | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public TypedMessageBuilder<O> orderingKey(byte[] orderingKey) { | ||
| underlyingBuilder.orderingKey(orderingKey); | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public TypedMessageBuilder<O> value(O value) { | ||
| underlyingBuilder.value(value); | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public TypedMessageBuilder<O> property(String name, String value) { | ||
| underlyingBuilder.property(name, value); | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public TypedMessageBuilder<O> properties(Map<String, String> properties) { | ||
| underlyingBuilder.properties(properties); | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public TypedMessageBuilder<O> eventTime(long timestamp) { | ||
| underlyingBuilder.eventTime(timestamp); | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public TypedMessageBuilder<O> sequenceId(long sequenceId) { | ||
| underlyingBuilder.sequenceId(sequenceId); | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public TypedMessageBuilder<O> replicationClusters(List<String> clusters) { | ||
| underlyingBuilder.replicationClusters(clusters); | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public TypedMessageBuilder<O> disableReplication() { | ||
| underlyingBuilder.disableReplication(); | ||
| return this; | ||
| } | ||
|
|
||
| @Override | ||
| public TypedMessageBuilder<O> loadConf(Map<String, Object> config) { | ||
| underlyingBuilder.loadConf(config); | ||
| return this; | ||
| } | ||
|
|
||
| public void setUnderlyingBuilder(TypedMessageBuilder<O> underlyingBuilder) { | ||
| this.underlyingBuilder = underlyingBuilder; | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sijie @wolfstudy the concern comes back to whether the functions api should depend of the pulsar-client-api
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jerrypeng let's stay in #4127 for the discussion