-
Notifications
You must be signed in to change notification settings - Fork 282
feat: Add support Apache Pulsar #3682
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
Open
lillo42
wants to merge
32
commits into
BrighterCommand:master
Choose a base branch
from
lillo42:add-support-pulsar
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
159ee9c
Init support to pulsar
91083e5
Merge branch 'master' into add-support-pulsar
892f467
Add comment
65c3d93
Add Pulsar unit test
2581d23
Add comments to header name
48e1889
feat: Update csproj
215f017
fix: build
211bd26
Merge branch 'master' into add-support-pulsar
5ebf049
feat: Add Pulsar CI
641f783
fix: error to run pulsar
ccd37c7
Merge branch 'master' into add-support-pulsar
lillo42 309f4c6
fix: build
lillo42 cb86df3
fix: Remove RocketMQ build & build tests
lillo42 1800a5f
fix: pulsar tests
lillo42 9a3d34d
Merge branch 'master' into add-support-pulsar
lillo42 f656cb9
fix: Try to fix unit tests
lillo42 fb17289
fix: Remove unnecessary vars
lillo42 9f88177
Merge branch 'master' into add-support-pulsar
lillo42 6cecb71
fix: Build
lillo42 233f841
fix: unit tests
lillo42 68fba51
Merge branch 'master' into add-support-pulsar
5ed1cc1
Update Pulsar docs
7cf9466
Merge branch 'master' into add-support-pulsar
lillo42 afe0fa3
Change the max of message same as NoOfPerformermers
lillo42 bbf95bf
Merge branch 'master' into add-support-pulsar
lillo42 afe30ba
Merge branch 'master' into add-support-pulsar
777734e
Merge branch 'master' into add-support-pulsar
00ecbbc
Merge branch 'master' into add-support-pulsar
lillo42 f302def
Revert unnecessary changes
lillo42 5f44003
Merge branch 'master' into add-support-pulsar
lillo42 34efccd
Merge with master
lillo42 443f065
Updata pacakges
lillo42 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
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 |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| services: | ||
| # Start zookeeper | ||
| zookeeper: | ||
| image: apachepulsar/pulsar:latest | ||
| container_name: zookeeper | ||
| restart: on-failure | ||
| environment: | ||
| - metadataStoreUrl=zk:zookeeper:2181 | ||
| - PULSAR_MEM=-Xms256m -Xmx256m -XX:MaxDirectMemorySize=256m | ||
| command: | ||
| - bash | ||
| - -c | ||
| - | | ||
| bin/apply-config-from-env.py conf/zookeeper.conf && \ | ||
| bin/generate-zookeeper-config.sh conf/zookeeper.conf && \ | ||
| exec bin/pulsar zookeeper | ||
| healthcheck: | ||
| test: ["CMD", "bin/pulsar-zookeeper-ruok.sh"] | ||
| interval: 10s | ||
| timeout: 5s | ||
| retries: 30 | ||
|
|
||
| # Init cluster metadata | ||
| pulsar-init: | ||
| container_name: pulsar-init | ||
| hostname: pulsar-init | ||
| image: apachepulsar/pulsar:latest | ||
| command: | ||
| - bash | ||
| - -c | ||
| - | | ||
| bin/pulsar initialize-cluster-metadata \ | ||
| --cluster cluster-a \ | ||
| --zookeeper zookeeper:2181 \ | ||
| --configuration-store zookeeper:2181 \ | ||
| --web-service-url http://broker:8080 \ | ||
| --broker-service-url pulsar://broker:6650 | ||
| depends_on: | ||
| zookeeper: | ||
| condition: service_healthy | ||
|
|
||
| # Start bookie | ||
| bookie: | ||
| image: apachepulsar/pulsar:latest | ||
| container_name: bookie | ||
| restart: on-failure | ||
| environment: | ||
| - clusterName=cluster-a | ||
| - zkServers=zookeeper:2181 | ||
| - metadataServiceUri=metadata-store:zk:zookeeper:2181 | ||
| # otherwise every time we run docker compose uo or down we fail to start due to Cookie | ||
| # See: https://github.com/apache/bookkeeper/blob/405e72acf42bb1104296447ea8840d805094c787/bookkeeper-server/src/main/java/org/apache/bookkeeper/bookie/Cookie.java#L57-68 | ||
| - advertisedAddress=bookie | ||
| - BOOKIE_MEM=-Xms512m -Xmx512m -XX:MaxDirectMemorySize=256m | ||
| depends_on: | ||
| zookeeper: | ||
| condition: service_healthy | ||
| pulsar-init: | ||
| condition: service_completed_successfully | ||
| # Map the local directory to the container to avoid bookie startup failure due to insufficient container disks. | ||
| command: bash -c "bin/apply-config-from-env.py conf/bookkeeper.conf && exec bin/pulsar bookie" | ||
|
|
||
| # Start broker | ||
| broker: | ||
| image: apachepulsar/pulsar:latest | ||
| container_name: broker | ||
| hostname: broker | ||
| restart: on-failure | ||
| environment: | ||
| - metadataStoreUrl=zk:zookeeper:2181 | ||
| - zookeeperServers=zookeeper:2181 | ||
| - clusterName=cluster-a | ||
| - managedLedgerDefaultEnsembleSize=1 | ||
| - managedLedgerDefaultWriteQuorum=1 | ||
| - managedLedgerDefaultAckQuorum=1 | ||
| - advertisedAddress=broker | ||
| - advertisedListeners=external:pulsar://127.0.0.1:6650 | ||
| - PULSAR_MEM=-Xms512m -Xmx512m -XX:MaxDirectMemorySize=256m | ||
| depends_on: | ||
| zookeeper: | ||
| condition: service_healthy | ||
| bookie: | ||
| condition: service_started | ||
| ports: | ||
| - "6650:6650" | ||
| - "8080:8080" | ||
| command: bash -c "bin/apply-config-from-env.py conf/broker.conf && exec bin/pulsar broker" |
63 changes: 63 additions & 0 deletions
63
src/Paramore.Brighter.MessagingGateway.Pulsar/HeaderNames.cs
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 |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| namespace Paramore.Brighter.MessagingGateway.Pulsar; | ||
|
|
||
| /// <summary> | ||
| /// Contains constant definitions for header names used in messaging systems, | ||
| /// particularly for Apache Pulsar integration with Brighter. | ||
| /// Includes standard headers, CloudEvents (CE) headers, and custom Brighter-Pulsar headers. | ||
| /// </summary> | ||
| public static class HeaderNames | ||
| { | ||
| /// <summary>Content type of the message payload (e.g., application/json)</summary> | ||
| public const string ContentType = "ContentType"; | ||
|
|
||
| /// <summary>Correlation ID for tracing related messages</summary> | ||
| public const string CorrelationId = "CorrelationId"; | ||
|
|
||
| /// <summary>CloudEvents-formatted unique message identifier</summary> | ||
| public const string MessageId = "CE-EventId"; | ||
|
|
||
| /// <summary>Number of times a message has been processed/requeued</summary> | ||
| public const string HandledCount = "HandledCount"; | ||
|
|
||
| /// <summary>Brighter message type classification (e.g., MT_COMMAND, MT_EVENT)</summary> | ||
| public const string MessageType = "MessageType"; | ||
|
|
||
| /// <summary>Reply destination for request-reply patterns</summary> | ||
| public const string ReplyTo = "ReplyTo"; | ||
|
|
||
| /// <summary>CloudEvents specification version (e.g., "1.0")</summary> | ||
| public const string SpecVersion = "CE-SpecVersion"; | ||
|
|
||
| /// <summary>CloudEvents event type descriptor</summary> | ||
| public const string Type = "CE-EventType"; | ||
|
|
||
| /// <summary>Timestamp of event occurrence in RFC3339 format</summary> | ||
| public const string Time = "CE-EventTime"; | ||
|
|
||
| /// <summary>CloudEvents subject describing event content</summary> | ||
| public const string Subject = "CE-Subject"; | ||
|
|
||
| /// <summary>CloudEvents schema URL for payload validation</summary> | ||
| public const string DataSchema = "CE-DataSchema"; | ||
|
|
||
| /// <summary>CloudEvents source URI identifying event origin</summary> | ||
| public const string Source = "CE-Source"; | ||
|
|
||
| /// <summary>W3C Trace Context traceparent value</summary> | ||
| public const string TraceParent = "CE-X-TraceParent"; | ||
|
|
||
| /// <summary>W3C Trace Context tracestate value</summary> | ||
| public const string TraceState = "CE-X-TraceState"; | ||
|
|
||
| /// <summary>OpenTelemetry baggage items (key-value pairs)</summary> | ||
| public const string Baggage = "CE-X-Baggage"; | ||
|
|
||
| /// <summary>Original message topic/routing key</summary> | ||
| public const string Topic = "Topic"; | ||
|
|
||
| /// <summary>Pulsar schema version identifier</summary> | ||
| public const string SchemaVersion = "Brighter-Pulsar-SchemaVersion"; | ||
|
|
||
| /// <summary>Pulsar message sequence identifier</summary> | ||
| public const string SequenceId = "Brighter-Pulsar-SequenceId"; | ||
| } |
23 changes: 23 additions & 0 deletions
23
...aramore.Brighter.MessagingGateway.Pulsar/Paramore.Brighter.MessagingGateway.Pulsar.csproj
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 |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFrameworks>$(BrighterTargetFrameworks)</TargetFrameworks> | ||
| <Description>Provides an implementation of the messaging gateway for decoupled invocation in the Paramore.Brighter pipeline, using Apache Pulsar</Description> | ||
| <PackageTags>Apache Pulsar;Pub/Sub;Command;Event;Service Activator;Decoupled;Invocation;Messaging;Remote;Command Dispatcher;Command Processor;Request;Service;Task Queue;Work Queue;Retry;Circuit Breaker;Availability</PackageTags> | ||
| <Authors>Rafael Andrade</Authors> | ||
| <Nullable>enable</Nullable> | ||
| <SignAssembly>false</SignAssembly> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\Paramore.Brighter\Paramore.Brighter.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="DotPulsar" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> | ||
| <PackageReference Include="System.Threading.Channels" /> | ||
| </ItemGroup> | ||
| </Project> |
119 changes: 119 additions & 0 deletions
119
src/Paramore.Brighter.MessagingGateway.Pulsar/PulsarBackgroundMessageConsumer.cs
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 |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| using System.Buffers; | ||
| using System.Threading; | ||
| using System.Threading.Channels; | ||
| using System.Threading.Tasks; | ||
| using DotPulsar.Abstractions; | ||
|
|
||
| namespace Paramore.Brighter.MessagingGateway.Pulsar; | ||
|
|
||
| /// <summary> | ||
| /// Background message consumer for Apache Pulsar that buffers messages in a bounded channel. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// This class manages a background message consumption loop that: | ||
| /// <list type="bullet"> | ||
| /// <item><description>Receives messages from Pulsar using an <see cref="IConsumer{T}"/></description></item> | ||
| /// <item><description>Writes messages to a bounded channel for consumption by other components</description></item> | ||
| /// <item><description>Implements reference counting for safe start/stop operations</description></item> | ||
| /// </list> | ||
| /// | ||
| /// The consumer uses a fire-and-forget pattern where the consumption loop runs independently once started. | ||
| /// </remarks> | ||
| /// <param name="maxLenght">Maximum number of messages to buffer in the channel</param> | ||
| /// <param name="consumer">Pulsar message consumer implementation</param> | ||
| public sealed class PulsarBackgroundMessageConsumer(int maxLenght, IConsumer<ReadOnlySequence<byte>> consumer) | ||
| { | ||
| private int _total; | ||
| private CancellationTokenSource? _cancellationTokenSource; | ||
| private readonly Channel<IMessage<ReadOnlySequence<byte>>> _channel = System.Threading.Channels.Channel.CreateBounded<IMessage<ReadOnlySequence<byte>>>(new BoundedChannelOptions(maxLenght) | ||
| { | ||
| SingleReader = false, SingleWriter = true | ||
| }); | ||
|
|
||
| /// <summary> | ||
| /// Provides read access to the message channel | ||
| /// </summary> | ||
| public ChannelReader<IMessage<ReadOnlySequence<byte>>> Reader => _channel.Reader; | ||
|
|
||
| /// <summary> | ||
| /// Gets the underlying Pulsar consumer instance | ||
| /// </summary> | ||
| public IConsumer<ReadOnlySequence<byte>> Consumer => consumer; | ||
|
|
||
| /// <summary> | ||
| /// Starts the background message consumption loop | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Implements reference counting: | ||
| /// <list type="bullet"> | ||
| /// <item><description>First call starts the background loop</description></item> | ||
| /// <item><description>Subsequent calls increment the reference count but don't start additional loops</description></item> | ||
| /// </list> | ||
| /// </remarks> | ||
| public void Start() | ||
| { | ||
| var total = Interlocked.Increment(ref _total); | ||
| if (total == 1) | ||
| { | ||
| _cancellationTokenSource = new CancellationTokenSource(); | ||
| _ = ExecuteAsync(_cancellationTokenSource.Token); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Background message consumption loop | ||
| /// </summary> | ||
| /// <param name="cancellationToken">Cancellation token to stop the loop</param> | ||
| /// <remarks> | ||
| /// Continuously performs: | ||
| /// <list type="number"> | ||
| /// <item><description>Receive message from Pulsar</description></item> | ||
| /// <item><description>Write message to output channel</description></item> | ||
| /// <item><description>Wait for channel write availability</description></item> | ||
| /// </list> | ||
| /// | ||
| /// <para>Errors during message reception are silently ignored to maintain loop continuity.</para> | ||
| /// </remarks> | ||
| private async Task ExecuteAsync(CancellationToken cancellationToken) | ||
| { | ||
| while (!cancellationToken.IsCancellationRequested) | ||
| { | ||
| try | ||
| { | ||
| var message = await consumer.Receive(cancellationToken); | ||
|
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. This feels like a pump within our pump? |
||
| if (message is null) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| await _channel.Writer.WriteAsync(message, cancellationToken); | ||
| await _channel.Writer.WaitToWriteAsync(cancellationToken); | ||
| } | ||
| catch | ||
| { | ||
| // Ignoring any errors | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Stops the background message consumption loop | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Implements reference counting: | ||
| /// <list type="bullet"> | ||
| /// <item><description>Decrements the reference count</description></item> | ||
| /// <item><description>Stops the loop when reference count reaches zero</description></item> | ||
| /// </list> | ||
| /// | ||
| /// Safe to call multiple times - only the last call that brings the count to zero will stop the loop. | ||
| /// </remarks> | ||
| public void Stop() | ||
| { | ||
| var total = Interlocked.Decrement(ref _total); | ||
| if (total == 0 && _cancellationTokenSource != null) | ||
| { | ||
| _cancellationTokenSource.Cancel(); | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
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.
Why do we have a background loop here? Why not just consume as we do elsewhere and rely on the single-threaded pump to scale performers?