Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/loops/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Apache kafka 卡夫卡流:第n次事件的行动_Apache Kafka_Apache Kafka Streams_Spring Cloud Stream_Spring Cloud Stream Binder Kafka - Fatal编程技术网

Apache kafka 卡夫卡流:第n次事件的行动

Apache kafka 卡夫卡流:第n次事件的行动,apache-kafka,apache-kafka-streams,spring-cloud-stream,spring-cloud-stream-binder-kafka,Apache Kafka,Apache Kafka Streams,Spring Cloud Stream,Spring Cloud Stream Binder Kafka,我试图找到在卡夫卡流中第n个事件上执行动作的最佳方式 我的案例:我有一个包含一些事件的输入流。我必须按eventType==login对它们进行过滤,并在同一accountId的第n次登录(比如第五次登录)时将此事件发送到输出流 经过一些调查和不同的尝试,我得到了下面代码的版本(我使用的是Kotlin) fun过程(){ val userLoginsStoreBuilder=Stores.keyValueStoreBuilder( Stores.persistentKeyValueStore(“

我试图找到在卡夫卡流中第n个事件上执行动作的最佳方式

我的案例:我有一个包含一些事件的输入流。我必须按eventType==login对它们进行过滤,并在同一accountId的第n次登录(比如第五次登录)时将此事件发送到输出流

经过一些调查和不同的尝试,我得到了下面代码的版本(我使用的是Kotlin)

fun过程(){
val userLoginsStoreBuilder=Stores.keyValueStoreBuilder(
Stores.persistentKeyValueStore(“登录”),
Serdes.String(),
Serdes.Integer()
)
val streamsBuilder=streamsBuilder().addStateStore(userCheckInsStoreBuilder)
val inputStream=streamsBuilder.stream(inputTopic)
inputStream.map{键,事件->
KeyValue(key,json.readValue(事件))
}.filter{,event->event.eventType==“login”}
.map{key,event->KeyValue(event.accountId,LoginEvent(key,event))}
.变换(
用户登录变压器(“登录”,5),
“登录”
)
.filter{,value->value}
.map{key,->KeyValue(key.eventKey,json.writeValueAsString(key.eventValue))}
.to(“第五次登录”,生成.with(Serdes.String(),Serdes.String()))
...
}
类UserLoginsTransformer(private val storeName:String,private val loginsThreshold:Int=5):
变压器供应商>{
重写fun get():Transformer>{
返回对象:Transformer>{
私有lateinit变量存储:KeyValueStore
@抑制(“未选中的_CAST”)
重写fun init(上下文:ProcessorContext){
store=context.getStateStore(storeName)作为KeyValueStore
}
覆盖有趣的转换(键:字符串,值:LoginEvent):键值{
val计数器=(存储.获取(键)?:0)+1
如果返回(计数器==loginsThreshold){
store.delete(键)
KeyValue(值,true)
}否则{
储存。放置(钥匙、柜台)
KeyValue(值,false)
}
}
覆盖乐趣关闭(){
}
}
}
}
我最担心的是
transform
函数在我的例子中不是线程安全的。我已经检查了在我的案例中使用的KV存储的实现,这是RocksDB存储(非事务性),因此值可能会在读取和比较之间更新,错误事件将发送到输出

我的其他想法:

  • 使用物化视图作为一个没有转换器的存储,但我一直在使用实现
  • 创建一个将使用TransactionalRocksDB的自定义持久存储(不确定是否值得)
  • 创建一个在内部使用ConcurrentHashMap的定制持久性KV存储(如果我们预期有许多用户,它可能会导致高内存消耗)
  • 还有一点需要注意:我正在使用SpringCloudStream,所以这个框架可能有一个内置的解决方案,但我没有找到

    如果有任何建议,我将不胜感激。提前谢谢

    我最担心的是转换函数在我的例子中不是线程安全的。我已经检查了在我的案例中使用的KV存储的实现,这是RocksDB存储(非事务性),因此值可能会在读取和比较之间更新,错误事件将发送到输出

    没有理由担心。如果使用多个线程运行,则每个线程都将拥有自己的RocksDB,该RocksDB存储总体数据的一个分片(注意,总体状态在输入主题分区中是分片的,并且单个分片永远不会由不同的线程处理)。因此,您的代码将正常工作。您唯一需要确保的是,数据是按
    accountId
    分区的,这样单个帐户的登录事件就可以转到同一个shard

    如果输入的数据在写入输入主题时已被
    accountId
    分区,则无需执行任何操作。如果没有,并且您可以控制上游应用程序,那么在上游的应用程序生成器中使用自定义分区器来获得所需的分区可能是最简单的。如果无法更改上游应用程序,则需要在将
    accountId
    设置为新密钥后重新分区数据,即在调用
    transform()
    之前执行
    到()

    我最担心的是转换函数在我的例子中不是线程安全的。我已经检查了在我的案例中使用的KV存储的实现,这是RocksDB存储(非事务性),因此值可能会在读取和比较之间更新,错误事件将发送到输出

    没有理由担心。如果使用多个线程运行,则每个线程都将拥有自己的RocksDB,该RocksDB存储总体数据的一个分片(注意,总体状态在输入主题分区中是分片的,并且单个分片永远不会由不同的线程处理)。因此,您的代码将正常工作。您唯一需要确保的是,数据是按
    accountId
    分区的,这样单个帐户的登录事件就可以转到同一个shard


    如果输入的数据在写入输入主题时已被
    accountId
    分区,则无需执行任何操作。如果没有,并且您可以控制上游应用程序,那么在上游的应用程序生成器中使用自定义分区器来获得所需的分区可能是最简单的。如果无法更改上游应用程序,则需要在将
    accountId
    设置为新密钥后重新分配数据,即在调用
    transform()
    之前执行
    through()
    ,我无法控制上游,因此需要重新分配数据
    data class Event(
        val payload: Any = {},
        val accountId: String,
        val eventType: String = ""
    )
    
    // intermediate class to keep the key and value of the original event
    data class LoginEvent(
        val eventKey: String,
        val eventValue: Event
    )
    
    fun process() {
            val userLoginsStoreBuilder = Stores.keyValueStoreBuilder(
                Stores.persistentKeyValueStore("logins"),
                Serdes.String(),
                Serdes.Integer()
            )
            val streamsBuilder = StreamsBuilder().addStateStore(userCheckInsStoreBuilder)
            val inputStream = streamsBuilder.stream<String, String>(inputTopic)
    
            inputStream.map { key, event ->
                KeyValue(key, json.readValue<Event>(event))
            }.filter { _, event -> event.eventType == "login" }
                 .map { key, event -> KeyValue(event.accountId, LoginEvent(key, event)) }
                 .transform(
                        UserLoginsTransformer("logins", 5),
                        "logins"
                    )
                 .filter { _, value -> value }
                 .map { key, _ -> KeyValue(key.eventKey, json.writeValueAsString(key.eventValue)) }
                 .to("fifth_login", Produced.with(Serdes.String(), Serdes.String()))
    
            ...
        }
    
    class UserLoginsTransformer(private val storeName: String, private val loginsThreshold: Int = 5) :
        TransformerSupplier<String, CheckInEvent, KeyValue< LoginEvent, Boolean>> {
    
        override fun get(): Transformer<String, LoginEvent, KeyValue< LoginEvent, Boolean>> {
            return object : Transformer<String, LoginEvent, KeyValue< LoginEvent, Boolean>> {
                private lateinit var store: KeyValueStore<String, Int>
    
                @Suppress("UNCHECKED_CAST")
                override fun init(context: ProcessorContext) {
                    store = context.getStateStore(storeName) as KeyValueStore<String, Int>
                }
    
                override fun transform(key: String, value: LoginEvent): KeyValue< LoginEvent, Boolean> {
                    val counter = (store.get(key) ?: 0) + 1
                    return if (counter == loginsThreshold) {
                        store.delete(key)
                        KeyValue(value, true)
                    } else {
                        store.put(key, counter)
                        KeyValue(value, false)
                    }
                }
    
                override fun close() {
                }
            }
        }
    }