Add basic example implementation
The basic example was created by directly translating the official AWS KCL Python sample found at https://github.com/awslabs/amazon-kinesis-client-python. Signed-off-by: Caleb Stewart <caleb.stewart94@gmail.com>
This commit is contained in:
parent
b12921da23
commit
0c630b3628
4 changed files with 221 additions and 0 deletions
|
|
@ -46,6 +46,8 @@ VMware-Go-KCL matches exactly the same interface and programming model from orig
|
||||||
* [Troubleshooting](https://docs.aws.amazon.com/streams/latest/dev/troubleshooting-consumers.html)
|
* [Troubleshooting](https://docs.aws.amazon.com/streams/latest/dev/troubleshooting-consumers.html)
|
||||||
* [Advanced Topics](https://docs.aws.amazon.com/streams/latest/dev/advanced-consumers.html)
|
* [Advanced Topics](https://docs.aws.amazon.com/streams/latest/dev/advanced-consumers.html)
|
||||||
|
|
||||||
|
As a starting point, you can check out the [examples](./examples) directory.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
The vmware-go-kcl-v2 project team welcomes contributions from the community. Before you start working with vmware-go-kcl-v2, please
|
The vmware-go-kcl-v2 project team welcomes contributions from the community. Before you start working with vmware-go-kcl-v2, please
|
||||||
|
|
|
||||||
15
examples/README.md
Normal file
15
examples/README.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Usage Examples
|
||||||
|
This directory provides some basic examples of using the `vmware-go-kcl-v2`
|
||||||
|
package. When in doubt, you should always defer to the upstream AWS KCL
|
||||||
|
documentation, however these examples should get you started in the right
|
||||||
|
direction.
|
||||||
|
|
||||||
|
## Basic Example
|
||||||
|
The [basic] example is a direct translation of the offical
|
||||||
|
[sample_kclpy_app.py] example. The example takes a single positional
|
||||||
|
argument representing the path to a JSON configuration file. An
|
||||||
|
[example configuration] file is also included.
|
||||||
|
|
||||||
|
[basic]: ./basic
|
||||||
|
[example configuration]: ./basic/sample.json
|
||||||
|
[sample_kclpy_app.py]: https://github.com/awslabs/amazon-kinesis-client-python/blob/master/samples/sample_kclpy_app.py
|
||||||
196
examples/basic/main.go
Normal file
196
examples/basic/main.go
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
kcl_config "github.com/vmware/vmware-go-kcl-v2/clientlibrary/config"
|
||||||
|
kcl_interfaces "github.com/vmware/vmware-go-kcl-v2/clientlibrary/interfaces"
|
||||||
|
kcl_worker "github.com/vmware/vmware-go-kcl-v2/clientlibrary/worker"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SampleConfig struct {
|
||||||
|
Stream string `json:"stream"`
|
||||||
|
Application string `json:"application"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
WorkerID string `json:"worker_id"`
|
||||||
|
CheckpointInterval int `json:"checkpoint_interval"`
|
||||||
|
CheckpointRetries int `json:"checkpoint_retries"`
|
||||||
|
CheckpointRetryInterval int `json:"checkpoint_retry_interval"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordProcessor struct {
|
||||||
|
CheckpointInterval time.Duration
|
||||||
|
CheckpointRetryInterval time.Duration
|
||||||
|
CheckpointRetries int
|
||||||
|
LastCheckpoint time.Time
|
||||||
|
LargestSequenceNumber *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called for each record that is pass to ProcessRecords
|
||||||
|
func (p *RecordProcessor) processRecord(data []byte, partitionKey string, sequenceNumber *big.Int) {
|
||||||
|
// Insert your processing logic here
|
||||||
|
logrus.Infof(
|
||||||
|
"Record (Partition Key: %v, Sequence Number: %d, Data Size: %v)",
|
||||||
|
partitionKey,
|
||||||
|
sequenceNumber,
|
||||||
|
len(data),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mostly useless, but copied from official AWS KCL Python example
|
||||||
|
func (p *RecordProcessor) shouldUpdateSequence(sequenceNumber *big.Int) bool {
|
||||||
|
return sequenceNumber.Cmp(p.LargestSequenceNumber) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkpoints with retries on retryable exceptions
|
||||||
|
func (p *RecordProcessor) checkpoint(checkpointer kcl_interfaces.IRecordProcessorCheckpointer, sequenceNumber *big.Int) {
|
||||||
|
// Convert the integer sequence number back to an AWS string
|
||||||
|
seq := aws.String(fmt.Sprintf("%d", sequenceNumber))
|
||||||
|
|
||||||
|
// Try to checkpoint
|
||||||
|
for n := 0; n < p.CheckpointRetries; n++ {
|
||||||
|
// NOTE: I don't know how to distinguish between retryable and non-retryable errors here
|
||||||
|
err := checkpointer.Checkpoint(seq)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("error while checkpointing: %v", err)
|
||||||
|
time.Sleep(p.CheckpointRetryInterval)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// Checkpoint successful, so we are done
|
||||||
|
p.LastCheckpoint = time.Now()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We failed over all retries
|
||||||
|
logrus.Warnf("all checkpoint retries failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IRecordProcessor implementation of ProcessRecords. Called for all incoming
|
||||||
|
// batches of records from Kinesis by the KCL
|
||||||
|
func (p *RecordProcessor) ProcessRecords(input *kcl_interfaces.ProcessRecordsInput) {
|
||||||
|
for _, record := range input.Records {
|
||||||
|
// Parse the sequence number to an integer
|
||||||
|
seq := big.NewInt(0)
|
||||||
|
seq, ok := seq.SetString(*record.SequenceNumber, 10)
|
||||||
|
if !ok || seq == nil {
|
||||||
|
logrus.Infof(
|
||||||
|
"error: faield to parse sequence number to int: %v",
|
||||||
|
*record.SequenceNumber,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the record
|
||||||
|
p.processRecord(record.Data, *record.PartitionKey, seq)
|
||||||
|
|
||||||
|
if p.shouldUpdateSequence(seq) {
|
||||||
|
p.LargestSequenceNumber = seq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Since(p.LastCheckpoint) >= p.CheckpointInterval {
|
||||||
|
p.checkpoint(input.Checkpointer, p.LargestSequenceNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RecordProcessor) Initialize(input *kcl_interfaces.InitializationInput) {
|
||||||
|
p.LargestSequenceNumber = big.NewInt(0)
|
||||||
|
p.LastCheckpoint = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RecordProcessor) Shutdown(input *kcl_interfaces.ShutdownInput) {
|
||||||
|
if input.ShutdownReason != kcl_interfaces.TERMINATE {
|
||||||
|
return
|
||||||
|
} else if err := input.Checkpointer.Checkpoint(nil); err != nil {
|
||||||
|
logrus.Errorf("shutdown checkpoint failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple processor factory which wraps a function in the
|
||||||
|
// appropriate KCL interface
|
||||||
|
type RecordProcessorFactory func() kcl_interfaces.IRecordProcessor
|
||||||
|
|
||||||
|
func (factory RecordProcessorFactory) CreateProcessor() kcl_interfaces.IRecordProcessor {
|
||||||
|
return factory()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) != 2 {
|
||||||
|
logrus.Fatalf("usage: %v path/to/config.json", os.Args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("failed to get worker hostname: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default sample configuration
|
||||||
|
sampleConfig := SampleConfig{
|
||||||
|
WorkerID: hostname,
|
||||||
|
CheckpointInterval: 60,
|
||||||
|
CheckpointRetries: 5,
|
||||||
|
CheckpointRetryInterval: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load configuration
|
||||||
|
stream, err := os.Open(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("%v: could not open config: %v", os.Args[1], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we close the config
|
||||||
|
defer stream.Close()
|
||||||
|
|
||||||
|
if err := json.NewDecoder(stream).Decode(&sampleConfig); err != nil {
|
||||||
|
logrus.Fatalf("%v: could not parse config: %v", os.Args[1], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the KCL configuration from our sample config
|
||||||
|
config := kcl_config.NewKinesisClientLibConfig(
|
||||||
|
sampleConfig.Application,
|
||||||
|
sampleConfig.Stream,
|
||||||
|
sampleConfig.Region,
|
||||||
|
sampleConfig.WorkerID,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create our record processor factory
|
||||||
|
factory := RecordProcessorFactory(func() kcl_interfaces.IRecordProcessor {
|
||||||
|
return &RecordProcessor{
|
||||||
|
CheckpointInterval: time.Duration(sampleConfig.CheckpointInterval) * time.Second,
|
||||||
|
CheckpointRetries: sampleConfig.CheckpointRetries,
|
||||||
|
CheckpointRetryInterval: time.Duration(sampleConfig.CheckpointRetryInterval) * time.Second,
|
||||||
|
LastCheckpoint: time.Now(),
|
||||||
|
LargestSequenceNumber: big.NewInt(0),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create the KCL worker
|
||||||
|
worker := kcl_worker.NewWorker(factory, config)
|
||||||
|
|
||||||
|
logrus.Infof(
|
||||||
|
"Starting KCL Worker (Application Name: %v, Stream Name: %v, Worker ID: %v)",
|
||||||
|
sampleConfig.Application,
|
||||||
|
sampleConfig.Stream,
|
||||||
|
sampleConfig.WorkerID,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start the KCL worker
|
||||||
|
if err := worker.Start(); err != nil {
|
||||||
|
logrus.Fatalf("failed to start kcl worker: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we shutdown
|
||||||
|
defer worker.Shutdown()
|
||||||
|
|
||||||
|
// Wait for an exit signal
|
||||||
|
sigchan := make(chan os.Signal)
|
||||||
|
signal.Notify(sigchan, os.Interrupt, os.Kill)
|
||||||
|
<-sigchan
|
||||||
|
}
|
||||||
8
examples/basic/sample.json
Normal file
8
examples/basic/sample.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"stream": "kclgosample",
|
||||||
|
"application": "GolangKCLSample",
|
||||||
|
"region": "us-east-1",
|
||||||
|
"checkpoint_interval": 60,
|
||||||
|
"checkpoint_retries": 5,
|
||||||
|
"checkpoint_retry_interval": 5
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue