-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: RebalanceListener interface added so a rebalance listener can b…
…e set to Streams (#100) Co-authored-by: Marcos Schroh <[email protected]>
- Loading branch information
1 parent
ddf0b26
commit d92423d
Showing
14 changed files
with
586 additions
and
20 deletions.
There are no files selected for viewing
This file contains 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 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,55 @@ | ||
# Stream with a Rebalance Listener | ||
|
||
Simple consumer example with `kstreams` that has a custom `RebalanceListener` | ||
|
||
## Requirements | ||
|
||
python 3.8+, poetry, docker-compose | ||
|
||
## Installation | ||
|
||
```bash | ||
poetry install | ||
``` | ||
|
||
## Usage | ||
|
||
1. Start the kafka cluster: From `kstreams` project root execute `./scripts/cluster/start` | ||
2. Inside this folder execute `poetry run app` | ||
3. From `kstreams` project root, you can use the `./scripts/cluster/events/send` to send events to the kafka cluster. A prompt will open. Enter messages to send. The command is: | ||
|
||
```bash | ||
./scripts/cluster/events/send "local--hello-world" | ||
``` | ||
|
||
Then, on the consume side, you should see something similar to the following logs: | ||
|
||
```bash | ||
❯ me@me-pc stream-with-rebalance-listener % poetry run app | ||
|
||
Consumer started | ||
|
||
Group Coordinator Request failed: [Error 15] CoordinatorNotAvailableError | ||
Group Coordinator Request failed: [Error 15] CoordinatorNotAvailableError | ||
Group Coordinator Request failed: [Error 15] CoordinatorNotAvailableError | ||
Group Coordinator Request failed: [Error 15] CoordinatorNotAvailableError | ||
Group Coordinator Request failed: [Error 15] CoordinatorNotAvailableError | ||
|
||
Partition revoked set() for stream <kstreams.streams.Stream object at 0x10a060650> | ||
Partition assigned {TopicPartition(topic='local--hello-world', partition=0)} for stream <kstreams.streams.Stream object at 0x10a060650> | ||
|
||
|
||
Event consumed: headers: (), payload: ConsumerRecord(topic='local--hello-world', partition=0, offset=0, timestamp=1660733921761, timestamp_type=0, key=None, value=b'boo', checksum=None, serialized_key_size=-1, serialized_value_size=3, headers=()) | ||
``` | ||
|
||
Then if you run the same program in a different terminal you should see that the callbacks are called again: | ||
|
||
```bash | ||
Partition revoked frozenset({TopicPartition(topic='local--hello-world', partition=0)}) for stream <kstreams.streams.Stream object at 0x10a060650> | ||
Partition assigned set() for stream <kstreams.streams.Stream object at 0x10a060650> | ||
``` | ||
|
||
## Note | ||
|
||
If you plan on using this example, pay attention to the `pyproject.toml` dependencies, where | ||
`kstreams` is pointing to the parent folder. You will have to set the latest version. |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains 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,19 @@ | ||
[tool.poetry] | ||
name = "stream-with-rebalance-listener" | ||
version = "0.1.0" | ||
description = "" | ||
authors = ["Marcos Schroh <[email protected]>"] | ||
readme = "README.md" | ||
packages = [{include = "stream_with_rebalance_listener"}] | ||
|
||
[tool.poetry.dependencies] | ||
python = "^3.8" | ||
aiorun = "^2022.4.1" | ||
kstreams = { path = "../../.", develop = true } | ||
|
||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" | ||
|
||
[tool.poetry.scripts] | ||
app = "stream_with_rebalance_listener.app:main" |
Empty file.
47 changes: 47 additions & 0 deletions
47
examples/stream-with-rebalance-listener/stream_with_rebalance_listener/app.py
This file contains 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,47 @@ | ||
import signal | ||
from typing import List | ||
|
||
import aiorun | ||
|
||
import kstreams | ||
|
||
stream_engine = kstreams.create_engine(title="my-stream-engine") | ||
|
||
|
||
class MyListener(kstreams.RebalanceListener): | ||
async def on_partitions_revoked( | ||
self, revoked: List[kstreams.TopicPartition] | ||
) -> None: | ||
print(f"Partition revoked {revoked} for stream {self.stream}") | ||
|
||
async def on_partitions_assigned( | ||
self, assigned: List[kstreams.TopicPartition] | ||
) -> None: | ||
print(f"Partition assigned {assigned} for stream {self.stream}") | ||
|
||
|
||
@stream_engine.stream( | ||
topics=["local--hello-world"], | ||
group_id="example-group", | ||
rebalance_listener=MyListener(), | ||
) | ||
async def consume(stream): | ||
print("Consumer started") | ||
try: | ||
async for cr in stream: | ||
print(f"Event consumed: headers: {cr.headers}, payload: {cr}") | ||
finally: | ||
# Terminate the program if something fails. (aiorun will cath this signal and properly shutdown this program.) | ||
signal.alarm(signal.SIGTERM) | ||
|
||
|
||
async def start(): | ||
await stream_engine.start() | ||
|
||
|
||
async def shutdown(loop): | ||
await stream_engine.stop() | ||
|
||
|
||
def main(): | ||
aiorun.run(start(), stop_on_unhandled_errors=True, shutdown_callback=shutdown) |
This file contains 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 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 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,91 @@ | ||
from typing import List | ||
|
||
from aiokafka.abc import ConsumerRebalanceListener | ||
|
||
from kstreams import TopicPartition | ||
|
||
|
||
# Can not use a Protocol here because aiokafka forces to have a concrete instance | ||
# that inherits from ConsumerRebalanceListener, if we use a protocol we will | ||
# have to force the end users to import the class and inherit from it, | ||
# then we will mix protocols and inheritance | ||
class RebalanceListener(ConsumerRebalanceListener): | ||
""" | ||
A callback interface that the user can implement to trigger custom actions | ||
when the set of partitions are assigned or revoked to the `Stream`. | ||
!!! Example | ||
```python | ||
from kstreams import RebalanceListener, TopicPartition | ||
from .resource import stream_engine | ||
class MyRebalanceListener(RebalanceListener): | ||
async def on_partitions_revoked( | ||
self, revoked: List[TopicPartition] | ||
) -> None: | ||
# Do something with the revoked partitions | ||
# or with the Stream | ||
print(self.stream) | ||
async def on_partitions_assigned( | ||
self, assigned: List[TopicPartition] | ||
) -> None: | ||
# Do something with the assigned partitions | ||
# or with the Stream | ||
print(self.stream) | ||
@stream_engine.stream(topic, rebalance_listener=MyRebalanceListener()) | ||
async def my_stream(stream: Stream): | ||
async for event in stream: | ||
... | ||
``` | ||
""" | ||
|
||
def __init__(self) -> None: | ||
self.stream = None | ||
|
||
async def on_partitions_revoked(self, revoked: List[TopicPartition]) -> None: | ||
""" | ||
Coroutine to be called *before* a rebalance operation starts and | ||
*after* the consumer stops fetching data. | ||
If you are using manual commit you have to commit all consumed offsets | ||
here, to avoid duplicate message delivery after rebalance is finished. | ||
Use cases: | ||
- cleanup or custom state save on the start of a rebalance operation | ||
- saving offsets in a custom store | ||
Attributes: | ||
revoked List[TopicPartitions]: Partitions that were assigned | ||
to the consumer on the last rebalance | ||
!!! note | ||
The `Stream` is available using `self.stream` | ||
""" | ||
... # pragma: no cover | ||
|
||
async def on_partitions_assigned(self, assigned: List[TopicPartition]) -> None: | ||
""" | ||
Coroutine to be called *after* partition re-assignment completes | ||
and *before* the consumer starts fetching data again. | ||
It is guaranteed that all the processes in a consumer group will | ||
execute their `on_partitions_revoked` callback before any instance | ||
executes its `on_partitions_assigned` callback. | ||
Use cases: | ||
- Load a state or cache warmup on completion of a successful | ||
partition re-assignment. | ||
Attributes: | ||
assigned List[TopicPartition]: Partitions assigned to the | ||
consumer (may include partitions that were previously assigned) | ||
!!! note | ||
The `Stream` is available using `self.stream` | ||
""" | ||
... # pragma: no cover |
This file contains 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.