@inponomarev
Иван Пономарёв, КУРС/МФТИ
![]() |
|
![]() |
![]() | Лекция 1.
Лекция 2.
| ![]() |
|
![]() |
|
![]() |
|





// hash the keyBytes to choose a partition
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;













Мониторинг! Логи!
Отслеживание действий пользователей
Выявление аномалий (в т. ч. попыток мошенничества)
![]() |
![]() |
![]() | Лекция 1.
Лекция 2.
| ![]() |
StreamsConfig config = ...;
//Здесь устанавливаем всякие опции
Topology topology = new StreamsBuilder()
//Здесь строим топологию
....build();Топология — конвейер обработчиков:

|
(Автор анимаций — Тагир Валеев, движущиеся картинки см. здесь) |
| |
| |
|
«Соединить два файла, привести их строки к lowercase, отсортировать, вывести три последних строки в алфавитном порядке»
cat file1 file2 | tr "[A-Z]" "[a-z]" | sort | tail -3StreamsConfig config = ...;
//Здесь устанавливаем всякие опции
Topology topology = new StreamsBuilder()
//Здесь строим топологию
....build();
//Это за нас делает SPRING-KAFKA
KafkaStreams streams = new KafkaStreams(topology, config);
streams.start();
...
streams.close();@Bean KafkaStreamsConfiguration
@Bean Topology
![]() |
|
//ВАЖНО!
@Bean(name =
KafkaStreamsDefaultConfiguration
.DEFAULT_STREAMS_CONFIG_BEAN_NAME)
public KafkaStreamsConfiguration getStreamsConfig() {
Map<String, Object> props = new HashMap<>();
//ВАЖНО!
props.put(StreamsConfig.APPLICATION_ID_CONFIG,
"stateless-demo-app");
//ВАЖНО!
props.put(StreamsConfig.NUM_STREAM_THREADS_CONFIG, 4);
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
...
KafkaStreamsConfiguration streamsConfig =
new KafkaStreamsConfiguration(props);
return streamsConfig;
}@Bean
NewTopic getFilteredTopic() {
Map<String, String> props = new HashMap<>();
props.put(
TopicConfig.CLEANUP_POLICY_CONFIG,
TopicConfig.CLEANUP_POLICY_COMPACT);
return new NewTopic("mytopic", 10, (short) 1).configs(props);
}
@Bean
public Topology createTopology(StreamsBuilder streamsBuilder) {
KStream<String, Bet> input = streamsBuilder.stream(...);
KStream<String, Long> gain
= input.mapValues(v -> Math.round(v.getAmount() * v.getOdds()));
gain.to(GAIN_TOPIC, Produced.with(Serdes.String(),
new JsonSerde<>(Long.class)));
return streamsBuilder.build();
}Больше сообщений в секунду? — больше машин с одинаковым application.id!



KafkaStreamsConfiguration config = new KafkaConfiguration()
.getStreamsConfig();
StreamsBuilder sb = new StreamsBuilder();
Topology topology = new TopologyConfiguration().createTopology(sb);
TopologyTestDriver topologyTestDriver =
new TopologyTestDriver(topology,
config.asProperties());TestInputTopic<String, Bet> inputTopic =
topologyTestDriver.createInputTopic(BET_TOPIC,
Serdes.String().serializer(),
new JsonSerde<>(Bet.class).serializer());
TestOutputTopic<String, Long> outputTopic =
topologyTestDriver.createOutputTopic(GAIN_TOPIC,
Serdes.String().deserializer(),
new JsonSerde<>(Long.class).deserializer());Bet bet = Bet.builder()
.bettor("John Doe")
.match("Germany-Belgium")
.outcome(Outcome.H)
.amount(100)
.odds(1.7).build();
inputTopic.pipeInput(bet.key(), bet);TestRecord<String, Long> record = outputTopic.readRecord();
assertEquals(bet.key(), record.key());
assertEquals(170L, record.value().longValue());default.deserialization.exception.handler — не смогли десериализовать
default.production.exception.handler — брокер отверг сообщение (например, оно слишком велико)

streams.setUncaughtExceptionHandler(
(Thread thread, Throwable throwable) -> {
. . .
});

В Спринге всё сложнее (см. код)

Java-стримы так не могут:
KStream<..> foo = ...
KStream<..> bar = foo.mapValues(…).map... to...
Kstream<..> baz = foo.filter(…).map... forEach...

С версии 2.8:
gain.split()
.branch((key, value) -> key.contains("A"),
Branched.withConsumer(ks -> ks.to("A")))
.branch((key, value) -> key.contains("B"),
Branched.withConsumer(ks -> ks.to("B")));

KStream<String, Integer> foo = ...
KStream<String, Integer> bar = ...
KStream<String, Integer> merge = foo.merge(bar);

![]() | Лекция 1.
Лекция 2.
| ![]() |
Facebook’s RocksDB — что это и зачем?
![]() |
|
TreeMap<K,V>Сохранение K,V в бинарном формате
Лексикографическая сортировка
Iterator (snapshot view)
Удаление диапазона (deleteRange)
Какова сумма выплат по сделанным ставкам, если сыграет исход?

KStream<String, Bet> input = streamsBuilder.
stream(BET_TOPIC, Consumed.with(Serdes.String(),
new JsonSerde<>(Bet.class)));
KStream<String, Long> counted =
new TotallingTransformer()
.transformStream(streamsBuilder, input);@Override
public KeyValue<String, Long> transform(String key, Bet value,
KeyValueStore<String, Long> stateStore) {
long current = Optional
.ofNullable(stateStore.get(key))
.orElse(0L);
current += value.getAmount();
stateStore.put(key, current);
return KeyValue.pair(key, current);
}@Test
void testTopology() {
topologyTestDriver.pipeInput(...);
topologyTestDriver.pipeInput(...);
KeyValueStore<String, Long> store =
topologyTestDriver
.getKeyValueStore(TotallingTransformer.STORE_NAME);
assertEquals(..., store.get(...));
assertEquals(..., store.get(...));
}Ребалансировка / репликация партиций state при запуске / выключении обработчиков.
$kafka-topics --zookeeper localhost --describe
Topic:bet-totalling-demo-app-totalling-store-changelog
PartitionCount:10
ReplicationFactor:1
Configs:cleanup.policy=compact










Явное при помощи
repartition(Repartitioned<K, V> repartitioned)
Неявное при операциях, меняющих ключ + stateful-операциях
KStream source = builder.stream("topic1");
KStream mapped = source.map(...);
KTable counts = mapped.groupByKey().aggregate(...);
KStream sink = mapped.leftJoin(counts, ...);

KStream source = builder.stream("topic1");
KStream shuffled = source.map(...).repartition(...);
KTable counts = shuffled.groupByKey().aggregate(...);
KStream sink = shuffled.leftJoin(counts, ...);

Key only: selectKey
Key and Value | Value Only |
|
|
|
|
|
|
|
|