github.com/Jeffail/benthos/v3@v3.65.0/lib/input/reader/kinesis_balanced.go (about) 1 //go:build !wasm 2 // +build !wasm 3 4 package reader 5 6 import ( 7 "context" 8 "time" 9 10 "github.com/Jeffail/benthos/v3/lib/log" 11 "github.com/Jeffail/benthos/v3/lib/message" 12 "github.com/Jeffail/benthos/v3/lib/message/batch" 13 "github.com/Jeffail/benthos/v3/lib/metrics" 14 "github.com/Jeffail/benthos/v3/lib/types" 15 sess "github.com/Jeffail/benthos/v3/lib/util/aws/session" 16 "github.com/patrobinson/gokini" 17 ) 18 19 //------------------------------------------------------------------------------ 20 21 // KinesisBalancedConfig is configuration values for the input type. 22 type KinesisBalancedConfig struct { 23 sess.Config `json:",inline" yaml:",inline"` 24 Stream string `json:"stream" yaml:"stream"` 25 DynamoDBTable string `json:"dynamodb_table" yaml:"dynamodb_table"` 26 DynamoDBBillingMode string `json:"dynamodb_billing_mode" yaml:"dynamodb_billing_mode"` 27 DynamoDBReadCapacity int64 `json:"dynamodb_read_provision" yaml:"dynamodb_read_provision"` 28 DynamoDBWriteCapacity int64 `json:"dynamodb_write_provision" yaml:"dynamodb_write_provision"` 29 // TODO: V4 Remove this. 30 MaxBatchCount int `json:"max_batch_count" yaml:"max_batch_count"` 31 Batching batch.PolicyConfig `json:"batching" yaml:"batching"` 32 StartFromOldest bool `json:"start_from_oldest" yaml:"start_from_oldest"` 33 } 34 35 // NewKinesisBalancedConfig creates a new Config with default values. 36 func NewKinesisBalancedConfig() KinesisBalancedConfig { 37 s := sess.NewConfig() 38 return KinesisBalancedConfig{ 39 Config: s, 40 Stream: "", 41 DynamoDBTable: "", 42 DynamoDBBillingMode: "", 43 DynamoDBReadCapacity: 0, 44 DynamoDBWriteCapacity: 0, 45 MaxBatchCount: 1, 46 Batching: batch.NewPolicyConfig(), 47 StartFromOldest: true, 48 } 49 } 50 51 //------------------------------------------------------------------------------ 52 53 // KinesisBalanced is a benthos reader.Type implementation that reads messages 54 // from an Amazon Kinesis stream. 55 type KinesisBalanced struct { 56 conf KinesisBalancedConfig 57 58 lastSequences map[string]*string 59 60 log log.Modular 61 stats metrics.Type 62 kc *gokini.KinesisConsumer 63 records chan *gokini.Records 64 shardID string 65 } 66 67 // NewKinesisBalanced creates a new Amazon Kinesis stream reader.Type. 68 func NewKinesisBalanced( 69 conf KinesisBalancedConfig, 70 log log.Modular, 71 stats metrics.Type, 72 ) (*KinesisBalanced, error) { 73 records := make(chan *gokini.Records) 74 consumer := &KinesisBalanced{ 75 conf: conf, 76 log: log, 77 stats: stats, 78 records: records, 79 } 80 sess, err := conf.GetSession() 81 if err != nil { 82 return nil, err 83 } 84 kc := &gokini.KinesisConsumer{ 85 StreamName: conf.Stream, 86 ShardIteratorType: "TRIM_HORIZON", 87 RecordConsumer: consumer, 88 TableName: conf.DynamoDBTable, 89 EmptyRecordBackoffMs: 1000, 90 DisableAutomaticCheckpoints: true, 91 Session: sess, 92 } 93 if !consumer.conf.StartFromOldest { 94 kc.ShardIteratorType = "LATEST" 95 } 96 if consumer.conf.DynamoDBBillingMode != "" { 97 kc.DynamoBillingMode = &consumer.conf.DynamoDBBillingMode 98 } 99 if consumer.conf.DynamoDBReadCapacity != 0 && consumer.conf.DynamoDBWriteCapacity != 0 { 100 kc.DynamoReadCapacityUnits = &consumer.conf.DynamoDBReadCapacity 101 kc.DynamoWriteCapacityUnits = &consumer.conf.DynamoDBWriteCapacity 102 } 103 104 consumer.kc = kc 105 return consumer, nil 106 } 107 108 // Connect attempts to establish a connection to the target Kinesis stream. 109 func (k *KinesisBalanced) Connect() error { 110 return k.ConnectWithContext(context.Background()) 111 } 112 113 // ConnectWithContext attempts to establish a connection to the target Kinesis 114 // stream. 115 func (k *KinesisBalanced) ConnectWithContext(ctx context.Context) error { 116 err := k.kc.StartConsumer() 117 118 k.log.Infof("Receiving Amazon Kinesis messages from stream: %v\n", k.conf.Stream) 119 return err 120 } 121 122 func (k *KinesisBalanced) setMetadata(record *gokini.Records, p types.Part) { 123 met := p.Metadata() 124 met.Set("kinesis_shard", k.shardID) 125 met.Set("kinesis_partition_key", record.PartitionKey) 126 met.Set("kinesis_sequence_number", record.SequenceNumber) 127 } 128 129 // ReadWithContext attempts to read a new message from the target Kinesis 130 // stream. 131 func (k *KinesisBalanced) ReadWithContext(ctx context.Context) (types.Message, AsyncAckFn, error) { 132 var record *gokini.Records 133 select { 134 case record = <-k.records: 135 case <-ctx.Done(): 136 return nil, nil, types.ErrTimeout 137 } 138 if record == nil { 139 return nil, nil, types.ErrTimeout 140 } 141 142 part := message.NewPart(record.Data) 143 k.setMetadata(record, part) 144 145 msg := message.New(nil) 146 msg.Append(part) 147 148 return msg, func(rctx context.Context, res types.Response) error { 149 return k.kc.Checkpoint(record.ShardID, record.SequenceNumber) 150 }, nil 151 } 152 153 // Read attempts to read a new message from the target Kinesis stream. 154 func (k *KinesisBalanced) Read() (types.Message, error) { 155 msg := message.New(nil) 156 157 record := <-k.records 158 if record == nil { 159 return nil, types.ErrTimeout 160 } 161 k.lastSequences[record.ShardID] = &record.SequenceNumber 162 { 163 part := message.NewPart(record.Data) 164 k.setMetadata(record, part) 165 msg.Append(part) 166 } 167 168 batchLoop: 169 for i := 1; i < k.conf.MaxBatchCount; i++ { 170 select { 171 case record := <-k.records: 172 if record != nil { 173 k.lastSequences[record.ShardID] = &record.SequenceNumber 174 part := message.NewPart(record.Data) 175 k.setMetadata(record, part) 176 msg.Append(part) 177 } else { 178 break batchLoop 179 } 180 default: 181 // Drained the buffer 182 break batchLoop 183 } 184 } 185 186 return msg, nil 187 } 188 189 // Acknowledge confirms whether or not our unacknowledged messages have been 190 // successfully propagated or not. 191 func (k *KinesisBalanced) Acknowledge(err error) error { 192 if err == nil && k.lastSequences != nil { 193 for shard, sequence := range k.lastSequences { 194 err := k.kc.Checkpoint(shard, *sequence) 195 if err != nil { 196 return err 197 } 198 delete(k.lastSequences, shard) 199 } 200 } 201 return nil 202 } 203 204 // CloseAsync begins cleaning up resources used by this reader asynchronously. 205 func (k *KinesisBalanced) CloseAsync() { 206 go k.kc.Shutdown() 207 } 208 209 // WaitForClose will block until either the reader is closed or a specified 210 // timeout occurs. 211 func (k *KinesisBalanced) WaitForClose(time.Duration) error { 212 return nil 213 } 214 215 // Init is required by the KinesisConsumer interface 216 func (k *KinesisBalanced) Init(shardID string) error { 217 return nil 218 } 219 220 // ProcessRecords implements the KinesisConsumer interface 221 func (k *KinesisBalanced) ProcessRecords(records []*gokini.Records, _ *gokini.KinesisConsumer) { 222 for _, record := range records { 223 k.records <- record 224 } 225 } 226 227 // Shutdown implements the KinesisConsumer interface 228 func (k *KinesisBalanced) Shutdown() { 229 k.log.Infof("Stopping processing of Stream %s Shard %s", k.conf.Stream, k.shardID) 230 close(k.records) 231 } 232 233 //------------------------------------------------------------------------------