├── .env.example ├── .gitignore ├── Dockerfile.app ├── Dockerfile.kafka ├── LICENSE.md ├── README.md ├── docker-compose.yml ├── docs ├── cheatsheet │ ├── 000-kafka-client-setup.md │ ├── 001-kafka-consume-or-produce.md │ ├── 002-kafka-topics-and-partitions.md │ ├── 003-kafka-consumer-groups.md │ └── 004-kafka-acl.md ├── guide │ ├── 001-intro.md │ ├── 002-getting-started.md │ ├── 003-topics-and-partitions.md │ ├── 004-producers.md │ ├── 005-consumers.md │ ├── 006-observability.md │ └── assets │ │ ├── 003-create-topic-popup.png │ │ ├── 003-open-topic-profile-page.png │ │ ├── 003-publish-first-message.png │ │ ├── 003-view-first-message.png │ │ ├── 005-consumer-group-after-rebalance.png │ │ ├── 005-consumer-group-preparing-rebalance.png │ │ ├── 005-edit-consumer-group-confirmation.png │ │ ├── 005-edit-consumer-group.png │ │ ├── 005-empty-consumer-group.png │ │ ├── 005-open-consumer-group-profile-page.png │ │ ├── 005-open-consumer-group-with-lag-profile-page.png │ │ └── 006-grafana-kafka-producers-and-consumers.png └── theory │ ├── assets │ ├── 004.png │ ├── 008.png │ ├── 010.png │ ├── 011.png │ ├── 012.png │ ├── 013.png │ ├── 018.png │ ├── 025.png │ ├── 028.png │ ├── 031.png │ ├── 035.png │ ├── 038.png │ ├── 041.png │ ├── 042.png │ ├── 046.png │ ├── 049.png │ ├── 052.png │ ├── 055.png │ ├── 069.png │ ├── 075.png │ ├── 080.png │ ├── 083.png │ ├── 090.png │ ├── 104.png │ ├── 108.png │ ├── 110.gif │ ├── 112.gif │ └── 121.png │ ├── slides.pdf │ └── speaker-notes.md ├── examples ├── consumer │ ├── go.mod │ ├── go.sum │ └── main.go └── producer │ ├── go.mod │ ├── go.sum │ └── main.go ├── grafana ├── dashboards │ ├── 001-kafka-status.json │ ├── 002-kafka-performance.json │ └── 003-kafka-producers-and-consumers.json └── provisioning │ ├── dashboards │ └── default.yaml │ └── datasources │ └── datasource.yaml ├── images └── sbermarket-tech-logo.svg ├── jmx-exporter └── kafka.yml └── prometheus └── prometheus.yml /.env.example: -------------------------------------------------------------------------------- 1 | GO_VERSION=1.20 2 | ZK_VERSION=3.8 3 | KAFKA_VERSION=3.3 4 | KAFKA_EXPORTER_VERSION=latest 5 | JMX_JAVAAGENT_VERSION=0.17.2 6 | REDPANDA_CONSOLE_VERSION=2.1.1 7 | GRAFANA_VERSION=latest 8 | PROMETHEUS_VERSION=latest 9 | 10 | JMX_JAVAAGENT_PORT=5556 11 | GRAFANA_PORT=3000 12 | PROMETHEUS_PORT=9090 13 | REDPANDA_CONSOLE_PORT=8080 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # VSCode 2 | .vscode/* 3 | !.vscode/settings.json 4 | !.vscode/tasks.json 5 | !.vscode/launch.json 6 | !.vscode/extensions.json 7 | *.code-workspace 8 | 9 | # Local History for Visual Studio Code 10 | .history/ 11 | 12 | # Mac/OSX 13 | .DS_Store 14 | 15 | # Environments 16 | .env 17 | 18 | # Go binaries 19 | examples/consumer/consumer 20 | examples/consumer/producer -------------------------------------------------------------------------------- /Dockerfile.app: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | 3 | ARG GO_VERSION="1.20" 4 | ARG BUILD_TARGET="consumer" 5 | ARG BUILD_TYPE="debug" 6 | 7 | ################################################################################ 8 | 9 | FROM docker.io/library/golang:${GO_VERSION} AS base-consumer 10 | WORKDIR /usr/src 11 | 12 | COPY examples/consumer/go.mod examples/consumer/go.sum ./ 13 | RUN go mod download && go mod verify 14 | 15 | COPY examples/consumer/ . 16 | RUN go build -v -o /usr/local/bin/app 17 | 18 | ################################################################################ 19 | 20 | FROM docker.io/library/golang:${GO_VERSION} AS base-producer 21 | WORKDIR /usr/src 22 | 23 | COPY examples/producer/go.mod examples/producer/go.sum ./ 24 | RUN go mod download && go mod verify 25 | 26 | COPY examples/producer/ . 27 | RUN go build -v -o /usr/local/bin/app 28 | 29 | ################################################################################ 30 | 31 | FROM base-${BUILD_TARGET} AS base 32 | 33 | FROM docker.io/library/debian:11 AS app-release 34 | COPY --from=base /usr/local/bin/app /usr/local/bin/app 35 | 36 | FROM base AS app-debug 37 | RUN set -eux; \ 38 | DEBIAN_FRONTEND=noninteractive apt-get update; \ 39 | DEBIAN_FRONTEND=noninteractive apt-get install -y -q tmux vim telnet; \ 40 | rm -rf /var/cache/apt; 41 | 42 | ################################################################################ 43 | 44 | FROM app-${BUILD_TYPE} AS app 45 | WORKDIR / 46 | CMD ["app"] -------------------------------------------------------------------------------- /Dockerfile.kafka: -------------------------------------------------------------------------------- 1 | ARG KAFKA_VERSION="3.3" 2 | 3 | FROM docker.io/bitnami/kafka:${KAFKA_VERSION} 4 | ARG JMX_JAVAAGENT_VERSION="0.17.2" 5 | 6 | RUN set -eux; \ 7 | curl \ 8 | https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/${JMX_JAVAAGENT_VERSION}/jmx_prometheus_javaagent-${JMX_JAVAAGENT_VERSION}.jar \ 9 | -o /opt/bitnami/kafka/libs/jmx_prometheus_javaagent-${JMX_JAVAAGENT_VERSION}.jar && \ 10 | chmod 664 /opt/bitnami/kafka/libs/jmx_prometheus_javaagent-${JMX_JAVAAGENT_VERSION}.jar -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2033 Gleb Goncharov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Интенсив «Kafka за 90 минут» 2 | 3 | [![Sponsored by SberMarket Tech](images/sbermarket-tech-logo.svg)](https://sbermarket.ru) 4 | 5 | Интенсив «Kafka за 90 минут» состоит из двух частей: теории и практики. Теория поможет составить ментальную модель Kafka, а практика — попробовать инструмент в действии и получить набор готовых конфигураций для применения их в своих лабораторных и тестовых средах на работе. 6 | 7 | ## Теория 8 | 9 | ### Содержание 10 | 11 | * Расскажем о сценариях использования Kafka. 12 | * Узнаем, что такое консумер, продюсер и брокер. 13 | * Разберём, как связаны топики, партиции и сегменты. 14 | * Поговорим о формате сообщений в Kafka. 15 | * Расскажем о лидере партиций, репликации данных и партицировании. 16 | * Поговорим о гарантиях доставки сообщений и идемпотентности. 17 | * Выясним, что такое консумер-группа и ребалансировка консумеров в ней. 18 | 19 | **Длительность**: 45 минут 20 | 21 | ### Материал 22 | 23 | - [Презентация](docs/theory/slides.pdf) 24 | - [Расшифровка](docs/theory/speaker-notes.md) 25 | 26 | ## Практика 27 | 28 | ### Содержание 29 | 30 | * Склонируем репозиторий с конфигурацией Docker Compose. 31 | * Подберём конфигурации топиков и создадим их. 32 | * Настроим и запустим продюсер. 33 | * Настроим и запустим консумер. 34 | * Изменим оффсет для консумер-группы. 35 | * Посмотрим на основные показатели в Grafana. 36 | 37 | **Длительность**: 30 минут 38 | 39 | ### Материал 40 | 41 | - [Перед началом](docs/guide/001-intro.md) 42 | - [Запуск кластера](docs/guide/002-getting-started.md) 43 | - [Создание топика](docs/guide/003-topics-and-partitions.md) 44 | - [Работа продюсера](docs/guide/004-producers.md) 45 | - [Работа консумера](docs/guide/005-consumers.md) 46 | - [Наблюдаемость Kafka](docs/guide/006-observability.md) 47 | 48 | ## Бонус-трек 49 | 50 | ### Материалы 51 | 52 | * [Kafka: настройка клиента](docs/cheatsheet/000-kafka-client-setup.md) 53 | * [Kafka: чтение и запись в топик](docs/cheatsheet/001-kafka-consume-or-produce.md) 54 | * [Kafka: управление топиками и партициями](docs/cheatsheet/002-kafka-topics-and-partitions.md) 55 | * [Kafka: управление консумер-группами](docs/cheatsheet/003-kafka-consumer-groups.md) 56 | * [Kafka: управление доступами к топикам](docs/cheatsheet/004-kafka-acl.md) -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | ################################################################################ 4 | 5 | x-app: &app 6 | build: &app-build 7 | context: . 8 | dockerfile: Dockerfile.app 9 | profiles: 10 | - app 11 | command: 12 | - /bin/sh 13 | - -c 14 | - "while : ; do sleep 60 ; done" 15 | restart: unless-stopped 16 | 17 | x-consumer: &consumer 18 | <<: *app 19 | command: 20 | - app 21 | environment: 22 | KAFKA_TOPIC: "example" 23 | KAFKA_BROKERS: "kafka-1:9092,kafka-2:9092,kafka-3:9092" 24 | KAFKA_CONSUMER_GROUP: "example-consumer-group" 25 | build: 26 | <<: *app-build 27 | args: 28 | BUILD_TARGET: "consumer" 29 | 30 | x-producer: &producer 31 | <<: *app 32 | command: 33 | - app 34 | environment: 35 | KAFKA_TOPIC: "example" 36 | KAFKA_BROKERS: "kafka-1:9092,kafka-2:9092,kafka-3:9092" 37 | build: 38 | <<: *app-build 39 | args: 40 | BUILD_TARGET: "producer" 41 | 42 | x-kafka: &kafka 43 | build: &kafka-build 44 | context: . 45 | dockerfile: Dockerfile.kafka 46 | args: 47 | JMX_JAVAAGENT_VERSION: "${JMX_JAVAAGENT_VERSION}" 48 | environment: &kafka-env 49 | HOME: /opt/bitnami/kafka 50 | KAFKA_CFG_ZOOKEEPER_CONNECT: "zookeeper:2181" 51 | KAFKA_ENABLE_KRAFT: "no" 52 | KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT" 53 | ALLOW_PLAINTEXT_LISTENER: "yes" 54 | KAFKA_OPTS: >- 55 | -javaagent:/opt/bitnami/kafka/libs/jmx_prometheus_javaagent-${JMX_JAVAAGENT_VERSION}.jar=${JMX_JAVAAGENT_PORT}:/etc/kafka/prometheus/kafka.yml 56 | volumes: 57 | - ./jmx-exporter/kafka.yml:/etc/kafka/prometheus/kafka.yml:ro 58 | depends_on: 59 | - zookeeper 60 | 61 | x-zookeeper: &zookeeper 62 | image: docker.io/bitnami/zookeeper:${ZK_VERSION} 63 | volumes: 64 | - "zookeeper:/bitnami" 65 | environment: 66 | ALLOW_ANONYMOUS_LOGIN: "yes" 67 | 68 | x-redpanda-console: &redpanda-console 69 | image: docker.redpanda.com/vectorized/console:v${REDPANDA_CONSOLE_VERSION} 70 | entrypoint: /bin/sh 71 | command: -c 'echo "$$CONSOLE_CONFIG_FILE" > /tmp/config.yml; /app/console' 72 | environment: 73 | CONFIG_FILEPATH: /tmp/config.yml 74 | CONSOLE_CONFIG_FILE: | 75 | kafka: 76 | brokers: ["kafka-1:9092", "kafka-2:9092", "kafka-3:9092"] 77 | schemaRegistry: 78 | enabled: false 79 | urls: ["http://redpanda-0:8081"] 80 | ports: 81 | - ${REDPANDA_CONSOLE_PORT}:8080 82 | depends_on: 83 | - kafka-1 84 | - kafka-2 85 | - kafka-3 86 | 87 | x-kafka-exporter: &kafka-exporter 88 | image: docker.io/bitnami/kafka-exporter:${KAFKA_EXPORTER_VERSION} 89 | restart: unless-stopped 90 | command: ["--kafka.server=kafka-1:9092"] 91 | # profiles: 92 | # - metrics 93 | 94 | x-prometheus: &prometheus 95 | image: quay.io/prometheus/prometheus:${PROMETHEUS_VERSION} 96 | volumes: 97 | - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml 98 | ports: 99 | - ${PROMETHEUS_PORT}:9090 100 | # profiles: 101 | # - metrics 102 | 103 | x-grafana: &grafana 104 | image: docker.io/grafana/grafana:${GRAFANA_VERSION} 105 | user: root 106 | restart: unless-stopped 107 | ports: 108 | - ${GRAFANA_PORT}:3000 109 | volumes: 110 | - grafana:/var/lib/grafana 111 | - ./grafana/provisioning:/etc/grafana/provisioning 112 | - ./grafana/dashboards:/var/lib/grafana/dashboards 113 | # profiles: 114 | # - metrics 115 | environment: 116 | GF_SECURITY_ADMIN_PASSWORD: admin 117 | 118 | ################################################################################ 119 | 120 | services: 121 | # Sample consumer 122 | consumer-1: 123 | <<: *consumer 124 | container_name: consumer-1 125 | consumer-2: 126 | <<: *consumer 127 | container_name: consumer-2 128 | consumer-3: 129 | <<: *consumer 130 | container_name: consumer-3 131 | 132 | # Sample producer 133 | producer: 134 | <<: *producer 135 | container_name: producer-1 136 | 137 | # Zookeeper quorum 138 | zookeeper: 139 | <<: *zookeeper 140 | container_name: zookeeper 141 | 142 | # Redpanda Console UI 143 | ui: 144 | <<: *redpanda-console 145 | container_name: ui 146 | 147 | # Kafka: broker #1 148 | kafka-1: 149 | <<: *kafka 150 | container_name: kafka-1 151 | environment: 152 | <<: *kafka-env 153 | KAFKA_BROKER_ID: 1 154 | 155 | # Kafka: broker #2 156 | kafka-2: 157 | <<: *kafka 158 | container_name: kafka-2 159 | environment: 160 | <<: *kafka-env 161 | KAFKA_BROKER_ID: 2 162 | 163 | # Kafka: broker #3 164 | kafka-3: 165 | <<: *kafka 166 | container_name: kafka-3 167 | environment: 168 | <<: *kafka-env 169 | KAFKA_BROKER_ID: 3 170 | 171 | # Grafana 172 | grafana: 173 | <<: *grafana 174 | container_name: grafana 175 | 176 | # Prometheus 177 | prometheus: 178 | <<: *prometheus 179 | container_name: prometheus 180 | 181 | # Prometheus 182 | kafka-exporter: 183 | <<: *kafka-exporter 184 | container_name: kafka-exporter 185 | 186 | ################################################################################ 187 | 188 | volumes: 189 | zookeeper: 190 | driver: local 191 | grafana: 192 | driver: local 193 | -------------------------------------------------------------------------------- /docs/cheatsheet/000-kafka-client-setup.md: -------------------------------------------------------------------------------- 1 | # Kafka: настройка клиентов 2 | 3 | ## Kafka CLI 4 | 5 | Создайте конфигурационный файл `$HOME/config.properties`. Для SASL-аутентификации с механизмом SCRAM поверх открытого текста. 6 | 7 | ``` 8 | bootstrap.servers=broker1:9092,broker2:9092,broker3:9092 9 | security.protocol=SASL_PLAINTEXT 10 | sasl.mechanism=SCRAM-SHA-512 11 | sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username="admin" password="keepinsecret"; 12 | ``` 13 | 14 | Если вы не используете конфигурацию, то оставьте файл пустым. 15 | 16 | ```bash 17 | touch $HOME/config.properties 18 | ``` 19 | 20 | ## kafkactl 21 | 22 | Создайте конфигурационный файл `$HOME/.config/kafkactl/config.yml`. 23 | 24 | ```yaml 25 | contexts: 26 | dev: 27 | brokers: 28 | - broker-1:9092 29 | - broker-2:9092 30 | - broker-3:9092 31 | kafkaversion: 2.7.2 32 | producer: 33 | maxmessagebytes: 1000000 34 | partitioner: hash 35 | requiredacks: WaitForAll 36 | requesttimeout: 10s 37 | sasl: 38 | enabled: true 39 | mechanism: scram-sha512 40 | username: admin 41 | password: keepinsecret 42 | 43 | current-context: dev 44 | ``` -------------------------------------------------------------------------------- /docs/cheatsheet/001-kafka-consume-or-produce.md: -------------------------------------------------------------------------------- 1 | # Kafka: чтение и запись 2 | 3 | ## Прочитать топик Kafka 4 | 5 | ### Redpanda Console 6 | 7 | Для просмотра сообщений в топике Kafka, необходимо найти его в секции Topics, а далее во вкладке Messages отобразить список сообщений. При нажатии на значок "плюс" вы можете также просмотреть структуру отдельного сообщения: key, headers и value. 8 | 9 | ### Kafka CLI 10 | 11 | Подключитесь к брокеру Kafka и введите команду, чтобы прочитать топик с самого начала. 12 | 13 | ``` 14 | bin/kafka-console-consumer.sh --consumer.config $HOME/config.properties \ 15 | --bootstrap-server $(hostname):9092 \ 16 | --topic "topic" \ 17 | --from-beginning 18 | ``` 19 | 20 | Показать в выводе время, ключ и значение. 21 | 22 | ``` 23 | bin/kafka-console-consumer.sh --consumer.config $HOME/config.properties \ 24 | --bootstrap-server $(hostname):9092 \ 25 | --topic "topic" \ 26 | --from-beginning \ 27 | --formatter kafka.tools.DefaultMessageFormatter \ 28 | --property print.timestamp=true \ 29 | --property print.key=true \ 30 | --property print.value=true 31 | ``` 32 | 33 | Прочитать сообщения в составе консумер-группы consumer-group. 34 | 35 | ``` 36 | bin/kafka-console-consumer.sh --consumer.config $HOME/config.properties \ 37 | --bootstrap-server $(hostname):9092 \ 38 | --topic "topic" \ 39 | --group "consumer-group" \ 40 | --to-latest 41 | ``` 42 | 43 | ## Записать в топик Kafka 44 | 45 | ### Kafka CLI 46 | Подключитесь к брокеру Kafka и введите команду, чтобы записать данные в топик из stdin. 47 | 48 | ``` 49 | bin/kafka-console-producer.sh --producer.config $HOME/producer.properties \ 50 | --broker-list $(hostname):9092 \ 51 | --topic "$(hostname):9092" 52 | ``` 53 | 54 | Записать данные в формате ключ-значение с разделителем-двоеточием. 55 | 56 | ``` 57 | bin/kafka-console-producer.sh --producer.config $HOME/producer.properties \ 58 | --broker-list $(hostname):9092 \ 59 | --topic "$(hostname):9092" \ 60 | --property parse.key=true \ 61 | --property key.separator=: 62 | ``` -------------------------------------------------------------------------------- /docs/cheatsheet/002-kafka-topics-and-partitions.md: -------------------------------------------------------------------------------- 1 | # Kafka: управление топиками и партициями 2 | 3 | ## Создать топик Kafka 4 | 5 | ### Kafka CLI 6 | Подключитесь к брокеру Kafka и выполните команду для создания топика. 7 | 8 | ``` 9 | bin/kafka-topics.sh --command-config $HOME/config.properties \ 10 | --topic "topic-name" \ 11 | --create \ 12 | --bootstrap-server $(hostname):9092 \ 13 | --replication-factor 2 \ 14 | --partitions 10 15 | ``` 16 | 17 | ## Изменить настройки топика Kafka 18 | 19 | ### Kafka CLI 20 | 21 | Подключитесь к брокеру Kafka и примените конфигурацию для топика. В примере указание политики устаревания по времени (48 часов). 22 | 23 | ``` 24 | bin/kafka-topics.sh --command-config $HOME/config.properties \ 25 | --topic "topic-name" \ 26 | --alter \ 27 | --bootstrap-server $(hostname):9092 \ 28 | --config retention.ms=$((48 * 60 * 60 * 1000)) 29 | ``` 30 | 31 | Определить новое число партиций, например, с целью масштабирования до 4 штук. 32 | 33 | ``` 34 | bin/kafka-topics.sh --command-config $HOME/config.properties \ 35 | --topic "topic-name" \ 36 | --alter \ 37 | --bootstrap-server $(hostname):9092 \ 38 | --partitions 4 39 | ``` 40 | 41 | Определить при этом брокеры для размещения партиций. 42 | 43 | ``` 44 | bin/kafka-topics.sh --command-config $HOME/config.properties \ 45 | --topic "topic-name" \ 46 | --alter \ 47 | --bootstrap-server $(hostname):9092 \ 48 | --replica-assignment 0:1:2,0:1:2,0:1:2,2:1:0 49 | --partitions 4 50 | ``` 51 | 52 | ## Распечатать список топиков Kafka 53 | 54 | ### Kafka CLI 55 | 56 | Подключитесь к брокеру Kafka и выполните следующую команду, чтобы увидеть список топиков со назначенной ему конфигурацией. 57 | 58 | ``` 59 | bin/kafka-topics.sh --command-config $HOME/config.properties \ 60 | --bootstrap-server $(hostname):9092 \ 61 | --describe \ 62 | --topics-with-overrides 63 | ``` 64 | 65 | Без системных топиков (например, `__consumer_offsets`). 66 | 67 | ``` 68 | bin/kafka-topics.sh --command-config $HOME/config.properties \ 69 | --bootstrap-server $(hostname):9092 \ 70 | --describe \ 71 | --topics-with-overrides \ 72 | --exclude-internal 73 | ``` 74 | 75 | Показать список топиков и его нереплицированные партиции. 76 | 77 | ``` 78 | bin/kafka-topics.sh --command-config $HOME/config.properties \ 79 | --bootstrap-server $(hostname):9092 \ 80 | --describe \ 81 | --under-replicated-partitions 82 | ``` 83 | 84 | Показать список топиков и список партиций без активных лидеров. 85 | 86 | ``` 87 | bin/kafka-topics.sh --command-config $HOME/config.properties \ 88 | --bootstrap-server $(hostname):9092 \ 89 | --describe \ 90 | --unavailable-partitions 91 | ``` 92 | 93 | ## Посмотреть конфигурацию топика Kafka 94 | 95 | ### Kafka CLI 96 | 97 | Подключитесь к брокеру Kafka и выполните следующую команду, чтобы увидеть конфигурацию топика. 98 | 99 | ``` 100 | bin/kafka-configs.sh --command-config $HOME/config.properties \ 101 | --bootstrap-server $(hostname):9092 \ 102 | --describe \ 103 | --entity-type topics \ 104 | --entity-name "topic-name" 105 | ``` 106 | 107 | ## Очистить топик Kafka 108 | 109 | ### Kafka CLI 110 | 111 | В настоящий момент не существует команды, что удаляла бы сообщение в Kafka-топике. Для этого необходимо установить низкий retention, а после — вернуть значение на место. Подключитесь к брокеру Kafka и укажите retention размером в минуту (60000ms). 112 | 113 | ``` 114 | bin/kafka-configs.sh --command-config $HOME/config.properties \ 115 | --zookeeper localhost:2181/kafka 116 | --alter \ 117 | --entity-type topics \ 118 | --entity-name "topic-name" \ 119 | --add-config retention.ms=60000 120 | ``` 121 | 122 | Подождите минуту, чтобы Kafka-брокеры успели удалить связанные сегменты данных, а после верните значение retention назад. Например, для 3 суток. 123 | 124 | ``` 125 | bin/kafka-configs.sh --command-config $HOME/config.properties \ 126 | --zookeeper localhost:2181/kafka 127 | --alter \ 128 | --entity-type topics \ 129 | --entity-name "topic-name" \ 130 | --add-config retention.ms=$((72 * 60 * 60 * 1000)) 131 | ``` 132 | 133 | ## Удалить топик Kafka 134 | 135 | Перед удалением необходимо убедиться, что в топике нет записи и нет чтения. Для этого в Redpanda UI во вкладке Consumers должно быть пусто, а во вкладке Messages не должно появляться новых сообщений. 136 | 137 | ### Kafka CLI 138 | Подключитесь к брокеру, остановите консумер-группы и удалите топик. 139 | 140 | ``` 141 | bin/kafka-topics.sh --command-config $HOME/config.properties \ 142 | --topic "topic-name" \ 143 | --delete \ 144 | --bootstrap-server $(hostname):9092 145 | ``` 146 | 147 | ## Распечатать оффсеты партиций топика Kafka 148 | 149 | ### Kafka CLI 150 | 151 | Подключитесь к брокеру и выполните команду, чтобы увидеть список партиций в топике с оффсетами в формате `topic-name:partition-id:offset`. 152 | 153 | ``` 154 | bin/kafka-run-class.sh kafka.tools.GetOffsetShell \ 155 | --command-config $HOME/config.properties \ 156 | --broker-list $(hostname):9092 \ 157 | --topic "topic-name" 158 | ``` 159 | 160 | Показать самый крайний оффсет (latest). 161 | 162 | ``` 163 | bin/kafka-run-class.sh kafka.tools.GetOffsetShell \ 164 | --command-config $HOME/config.properties \ 165 | --broker-list $(hostname):9092 \ 166 | --topic "topic-name" \ 167 | --time -1 168 | ``` 169 | 170 | Показать самый ранний оффсет (earliest). 171 | 172 | ``` 173 | bin/kafka-run-class.sh kafka.tools.GetOffsetShell \ 174 | --command-config $HOME/config.properties \ 175 | --broker-list $(hostname):9092 \ 176 | --topic "topic-name" \ 177 | --time -2 178 | ``` 179 | 180 | Посмотреть оффсет партиции топика Kafka. 181 | 182 | ``` 183 | bin/kafka-run-class.sh kafka.tools.GetOffsetShell \ 184 | --command-config $HOME/config.properties \ 185 | --broker-list $(hostname):9092 \ 186 | --topic "topic-name" \ 187 | --partitions "partition-id1, partition-id2" 188 | ``` 189 | 190 | ## Увеличить фактор репликации топика Kafka 191 | 192 | ### Kafka CLI 193 | 194 | Подготовьте план переназначения партиции в формате JSON. В примере мы используем фактор репликации 3. 195 | 196 | ``` 197 | /tmp/reassignment.json 198 | { 199 | "version": 1, 200 | "partitions": [ 201 | { "topic": "topic-name", "partition": 0, "replicas": [0, 1, 2] }, 202 | { "topic": "topic-name", "partition": 1, "replicas": [0, 1, 2] }, 203 | { "topic": "topic-name", "partition": 2, "replicas": [0, 1, 2] } 204 | ] 205 | } 206 | ``` 207 | 208 | Скопируйте конфигурацию на брокер и выполните следующую команду: 209 | 210 | ``` 211 | bin/kafka-reassign-partitions.sh --bootstrap-server $(hostname):9092 \ 212 | --command-config $HOME/config.properties \ 213 | --reassignment-json-file /tmp/reassignment.json \ 214 | --execute 215 | ``` 216 | 217 | ## Перенести партиции топика Kafka на другой брокер 218 | 219 | ### Kafka CLI 220 | 221 | Подготовьте план переназначения партиции в формате JSON. В поле replicas укажите желаемые идентификаторы брокеров. 222 | 223 | ``` 224 | /tmp/reassignment.json 225 | { 226 | "version": 1, 227 | "partitions": [ 228 | { "topic": "topic-name", "partition": 0, "replicas": [0, 1] }, 229 | { "topic": "topic-name", "partition": 1, "replicas": [1, 2] }, 230 | { "topic": "topic-name", "partition": 2, "replicas": [0, 2] } 231 | ] 232 | } 233 | ``` 234 | 235 | Скопируйте конфигурацию на брокер и выполните следующую команду. 236 | 237 | ``` 238 | bin/kafka-reassign-partitions.sh --bootstrap-server $(hostname):9092 \ 239 | --command-config $HOME/config.properties \ 240 | --reassignment-json-file /tmp/reassignment.json \ 241 | --execute 242 | ``` -------------------------------------------------------------------------------- /docs/cheatsheet/003-kafka-consumer-groups.md: -------------------------------------------------------------------------------- 1 | # Kafka: управление консумер-группами 2 | 3 | ## Создать новую консумер-группу Kafka 4 | 5 | Консумер-группы не требуется создавать явно. Соответствующая группа будет создана автоматически при верной настройке консумера на работу с группами. 6 | 7 | ## Распечатать список консумер-групп Kafka 8 | 9 | ### Kafka CLI 10 | 11 | Подключитесь к брокеру Kafka и выполните команду, чтобы вывести на экран список консумер-групп. 12 | 13 | ``` 14 | bin/kafka-consumer-groups.sh --command-config $HOME/config.properties \ 15 | --bootstrap-server $(hostname):9092 \ 16 | --list 17 | ``` 18 | 19 | ## Посмотреть конфигурацию консумер-группы Kafka 20 | 21 | ### Kafka CLI 22 | 23 | Подключитесь к брокеру Kafka и выполните команду, чтобы вывести на экран конфигурацию выбранной консумер-группы и её состояния. 24 | 25 | ``` 26 | bin/kafka-consumer-groups.sh --command-config $HOME/config.properties \ 27 | --bootstrap-server $(hostname):9092 \ 28 | --group "group-name" \ 29 | --state \ 30 | --describe 31 | ``` 32 | 33 | ## Изменить или удалить консумер-группы Kafka 34 | 35 | Любое изменение состава или конфигурации консумер-группы проводится с полным выходом участников из неё. Обратите внимание, что в момент остановки и запуска приложения данные не будут прочитаны. 36 | 37 | Для изменения: 38 | 39 | - Остановите приложение. 40 | - Измените консумер-группу. 41 | - Запустите приложение. 42 | 43 | ### Kafka CLI 44 | 45 | Переместить оффсет для консумер-группы. Например, на 1 января 2022 года на 00:00 MSK. 46 | 47 | ``` 48 | bin/kafka-consumer-groups.sh --command-config $HOME/config.properties \ 49 | --bootstrap-server $(hostname):9092 \ 50 | --topic "topic-name" \ 51 | --group "group-name" \ 52 | --reset-offsets \ 53 | --to-datetime 2022-01-01T00:00:00.000+0300 \ 54 | --execute 55 | ``` 56 | 57 | Переместить оффсет всех партиций на пять записей вперёд. 58 | 59 | ``` 60 | bin/kafka-consumer-groups.sh --command-config $HOME/config.properties \ 61 | --bootstrap-server $(hostname):9092 \ 62 | --topic "topic-name:0" \ 63 | --group "group-name" \ 64 | --reset-offsets \ 65 | --shift-to 5 \ 66 | --execute 67 | ``` 68 | 69 | Переместить оффсет партиции №0 на самый ранний (начать читать с начала). 70 | 71 | ``` 72 | bin/kafka-consumer-groups.sh --command-config $HOME/config.properties \ 73 | --bootstrap-server $(hostname):9092 \ 74 | --topic "topic-name:0" \ 75 | --group "group-name" \ 76 | --reset-offsets \ 77 | --to-earliest \ 78 | --execute 79 | ``` 80 | 81 | Переместить оффсет для партиций №3 и №4 на самый крайний (пропустить все сообщения и начать с конца). 82 | 83 | ``` 84 | bin/kafka-consumer-groups.sh --command-config $HOME/config.properties \ 85 | --bootstrap-server $(hostname):9092 \ 86 | --topic "topic-name:3,4" \ 87 | --group "group-name" \ 88 | --reset-offsets \ 89 | --to-latest \ 90 | --execute 91 | ``` 92 | 93 | Распечатать текущую позицию оффсетов консумер-группы. Предварительно необходимо остановить приложение-консумер. 94 | 95 | ``` 96 | bin/kafka-consumer-groups.sh --command-config $HOME/config.properties \ 97 | --bootstrap-server $(hostname):9092 \ 98 | --topic "topic-name" \ 99 | --group "group-name" \ 100 | --reset-offsets \ 101 | --to-current \ 102 | --export \ 103 | --execute 104 | ``` 105 | 106 | Восстановить оффсеты консумер-группы из CSV-файла offsets.csv. 107 | 108 | ``` 109 | bin/kafka-consumer-groups.sh --command-config $HOME/config.properties \ 110 | --bootstrap-server $(hostname):9092 \ 111 | --topic "topic-name" \ 112 | --group "group-name" \ 113 | --reset-offsets \ 114 | --from-file offsets.csv \ 115 | --execute 116 | ``` 117 | 118 | Удалить оффсет консумер-группы 119 | 120 | ``` 121 | bin/kafka-consumer-groups.sh --command-config $HOME/config.properties \ 122 | --bootstrap-server $(hostname):9092 \ 123 | --topic "topic-name" \ 124 | --group "group-name" \ 125 | --delete-offsets 126 | ``` 127 | 128 | Удалить консумер-группу. 129 | 130 | ``` 131 | bin/kafka-consumer-groups.sh --command-config $HOME/config.properties \ 132 | --bootstrap-server $(hostname):9092 \ 133 | --group "group-name" \ 134 | --delete 135 | ``` -------------------------------------------------------------------------------- /docs/cheatsheet/004-kafka-acl.md: -------------------------------------------------------------------------------- 1 | # Kafka: управление доступами к топикам 2 | 3 | ## Создать нового пользователя 4 | 5 | ### Kafka CLI 6 | 7 | Подключитесь к брокеру и выполните команду. Укажите в параметре password случайно сгенерированный пароль. 8 | 9 | ``` 10 | bin/kafka-configs.sh --alter \ 11 | --entity-type users \ 12 | --entity-name "john-doe" \ 13 | --bootstrap-server $(hostname):9092 \ 14 | --command-config /etc/kafka/config.properties \ 15 | --add-config 'SCRAM-SHA-256=[iterations=8192,password=keepinsecret],SCRAM-SHA-512=[password=keepinsecret]' 16 | ``` 17 | 18 | ## Распечатать список пользователей 19 | 20 | ### Kafka CLI 21 | 22 | Подключитесь к брокеру и выполните команду. 23 | 24 | ``` 25 | bin/kafka-configs.sh --describe \ 26 | --entity-type users \ 27 | --bootstrap-server $(hostname):9092 \ 28 | --command-config /etc/kafka/config.properties 29 | ``` 30 | 31 | ## Удалить пользователя 32 | 33 | ### Kafka CLI 34 | 35 | Подключитесь к брокеру и выполните команду для выдачи прав на чтение топика консумеру. 36 | 37 | ``` 38 | bin/kafka-configs.sh --delete \ 39 | --entity-type users \ 40 | --entity-name "john-doe" \ 41 | --bootstrap-server $(hostname):9092 \ 42 | --command-config /etc/kafka/config.properties 43 | ``` 44 | 45 | ## Выдать права консумер-группе 46 | 47 | ### Kafka CLI 48 | 49 | Подключитесь к брокеру и выполните команду для выдачи прав на чтение топика консумером. 50 | 51 | ``` 52 | bin/kafka-acls.sh --add \ 53 | --bootstrap-server $(hostname):9092 \ 54 | --allow-principal User:producer-user \ 55 | --topic "topic-name" \ 56 | --operation Write \ 57 | --command-config /etc/kafka/config.properties 58 | ``` 59 | 60 | ## Выдать права продюсеру 61 | 62 | ### Kafka CLI 63 | 64 | Подключитесь к брокеру и выполните команду для выдачи прав на запись в топик продюсеру. 65 | 66 | ``` 67 | bin/kafka-acls.sh --add \ 68 | --bootstrap-server $(hostname):9092 \ 69 | --allow-principal User:producer-user \ 70 | --topic "topic-name" \ 71 | --operation Write \ 72 | --command-config /etc/kafka/config.properties 73 | ``` 74 | 75 | ## Распечатать список прав 76 | 77 | ### Kafka CLI 78 | 79 | Подключитесь к брокеру и выполните команду. 80 | 81 | ``` 82 | bin/kafka-acls.sh --list \ 83 | --bootstrap-server $(hostname):9092 \ 84 | --command-config /etc/kafka/config.properties 85 | ``` 86 | 87 | ## Забрать права у продюсера 88 | 89 | ### Kafka CLI 90 | 91 | Подключитесь к брокеру и выполните команду, чтобы отобрать права. 92 | 93 | ``` 94 | bin/kafka-acls.sh --remove \ 95 | --bootstrap-server $(hostname):9092 \ 96 | --allow-principal User:producer-user \ 97 | --topic "topic-name" \ 98 | --operation Write \ 99 | --command-config /etc/kafka/config.properties 100 | ``` 101 | 102 | ## Забрать права у консумера 103 | 104 | ### Kafka CLI 105 | 106 | Подключитесь к брокеру и выполните команду, чтобы отобрать права. 107 | 108 | ``` 109 | bin/kafka-acls.sh --remove \ 110 | --bootstrap-server $(hostname):9092 \ 111 | --allow-principal User:consumer-user \ 112 | --topic "topic-name" \ 113 | --operation Read \ 114 | --command-config /etc/kafka/config.properties 115 | ``` 116 | 117 | ## Удаление пользователя 118 | 119 | ### Kafka CLI 120 | 121 | Чтобы удалить пользователя, достаточно забрать все права со всех топиков, которые ему назначены. -------------------------------------------------------------------------------- /docs/guide/001-intro.md: -------------------------------------------------------------------------------- 1 | # Подготовка 2 | 3 | Для работы вам необходимо: 4 | 5 | - [Docker](https://www.docker.com/products/docker-desktop/) _(24.0 или старше)_ 6 | - [Docker Compose](https://docs.docker.com/compose/) _(v2.19.0 или старше)_ 7 | - Доступ в Интернет. 8 | 9 | Мы тестировали курс только на Linux и Mac OS, но, скорее всего, команды без проблем заработают и под Windows. 10 | 11 | Рекомендуем заранее загрузить Docker-образы на хорошем Интернет-подключении (1.3GB). 12 | 13 | Скопируйте конфигурационный файл: 14 | 15 | ```bash 16 | cp .env.example .env 17 | ``` 18 | 19 | Загрузите необходимые образы и соберите тестовые приложения. 20 | 21 | ```bash 22 | docker-compose --profile app pull 23 | docker-compose --profile app build 24 | ``` 25 | 26 | _На соединении в 8Mbps вам потребуется около 20-25 минут на загрузку образов_. 27 | 28 | --- 29 | 30 | ✅ Готово. Переходите к [запуску кластера](./002-getting-started.md). 31 | -------------------------------------------------------------------------------- /docs/guide/002-getting-started.md: -------------------------------------------------------------------------------- 1 | # Начало работы 2 | 3 | ## Запуск кластера 4 | 5 | Убедитесь, что конфигурационный файл `.env` есть в директории проекта: 6 | 7 | ```bash 8 | stat .env 9 | ``` 10 | 11 | Запустите кластер: 12 | 13 | ```bash 14 | docker-compose up -d 15 | ``` 16 | 17 | Если вы всё выполнили верно, то в списке запущенных контейнеров вы увидите Kafka, Zookeeper и Redpanda Console UI. 18 | 19 | ```bash 20 | docker-compose ps 21 | ``` 22 | 23 | ```text 24 | NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS 25 | grafana docker.io/grafana/grafana:latest "/run.sh" grafana 9 seconds ago Up 7 seconds 0.0.0.0:3000->3000/tcp 26 | kafka-1 kafka-workshop-kafka-1 "/opt/bitnami/script…" kafka-1 8 seconds ago Up 7 seconds 9092/tcp 27 | kafka-2 kafka-workshop-kafka-2 "/opt/bitnami/script…" kafka-2 8 seconds ago Up 7 seconds 9092/tcp 28 | kafka-3 kafka-workshop-kafka-3 "/opt/bitnami/script…" kafka-3 8 seconds ago Up 7 seconds 9092/tcp 29 | kafka-exporter docker.io/bitnami/kafka-exporter:latest "kafka_exporter --ka…" kafka-exporter 9 seconds ago Up 2 seconds 9308/tcp 30 | prometheus quay.io/prometheus/prometheus:latest "/bin/prometheus --c…" prometheus 9 seconds ago Up 7 seconds 0.0.0.0:9090->9090/tcp 31 | ui docker.redpanda.com/vectorized/console:v2.1.1 "/bin/sh -c 'echo \"$…" ui 8 seconds ago Up 6 seconds 0.0.0.0:8080->8080/tcp 32 | zookeeper docker.io/bitnami/zookeeper:3.8 "/opt/bitnami/script…" zookeeper 9 seconds ago Up 7 seconds 2181/tcp, 2888/tcp, 3888/tcp, 8080/tcp 33 | ``` 34 | 35 | ## Состав кластера 36 | 37 | Стенд Kafka предназначен для локальных экспериментов при изучении Apache Kafka и состоит из следующих групп программ: 38 | 39 | | **Название** | **Контейнеры** | **Описание** | 40 | | ----------------- | ---------------- | ---------------- | 41 | | **Кластер** | 42 | | Kafka | `kafka-1`, `kafka-2`, `kafka-3` | Основной кластер | 43 | | Zookeeper | `zookeeper` | Координатор и менеджер кворума | 44 | | Redpanda Console | `ui` | UI для управления Kafka | 45 | | **Приложения** | 46 | | Консумер | `consumer-1` | Пример программы на Go для чтения данных из Kafka | 47 | | Продюсер | `producer` | Пример программы на Go для чтения данных из Kafka | 48 | | **Наблюдаемость** | 49 | | Kafka Exporter | `kafka-exporter` | Экспортер метрик Kafka в формате PromQL | 50 | | Prometheus | `prometheus` | Сервер метрик в формате PromQL | 51 | | Grafana | `grafana` | Сервер визуализации метрик | 52 | 53 | ## Интерфейсы 54 | 55 | Кластер представляет следующие публичные интерфейсы: 56 | 57 | - [Prometheus UI](http://localhost:9090/) (`http://localhost:9090`) 58 | - [Веб-интерфейс UI (Redpanda Console)](http://localhost:8080/) (`http://localhost:8080`) 59 | - [Grafana UI](http://localhost:3000/) (`http://localhost:3000`) 60 | 61 | ## Доступ 62 | 63 | Для демонстрации возможностей мы не используем авторизацию, за исключением Grafana. 64 | 65 | * Имя пользователя: `admin` 66 | * Пароль: `admin` 67 | 68 | --- 69 | 70 | ✅ Готово. Переходите к [созданию топика](./003-topics-and-partitions.md). 71 | -------------------------------------------------------------------------------- /docs/guide/003-topics-and-partitions.md: -------------------------------------------------------------------------------- 1 | # Создание топика 2 | 3 | Откройте веб-интерфейс [Redpanda Console](http://localhost:8080/topcs) и перейдите на вкладку "Topics". Создайте топик "example". 4 | 5 | - Имя топика: `example` 6 | - Число партиций: `6` 7 | - Фактор репликации: `3` 8 | - Минимальное число ISR: `2` 9 | - Политика устаревания: `10` минут 10 | 11 | ![Redpanda Console: Create topic](./assets/003-create-topic-popup.png) 12 | 13 | Перейдите по ссылке на [страницу уже созданного Kafka-топика](http://localhost:8080/topics/example#messages). 14 | 15 | ![Redpanda Console: Open topic profile page](./assets/003-open-topic-profile-page.png) 16 | 17 | Запишите в топик ваше первое сообщение (Actions — Publish messages). 18 | 19 | ![Redpanda Console: Publish first message](./assets/003-publish-first-message.png) 20 | 21 | Если вы всё сделали верно, то в топике появится первое сообщение. 22 | 23 | ![Redpanda Console: View first message](./assets/003-view-first-message.png) 24 | 25 | Усложним пример: запустим консумер и продюсер, что пишут и читают топик `example`. В `docker-compose.yml` мы заранее подготовили примеры программ продюсера и консумера. Запустите приложения: 26 | 27 | ```bash 28 | docker-compose --profile app up -d 29 | ``` 30 | 31 | Если вы всё сделали верно, то в списке контейнеров вы увидите программу-консумер и продюсер: 32 | 33 | ``` 34 | NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS 35 | consumer-1 kafka-workshop-consumer-1 "app" consumer-1 5 seconds ago Up 4 seconds 36 | grafana docker.io/grafana/grafana:latest "/run.sh" grafana 6 minutes ago Up 6 minutes 0.0.0.0:3000->3000/tcp 37 | kafka-1 kafka-workshop-kafka-1 "/opt/bitnami/script…" kafka-1 6 minutes ago Up 6 minutes 9092/tcp 38 | kafka-2 kafka-workshop-kafka-2 "/opt/bitnami/script…" kafka-2 6 minutes ago Up 6 minutes 9092/tcp 39 | kafka-3 kafka-workshop-kafka-3 "/opt/bitnami/script…" kafka-3 6 minutes ago Up 6 minutes 9092/tcp 40 | kafka-exporter docker.io/bitnami/kafka-exporter:latest "kafka_exporter --ka…" kafka-exporter 6 minutes ago Up 6 minutes 9308/tcp 41 | producer-1 kafka-workshop-producer "app" producer 5 seconds ago Up 4 seconds 42 | prometheus quay.io/prometheus/prometheus:latest "/bin/prometheus --c…" prometheus 6 minutes ago Up 6 minutes 0.0.0.0:9090->9090/tcp 43 | ui docker.redpanda.com/vectorized/console:v2.1.1 "/bin/sh -c 'echo \"$…" ui 6 minutes ago Up 6 minutes 0.0.0.0:8080->8080/tcp 44 | zookeeper docker.io/bitnami/zookeeper:3.8 "/opt/bitnami/script…" zookeeper 6 minutes ago Up 6 minutes 2181/tcp, 2888/tcp, 3888/tcp, 8080/tcp 45 | ``` 46 | 47 | --- 48 | 49 | ✅ Готово. Переходите к [работе с продюсерами](./004-producers.md). 50 | -------------------------------------------------------------------------------- /docs/guide/004-producers.md: -------------------------------------------------------------------------------- 1 | # Работа продюсера 2 | 3 | Мы заранее подготовили пример программы-продюсера на Go. Для простоты, представьте себе приложение, что собирает координаты сборщиков и курьеров СберМаркет (кандидатов), а затем отправляет их в один топик `example` для будущих потребителей. Продюсер записывает сообщения в формате JSON, передавая идентификатор кандидата (`id`), широту (`lat`) и долготу (`lon`). 4 | 5 | ```json 6 | { 7 | "id": 1, 8 | "lat": -17.000132, 9 | "lon": 28.587008 10 | } 11 | ``` 12 | 13 | Посмотрите пример записываемых событий: 14 | 15 | ``` 16 | docker-compose --profile app logs -f producer 17 | ``` 18 | 19 | ``` 20 | producer-1 | 2023/02/25 18:51:36 message written at topic example: 6 = {"id":6,"lat":-56.998822,"lon":-120.432007} 21 | producer-1 | 2023/02/25 18:51:38 message written at topic example: 1 = {"id":1,"lat":-46.10566,"lon":-21.857902} 22 | producer-1 | 2023/02/25 18:51:40 message written at topic example: 3 = {"id":3,"lat":73.333584,"lon":-113.570201} 23 | producer-1 | 2023/02/25 18:51:42 message written at topic example: 1 = {"id":1,"lat":40.295088,"lon":-78.754235} 24 | producer-1 | 2023/02/25 18:51:44 message written at topic example: 5 = {"id":5,"lat":-16.964839,"lon":40.875567} 25 | producer-1 | 2023/02/25 18:51:46 message written at topic example: 5 = {"id":5,"lat":37.125313,"lon":-99.247528} 26 | producer-1 | 2023/02/25 18:51:48 message written at topic example: 4 = {"id":4,"lat":43.214251,"lon":116.305726} 27 | producer-1 | 2023/02/25 18:51:50 message written at topic example: 1 = {"id":1,"lat":55.444785,"lon":170.116819} 28 | producer-1 | 2023/02/25 18:51:52 message written at topic example: 3 = {"id":3,"lat":83.039649,"lon":-136.749644} 29 | producer-1 | 2023/02/25 18:51:54 message written at topic example: 3 = {"id":3,"lat":-17.167125,"lon":157.868446} 30 | producer-1 | 2023/02/25 18:51:56 message written at topic example: 4 = {"id":4,"lat":81.208194,"lon":121.136919} 31 | ``` 32 | 33 | Откройте [исходный код продюсера](../../examples/producer/main.go). Обратите внимание на настройки `kafka.Writer`: 34 | 35 | ```go 36 | w := &kafka.Writer{ 37 | Addr: kafka.TCP(addrs...), 38 | Topic: topic, 39 | Balancer: &kafka.Hash{}, 40 | } 41 | ``` 42 | 43 | Опция `Balancer` определяет стратегию балансировки событий между партициями. В нашем примере мы используем хеш-функцию от передаваемого ключа (`key`). В качестве ключа используем идентификатор кандидата, чтобы события одного и того же пользователя сохранялись в верном порядке. 44 | 45 | ```go 46 | payload := kafka.Message{ 47 | Key: []byte(strconv.Itoa(candidate_id)), 48 | Value: []byte(msg), 49 | } 50 | 51 | err = w.WriteMessages(context.Background(), payload) 52 | 53 | if err != nil { 54 | log.Fatal("failed to write messages:", err) 55 | } 56 | ``` 57 | 58 | Откройте [веб-интерфейс Redpanda Console](http://localhost:8080/topics/example#messages) и посмотрите содержимое любого сообщения. Убедитесь, что поля ключ и тело заполнены так, как ожидается. 59 | 60 | --- 61 | 62 | ✅ Готово. Переходите к [работе с консумерами](./005-consumers.md). -------------------------------------------------------------------------------- /docs/guide/005-consumers.md: -------------------------------------------------------------------------------- 1 | # Работа с консумерами 2 | 3 | ## Запуск консумеров 4 | 5 | Мы также подготовили консумер на Go: программа читает топик и распечатывает сообщение на экран. В тестовом кластере сейчас работает сразу три консумера — все они представляют собой один и тот же экземпляр запущенной программы. 6 | 7 | Посмотрите пример прочитанных событий: 8 | 9 | ```bash 10 | docker-compose --profile app logs -f consumer-1 consumer-2 consumer-3 11 | ``` 12 | 13 | ``` 14 | consumer-3 | 2023/02/26 09:27:17 message at topic/partition/offset example/2/2597: 5 = {"id":5,"lat":70.305836,"lon":-82.353797} 15 | consumer-2 | 2023/02/26 09:27:17 message at topic/partition/offset example/5/1446: 6 = {"id":6,"lat":-68.561725,"lon":41.288281} 16 | consumer-2 | 2023/02/26 09:27:17 message at topic/partition/offset example/5/1447: 6 = {"id":6,"lat":-18.154029,"lon":-62.287865} 17 | consumer-1 | 2023/02/26 09:27:17 message at topic/partition/offset example/4/1421: 1 = {"id":1,"lat":-50.646267,"lon":10.077705} 18 | consumer-1 | 2023/02/26 09:27:17 message at topic/partition/offset example/4/1422: 1 = {"id":1,"lat":-47.339896,"lon":-33.38711} 19 | ``` 20 | 21 | Обратите внимание, что каждый консумер читает только события, связанные соответствующим ключом. Это возможно благодаря партицированию по хешу со стороны продюсера. 22 | 23 | Откройте исходный [код консумера](../../examples/consumer/main.go) и посмотрите описание `kafka.Reader`. Программа подключается к брокерам, поллит данные в соответствии с выбранной минимальной величиной байт для одной пачки данных (`MinBytes`) и максимальным размером сообщения (`MaxBytes`). 24 | 25 | ```go 26 | r := kafka.NewReader(kafka.ReaderConfig{ 27 | Brokers: addrs, 28 | GroupID: group, 29 | Topic: topic, 30 | MinBytes: 10e2, // 1KB 31 | MaxBytes: 10e6, // 10MB 32 | }) 33 | ``` 34 | 35 | Само чтение же представляет собой бесконечный цикл: 36 | - Чтение пачки данных 37 | - Обработка 38 | - Коммит оффсета 39 | 40 | В представленном примере чтение данных и коммит оффсета совмещены в функции `ReadMessage` и выполняются неявно (implicit). 41 | 42 | ```go 43 | for { 44 | m, err := r.ReadMessage(context.Background()) 45 | if err != nil { 46 | break 47 | } 48 | log.Print(fmt.Sprintf("message at topic/partition/offset %v/%v/%v: %s = %s\n", m.Topic, m.Partition, m.Offset, string(m.Key), string(m.Value))) 49 | } 50 | ``` 51 | 52 | Однако обе эти операции могут быть выполнены явно (explicit). Например, сначала вы считываете порцию данных, обрабатываете её, а уже в самом конце — коммитите оффсет, перемещаясь к следующей порции записей. Например: 53 | 54 | ```go 55 | ctx := context.Background() 56 | for { 57 | m, err := r.FetchMessage(ctx) 58 | if err != nil { 59 | break 60 | } 61 | log.Print(fmt.Sprintf("message at topic/partition/offset %v/%v/%v: %s = %s\n", m.Topic, m.Partition, m.Offset, string(m.Key), string(m.Value))) 62 | if err := r.CommitMessages(ctx, m); err != nil { 63 | log.Fatal("failed to commit messages:", err) 64 | } 65 | } 66 | ``` 67 | 68 | Для простоты оба примера иллюстрируют синхронную работу консумера, но для улучшения производительности операции могут быть выполнены и асинхронно. 69 | 70 | ## Управление консумер-группой 71 | 72 | Посмотрим на консумер-группу детальнее. Откройте веб-интерфейс [Redpanda Console](http://localhost:8080/groups/example-consumer-group), перейдите на вкладку "Consumer groups" и откройте профиль консумер-группы `example-consumer-group`. 73 | 74 | ![Redpanda Console: Open consumer group profile page](./assets/005-open-consumer-group-profile-page.png) 75 | 76 | На странице видно, что в группу `example-consumer-group` входят три консумера, каждый из которых определяется уникальным именем (Assigned member) и адресом узла (Host). Важно, что каждый консумер в группе читает одну или несколько партиций, однако одна партиция может быть прочитана только одним консумером в текущей группе. 77 | 78 | Каждый консумер в группе характеризуется текущим оффсетом (Group Offset) относительно конечного оффсета партиции (Log End Offset). Расстояние между конечным оффсетом партиции и текущим оффсетом группы является лагом (Lag). 79 | 80 | Вы можете управлять положением оффсета консумер-группы на уровне брокера. Например, если вам требуется прочитать сообщения за пределами текущего оффсета, вы можете переместить его на позицию в любое другое место. 81 | 82 | Однако для этого важно, чтобы консумер-группа была **пуста**: то есть, в ней не было **ни одного** участника. 83 | 84 | Остановите имеющиеся консумеры. 85 | 86 | ```bash 87 | docker-compose --profile app stop consumer-1 consumer-2 consumer-3 88 | ``` 89 | 90 | Обновите страницу [Redpanda Console](http://localhost:8080/groups/example-consumer-group). 91 | 92 | ![Redpanda Console: Empty consumer group](./assets/005-empty-consumer-group.png) 93 | 94 | Консумер-группа пуста (Empty). Теперь вы можете отредактировать её (Edit group). Переключите оффсет для всей группы, «отмотав» его на 5 минут назад. 95 | 96 | ![Redpanda Console: Edit consumer group](./assets/005-edit-consumer-group.png) 97 | 98 | Подтвердите изменение. 99 | 100 | ![Redpanda Console: Edit consumer group](./assets/005-edit-consumer-group-confirmation.png) 101 | 102 | Готово. Обратите внимание на значения лага партиций. 103 | 104 | ![Redpanda Console: Consumer group with lag](./assets/005-open-consumer-group-with-lag-profile-page.png) 105 | 106 | Теперь, когда мы запустим консумеры, они вновь вернутся к чтению с обозначенных позиций. Запустите остановленные консумеры: 107 | 108 | ```bash 109 | docker-compose --profile app start consumer-1 consumer-2 consumer-3 110 | ``` 111 | 112 | Посмотрите на лог консумер-групп и убедитесь, что сообщения были прочитаны повторно. 113 | 114 | ```bash 115 | docker-compose --profile app logs -f consumer-1 consumer-2 consumer-3 116 | ``` 117 | 118 | ## Ребалансировка консумер-группы 119 | 120 | Любая смена композиции в консумер-группе (например, добавление или выход участника из группы, а также изменение числа партиций) приводит к её ребалансировке — процессу перераспределения партиций между «живыми» участниками. Сейчас в консумер-группе три консумера, каждый из которых читает две партиции. 121 | 122 | Остановите один из работающих консумеров, перейдите на страницу консумер-группы в [Redpanda Console](http://localhost:8080/groups/example-consumer-group). 123 | 124 | ```bash 125 | docker-compose --profile app stop consumer-1 126 | ``` 127 | 128 | Сначала никаких изменений не будет, а после, на короткое время, группа перейдёт в состояние `PreparingRebalance`. 129 | 130 | ![Redpanda Console: Consumer group is in preparing rebalance state](./assets/005-consumer-group-preparing-rebalance.png) 131 | 132 | В этот момент все консумеры прекращают чтение и дожидаются синхронизации всех имеющихся участников в группе. После окончания ребалансировки, партиции перераспределяются между участниками и консумеры продолжают чтение с сохранённых позиций. 133 | 134 | ![Redpanda Console: Consumer group after rebalance](./assets/005-consumer-group-after-rebalance.png) 135 | 136 | Как можно заметить, теперь партиции распределились между двумя консумерами: по три на каждого участника группы. 137 | 138 | --- 139 | 140 | ✅ Готово. Теперь переходите к [изучению метрик Kafka](./006-observability.md). -------------------------------------------------------------------------------- /docs/guide/006-observability.md: -------------------------------------------------------------------------------- 1 | # Наблюдаемость Kafka 2 | 3 | Пока мы экспериментировали с консумерами и продюсерами, Prometheus-сервер собирал метрики с Kafka-брокеров. Давайте уделим время последней, но не по важности, теме наблюдаемости Kafka. 4 | 5 | Откройте [Grafana-дашборд "Kafka: Producers & Consumers"](http://localhost:3000/d/kafka-consumers-and-producers). 6 | 7 | - **Имя пользователя**: `admin` 8 | - **Пароль**: `admin` 9 | 10 | Kafka представляет широкие возможности для снятия телеметрии с JMX. В экосистеме Prometheus для снятия метрик мы используем JMX Exporter с описанием правил сбора метрик MBeans. В других системах мониторинга вы можете использовать другие инструменты. 11 | 12 | ![Grafana: Producers & Consumers](./assets/006-grafana-kafka-producers-and-consumers.png) 13 | 14 | Откройте [Grafana-дашборд "Kafka: Performance"](http://localhost:3000/d/kafka-performance) и оцените изменение ключевых метрик. 15 | 16 | > Для развёртки продуктивного кластера советуем ознакомиться с [рекомендациями Confluent по сбору метрик](https://docs.confluent.io/platform/current/kafka/monitoring.html). В примере используем только часть метрик, чтобы не переусложнять понимание основных процессов. 17 | 18 | --- 19 | 20 | ✅ Готово. Поздравляю, вы завершили базовый курс знакомства с Kafka. 21 | 22 | Для остановки стенда используйте команду: 23 | 24 | ```bash 25 | docker-compose --profile app down 26 | ``` 27 | 28 | 🎉 Спасибо, что приняли участие в интенсиве «Kafka за 90 минут»! 29 | 30 | --- 31 | 32 | [Вернуться на главную](../../README.md) -------------------------------------------------------------------------------- /docs/guide/assets/003-create-topic-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/003-create-topic-popup.png -------------------------------------------------------------------------------- /docs/guide/assets/003-open-topic-profile-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/003-open-topic-profile-page.png -------------------------------------------------------------------------------- /docs/guide/assets/003-publish-first-message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/003-publish-first-message.png -------------------------------------------------------------------------------- /docs/guide/assets/003-view-first-message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/003-view-first-message.png -------------------------------------------------------------------------------- /docs/guide/assets/005-consumer-group-after-rebalance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/005-consumer-group-after-rebalance.png -------------------------------------------------------------------------------- /docs/guide/assets/005-consumer-group-preparing-rebalance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/005-consumer-group-preparing-rebalance.png -------------------------------------------------------------------------------- /docs/guide/assets/005-edit-consumer-group-confirmation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/005-edit-consumer-group-confirmation.png -------------------------------------------------------------------------------- /docs/guide/assets/005-edit-consumer-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/005-edit-consumer-group.png -------------------------------------------------------------------------------- /docs/guide/assets/005-empty-consumer-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/005-empty-consumer-group.png -------------------------------------------------------------------------------- /docs/guide/assets/005-open-consumer-group-profile-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/005-open-consumer-group-profile-page.png -------------------------------------------------------------------------------- /docs/guide/assets/005-open-consumer-group-with-lag-profile-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/005-open-consumer-group-with-lag-profile-page.png -------------------------------------------------------------------------------- /docs/guide/assets/006-grafana-kafka-producers-and-consumers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/guide/assets/006-grafana-kafka-producers-and-consumers.png -------------------------------------------------------------------------------- /docs/theory/assets/004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/004.png -------------------------------------------------------------------------------- /docs/theory/assets/008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/008.png -------------------------------------------------------------------------------- /docs/theory/assets/010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/010.png -------------------------------------------------------------------------------- /docs/theory/assets/011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/011.png -------------------------------------------------------------------------------- /docs/theory/assets/012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/012.png -------------------------------------------------------------------------------- /docs/theory/assets/013.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/013.png -------------------------------------------------------------------------------- /docs/theory/assets/018.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/018.png -------------------------------------------------------------------------------- /docs/theory/assets/025.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/025.png -------------------------------------------------------------------------------- /docs/theory/assets/028.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/028.png -------------------------------------------------------------------------------- /docs/theory/assets/031.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/031.png -------------------------------------------------------------------------------- /docs/theory/assets/035.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/035.png -------------------------------------------------------------------------------- /docs/theory/assets/038.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/038.png -------------------------------------------------------------------------------- /docs/theory/assets/041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/041.png -------------------------------------------------------------------------------- /docs/theory/assets/042.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/042.png -------------------------------------------------------------------------------- /docs/theory/assets/046.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/046.png -------------------------------------------------------------------------------- /docs/theory/assets/049.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/049.png -------------------------------------------------------------------------------- /docs/theory/assets/052.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/052.png -------------------------------------------------------------------------------- /docs/theory/assets/055.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/055.png -------------------------------------------------------------------------------- /docs/theory/assets/069.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/069.png -------------------------------------------------------------------------------- /docs/theory/assets/075.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/075.png -------------------------------------------------------------------------------- /docs/theory/assets/080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/080.png -------------------------------------------------------------------------------- /docs/theory/assets/083.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/083.png -------------------------------------------------------------------------------- /docs/theory/assets/090.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/090.png -------------------------------------------------------------------------------- /docs/theory/assets/104.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/104.png -------------------------------------------------------------------------------- /docs/theory/assets/108.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/108.png -------------------------------------------------------------------------------- /docs/theory/assets/110.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/110.gif -------------------------------------------------------------------------------- /docs/theory/assets/112.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/112.gif -------------------------------------------------------------------------------- /docs/theory/assets/121.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/assets/121.png -------------------------------------------------------------------------------- /docs/theory/slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gongled/kafka-workshop/1ab1245306e24498dac9de641b0fe416898b64eb/docs/theory/slides.pdf -------------------------------------------------------------------------------- /docs/theory/speaker-notes.md: -------------------------------------------------------------------------------- 1 | # Теория. Ментальная модель Kafka. 2 | 3 | ## Предисловие 4 | 5 | В СберМаркете мы широко используем Kafka в качестве шины данных для микросервисов и знаем непонаслышке, что работать с Kafka нужно уметь. Интенсив «Kafka за 90 минут» состоит из двух частей: теоретическая и практическая. 6 | 7 | В теоретической части мы: 8 | - Посмотрим сценарии использования 9 | - Узнаем что такое консумер, продюсер и брокер 10 | - Поймём как связаны топики, партиции и сегменты 11 | - Посмотрим на формат сообщения Kafka 12 | - Поймём зачем нужен лидер партиции и как реплицируются данные 13 | - Зачем нужно партицирование 14 | - Узнаем какие есть гарантии доставки сообщений 15 | - Затронем идемпотентность обработки событий 16 | - Определим что такое консумер-группа 17 | - Посмотрим на ребалансировку консумеров 18 | 19 | ## Предпосылки 20 | 21 | Долгое время инженеры разрабатывали программы, оперирующие объектами реального мира, сохраняя состояние о них в базы данных. Будь то, например, пользователи, заказы или товары. Представление о вещах мира как об объектах широко распространено среди ИТ-разработки (будь то парадигма ООП), однако сейчас больше компаний и технических команд всё чаще предпочитают думать не о самих объектах, а событиях, которые они порождают — то бишь, изменениях объектов во времени. 22 | 23 | ![Предпосылки: сервисы создают события](./assets/004.png) 24 | 25 | Популярность событийно-ориентированного подхода вызвана стремлением компаний снизить связность сервисов друг с другом (что крайне важно при микросервисной трансформации) и улучшить устойчивость приложений к сбоям за счёт изоляции поставщиков данных и их потребителей. 26 | 27 | События, проходящие в системах, как и объекты, также можно хранить в традиционных реляционных базах данных, однако это достаточно громоздко и неэффективно. Вместо этого мы используем структуру под названием лог. 28 | 29 | ## Устройство Apache Kafka 30 | 31 | **Лог** — это упорядоченный поток событий во времени. Некоторое событие происходит и попадает всегда в конец лога, оставаясь неизменным. 32 | 33 | ![Поток событий как лог](./assets/008.png) 34 | 35 | **Apache Kafka** — это система по управлению такими логами и платформа, призванная соединить поставщиков данных и их потребителей, предоставив возможность получать упорядоченный поток событий в реальном времени. 36 | 37 | ### Продюсеры 38 | 39 | Чтобы записать события в кластер Kafka, есть продюсеры. **Продюсер** — это приложение, которое вы разрабатываете. 40 | 41 | ![Продюсеры](./assets/010.png) 42 | 43 | Программа-продюсер записывает сообщение в Kafka, а Kafka сохраняет события, возвращает подтверждение (_acknowledgement_) о записи, продюсер получает его и начинает следующую запись. И так далее. 44 | 45 | ### Брокеры 46 | 47 | Сам же кластер Kafka состоит из брокеров. Представьте себе дата-центр и серверы в нём. В первом приближении думайте о Kafka-брокере как о компьютере: это процесс в операционной системе с доступом к своему локальному диску. 48 | 49 | ![Продюсеры](./assets/011.png) 50 | 51 | Все брокеры соединены друг с другом сетью, действуя сообща как единый Kafka-кластер. Когда продюсеры пишут события в Kafka-кластер, они работают с брокерами в нём. 52 | 53 | В облачной среде Kafka-кластер не обязательно работает на выделенных серверах, а может быть виртуальными машинами или контейнерами в Kubernetes. Главное — каждый кластер Kafka состоит из брокеров. 54 | 55 | ### Консумеры 56 | 57 | События, записанные продюсерами на локальные диски брокеров, могут быть прочитаны консумерами. **Консумер** — это также приложение, которое вы разрабатываете. В этом случае по-прежнему кластер Kafka — это нечто, обслуживаемое инфраструктурой, но что делаете вы как пользователь — пишете продюсер и консумер. Программа-консумер подписывается на события (поллит) и получает данные в ответ. И так по кругу. 58 | 59 | ![Консумеры](./assets/012.png) 60 | 61 | Консумером может быть программа, подбирающая кандидата на основе координат партнёра, или при появлении заказа — инициирующая новую сборку. При этом консумер также может быть и продюсером, создавая новые события для размещения в Kafka для других сервисов. 62 | 63 | --- 64 | 65 |
66 | TL;DR 67 | Основы кластера Kafka — это продюсер, брокер и консумер. Продюсер пишет сообщения в лог брокера, а консумер его читает. 68 |
69 | 70 | --- 71 | 72 | ## Архитектура Kafka 73 | 74 | Итак, давайте посмотрим на архитектуру Kafka внимательнее. Слева есть продюсеры, в середине брокеры, а справа — консумеры. Kafka же представляет собой группу брокеров, связанных с Zookeeper-кворумом. Kafka использует Zookeeper для достижения консенсуса состояния в распределённой системе: есть несколько вещей, с которыми должен быть «согласен» каждый брокер и Zookeeper помогает достичь этого «согласия» внутри кластера. 75 | 76 | ![Архитектура](./assets/013.png) 77 | 78 | > _Начиная с Kafka 3.4 необходимость в использовании Zookeeper отпала: для арбитража используется собственный протокол KRaft, решающий те же задачи, но на уровне брокеров, однако для простоты мы пока остановимся на традиционной схеме_. 79 | 80 | Так вот, Zookeeper представляет собой выделенный кластер серверов для образования кворума и поддержки внутренних процессов Kafka. Благодаря Zookeeper, кластером Kafka можно управлять: добавлять пользователей, топики и задавать им настройки. 81 | 82 | ![Архитектура](./assets/018.png) 83 | 84 | Zookeeper также помогает при обнаружении сбоя в мастере, провести выборы нового и сохранить работоспособность кластера Kafka. И, что немаловажно, Zookeeper хранит в себе все авторизационные данные и ограничения (Access Control Lists) при работе консумеров и продюсеров с брокерами. 85 | 86 | --- 87 | 88 |
89 | TL;DR 90 | Кластер Kafka позволяет изолировать консумеры и продюсеры друг от друга. Продюсер ничего не знает о консумерах при записи данных в брокер, а консумер — ничего не знает о продюсере данных. 91 |
92 | 93 | --- 94 | 95 | ## Устройство брокеров 96 | 97 | Теперь поговорим отдельно о брокерах. Наверняка вы несколько раз слышали про какие-то топики, теперь коротко о том, что это такое. События в Kafka-брокерах образуют топики. 98 | 99 | ### Топики 100 | 101 | Топик — это логическое представление категорий сообщений в группы. Например, события по статусам заказов, координат партнёров, маршрутных листов и так далее. 102 | 103 | ![Топик](./assets/025.png) 104 | 105 | Ключевое слово здесь — **логическое**. Мы создаём топики для событий общей группы и стараемся не смешивать их друг с другом. Например, координаты партнёров не должны быть в том же топике, что и статусы заказов, а обновлённые статусы по заказам — не быть вперемешку с обновлением регистраций пользователей. 106 | 107 | О топике стоит думать как о логе: вы пишете событие в конец, не разрушая при этом старые события. Один продюсер может писать в один или несколько топиков, в один топик могут писать один или более продюсеров, как и много консумеров могут читать из одного топика, как и один консумер может читать сразу несколько топиков. 108 | 109 | Теоретически, нет никаких ограничений на число этих топиков, но практически это ограничено числом партиций, о которых мы поговорим позднее. 110 | 111 | ### Партиции и сегменты 112 | 113 | Топиков в кластере Kafka может быть много и нет ограничений на их создание. Однако рано или поздно, компьютер, выполняя операции на процессоре и вводе-выводе, упирается в свой предел. К сожалению, мы не можем увеличивать мощность и производительность компьютеров бесконечно, а потому топик следует делить на части. 114 | 115 | ![Топики, партиции и сегменты](./assets/028.png) 116 | 117 | В Kafka эти части называются **партициями**. Каждый Kafka-топик состоит из одной или более партиций, каждая из которых может быть размещена на разных брокерах. Это то, благодаря чему Kafka может масштабироваться: пользователь может создать топик, разделить его на партиции и разместить каждую из них на своём брокере. 118 | 119 | Формально партиция — это и есть строго упорядоченный лог сообщений. Каждое сообщение в партиции добавлено в конец без возможности изменить его в будущем и как-то повлиять на уже записанные сообщения. При этом сам топик вцелом не имеет никакого порядка, но порядок сообщений всегда есть в одной из его партиций. 120 | 121 | Партиции же представлены физически на дисках в виде **сегментов** — отдельных файлов, что могут быть созданы, отротированы или удалены в соответствии с настройкой устаревания данных в них. Обычно, если вы не администрируете кластер Kafka, вам не приходится много думать о сегментах партиций, но вы должны помнить о партициях, как о модели хранения данных в топиках Kafka. 122 | 123 | Давайте посмотрим на всё детальнее. В кластере Kafka есть три брокера: 101, 102 и 103. Топик A отмечен бирюзовым, топик B — жёлтым, а C — оранжевым. На картинке я нарисовал их по три у каждого, но число партиций может быть разным у каждого топика — это настраивается для каждого топика. 124 | 125 | ![Топики, партиции и сегменты](./assets/031.png) 126 | 127 | Как вы видите, партиции как-то распределены между брокерами — кластер Kafka делает это автоматически при создании топика. Kafka автоматически не следит за размером каждой партиции, не занимается ребалансировкой записи и чтения в кластере, не перемещает партиции между брокерами. Конечно же, для этого уже существуют как опенсорс-инструменты, так и готовые enterprise-платформы как Confluent, но эту часть мы сейчас пропустим. 128 | 129 | Так вот, как вы видите, каждая партиция на брокере представлена набором сегментов. Число сегментов у партиций топиков тоже может быть разным и варьируется в зависимости от интенсивности записи и настроек размера сегмента. 130 | 131 | Итак, таким образом, увеличивая число брокеров в кластере, мы можем масштабировать систему. Kafka поддерживает добавление или удаление брокеров в кластере и с помощью встроенных инструментов позволяет, например, переносить партиции между брокерами, равномерно распределяя их. 132 | 133 | Позволяет добавлять новые партиции в топике, тем самым увеличивая параллелизм записи и чтения со стороны продюсера и консумера соответственно. 134 | 135 | Позволяет изменять настройки сегментов и, тем самым, управлять частотой и интенсивностью ротации отдельных файлов на дисках брокеров. Давайте посмотрим подробнее на устройство сегмента. 136 | 137 | #### Поток данных как лог 138 | 139 | Предлагаю думать о сегменте как об обычном лог-файле: каждая следующая запись добавляется в конец файла и не меняет записей раньше. Фактически, это очередь FIFO (First-In-First-Out) и Kafka реализует именно эту модель. LIFO (Last-In-First-Out), очереди с приоритетами — мимо, это не про Kafka. 140 | 141 | ![Лог](./assets/035.png) 142 | 143 | Говоря о сегменте, и семантически, и физически, сообщения внутри сегмента не могут быть удалены: они иммутабельны. Всё, что мы можем указать — это как долго Kafka-брокер будет хранить события, настраивая политику устаревания данных (Retention Policy). 144 | 145 | Числа внутри сегмента — это реальные сообщения системы. Эти сообщения имеют порядковый номера или оффсеты, что монотонно увеличиваются со временем. У каждой партиции свой собственный счётчик, никак не пересекающийся с другими партициями. Так, позиции 0, 1, 2, 3 и так далее у каждой партиции своя. Таким образом, продюсеры пишут сообщения в партиции, а брокер адресует каждое из таких сообщений своим порядковым номером. 146 | 147 | > Если вам интересно, есть ли у этого есть свой предел: то технически — да, но практически едва ли. Если писать по 1ТБ в день, то счётчик исчерпается где-то через 4 млн. дней или чуть меньше 11000 лет. 148 | 149 | Так вот, помимо продюсеров, что пишут сообщения, есть консумеры, что их читают. Несмотря на то, что консумеры называются так от слова consume (англ. потреблять), повторюсь, никакого физического потребления или уничтожения сообщения не происходит — он просто их читает. 150 | 151 | Это значит, что у лога может быть несколько консумеров, читающих лог с разных позиций, имеющих свою позицию чтения, совершенно не мещающих друг другу. При этом нет никакой необходимости у консумеров читать сообщения сразу же после записи со стороны продюсера. При желании, эти события могут быть прочитаны спустя дни, недели, месяцы, или же их можно прочитать несколько раз спустя какое-то время. 152 | 153 | ![Поток данных](./assets/038.png) 154 | 155 | Начальная позиция первого сообщения в логе называется log-start offset. Позиция сообщения, записанного последним — log-end offset. Позиция консумера сейчас — текущим оффсетом или current offset. 156 | 157 | ![Поток данных](./assets/041.png) 158 | 159 | Расстояние между конечным оффсетом и текущим оффсетом консумера называют лагом. Лаг — это первое, за чем стоит следить в своих приложениях. При этом очевидно, что допустимый лаг для каждого приложения свой, что тесно связано с бизнес-логикой и требованиями к работе системы. 160 | 161 | Возвращаясь к теме, теперь давайте же посмотрим на то, из чего состоит отдельное сообщение Kafka. Если упростить, то структура сообщения Kafka представлена опциональными заголовками, что могут быть добавлены со стороны продюсера, ключом партицирования, пэйлоадом и временем (timestamp). 162 | 163 | ![Сообщение в Kafka](./assets/042.png) 164 | 165 | Важно то, что каждое событие — это пара ключ-значение. Ключ партицирования может быть любой: числовой, строковый, объект или вовсе пустой. Значение же также может быть любым и представлено числом, строкой или объектом в своей предметной области, который вы можете как-то сериализовать (JSON, Protobuf, …) и хранить. 166 | 167 | В сообщении продюсер также может указать время или же за него это сделает брокер в момент приёма сообщения. Заголовки также опциональны и выглядят как в HTTP-протоколе: просто строковые пары ключ-значение. В них не следует хранить сами данные, но они могут использоваться для передачи мета-данных, например, для трассировки, сообщения MIME-type, цифровой подписи и т.д. 168 | 169 | #### Устаревание данных 170 | 171 | Возвращаясь к сегментам, стоит также поговорить и о том, как они ротируются — закрываются старые и открываются новые. Когда сегмент достигает своего предела, он закрывается и вместо него открывается новый. Сегмент, в который сейчас записываются данные, называют активным сегментом. По сути, открытый файл процессом брокера. Сегменты, в которых больше нет записи, называются закрытыми. 172 | 173 | ![Сообщение в Kafka](./assets/046.png) 174 | 175 | Максимальная длина сегмента в байтах настраивается глобально или индивидуально на каждый топик: его размер определяет как часто старые файлы будут сменяться новыми. Если вы пишете много больших сообщений, то вам следует увеличивать размер сегмента и, наоборот, вам не следует использовать большие сегменты, если вы пишете небольшие сообщения очень редко. 176 | 177 | Простыми словами, настроенная политика устаревания не означает, что из топика пропадут события старше, скажем, 7 дней. Kafka удаляет закрытые сегменты партиций, а число таких партиций зависит от размера сегмента и интенсивности записи в партиции. 178 | 179 | Однако ничто не мешает хранить сообщения дольше или даже вовсе не удалять их. Многие пользователи ошибочно полагают, что Kafka не предназначен для хранения исторических данных. На мой взгляд, для того нет никаких ограничений, кроме размера дисков и числа брокеров для хранения данных. 180 | 181 | #### Репликация данных 182 | 183 | Теперь несколько слов о надёжности. Было бы странно, если бы одна партиция могла существовать на одном брокере, ведь в случае сбоя, была бы недоступна часть данных, делая нашу систему хрупкой и ненадёжной. Разумеется, Kafka представляет механизм репликации партиций между брокерами. 184 | 185 | ![Репликация данных](./assets/049.png) 186 | 187 | Каждая партиция имеет настраиваемое число реплик, например, три — как указано на изображении. Одна из этих реплик партиции называется лидером, а другие, политкорректно, фолловерами. Продюсер подключается к брокеру, на котором расположена лидер-партиция, чтобы записать в неё данные. 188 | 189 | ![Репликация данных](./assets/052.png) 190 | 191 | Записанные в лидерскую партицию данные автоматически реплицируются фолловерами внутри кластера Kafka: они подключаются к лидеру, читают данные и сохраняют к себе на диск асинхронно. В хорошо функционирующем кластере Kafka репликация занимает доли секунд. 192 | 193 | Консумеры же со своей стороны также читают из лидерской партиции, что позволяет достичь консистентности при работе с данными. Задача же фолловеров, как уже говорил, просто сводится к копированию данных от лидера. 194 | 195 | > Начиная с версии 2.4, Kafka поддерживается возможность чтения консумерами из фолловеров, основываясь на их взаимном расположении. Это полезно для сокращения задержек при обращении к ближайшему брокеру, скажем, в одной зоне доступности, однако из-за асинхронной работы репликации, взамен вы получаете от фолловеров менее актуальные данные, чем они есть в лидерской партиции. 196 | 197 | ![Репликация данных](./assets/055.png) 198 | 199 | Говоря о лидерах и фолловерах, то эти роли не статичны. Kafka автоматически выбирает лидера и фолловеров партиций в кластере. Так, в случае сбоя брокера, роль лидера достанется одной из реплик, а консумеры и продюсеры, согласно протоколу, должны получить обновления и переподключиться к брокерам с новыми лидерами партиций. 200 | 201 | --- 202 | 203 |
204 | TL;DR 205 | Топики в Kafka разделены на партиции. Увеличение партиций увеличивает параллелизм чтения и записи. Партиции находятся на одном или нескольких брокерах, что позволяет кластеру масштабироваться. 206 | 207 | 208 | Партиции хранятся на локальных дисках брокеров и представлены набором лог-файлов — сегментов, запись в которых идёт в конец, а уже сохранённые события — неизменны. 209 | 210 | Каждое сообщение в таком логе определяется порядковым номером — оффсетом. Этот номер монотонно увеличивается при записи для каждой партиции. 211 | 212 | Лог-файлы на диске устаревают по времени или размеру. Это настраивается глобально или индивидуально в каждом топике. 213 | 214 | Для отказоустойчивости, партиции могут реплицироваться. Число реплик (он же фактор репликации) настраивается как глобально по умолчанию, так и в каждом топике кастомно. 215 | 216 | Реплики партициий могут быть лидерами или фолловерами. Традиционно консумеры и продюсеры работают с лидерами, а фолловеры только догоняют лидера. 217 |
218 | 219 | --- 220 | 221 | ## Продюсеры 222 | 223 | Закончив рассматривать брокер, предлагаю перейти к продюсерам и понять принцип их работы. Как мы уже знаем, продюсеры пишут сообщения в партиции, партиции расположены на брокерах, но как продюсер узнаёт о том, какое сообщение и в какую партицию ему записать. 224 | 225 | #### Балансировка и партицирование 226 | 227 | ![Балансировка и партицирование](./assets/069.png) 228 | 229 | Ответ прост: он знает. Программа-продюсер может указать ключ у сообщения и сама определить номер партиции, например, разделяя вычисленный хеш на число партиций, тем самым сохраняя сообщения одного идентификатора в одну и ту же партицию. Это очень распространённая стратегия партицирования, позволяющая добиться строгой очерёдности событий при чтении за счёт сохранения сообщений в одну партицию, а не в несколько. 230 | 231 | Например, этим ключом может быть номер карты при установке и сброса динамического лимита. В таком случае мы гарантируем, что события по одной карте будут следовать строго в порядке их записи в партицию и никак иначе. 232 | 233 | Если программа-продюсер не указывает ключ, что стратегия партицирования по умолчанию round-robin — сообщения будут попадать в партиции по очереди. Это неплохая стратегия партицирования в ряде бизнес-сценариев, где не важна очерёдность событий, но равномерное распределение сообщений между партициями. 234 | 235 | Также вы можете реализовать собственную стратегию партицирования, например, используя List-based partitioning, Composite partitioning, Range-based partitioning и другие алгоритмы. Вся логика реализации партицирования данных реализуется на стороне продюсера. 236 | 237 | #### Дизайн продюсера 238 | 239 | Типичная программа-продюсер работает следующим образом: пэйлоад упаковывается в структуру с указанием топика, партиции и ключа партицирования. Далее пэйлоад сериализуется в подходящий формат (скажем, JSON, Protobuf, Avro или ваш кастомный формат с поддержкой схем), следом сообщению назначается партиция согласно передаваемому ключу и выбранному алгоритму, группируется в пачки выбранных размеров и пересылаются брокеру Kafka для сохранения. 240 | 241 | ![Дизайн продюсера](./assets/075.png) 242 | 243 | В зависимости от настроек продюсера, тот дожидается окончания записи в кластере Kafka и ответного подтверждающего сообщения. В случае, если продюсер не смог записать сообщение, то, тот может попытаться отправить сообщение повторно. И так по кругу. 244 | 245 | Каждый из параметров в цепочке может быть индивидуально настроен каждым продюсером. Например, вы можете выбрать алгоритм партицирования, определить размер батча (выбрав баланс между задержкой и пропускной способностью), а также выбрать семантику доставки сообщений. 246 | 247 | #### Семантики доставки 248 | 249 | Говоря о семантике доставки, то в любых очередях всегда есть выбор между скоростью доставки и накладными расходами на надёжность. Цветные квадратики на слайде — это сообщения, которые мы будем записывать в очередь, выбирая нужную из семантик. 250 | 251 | ![Семантики доставки](./assets/080.png) 252 | 253 | - Семантика _at-most once_ означает, что при доставке сообщений нас устраивают потери сообщений, но не их дубликаты. Это наиболее слабая гарантия, реализуемая всеми брокерами очередей. 254 | - Семантика _at-least once_ означает, что мы не хотим терять сообщения, но нас устраивают возможные дубликаты. 255 | - Семантика _exactly-once_ означает, что мы хотим доставить одно и только одно сообщение, ничего не теряя и ничего не дублируя. 256 | 257 | На первый взгляд кажется, что любое приложение должно реализовывать семантику exactly once, однако это далеко не всегда так. Например, при передаче партнёрских координат, полагаю, вовсе не обязательно сохранять каждую точку из них и вполне хватит at-most once. Или, скажем, при обработке идемпотентных событий, нас вполне может и устроить дубль, если статусная модель предполагает корректную обработку. 258 | 259 | В распределённых системах у exactly-once есть своя цена: высокая надёжность означает высокие задержки. Давайте рассмотрим инструменты, что предлагает Kafka для реализации всех трёх семантик доставки сообщений в брокер. 260 | 261 | #### Надёжность доставки 262 | 263 | Со стороны продюсера разработчик определяет надёжность доставки сообщения до Kafka с помощью параметра acks. Указывая `0` (`none`), в других библиотеках название может отличаться, продюсер будет отправлять сообщения в Kafka, не дожидаясь никаких подтверждений записи на диск со стороны брокера. 264 | 265 | ![Надёжность доставки](./assets/083.png) 266 | 267 | Это самая слабая гарантия, поскольку в случае выхода брокера из строя или сетевых проблем, вы так и не узнаете о том, попало ли сообщение в лог или же просто потерялось. 268 | 269 | Указывая настройку в `1` (`leader`), продюсер при записи будет дожидаться ответа от брокера с лидерской партицией. Это будет означать, что сообщение сохранено на диск одного брокера. В этом случае вы получаете гарантию того, что сообщение было получено по крайней мере один раз, однако это по-прежнему не страхует вас от проблем в самом кластере. 270 | 271 | Представьте, что в момент подтверждения брокер с лидерской партиции выходит из строя, а фолловеры не успели отреплицировать с него данные. В таком случае вы теряете сообщение и снова не узнаёте об этом. Такие ситуации редки, но возможны. 272 | 273 | И, наконец, устанавливая acks в `-1` (`all`), вы просите брокера с лидерской партицией отправить вам подтверждение только тогда, когда запись попадёт не только на локальный диск брокера, но и в реплики-фолловеры. Число этих реплик устанавливается настройкой `min.insync.replicas`. 274 | 275 | Частой ошибой при конфигурировании топика является выбор `min.insync.replicas`, равным числу реплик. Так, в случае выхода из строя брокера и потери одной реплики, продюсер более не сможет записать сообщение в кластер, поскольку не дождётся подтверждения. Рекомендуется устанавливать `min.insync.replicas` на единицу меньше числа реплик, чтобы переживать такие ситуации. Например, для фактора репликации `3` — это значение равно `2`. 276 | 277 | Очевидно, что третья схема достаточно надёжна, но и требует больше накладных расходов: мало того, чтобы нужно сохранить на диск, так ещё и дождаться, пока фолловеры отреплицируют сообщения и сохранят их к себе на диск в лог. 278 | 279 | #### Идемпотентные продюсеры 280 | 281 | ![Идемпотентность продюсеров](./assets/090.png) 282 | 283 | Однако даже с выбором `acks=all`, по-прежнему возможны дубликаты сообщений. В штатном режиме работы продюсер отправляет сообщение брокеру, а тот сохраняет данные в логе на диске, и отправляет подтверждение продюсеру. После чего тот снова формирует новую пачку сообщений и так далее. Однако так бывает не всегда: в работе программ бывают различные сбои и что-то может пойти не так. 284 | 285 | А что, если брокер не смог отправить подтверждение продюсеру из-за сетевых проблем? В таком случае, продюсер повторно отправляет сообщение брокеру. Брокер послушно сохраняет добавляет ещё одно сообщение в лог, образуя дубликат. 286 | 287 | Эта проблема также решается в Kafka благодаря транзакционному API и использованию идемпотентности. В брокере есть специальная опция, включающая идемпотентность (`enable.idempotence`). Так каждому сообщению будет проставлен идентификатор продюсера (PID) и монотонно увеличивающийся sequence number (как, например, реализовано в протоколе TCP). Благодаря этому, сообщения дубликаты от одного продюсера с одинаковым PID будут отброшены на стороне брокера. 288 | 289 | Строго говоря, если вы используете `acks=all`, то нет никаких причин не включать `enable.idempotence` для своих продюсеров. Так вы добьётесь гарантии exactly once при записи в брокер и избежите дубликатов, однако, как я уже отметил, у этого могущества есть своя цена — запись будет проходить дольше. 290 | 291 | --- 292 | 293 |
294 | TL;DR 295 | Продюсеры партицируют данные в топиках самостоятельно. Он также сам определяет алгоритм партицирования: он может быть как банальный round-robin и hash-based, так и кастомный, реализованный программистом. Важно помнить, что очерёдность сообщений гарантируется только для одной партиции. 296 | 297 | 298 | Продюсер волен сам выбрать размер батча и число ретраев при отправке сообщений. Протокол Kafka предоставляет гарантии доставки всех трёх семантик: at-most once, at-least once и exactly-once. 299 | 300 | При этом exactly once нужен не каждому сервису. Оцените потребности ваших приложений и используйте настройки с умом. 301 | 302 | У exactly once есть цена. Для надёжной записи вам необходимо использовать подтверждение как от лидера, так и от реплик, включить идемпотентность и использовать транзакционный API. Всё это сказывается на времени записи. 303 | 304 | Не забывайте, что сломаться в пути может что угодно: например, просесть сеть или сломаться сам брокер. Переходящие процессы в кластере, как выбор лидера, редкость, но это случается, а клиенты должны уметь их грамотно обрабатывать. 305 | 306 | Если вы хотите писать в Kafka надёжно, указывайте при создании топика `min.insync.replicas` меньше, чем всего реплик. В противном случае, лишившись брокера в случай аварии, вы рискуете вовсе ничего не записать, поскольку не сможете дождаться подтверждения записи. 307 | 308 | Если уж вы и указываете `acks=all`, то включайте и `enable.idempotence`. Накладных расходов на идемпотентность нет. 309 |
310 | 311 | --- 312 | 313 | ## Консумеры 314 | 315 | О брокерах поговорили, о продюсерах — тоже. Теперь о консумерах: как программы-консумеры читают данные из Kafka. 316 | 317 | #### Дизайн консумера 318 | 319 | ![Дизайн консумера](./assets/104.png) 320 | 321 | Типичная программа-консумер работает так: при запуске внутри консумера работает таймер, что периодически поллит новые записи из партиций брокеров. Поллер получает список батчей, связанных с топиками и партициями, из которых читает консумер. Далее полученные сообщения в батчах десериализуются. Далее консумер, как правило, как-то обрабатывает сообщения. 322 | 323 | В конце чтения консумер может закоммитить оффсет — запомнить позицию считанных записей и продолжить чтение новой порции данных. Читать можно как синхронно, так и асинхронно. Оффсет можно коммитить или не коммитить вовсе. 324 | 325 | Главное здесь, что консумер периодически вычитывает новую порцию данных, десериализует их и следом обрабатывает. 326 | 327 | #### Консумер-группы 328 | 329 | Однако было бы странно, если бы чтением всех партиций занимался только один консумер. Они могут быть объединены в кластер — **консумер-группы**. 330 | 331 | ![Консумер-группы](./assets/108.png) 332 | 333 | Перед глазами у вас по-прежнему каноничная диаграмма: с левой стороны продюсеры, в середине топики, а справа расположены консумеры. Есть два продюсера, каждый из которых пишет в свой топик, у каждого топика есть три партиции. 334 | 335 | Есть одна консумер-группа с двумя экземплярами одной и той же программы. Т.е. это одна и та же программа, запущенная два раза. Эта программа-консумер читает два топика: X и Y. 336 | 337 | Консумер подключается к брокеру с лидерской партицией, поллит изменения в партиции, считывает сообщения, наполняет буфер, а затем проводит обработку полученных сообщений. 338 | 339 | Обратите внимание на распределение партиций между ними: они распределены кооперативно: каждому потребителю досталось по три партиции. Распределение партиций между консумерами в пределах одной группы выполняется автоматически на стороне брокеров Kafka. Kafka старается честно распределять партиции между консумер-группами, насколько это возможно. 340 | 341 | При этом каждая такая группа имеет свой идентификатор, позволяя регистрироваться на брокерах Kafka. Пространство имён консумер-групп глобально, а значит имена консумер-групп в кластере Kafka уникальны. 342 | 343 | И, наконец, самое главное: Kafka сохраняет на своей стороне текущий оффсет по каждой партиции топиков, входящих в состав консумер-группы. Это значит, что при подключении или отключении консумеров от группы, чтение продолжится с последней сохранённой позиции. Это делает консумер-группы незаменимыми при работе event-driven систем: мы можем без проблем деплоить наши приложения, не задумываясь о хранении оффсета на стороне клиента. 344 | 345 | Для этого консумер в группе, после обработки прочитанных сообщений отправляет запрос на сохранение оффсета — или же коммитит свой оффсет. Технически, нет никаких ограничений на то, чтобы коммитить оффсет и до обработки сообщений, однако для большинства сценариев разумнее делать это после. 346 | 347 | #### Ребалансировка консумер-групп 348 | 349 | Давайте рассмотрим сценарий, когда композиция группы меняется. В кластере Kafka консумер-группы создаются автоматически при подключении консумеров к кластеру и создавать её вручную нет необходимости, но это возможно через её инструментарий. У новой группы отсутствуют сохранённые оффсеты партиций топиков и по умолчанию равны `-1`. 350 | 351 | ![Ребалансировка консумер-групп](./assets/110.gif) 352 | 353 | При появлении новых участников в группе (`JoinGroup`), в специальном процессе брокера Group Coordinator первому вошедшему консумеру присваивается роль Group Leader. 354 | 355 | Лидер группы отвечает за распределение партиций между всеми участниками группы. Процесс поиска лидера группы, назначения партиций, стабилизации и подключения консумеров в группе к брокерам называется **ребалансировкой консумер-группы**. 356 | 357 | Процесс ребалансировки группы по умолчанию заставляет все консумеры в группе прекратить чтение, дождаться полной синхронизации участников, чтобы обрести новые партиции для чтения. В Kafka есть и другие стратегии ребалансировки группы, включая Static membership или Cooperative Incremental Partition Assignor, но об этом как-нибудь в другой раз. 358 | 359 | Как только группа стала стабильной, а её участники получили партиции, то консумеры в ней начинают чтение. Поскольку группа новая и раньше не существовала, то консумер выбирает позицию чтения оффсета: с самого начала (earliest) или же с конца (latest). Топик мог существовать несколько месяцев, а консумер появиться совсем недавно и важно решить: читать ли все сообщения или же достаточно читать с конца самые последние, пропустив всю историю. 360 | 361 | Выбор между двумя опциями зависит от бизнес-логики протекающих внутри топика событий. Учитывайте это при настройке консумеров. 362 | 363 | ![Ребалансировка консумер-групп](./assets/112.gif) 364 | 365 | Если в группу добавить нового участника, процесс ребалансировки запускается вновь. Новому участнику, как и уже имеющимся консумерам в группе будут назначены партиции, а лидер группы постарается их распределить между всеми более или менее честно, согласно выбранной им настраиваемой стратегии. Затем группа вновь переходит в стабильное состояние. 366 | 367 | Для того, чтобы Group Coordinator в кластере Kafka знал о том, какие из его участников активны и работают, а какие уже нет — каждый консумер в группе регулярно в равные промежутки времени отправляет Heartbeat-сообщение. Это значение настраивается программой-консумером перед запуском. 368 | 369 | Также консумер объявляет и время жизни сессии — если за это время консумер не смог отправить ни одно из Heartbeat-сообщений до брокера, то он покидает группу. Брокер же, напротив, не получив ни одно из Heartbeat-сообщений от своих консумеров, запускает процесс ребалансировки консумеров в группе. 370 | 371 | Процесс ребалансировки достаточно болезненный для больших консумер-групп с множеством топиков, поскольку вызывает Stop-The-World во всей группе при малейшей смене композиции участников группы или состава партиций в топиках. Например, при смене лидера партиции в случае выхода брокера из кластера (по причине аварии или плановых работ), Group Coordinator также инициирует ребалансировку. 372 | 373 | Поэтому базовая рекомендация к разработчикам программ-консумеров: использовать по одной консумер-группе на топик, а также держать число потребителей не слишком большим, чтобы не запускать ребалансировку много раз, но и не слишком маленьким, чтобы сохранять производительность и надёжность при чтении. 374 | 375 | Также значения интервала Heartbeat и время жизни сессии следует устанавливать так, чтобы Heartbeat-интервал был в три-четыре раза меньше, Session timeout. Сами значения выбирайте не слишком большими, чтобы не увеличивать время до обнаружения «выпавшего» консумера из группы, но и не слишком маленьким, чтобы в случае малейших сетевых проблем, группа не уходила в ребалансировку. 376 | 377 | ![Ребалансировка консумер-групп](./assets/121.png) 378 | 379 | Рассмотрим ещё один гипотетический сценарий: партиций в топике 4, а консумеров в группе 5. В этом случае группа будет стабилизирована, однако участники, которым не достанется ни одна из партиций, будут бездействовать. Такое происходит потому, что с одной партицией в группе может работать только один консумер. Т.е. два и более консумеров не могут читать из одной партиции в группе. 380 | 381 | Таким образом, проистекает следующая базовая рекомендация: устанавливайте достаточное число партиций на старте, чтобы вы могли горизонтально масштабировать ваше приложение. Увеличение партиций в моменте не принесёт вам почти никакого результата, поскольку уже записанные в лог сообщения не могут быть перемещены и распределены между новыми партициями средствами Kafka, а репартицирование своими силами всегда несёт риски, связанные с очерёдностью и идемпотентностью. 382 | 383 | --- 384 | 385 |
386 | TL;DR 387 | Партиции в консумер-группах распределяются автоматически Group Coordinator-ом при помощи Group leader — первого участника в группе. Каждый консумер в группе может читать одну и более партиций разных топиков. Если консумеру не достанется партиции, то он будет бездействовать, что мешает масштабированию. 388 | 389 | 390 | Основное преимущество консумер-группы перед обычным консумером — в хранении оффсета партиций на стороне брокера. Это позволяет консумерам прерывать работу, а после возобновлять её с того же места, где было окончено чтение. 391 | 392 | Для проверки живости консумеров, те обязаны отправлять брокеру Heartbeat-сообщение. Если консумер не успел отправить их брокеру, то консумер может покинуть группу сам, либо же брокер, не получив подтверждение, сам выбросит консумера из группы, что запустит ребалансировку. 393 | 394 | Любая смена композиции партиций в топиках и участников в группе запускает ребалансировку. Ребалансировка — болезненный процесс для консумеров. В этот момент все консумеры остановят чтение и не начнут его до полной синхронизации и стабилизации группы. Есть различные алгоритмы ребалансировки, позволяющие смягчить процесс (например, StickyAssignor, Static membership, Incremental Cooperative Rebalancing, …), но по умолчанию — это Stop-The-World. 395 | 396 | В новом консумере важно правильно выбрать политику оффсета. Иногда читать с начала не нужно и достаточно «перемотать» оффсет в конец и сразу получать только новые события. 397 | 398 | И, наконец, два и более консумеров в группе не могут читать из одной и той же партиции. Чтобы не оказаться в ситуации, когда вам некуда масштабироваться при чтении, заранее установите достаточное число партиций. 399 |
400 | 401 | --- 402 | 403 | 🎉 Поздравляю! Вы прочитали до конца. Сделайте перерыв на 5-10 минут, а после — переходите [к практике](../guide/001-intro.md). 404 | -------------------------------------------------------------------------------- /examples/consumer/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gongled/kafka-workshop/examples/consumer 2 | 3 | go 1.16 4 | 5 | require github.com/segmentio/kafka-go v0.4.38 6 | -------------------------------------------------------------------------------- /examples/consumer/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= 5 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 6 | github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= 7 | github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 8 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 9 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 10 | github.com/segmentio/kafka-go v0.4.38 h1:iQdOBbUSdfuYlFpvjuALgj7N6DrdPA0HfB4AhREOdtg= 11 | github.com/segmentio/kafka-go v0.4.38/go.mod h1:ikyuGon/60MN/vXFgykf7Zm8P5Be49gJU6vezwjnnhU= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 14 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 15 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 16 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 17 | github.com/xdg/scram v1.0.5 h1:TuS0RFmt5Is5qm9Tm2SoD89OPqe4IRiFtyFY4iwWXsw= 18 | github.com/xdg/scram v1.0.5/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= 19 | github.com/xdg/stringprep v1.0.3 h1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4= 20 | github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= 21 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= 22 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 23 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 24 | golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 h1:8NSylCMxLW4JvserAndSgFL7aPli6A68yf0bYFTcWCM= 25 | golang.org/x/net v0.0.0-20220706163947-c90051bbdb60/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 26 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 28 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 29 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 30 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 31 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 32 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 33 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 34 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 35 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 36 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 37 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 38 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 39 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | -------------------------------------------------------------------------------- /examples/consumer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "os" 9 | "strings" 10 | 11 | "github.com/segmentio/kafka-go" 12 | ) 13 | 14 | var ( 15 | brokers = "" 16 | group = "" 17 | topic = "" 18 | ) 19 | 20 | func init() { 21 | flag.StringVar(&brokers, "brokers", os.Getenv("KAFKA_BROKERS"), "Kafka bootstrap brokers to connect to, as a comma separated list") 22 | flag.StringVar(&group, "group", os.Getenv("KAFKA_CONSUMER_GROUP"), "Kafka consumer group definition") 23 | flag.StringVar(&topic, "topic", os.Getenv("KAFKA_TOPIC"), "Kafka topic to be consumed") 24 | flag.Parse() 25 | 26 | if len(brokers) == 0 { 27 | panic("no Kafka bootstrap brokers defined, please set the -brokers flag") 28 | } 29 | 30 | if len(topic) == 0 { 31 | panic("no topic given to be consumed, please set the -topic flag") 32 | } 33 | 34 | if len(group) == 0 { 35 | panic("no Kafka consumer group defined, please set the -group flag") 36 | } 37 | } 38 | 39 | func main() { 40 | // make a new reader that consumes from topic-A 41 | addrs := strings.Split(brokers, ",") 42 | r := kafka.NewReader(kafka.ReaderConfig{ 43 | Brokers: addrs, 44 | GroupID: group, 45 | Topic: topic, 46 | MinBytes: 10e2, // 1KB 47 | MaxBytes: 10e6, // 10MB 48 | }) 49 | 50 | log.Print("Starting consumer program...") 51 | log.Print(fmt.Sprintf("Brokers (%s), topic (%s), consumer group (%s)", brokers, topic, group)) 52 | 53 | for { 54 | m, err := r.ReadMessage(context.Background()) 55 | if err != nil { 56 | break 57 | } 58 | log.Print(fmt.Sprintf("message at topic/partition/offset %v/%v/%v: %s = %s\n", m.Topic, m.Partition, m.Offset, string(m.Key), string(m.Value))) 59 | } 60 | 61 | if err := r.Close(); err != nil { 62 | log.Fatal("failed to close reader:", err) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/producer/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gongled/kafka-workshop/examples/producer 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/brianvoe/gofakeit/v6 v6.20.1 // indirect 7 | github.com/segmentio/kafka-go v0.4.38 8 | ) 9 | -------------------------------------------------------------------------------- /examples/producer/go.sum: -------------------------------------------------------------------------------- 1 | github.com/brianvoe/gofakeit/v6 v6.20.1 h1:8ihJ60OvPnPJ2W6wZR7M+TTeaZ9bml0z6oy4gvyJ/ek= 2 | github.com/brianvoe/gofakeit/v6 v6.20.1/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= 7 | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 8 | github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= 9 | github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 12 | github.com/segmentio/kafka-go v0.4.38 h1:iQdOBbUSdfuYlFpvjuALgj7N6DrdPA0HfB4AhREOdtg= 13 | github.com/segmentio/kafka-go v0.4.38/go.mod h1:ikyuGon/60MN/vXFgykf7Zm8P5Be49gJU6vezwjnnhU= 14 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 16 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 17 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 18 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 19 | github.com/xdg/scram v1.0.5 h1:TuS0RFmt5Is5qm9Tm2SoD89OPqe4IRiFtyFY4iwWXsw= 20 | github.com/xdg/scram v1.0.5/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= 21 | github.com/xdg/stringprep v1.0.3 h1:cmL5Enob4W83ti/ZHuZLuKD/xqJfus4fVPwE+/BDm+4= 22 | github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= 23 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= 24 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 25 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 26 | golang.org/x/net v0.0.0-20220706163947-c90051bbdb60 h1:8NSylCMxLW4JvserAndSgFL7aPli6A68yf0bYFTcWCM= 27 | golang.org/x/net v0.0.0-20220706163947-c90051bbdb60/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 28 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 31 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 32 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 33 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 34 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 35 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 36 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 37 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 38 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 39 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 40 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 41 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 42 | -------------------------------------------------------------------------------- /examples/producer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/brianvoe/gofakeit/v6" 15 | "github.com/segmentio/kafka-go" 16 | ) 17 | 18 | var ( 19 | brokers = "" 20 | topic = "" 21 | ) 22 | 23 | type CandidateLocation struct { 24 | ID int `json:"id"` 25 | Lat float64 `json:"lat"` 26 | Lon float64 `json:"lon"` 27 | } 28 | 29 | func init() { 30 | flag.StringVar(&brokers, "brokers", os.Getenv("KAFKA_BROKERS"), "Kafka bootstrap brokers to connect to, as a comma separated list") 31 | flag.StringVar(&topic, "topic", os.Getenv("KAFKA_TOPIC"), "Kafka topic to be produced") 32 | flag.Parse() 33 | 34 | if len(brokers) == 0 { 35 | panic("no Kafka bootstrap brokers defined, please set the -brokers flag") 36 | } 37 | 38 | if len(topic) == 0 { 39 | panic("no topic given to be consumed, please set the -topic flag") 40 | } 41 | 42 | } 43 | 44 | func main() { 45 | // make a writer that produces to topic-A, using the round-robin distribution 46 | addrs := strings.Split(brokers, ",") 47 | w := &kafka.Writer{ 48 | Addr: kafka.TCP(addrs...), 49 | Topic: topic, 50 | // Use round-robin partitioning to avoid message ordering 51 | // Balancer: &kafka.RoundRobin{}, 52 | Balancer: &kafka.Hash{}, 53 | } 54 | 55 | log.Print("Starting producer program...") 56 | log.Print(fmt.Sprintf("Brokers (%s), topic (%s)", brokers, topic)) 57 | 58 | for { 59 | candidate_id := gofakeit.Number(1, 6) 60 | location := CandidateLocation{ 61 | ID: candidate_id, 62 | Lat: gofakeit.Latitude(), 63 | Lon: gofakeit.Longitude(), 64 | } 65 | 66 | msg, err := json.Marshal(location) 67 | 68 | if err != nil { 69 | panic(err) 70 | } 71 | 72 | payload := kafka.Message{ 73 | Key: []byte(strconv.Itoa(candidate_id)), 74 | Value: []byte(msg), 75 | } 76 | 77 | err = w.WriteMessages(context.Background(), payload) 78 | 79 | if err != nil { 80 | log.Fatal("failed to write messages:", err) 81 | } 82 | 83 | log.Print(fmt.Sprintf("message written at topic %s: %s = %s\n", w.Topic, string(payload.Key), string(payload.Value))) 84 | 85 | time.Sleep(1 * time.Second) 86 | } 87 | 88 | if err := w.Close(); err != nil { 89 | log.Fatal("failed to close writer:", err) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /grafana/dashboards/001-kafka-status.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": { 7 | "type": "datasource", 8 | "uid": "grafana" 9 | }, 10 | "enable": true, 11 | "hide": true, 12 | "iconColor": "rgba(0, 211, 255, 1)", 13 | "name": "Annotations & Alerts", 14 | "target": { 15 | "limit": 100, 16 | "matchAny": false, 17 | "tags": [], 18 | "type": "dashboard" 19 | }, 20 | "type": "dashboard" 21 | } 22 | ] 23 | }, 24 | "editable": true, 25 | "fiscalYearStartMonth": 0, 26 | "graphTooltip": 1, 27 | "links": [], 28 | "liveNow": false, 29 | "panels": [ 30 | { 31 | "collapsed": false, 32 | "datasource": { 33 | "type": "prometheus", 34 | "uid": "000000002" 35 | }, 36 | "gridPos": { 37 | "h": 1, 38 | "w": 24, 39 | "x": 0, 40 | "y": 0 41 | }, 42 | "id": 32, 43 | "panels": [], 44 | "targets": [ 45 | { 46 | "datasource": { 47 | "type": "prometheus", 48 | "uid": "000000002" 49 | }, 50 | "refId": "A" 51 | } 52 | ], 53 | "title": "Overview", 54 | "type": "row" 55 | }, 56 | { 57 | "datasource": { 58 | "type": "prometheus", 59 | "uid": "${datasource}" 60 | }, 61 | "description": "", 62 | "fieldConfig": { 63 | "defaults": { 64 | "color": { 65 | "mode": "thresholds" 66 | }, 67 | "mappings": [], 68 | "thresholds": { 69 | "mode": "absolute", 70 | "steps": [ 71 | { 72 | "color": "green", 73 | "value": null 74 | }, 75 | { 76 | "color": "dark-red", 77 | "value": 1 78 | }, 79 | { 80 | "color": "#EAB839", 81 | "value": 2 82 | }, 83 | { 84 | "color": "green", 85 | "value": 3 86 | } 87 | ] 88 | } 89 | }, 90 | "overrides": [] 91 | }, 92 | "gridPos": { 93 | "h": 4, 94 | "w": 3, 95 | "x": 0, 96 | "y": 1 97 | }, 98 | "id": 14, 99 | "interval": "1m", 100 | "links": [], 101 | "options": { 102 | "colorMode": "background", 103 | "graphMode": "area", 104 | "justifyMode": "auto", 105 | "orientation": "auto", 106 | "reduceOptions": { 107 | "calcs": [ 108 | "lastNotNull" 109 | ], 110 | "fields": "", 111 | "values": false 112 | }, 113 | "text": {}, 114 | "textMode": "auto" 115 | }, 116 | "pluginVersion": "9.3.6", 117 | "targets": [ 118 | { 119 | "datasource": { 120 | "type": "prometheus", 121 | "uid": "${datasource}" 122 | }, 123 | "editorMode": "code", 124 | "exemplar": true, 125 | "expr": "count(kafka_server_kafkaserver_brokerstate{job=~\"$job\"})", 126 | "format": "time_series", 127 | "instant": true, 128 | "interval": "", 129 | "intervalFactor": 1, 130 | "legendFormat": "{{topic}}", 131 | "refId": "A" 132 | } 133 | ], 134 | "title": "Brokers", 135 | "type": "stat" 136 | }, 137 | { 138 | "datasource": { 139 | "type": "prometheus", 140 | "uid": "${datasource}" 141 | }, 142 | "description": "", 143 | "fieldConfig": { 144 | "defaults": { 145 | "color": { 146 | "mode": "thresholds" 147 | }, 148 | "mappings": [], 149 | "thresholds": { 150 | "mode": "absolute", 151 | "steps": [ 152 | { 153 | "color": "green", 154 | "value": null 155 | }, 156 | { 157 | "color": "light-orange", 158 | "value": 200 159 | } 160 | ] 161 | } 162 | }, 163 | "overrides": [] 164 | }, 165 | "gridPos": { 166 | "h": 4, 167 | "w": 3, 168 | "x": 3, 169 | "y": 1 170 | }, 171 | "id": 20, 172 | "interval": "1m", 173 | "links": [], 174 | "options": { 175 | "colorMode": "background", 176 | "graphMode": "area", 177 | "justifyMode": "auto", 178 | "orientation": "auto", 179 | "reduceOptions": { 180 | "calcs": [ 181 | "lastNotNull" 182 | ], 183 | "fields": "", 184 | "values": false 185 | }, 186 | "text": {}, 187 | "textMode": "auto" 188 | }, 189 | "pluginVersion": "9.3.6", 190 | "targets": [ 191 | { 192 | "datasource": { 193 | "type": "prometheus", 194 | "uid": "${datasource}" 195 | }, 196 | "editorMode": "code", 197 | "exemplar": true, 198 | "expr": "count(count(kafka_cluster_partition_replicascount{job=~\"$job\"}) by (topic))", 199 | "format": "time_series", 200 | "instant": true, 201 | "interval": "", 202 | "intervalFactor": 1, 203 | "legendFormat": "{{topic}}", 204 | "refId": "A" 205 | } 206 | ], 207 | "title": "Topics", 208 | "type": "stat" 209 | }, 210 | { 211 | "datasource": { 212 | "type": "prometheus", 213 | "uid": "${datasource}" 214 | }, 215 | "description": "", 216 | "fieldConfig": { 217 | "defaults": { 218 | "color": { 219 | "mode": "thresholds" 220 | }, 221 | "mappings": [], 222 | "thresholds": { 223 | "mode": "absolute", 224 | "steps": [ 225 | { 226 | "color": "green", 227 | "value": null 228 | }, 229 | { 230 | "color": "#EAB839", 231 | "value": 2000 232 | }, 233 | { 234 | "color": "green", 235 | "value": 3000 236 | } 237 | ] 238 | } 239 | }, 240 | "overrides": [] 241 | }, 242 | "gridPos": { 243 | "h": 4, 244 | "w": 3, 245 | "x": 6, 246 | "y": 1 247 | }, 248 | "id": 21, 249 | "interval": "1m", 250 | "links": [], 251 | "options": { 252 | "colorMode": "background", 253 | "graphMode": "area", 254 | "justifyMode": "auto", 255 | "orientation": "auto", 256 | "reduceOptions": { 257 | "calcs": [ 258 | "lastNotNull" 259 | ], 260 | "fields": "", 261 | "values": false 262 | }, 263 | "text": {}, 264 | "textMode": "auto" 265 | }, 266 | "pluginVersion": "9.3.6", 267 | "targets": [ 268 | { 269 | "datasource": { 270 | "type": "prometheus", 271 | "uid": "${datasource}" 272 | }, 273 | "editorMode": "code", 274 | "exemplar": true, 275 | "expr": "count(count(kafka_cluster_partition_replicascount{job=~\"$job\"}) by (topic, partition))", 276 | "format": "time_series", 277 | "instant": true, 278 | "interval": "", 279 | "intervalFactor": 1, 280 | "legendFormat": "{{topic}}", 281 | "refId": "A" 282 | } 283 | ], 284 | "title": "Partitions", 285 | "type": "stat" 286 | }, 287 | { 288 | "datasource": { 289 | "type": "prometheus", 290 | "uid": "${datasource}" 291 | }, 292 | "description": "", 293 | "fieldConfig": { 294 | "defaults": { 295 | "color": { 296 | "mode": "thresholds" 297 | }, 298 | "mappings": [], 299 | "thresholds": { 300 | "mode": "absolute", 301 | "steps": [ 302 | { 303 | "color": "green", 304 | "value": null 305 | }, 306 | { 307 | "color": "green", 308 | "value": 6000 309 | } 310 | ] 311 | } 312 | }, 313 | "overrides": [] 314 | }, 315 | "gridPos": { 316 | "h": 4, 317 | "w": 3, 318 | "x": 9, 319 | "y": 1 320 | }, 321 | "id": 43, 322 | "interval": "1m", 323 | "links": [], 324 | "options": { 325 | "colorMode": "background", 326 | "graphMode": "area", 327 | "justifyMode": "auto", 328 | "orientation": "auto", 329 | "reduceOptions": { 330 | "calcs": [ 331 | "lastNotNull" 332 | ], 333 | "fields": "", 334 | "values": false 335 | }, 336 | "text": {}, 337 | "textMode": "auto" 338 | }, 339 | "pluginVersion": "9.3.6", 340 | "targets": [ 341 | { 342 | "datasource": { 343 | "type": "prometheus", 344 | "uid": "${datasource}" 345 | }, 346 | "editorMode": "code", 347 | "exemplar": true, 348 | "expr": "sum(kafka_cluster_partition_replicascount{job=~\"$job\"}) - sum(kafka_cluster_partition_underreplicated{job=~\"$job\"})", 349 | "format": "time_series", 350 | "instant": true, 351 | "interval": "", 352 | "intervalFactor": 1, 353 | "legendFormat": "{{topic}}", 354 | "refId": "A" 355 | } 356 | ], 357 | "title": "Replicated partitions", 358 | "type": "stat" 359 | }, 360 | { 361 | "datasource": { 362 | "type": "prometheus", 363 | "uid": "${datasource}" 364 | }, 365 | "description": "", 366 | "fieldConfig": { 367 | "defaults": { 368 | "color": { 369 | "mode": "thresholds" 370 | }, 371 | "mappings": [], 372 | "thresholds": { 373 | "mode": "absolute", 374 | "steps": [ 375 | { 376 | "color": "green", 377 | "value": null 378 | }, 379 | { 380 | "color": "green", 381 | "value": 0 382 | }, 383 | { 384 | "color": "yellow", 385 | "value": 10 386 | }, 387 | { 388 | "color": "orange", 389 | "value": 100 390 | }, 391 | { 392 | "color": "red", 393 | "value": 200 394 | } 395 | ] 396 | } 397 | }, 398 | "overrides": [] 399 | }, 400 | "gridPos": { 401 | "h": 4, 402 | "w": 3, 403 | "x": 12, 404 | "y": 1 405 | }, 406 | "id": 18, 407 | "interval": "1m", 408 | "links": [], 409 | "options": { 410 | "colorMode": "background", 411 | "graphMode": "area", 412 | "justifyMode": "auto", 413 | "orientation": "auto", 414 | "reduceOptions": { 415 | "calcs": [ 416 | "lastNotNull" 417 | ], 418 | "fields": "", 419 | "values": false 420 | }, 421 | "text": {}, 422 | "textMode": "auto" 423 | }, 424 | "pluginVersion": "9.3.6", 425 | "targets": [ 426 | { 427 | "datasource": { 428 | "type": "prometheus", 429 | "uid": "${datasource}" 430 | }, 431 | "editorMode": "code", 432 | "exemplar": true, 433 | "expr": "sum(kafka_cluster_partition_underreplicated{job=~\"$job\"})", 434 | "format": "time_series", 435 | "instant": true, 436 | "interval": "", 437 | "intervalFactor": 1, 438 | "legendFormat": "{{topic}}", 439 | "refId": "A" 440 | } 441 | ], 442 | "title": "Non-replicated partitions", 443 | "type": "stat" 444 | }, 445 | { 446 | "datasource": { 447 | "type": "prometheus", 448 | "uid": "${datasource}" 449 | }, 450 | "description": "", 451 | "fieldConfig": { 452 | "defaults": { 453 | "color": { 454 | "mode": "thresholds" 455 | }, 456 | "mappings": [], 457 | "thresholds": { 458 | "mode": "absolute", 459 | "steps": [ 460 | { 461 | "color": "green", 462 | "value": null 463 | }, 464 | { 465 | "color": "green", 466 | "value": 0 467 | }, 468 | { 469 | "color": "red", 470 | "value": 1 471 | } 472 | ] 473 | } 474 | }, 475 | "overrides": [] 476 | }, 477 | "gridPos": { 478 | "h": 4, 479 | "w": 3, 480 | "x": 15, 481 | "y": 1 482 | }, 483 | "id": 12, 484 | "interval": "1m", 485 | "links": [], 486 | "options": { 487 | "colorMode": "background", 488 | "graphMode": "area", 489 | "justifyMode": "auto", 490 | "orientation": "auto", 491 | "reduceOptions": { 492 | "calcs": [ 493 | "lastNotNull" 494 | ], 495 | "fields": "", 496 | "values": false 497 | }, 498 | "text": {}, 499 | "textMode": "auto" 500 | }, 501 | "pluginVersion": "9.3.6", 502 | "targets": [ 503 | { 504 | "datasource": { 505 | "type": "prometheus", 506 | "uid": "${datasource}" 507 | }, 508 | "editorMode": "code", 509 | "exemplar": true, 510 | "expr": "sum(kafka_controller_kafkacontroller_offlinepartitionscount{job=~\"$job\"})", 511 | "format": "time_series", 512 | "instant": true, 513 | "interval": "", 514 | "intervalFactor": 1, 515 | "legendFormat": "{{topic}}", 516 | "refId": "A" 517 | } 518 | ], 519 | "title": "Unavailable partitions", 520 | "type": "stat" 521 | }, 522 | { 523 | "datasource": { 524 | "type": "prometheus", 525 | "uid": "${datasource}" 526 | }, 527 | "description": "", 528 | "fieldConfig": { 529 | "defaults": { 530 | "color": { 531 | "mode": "thresholds" 532 | }, 533 | "mappings": [], 534 | "thresholds": { 535 | "mode": "absolute", 536 | "steps": [ 537 | { 538 | "color": "green", 539 | "value": null 540 | }, 541 | { 542 | "color": "green", 543 | "value": 0 544 | }, 545 | { 546 | "color": "#EAB839", 547 | "value": 10 548 | }, 549 | { 550 | "color": "orange", 551 | "value": 25 552 | }, 553 | { 554 | "color": "red", 555 | "value": 50 556 | } 557 | ] 558 | }, 559 | "unit": "ms" 560 | }, 561 | "overrides": [] 562 | }, 563 | "gridPos": { 564 | "h": 4, 565 | "w": 3, 566 | "x": 18, 567 | "y": 1 568 | }, 569 | "id": 52, 570 | "interval": "5m", 571 | "links": [], 572 | "options": { 573 | "colorMode": "background", 574 | "graphMode": "area", 575 | "justifyMode": "auto", 576 | "orientation": "auto", 577 | "reduceOptions": { 578 | "calcs": [ 579 | "lastNotNull" 580 | ], 581 | "fields": "", 582 | "values": false 583 | }, 584 | "text": {}, 585 | "textMode": "auto" 586 | }, 587 | "pluginVersion": "9.3.6", 588 | "targets": [ 589 | { 590 | "datasource": { 591 | "type": "prometheus", 592 | "uid": "${datasource}" 593 | }, 594 | "editorMode": "code", 595 | "exemplar": true, 596 | "expr": "quantile(0.99, rate(kafka_network_requestmetrics_totaltimems{job=~\"$job\",quantile=\"0.99\"}[$__rate_interval]))", 597 | "format": "time_series", 598 | "instant": true, 599 | "interval": "", 600 | "intervalFactor": 1, 601 | "legendFormat": "__auto", 602 | "refId": "A" 603 | } 604 | ], 605 | "title": "API latency, p99", 606 | "type": "stat" 607 | }, 608 | { 609 | "collapsed": false, 610 | "gridPos": { 611 | "h": 1, 612 | "w": 24, 613 | "x": 0, 614 | "y": 5 615 | }, 616 | "id": 54, 617 | "panels": [], 618 | "title": "Consumers & producers", 619 | "type": "row" 620 | }, 621 | { 622 | "datasource": { 623 | "type": "prometheus", 624 | "uid": "${datasource}" 625 | }, 626 | "description": "", 627 | "fieldConfig": { 628 | "defaults": { 629 | "color": { 630 | "mode": "thresholds" 631 | }, 632 | "mappings": [], 633 | "thresholds": { 634 | "mode": "absolute", 635 | "steps": [ 636 | { 637 | "color": "green", 638 | "value": null 639 | } 640 | ] 641 | } 642 | }, 643 | "overrides": [] 644 | }, 645 | "gridPos": { 646 | "h": 4, 647 | "w": 4, 648 | "x": 0, 649 | "y": 6 650 | }, 651 | "id": 16, 652 | "interval": "1m", 653 | "links": [], 654 | "options": { 655 | "colorMode": "background", 656 | "graphMode": "area", 657 | "justifyMode": "auto", 658 | "orientation": "auto", 659 | "reduceOptions": { 660 | "calcs": [ 661 | "lastNotNull" 662 | ], 663 | "fields": "", 664 | "values": false 665 | }, 666 | "text": {}, 667 | "textMode": "auto" 668 | }, 669 | "pluginVersion": "9.3.6", 670 | "targets": [ 671 | { 672 | "datasource": { 673 | "type": "prometheus", 674 | "uid": "${datasource}" 675 | }, 676 | "editorMode": "code", 677 | "exemplar": true, 678 | "expr": "count(count(kafka_consumergroup_current_offset{}) by (consumergroup) > 0)", 679 | "format": "time_series", 680 | "instant": true, 681 | "interval": "", 682 | "intervalFactor": 1, 683 | "legendFormat": "{{topic}}", 684 | "refId": "A" 685 | } 686 | ], 687 | "title": "Consumer groups", 688 | "type": "stat" 689 | }, 690 | { 691 | "datasource": { 692 | "type": "prometheus", 693 | "uid": "${datasource}" 694 | }, 695 | "description": "", 696 | "fieldConfig": { 697 | "defaults": { 698 | "color": { 699 | "mode": "thresholds" 700 | }, 701 | "mappings": [], 702 | "thresholds": { 703 | "mode": "absolute", 704 | "steps": [ 705 | { 706 | "color": "green", 707 | "value": null 708 | }, 709 | { 710 | "color": "yellow", 711 | "value": 50000 712 | }, 713 | { 714 | "color": "orange", 715 | "value": 75000 716 | }, 717 | { 718 | "color": "red", 719 | "value": 100000 720 | } 721 | ] 722 | }, 723 | "unit": "short" 724 | }, 725 | "overrides": [] 726 | }, 727 | "gridPos": { 728 | "h": 4, 729 | "w": 4, 730 | "x": 4, 731 | "y": 6 732 | }, 733 | "id": 47, 734 | "interval": "1m", 735 | "links": [], 736 | "options": { 737 | "colorMode": "background", 738 | "graphMode": "area", 739 | "justifyMode": "auto", 740 | "orientation": "auto", 741 | "reduceOptions": { 742 | "calcs": [ 743 | "lastNotNull" 744 | ], 745 | "fields": "", 746 | "values": false 747 | }, 748 | "text": {}, 749 | "textMode": "auto" 750 | }, 751 | "pluginVersion": "9.3.6", 752 | "targets": [ 753 | { 754 | "datasource": { 755 | "type": "prometheus", 756 | "uid": "${datasource}" 757 | }, 758 | "editorMode": "code", 759 | "exemplar": false, 760 | "expr": "quantile(1.0, sum(kafka_consumergroup_lag{}) by (consumergroup, topic))", 761 | "format": "time_series", 762 | "instant": true, 763 | "interval": "", 764 | "intervalFactor": 1, 765 | "legendFormat": "__auto", 766 | "range": false, 767 | "refId": "A" 768 | } 769 | ], 770 | "title": "Consumer group offset (max)", 771 | "type": "stat" 772 | }, 773 | { 774 | "datasource": { 775 | "type": "prometheus", 776 | "uid": "${datasource}" 777 | }, 778 | "description": "", 779 | "fieldConfig": { 780 | "defaults": { 781 | "color": { 782 | "mode": "thresholds" 783 | }, 784 | "mappings": [], 785 | "thresholds": { 786 | "mode": "absolute", 787 | "steps": [ 788 | { 789 | "color": "green", 790 | "value": null 791 | } 792 | ] 793 | }, 794 | "unit": "short" 795 | }, 796 | "overrides": [] 797 | }, 798 | "gridPos": { 799 | "h": 4, 800 | "w": 4, 801 | "x": 8, 802 | "y": 6 803 | }, 804 | "id": 44, 805 | "interval": "1m", 806 | "links": [], 807 | "options": { 808 | "colorMode": "background", 809 | "graphMode": "area", 810 | "justifyMode": "auto", 811 | "orientation": "auto", 812 | "reduceOptions": { 813 | "calcs": [ 814 | "lastNotNull" 815 | ], 816 | "fields": "", 817 | "values": false 818 | }, 819 | "text": {}, 820 | "textMode": "auto" 821 | }, 822 | "pluginVersion": "9.3.6", 823 | "targets": [ 824 | { 825 | "datasource": { 826 | "type": "prometheus", 827 | "uid": "${datasource}" 828 | }, 829 | "editorMode": "code", 830 | "exemplar": true, 831 | "expr": "sum(rate(kafka_server_brokertopicmetrics_messagesin_total{job=~\"$job\"}[$__rate_interval]))", 832 | "format": "time_series", 833 | "instant": true, 834 | "interval": "", 835 | "intervalFactor": 1, 836 | "legendFormat": "{{topic}}", 837 | "refId": "A" 838 | } 839 | ], 840 | "title": "Messages rate", 841 | "type": "stat" 842 | }, 843 | { 844 | "datasource": { 845 | "type": "prometheus", 846 | "uid": "${datasource}" 847 | }, 848 | "description": "", 849 | "fieldConfig": { 850 | "defaults": { 851 | "color": { 852 | "mode": "thresholds" 853 | }, 854 | "mappings": [], 855 | "thresholds": { 856 | "mode": "absolute", 857 | "steps": [ 858 | { 859 | "color": "green", 860 | "value": null 861 | }, 862 | { 863 | "color": "orange", 864 | "value": 65535000 865 | }, 866 | { 867 | "color": "dark-red", 868 | "value": 131070000 869 | } 870 | ] 871 | }, 872 | "unit": "bytes" 873 | }, 874 | "overrides": [] 875 | }, 876 | "gridPos": { 877 | "h": 4, 878 | "w": 4, 879 | "x": 12, 880 | "y": 6 881 | }, 882 | "id": 45, 883 | "interval": "1m", 884 | "links": [], 885 | "options": { 886 | "colorMode": "background", 887 | "graphMode": "area", 888 | "justifyMode": "auto", 889 | "orientation": "auto", 890 | "reduceOptions": { 891 | "calcs": [ 892 | "lastNotNull" 893 | ], 894 | "fields": "", 895 | "values": false 896 | }, 897 | "text": {}, 898 | "textMode": "auto" 899 | }, 900 | "pluginVersion": "9.3.6", 901 | "targets": [ 902 | { 903 | "datasource": { 904 | "type": "prometheus", 905 | "uid": "${datasource}" 906 | }, 907 | "editorMode": "code", 908 | "exemplar": true, 909 | "expr": "sum(rate(kafka_server_brokertopicmetrics_bytesin_total{job=~\"$job\"}[$__rate_interval]))", 910 | "format": "time_series", 911 | "instant": true, 912 | "interval": "", 913 | "intervalFactor": 1, 914 | "legendFormat": "{{topic}}", 915 | "refId": "A" 916 | } 917 | ], 918 | "title": "Throughput (in)", 919 | "type": "stat" 920 | }, 921 | { 922 | "datasource": { 923 | "type": "prometheus", 924 | "uid": "${datasource}" 925 | }, 926 | "description": "", 927 | "fieldConfig": { 928 | "defaults": { 929 | "color": { 930 | "mode": "thresholds" 931 | }, 932 | "mappings": [], 933 | "thresholds": { 934 | "mode": "absolute", 935 | "steps": [ 936 | { 937 | "color": "green", 938 | "value": null 939 | }, 940 | { 941 | "color": "orange", 942 | "value": 65535000 943 | }, 944 | { 945 | "color": "red", 946 | "value": 131070000 947 | } 948 | ] 949 | }, 950 | "unit": "bytes" 951 | }, 952 | "overrides": [] 953 | }, 954 | "gridPos": { 955 | "h": 4, 956 | "w": 4, 957 | "x": 16, 958 | "y": 6 959 | }, 960 | "id": 46, 961 | "interval": "1m", 962 | "links": [], 963 | "options": { 964 | "colorMode": "background", 965 | "graphMode": "area", 966 | "justifyMode": "auto", 967 | "orientation": "auto", 968 | "reduceOptions": { 969 | "calcs": [ 970 | "lastNotNull" 971 | ], 972 | "fields": "", 973 | "values": false 974 | }, 975 | "text": {}, 976 | "textMode": "auto" 977 | }, 978 | "pluginVersion": "9.3.6", 979 | "targets": [ 980 | { 981 | "datasource": { 982 | "type": "prometheus", 983 | "uid": "${datasource}" 984 | }, 985 | "editorMode": "code", 986 | "exemplar": true, 987 | "expr": "sum(rate(kafka_server_brokertopicmetrics_bytesout_total{job=~\"$job\"}[$__rate_interval]))", 988 | "format": "time_series", 989 | "instant": true, 990 | "interval": "", 991 | "intervalFactor": 1, 992 | "legendFormat": "{{topic}}", 993 | "refId": "A" 994 | } 995 | ], 996 | "title": "Throughput (out)", 997 | "type": "stat" 998 | } 999 | ], 1000 | "refresh": "30s", 1001 | "schemaVersion": 37, 1002 | "style": "dark", 1003 | "tags": [], 1004 | "templating": { 1005 | "list": [ 1006 | { 1007 | "current": { 1008 | "selected": false, 1009 | "text": "Prometheus", 1010 | "value": "Prometheus" 1011 | }, 1012 | "hide": 0, 1013 | "includeAll": false, 1014 | "label": "Datasource", 1015 | "multi": false, 1016 | "name": "datasource", 1017 | "options": [], 1018 | "query": "prometheus", 1019 | "queryValue": "", 1020 | "refresh": 1, 1021 | "regex": "/Prometheus/", 1022 | "skipUrlSync": false, 1023 | "type": "datasource" 1024 | }, 1025 | { 1026 | "current": { 1027 | "selected": true, 1028 | "text": [ 1029 | "All" 1030 | ], 1031 | "value": [ 1032 | "$__all" 1033 | ] 1034 | }, 1035 | "datasource": { 1036 | "type": "prometheus", 1037 | "uid": "${datasource}" 1038 | }, 1039 | "definition": "label_values(kafka_cluster_partition_insyncreplicascount{}, job)", 1040 | "hide": 0, 1041 | "includeAll": true, 1042 | "label": "Job", 1043 | "multi": true, 1044 | "name": "job", 1045 | "options": [], 1046 | "query": { 1047 | "query": "label_values(kafka_cluster_partition_insyncreplicascount{}, job)", 1048 | "refId": "StandardVariableQuery" 1049 | }, 1050 | "refresh": 1, 1051 | "regex": "", 1052 | "skipUrlSync": false, 1053 | "sort": 1, 1054 | "type": "query" 1055 | } 1056 | ] 1057 | }, 1058 | "time": { 1059 | "from": "now-1h", 1060 | "to": "now" 1061 | }, 1062 | "timepicker": {}, 1063 | "timezone": "Europe/Moscow", 1064 | "title": "Kafka: Overview", 1065 | "uid": "kafka-overview", 1066 | "version": 3, 1067 | "weekStart": "" 1068 | } -------------------------------------------------------------------------------- /grafana/dashboards/002-kafka-performance.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": { 7 | "type": "datasource", 8 | "uid": "grafana" 9 | }, 10 | "enable": true, 11 | "hide": true, 12 | "iconColor": "rgba(0, 211, 255, 1)", 13 | "name": "Annotations & Alerts", 14 | "target": { 15 | "limit": 100, 16 | "matchAny": false, 17 | "tags": [], 18 | "type": "dashboard" 19 | }, 20 | "type": "dashboard" 21 | } 22 | ] 23 | }, 24 | "editable": true, 25 | "fiscalYearStartMonth": 0, 26 | "graphTooltip": 1, 27 | "links": [ 28 | { 29 | "asDropdown": false, 30 | "icon": "external link", 31 | "includeVars": false, 32 | "keepTime": false, 33 | "tags": [ 34 | "Apache Kafka" 35 | ], 36 | "targetBlank": true, 37 | "title": "Dashboards", 38 | "tooltip": "", 39 | "type": "dashboards", 40 | "url": "" 41 | } 42 | ], 43 | "liveNow": false, 44 | "panels": [ 45 | { 46 | "collapsed": false, 47 | "gridPos": { 48 | "h": 1, 49 | "w": 24, 50 | "x": 0, 51 | "y": 0 52 | }, 53 | "id": 37, 54 | "panels": [], 55 | "title": "API performance", 56 | "type": "row" 57 | }, 58 | { 59 | "datasource": { 60 | "type": "prometheus", 61 | "uid": "${datasource}" 62 | }, 63 | "fieldConfig": { 64 | "defaults": { 65 | "color": { 66 | "mode": "palette-classic" 67 | }, 68 | "custom": { 69 | "axisCenteredZero": false, 70 | "axisColorMode": "text", 71 | "axisLabel": "", 72 | "axisPlacement": "auto", 73 | "barAlignment": 0, 74 | "drawStyle": "line", 75 | "fillOpacity": 0, 76 | "gradientMode": "none", 77 | "hideFrom": { 78 | "legend": false, 79 | "tooltip": false, 80 | "viz": false 81 | }, 82 | "lineInterpolation": "linear", 83 | "lineWidth": 1, 84 | "pointSize": 5, 85 | "scaleDistribution": { 86 | "type": "linear" 87 | }, 88 | "showPoints": "auto", 89 | "spanNulls": false, 90 | "stacking": { 91 | "group": "A", 92 | "mode": "none" 93 | }, 94 | "thresholdsStyle": { 95 | "mode": "area" 96 | } 97 | }, 98 | "mappings": [], 99 | "thresholds": { 100 | "mode": "absolute", 101 | "steps": [ 102 | { 103 | "color": "green", 104 | "value": null 105 | }, 106 | { 107 | "color": "red", 108 | "value": 10 109 | } 110 | ] 111 | }, 112 | "unit": "ms" 113 | }, 114 | "overrides": [] 115 | }, 116 | "gridPos": { 117 | "h": 8, 118 | "w": 12, 119 | "x": 0, 120 | "y": 1 121 | }, 122 | "id": 39, 123 | "interval": "15s", 124 | "options": { 125 | "legend": { 126 | "calcs": [], 127 | "displayMode": "list", 128 | "placement": "bottom", 129 | "showLegend": true 130 | }, 131 | "tooltip": { 132 | "mode": "single", 133 | "sort": "none" 134 | } 135 | }, 136 | "targets": [ 137 | { 138 | "datasource": { 139 | "type": "prometheus", 140 | "uid": "${datasource}" 141 | }, 142 | "editorMode": "code", 143 | "expr": "quantile(0.99, (rate(kafka_network_requestmetrics_totaltimems{quantile=\"0.50\",instance=~\"$instance\",request=~\"$request\"}[$__rate_interval])))", 144 | "legendFormat": "mean", 145 | "range": true, 146 | "refId": "A" 147 | }, 148 | { 149 | "datasource": { 150 | "type": "prometheus", 151 | "uid": "${datasource}" 152 | }, 153 | "editorMode": "code", 154 | "expr": "quantile(0.99, (rate(kafka_network_requestmetrics_totaltimems{quantile=\"0.95\",instance=~\"$instance\",request=~\"$request\"}[$__rate_interval])))", 155 | "hide": false, 156 | "legendFormat": "p95", 157 | "range": true, 158 | "refId": "B" 159 | }, 160 | { 161 | "datasource": { 162 | "type": "prometheus", 163 | "uid": "${datasource}" 164 | }, 165 | "editorMode": "code", 166 | "expr": "quantile(0.99, (rate(kafka_network_requestmetrics_totaltimems{quantile=\"0.99\",instance=~\"$instance\",request=~\"$request\"}[$__rate_interval])))", 167 | "hide": false, 168 | "legendFormat": "p99", 169 | "range": true, 170 | "refId": "C" 171 | } 172 | ], 173 | "title": "API requests latency (RequestMetrics)", 174 | "type": "timeseries" 175 | }, 176 | { 177 | "datasource": { 178 | "type": "prometheus", 179 | "uid": "${datasource}" 180 | }, 181 | "fieldConfig": { 182 | "defaults": { 183 | "color": { 184 | "mode": "palette-classic" 185 | }, 186 | "custom": { 187 | "axisCenteredZero": false, 188 | "axisColorMode": "text", 189 | "axisLabel": "", 190 | "axisPlacement": "auto", 191 | "barAlignment": 0, 192 | "drawStyle": "bars", 193 | "fillOpacity": 100, 194 | "gradientMode": "none", 195 | "hideFrom": { 196 | "legend": false, 197 | "tooltip": false, 198 | "viz": false 199 | }, 200 | "lineInterpolation": "linear", 201 | "lineWidth": 1, 202 | "pointSize": 5, 203 | "scaleDistribution": { 204 | "type": "linear" 205 | }, 206 | "showPoints": "auto", 207 | "spanNulls": false, 208 | "stacking": { 209 | "group": "A", 210 | "mode": "normal" 211 | }, 212 | "thresholdsStyle": { 213 | "mode": "off" 214 | } 215 | }, 216 | "mappings": [], 217 | "thresholds": { 218 | "mode": "absolute", 219 | "steps": [ 220 | { 221 | "color": "green", 222 | "value": null 223 | }, 224 | { 225 | "color": "red", 226 | "value": 80 227 | } 228 | ] 229 | }, 230 | "unit": "bytes" 231 | }, 232 | "overrides": [] 233 | }, 234 | "gridPos": { 235 | "h": 8, 236 | "w": 12, 237 | "x": 12, 238 | "y": 1 239 | }, 240 | "id": 41, 241 | "options": { 242 | "legend": { 243 | "calcs": [], 244 | "displayMode": "list", 245 | "placement": "bottom", 246 | "showLegend": true 247 | }, 248 | "tooltip": { 249 | "mode": "single", 250 | "sort": "none" 251 | } 252 | }, 253 | "targets": [ 254 | { 255 | "datasource": { 256 | "type": "prometheus", 257 | "uid": "${datasource}" 258 | }, 259 | "editorMode": "code", 260 | "expr": "sum(kafka_network_requestmetrics_requestbytes{instance=~\"$instance\",request=~\"$request\"}) by (request) > 0", 261 | "legendFormat": "{{request}}", 262 | "range": true, 263 | "refId": "A" 264 | } 265 | ], 266 | "title": "API requests throughput (RequestMetrics)", 267 | "type": "timeseries" 268 | }, 269 | { 270 | "datasource": { 271 | "type": "prometheus", 272 | "uid": "${datasource}" 273 | }, 274 | "fieldConfig": { 275 | "defaults": { 276 | "color": { 277 | "mode": "palette-classic" 278 | }, 279 | "custom": { 280 | "axisCenteredZero": false, 281 | "axisColorMode": "text", 282 | "axisLabel": "", 283 | "axisPlacement": "auto", 284 | "barAlignment": 0, 285 | "drawStyle": "line", 286 | "fillOpacity": 0, 287 | "gradientMode": "none", 288 | "hideFrom": { 289 | "legend": false, 290 | "tooltip": false, 291 | "viz": false 292 | }, 293 | "lineInterpolation": "linear", 294 | "lineWidth": 1, 295 | "pointSize": 5, 296 | "scaleDistribution": { 297 | "type": "linear" 298 | }, 299 | "showPoints": "never", 300 | "spanNulls": false, 301 | "stacking": { 302 | "group": "A", 303 | "mode": "normal" 304 | }, 305 | "thresholdsStyle": { 306 | "mode": "off" 307 | } 308 | }, 309 | "mappings": [], 310 | "thresholds": { 311 | "mode": "absolute", 312 | "steps": [ 313 | { 314 | "color": "green", 315 | "value": null 316 | }, 317 | { 318 | "color": "red", 319 | "value": 80 320 | } 321 | ] 322 | }, 323 | "unit": "short" 324 | }, 325 | "overrides": [ 326 | { 327 | "matcher": { 328 | "id": "byName", 329 | "options": "Errors" 330 | }, 331 | "properties": [ 332 | { 333 | "id": "color", 334 | "value": { 335 | "fixedColor": "red", 336 | "mode": "fixed" 337 | } 338 | } 339 | ] 340 | } 341 | ] 342 | }, 343 | "gridPos": { 344 | "h": 10, 345 | "w": 12, 346 | "x": 0, 347 | "y": 9 348 | }, 349 | "id": 40, 350 | "options": { 351 | "legend": { 352 | "calcs": [], 353 | "displayMode": "list", 354 | "placement": "bottom", 355 | "showLegend": true 356 | }, 357 | "tooltip": { 358 | "mode": "single", 359 | "sort": "none" 360 | } 361 | }, 362 | "targets": [ 363 | { 364 | "datasource": { 365 | "type": "prometheus", 366 | "uid": "${datasource}" 367 | }, 368 | "editorMode": "code", 369 | "expr": "sum(rate(kafka_network_requestmetrics_requests_total{instance=~\"$instance\",request=~\"$request\"}[$__interval])) by (request)", 370 | "hide": false, 371 | "legendFormat": "{{request}}", 372 | "range": true, 373 | "refId": "B" 374 | } 375 | ], 376 | "title": "API requests total (RequestMetrics)", 377 | "type": "timeseries" 378 | }, 379 | { 380 | "datasource": { 381 | "type": "prometheus", 382 | "uid": "${datasource}" 383 | }, 384 | "fieldConfig": { 385 | "defaults": { 386 | "color": { 387 | "mode": "palette-classic" 388 | }, 389 | "custom": { 390 | "axisCenteredZero": false, 391 | "axisColorMode": "text", 392 | "axisLabel": "", 393 | "axisPlacement": "auto", 394 | "barAlignment": 0, 395 | "drawStyle": "bars", 396 | "fillOpacity": 100, 397 | "gradientMode": "none", 398 | "hideFrom": { 399 | "legend": false, 400 | "tooltip": false, 401 | "viz": false 402 | }, 403 | "lineInterpolation": "linear", 404 | "lineWidth": 1, 405 | "pointSize": 5, 406 | "scaleDistribution": { 407 | "type": "linear" 408 | }, 409 | "showPoints": "auto", 410 | "spanNulls": false, 411 | "stacking": { 412 | "group": "A", 413 | "mode": "normal" 414 | }, 415 | "thresholdsStyle": { 416 | "mode": "off" 417 | } 418 | }, 419 | "mappings": [], 420 | "thresholds": { 421 | "mode": "absolute", 422 | "steps": [ 423 | { 424 | "color": "green", 425 | "value": null 426 | }, 427 | { 428 | "color": "red", 429 | "value": 80 430 | } 431 | ] 432 | }, 433 | "unit": "percentunit" 434 | }, 435 | "overrides": [] 436 | }, 437 | "gridPos": { 438 | "h": 10, 439 | "w": 12, 440 | "x": 12, 441 | "y": 9 442 | }, 443 | "id": 74, 444 | "interval": "1m", 445 | "options": { 446 | "legend": { 447 | "calcs": [], 448 | "displayMode": "list", 449 | "placement": "bottom", 450 | "showLegend": true 451 | }, 452 | "tooltip": { 453 | "mode": "single", 454 | "sort": "none" 455 | } 456 | }, 457 | "targets": [ 458 | { 459 | "datasource": { 460 | "type": "prometheus", 461 | "uid": "${datasource}" 462 | }, 463 | "editorMode": "code", 464 | "expr": "sum(rate(kafka_network_requestmetrics_errors_total{instance=~\"$instance\",request=~\"$request\",error!=\"NONE\"}[$__rate_interval])) by (request) / sum(rate(kafka_network_requestmetrics_requests_total{instance=~\"$instance\",request=~\"$request\"}[$__rate_interval])) by (request) > 0", 465 | "hide": false, 466 | "legendFormat": "{{request}}", 467 | "range": true, 468 | "refId": "B" 469 | } 470 | ], 471 | "title": "API requests error rate (RequestMetrics)", 472 | "type": "timeseries" 473 | }, 474 | { 475 | "collapsed": false, 476 | "gridPos": { 477 | "h": 1, 478 | "w": 24, 479 | "x": 0, 480 | "y": 19 481 | }, 482 | "id": 71, 483 | "panels": [], 484 | "title": "Networking", 485 | "type": "row" 486 | }, 487 | { 488 | "datasource": { 489 | "type": "prometheus", 490 | "uid": "${datasource}" 491 | }, 492 | "description": "", 493 | "fieldConfig": { 494 | "defaults": { 495 | "color": { 496 | "mode": "palette-classic" 497 | }, 498 | "custom": { 499 | "axisCenteredZero": false, 500 | "axisColorMode": "text", 501 | "axisLabel": "", 502 | "axisPlacement": "auto", 503 | "barAlignment": 0, 504 | "drawStyle": "line", 505 | "fillOpacity": 10, 506 | "gradientMode": "none", 507 | "hideFrom": { 508 | "legend": false, 509 | "tooltip": false, 510 | "viz": false 511 | }, 512 | "lineInterpolation": "linear", 513 | "lineWidth": 1, 514 | "pointSize": 5, 515 | "scaleDistribution": { 516 | "type": "linear" 517 | }, 518 | "showPoints": "never", 519 | "spanNulls": false, 520 | "stacking": { 521 | "group": "A", 522 | "mode": "none" 523 | }, 524 | "thresholdsStyle": { 525 | "mode": "off" 526 | } 527 | }, 528 | "mappings": [], 529 | "thresholds": { 530 | "mode": "absolute", 531 | "steps": [ 532 | { 533 | "color": "green", 534 | "value": null 535 | }, 536 | { 537 | "color": "red", 538 | "value": 80 539 | } 540 | ] 541 | }, 542 | "unit": "bytes" 543 | }, 544 | "overrides": [] 545 | }, 546 | "gridPos": { 547 | "h": 8, 548 | "w": 12, 549 | "x": 0, 550 | "y": 20 551 | }, 552 | "id": 60, 553 | "links": [], 554 | "options": { 555 | "legend": { 556 | "calcs": [ 557 | "min", 558 | "mean", 559 | "lastNotNull" 560 | ], 561 | "displayMode": "table", 562 | "placement": "right", 563 | "showLegend": true, 564 | "sortBy": "Last *", 565 | "sortDesc": true 566 | }, 567 | "tooltip": { 568 | "mode": "multi", 569 | "sort": "desc" 570 | } 571 | }, 572 | "pluginVersion": "9.2.6", 573 | "targets": [ 574 | { 575 | "datasource": { 576 | "type": "prometheus", 577 | "uid": "${datasource}" 578 | }, 579 | "editorMode": "code", 580 | "expr": "sum(rate(kafka_server_brokertopicmetrics_bytesin_total{instance=~\"$instance\",topic!=\"\"}[$__rate_interval])) by (instance) + sum(rate(kafka_server_brokertopicmetrics_bytesout_total{instance=~\"$instance\",topic!=\"\"}[$__rate_interval])) by (instance)", 581 | "format": "time_series", 582 | "hide": false, 583 | "intervalFactor": 1, 584 | "legendFormat": "{{kubernetes_pod_name}}", 585 | "range": true, 586 | "refId": "A" 587 | } 588 | ], 589 | "title": "Bytes in/out", 590 | "type": "timeseries" 591 | }, 592 | { 593 | "datasource": { 594 | "type": "prometheus", 595 | "uid": "${datasource}" 596 | }, 597 | "description": "", 598 | "fieldConfig": { 599 | "defaults": { 600 | "color": { 601 | "mode": "palette-classic" 602 | }, 603 | "custom": { 604 | "axisCenteredZero": false, 605 | "axisColorMode": "text", 606 | "axisLabel": "", 607 | "axisPlacement": "auto", 608 | "barAlignment": 0, 609 | "drawStyle": "line", 610 | "fillOpacity": 10, 611 | "gradientMode": "none", 612 | "hideFrom": { 613 | "legend": false, 614 | "tooltip": false, 615 | "viz": false 616 | }, 617 | "lineInterpolation": "linear", 618 | "lineWidth": 1, 619 | "pointSize": 5, 620 | "scaleDistribution": { 621 | "type": "linear" 622 | }, 623 | "showPoints": "never", 624 | "spanNulls": false, 625 | "stacking": { 626 | "group": "A", 627 | "mode": "none" 628 | }, 629 | "thresholdsStyle": { 630 | "mode": "off" 631 | } 632 | }, 633 | "mappings": [], 634 | "thresholds": { 635 | "mode": "absolute", 636 | "steps": [ 637 | { 638 | "color": "green", 639 | "value": null 640 | }, 641 | { 642 | "color": "red", 643 | "value": 80 644 | } 645 | ] 646 | }, 647 | "unit": "short" 648 | }, 649 | "overrides": [] 650 | }, 651 | "gridPos": { 652 | "h": 8, 653 | "w": 12, 654 | "x": 12, 655 | "y": 20 656 | }, 657 | "id": 62, 658 | "links": [], 659 | "options": { 660 | "legend": { 661 | "calcs": [ 662 | "min", 663 | "mean", 664 | "lastNotNull" 665 | ], 666 | "displayMode": "table", 667 | "placement": "right", 668 | "showLegend": true, 669 | "sortBy": "Last *", 670 | "sortDesc": true 671 | }, 672 | "tooltip": { 673 | "mode": "multi", 674 | "sort": "desc" 675 | } 676 | }, 677 | "pluginVersion": "9.2.6", 678 | "targets": [ 679 | { 680 | "datasource": { 681 | "type": "prometheus", 682 | "uid": "${datasource}" 683 | }, 684 | "editorMode": "code", 685 | "expr": "sum(rate(kafka_server_brokertopicmetrics_messagesin_total{instance=~\"$instance\",topic!=\"\"}[$__rate_interval])) by (instance)", 686 | "format": "time_series", 687 | "hide": false, 688 | "intervalFactor": 1, 689 | "legendFormat": "{{kubernetes_pod_name}}", 690 | "range": true, 691 | "refId": "A" 692 | } 693 | ], 694 | "title": "Messages in", 695 | "type": "timeseries" 696 | }, 697 | { 698 | "datasource": { 699 | "type": "prometheus", 700 | "uid": "${datasource}" 701 | }, 702 | "description": "", 703 | "fieldConfig": { 704 | "defaults": { 705 | "color": { 706 | "mode": "palette-classic" 707 | }, 708 | "custom": { 709 | "axisCenteredZero": false, 710 | "axisColorMode": "text", 711 | "axisLabel": "", 712 | "axisPlacement": "auto", 713 | "barAlignment": 0, 714 | "drawStyle": "line", 715 | "fillOpacity": 10, 716 | "gradientMode": "none", 717 | "hideFrom": { 718 | "legend": false, 719 | "tooltip": false, 720 | "viz": false 721 | }, 722 | "lineInterpolation": "linear", 723 | "lineWidth": 1, 724 | "pointSize": 5, 725 | "scaleDistribution": { 726 | "type": "linear" 727 | }, 728 | "showPoints": "never", 729 | "spanNulls": false, 730 | "stacking": { 731 | "group": "A", 732 | "mode": "none" 733 | }, 734 | "thresholdsStyle": { 735 | "mode": "off" 736 | } 737 | }, 738 | "mappings": [], 739 | "thresholds": { 740 | "mode": "absolute", 741 | "steps": [ 742 | { 743 | "color": "green", 744 | "value": null 745 | }, 746 | { 747 | "color": "red", 748 | "value": 80 749 | } 750 | ] 751 | }, 752 | "unit": "bytes" 753 | }, 754 | "overrides": [] 755 | }, 756 | "gridPos": { 757 | "h": 11, 758 | "w": 12, 759 | "x": 0, 760 | "y": 28 761 | }, 762 | "id": 61, 763 | "links": [], 764 | "options": { 765 | "legend": { 766 | "calcs": [ 767 | "min", 768 | "mean", 769 | "lastNotNull" 770 | ], 771 | "displayMode": "table", 772 | "placement": "right", 773 | "showLegend": true, 774 | "sortBy": "Last *", 775 | "sortDesc": true 776 | }, 777 | "tooltip": { 778 | "mode": "multi", 779 | "sort": "desc" 780 | } 781 | }, 782 | "pluginVersion": "9.2.6", 783 | "targets": [ 784 | { 785 | "datasource": { 786 | "type": "prometheus", 787 | "uid": "${datasource}" 788 | }, 789 | "editorMode": "code", 790 | "expr": "sum(rate(kafka_server_brokertopicmetrics_replicationbytesin_total{instance=~\"$instance\"}[$__rate_interval])) by (instance) + sum(rate(kafka_server_brokertopicmetrics_replicationbytesin_total{instance=~\"$instance\"}[$__rate_interval])) by (instance)", 791 | "format": "time_series", 792 | "hide": false, 793 | "intervalFactor": 1, 794 | "legendFormat": "{{kubernetes_pod_name}}", 795 | "range": true, 796 | "refId": "A" 797 | } 798 | ], 799 | "title": "Replication bytes in/out", 800 | "type": "timeseries" 801 | }, 802 | { 803 | "datasource": { 804 | "type": "prometheus", 805 | "uid": "${datasource}" 806 | }, 807 | "description": "", 808 | "fieldConfig": { 809 | "defaults": { 810 | "color": { 811 | "mode": "palette-classic" 812 | }, 813 | "custom": { 814 | "axisCenteredZero": false, 815 | "axisColorMode": "text", 816 | "axisLabel": "", 817 | "axisPlacement": "auto", 818 | "barAlignment": 0, 819 | "drawStyle": "bars", 820 | "fillOpacity": 100, 821 | "gradientMode": "none", 822 | "hideFrom": { 823 | "legend": false, 824 | "tooltip": false, 825 | "viz": false 826 | }, 827 | "lineInterpolation": "linear", 828 | "lineWidth": 1, 829 | "pointSize": 5, 830 | "scaleDistribution": { 831 | "type": "linear" 832 | }, 833 | "showPoints": "never", 834 | "spanNulls": false, 835 | "stacking": { 836 | "group": "A", 837 | "mode": "normal" 838 | }, 839 | "thresholdsStyle": { 840 | "mode": "off" 841 | } 842 | }, 843 | "mappings": [], 844 | "thresholds": { 845 | "mode": "absolute", 846 | "steps": [ 847 | { 848 | "color": "green", 849 | "value": null 850 | }, 851 | { 852 | "color": "red", 853 | "value": 80 854 | } 855 | ] 856 | }, 857 | "unit": "short" 858 | }, 859 | "overrides": [] 860 | }, 861 | "gridPos": { 862 | "h": 11, 863 | "w": 12, 864 | "x": 12, 865 | "y": 28 866 | }, 867 | "id": 64, 868 | "links": [], 869 | "options": { 870 | "legend": { 871 | "calcs": [ 872 | "min", 873 | "mean", 874 | "lastNotNull" 875 | ], 876 | "displayMode": "table", 877 | "placement": "right", 878 | "showLegend": true, 879 | "sortBy": "Mean", 880 | "sortDesc": true 881 | }, 882 | "tooltip": { 883 | "mode": "multi", 884 | "sort": "desc" 885 | } 886 | }, 887 | "pluginVersion": "9.2.6", 888 | "targets": [ 889 | { 890 | "datasource": { 891 | "type": "prometheus", 892 | "uid": "${datasource}" 893 | }, 894 | "editorMode": "code", 895 | "expr": "sum(rate(kafka_network_requestmetrics_errors_total{instance=~\"$instance\",error!=\"NONE\"}[$__rate_interval])) by (instance, error, request) > 0", 896 | "format": "time_series", 897 | "hide": false, 898 | "intervalFactor": 1, 899 | "legendFormat": "{{kubernetes_pod_name}} ({{error}}) [{{request}}]", 900 | "range": true, 901 | "refId": "A" 902 | } 903 | ], 904 | "title": "Network request errors", 905 | "type": "timeseries" 906 | }, 907 | { 908 | "datasource": { 909 | "type": "prometheus", 910 | "uid": "${datasource}" 911 | }, 912 | "description": "", 913 | "fieldConfig": { 914 | "defaults": { 915 | "color": { 916 | "mode": "palette-classic" 917 | }, 918 | "custom": { 919 | "axisCenteredZero": false, 920 | "axisColorMode": "text", 921 | "axisLabel": "", 922 | "axisPlacement": "auto", 923 | "barAlignment": 0, 924 | "drawStyle": "line", 925 | "fillOpacity": 0, 926 | "gradientMode": "none", 927 | "hideFrom": { 928 | "legend": false, 929 | "tooltip": false, 930 | "viz": false 931 | }, 932 | "lineInterpolation": "linear", 933 | "lineWidth": 1, 934 | "pointSize": 5, 935 | "scaleDistribution": { 936 | "type": "linear" 937 | }, 938 | "showPoints": "never", 939 | "spanNulls": false, 940 | "stacking": { 941 | "group": "A", 942 | "mode": "none" 943 | }, 944 | "thresholdsStyle": { 945 | "mode": "off" 946 | } 947 | }, 948 | "mappings": [], 949 | "min": 0, 950 | "thresholds": { 951 | "mode": "absolute", 952 | "steps": [ 953 | { 954 | "color": "green", 955 | "value": null 956 | }, 957 | { 958 | "color": "red", 959 | "value": 80 960 | } 961 | ] 962 | }, 963 | "unit": "ms" 964 | }, 965 | "overrides": [] 966 | }, 967 | "gridPos": { 968 | "h": 11, 969 | "w": 12, 970 | "x": 0, 971 | "y": 39 972 | }, 973 | "id": 69, 974 | "links": [], 975 | "options": { 976 | "legend": { 977 | "calcs": [ 978 | "min", 979 | "mean", 980 | "lastNotNull" 981 | ], 982 | "displayMode": "table", 983 | "placement": "right", 984 | "showLegend": true, 985 | "sortBy": "Mean", 986 | "sortDesc": true 987 | }, 988 | "tooltip": { 989 | "mode": "multi", 990 | "sort": "desc" 991 | } 992 | }, 993 | "pluginVersion": "9.2.6", 994 | "targets": [ 995 | { 996 | "datasource": { 997 | "type": "prometheus", 998 | "uid": "${datasource}" 999 | }, 1000 | "editorMode": "code", 1001 | "expr": "sum(rate(kafka_network_requestmetrics_localtimems{instance=~\"$instance\"}[$__rate_interval])) by (instance) >= 0", 1002 | "format": "time_series", 1003 | "hide": false, 1004 | "intervalFactor": 1, 1005 | "legendFormat": "{{kubernetes_pod_name}}", 1006 | "range": true, 1007 | "refId": "A" 1008 | } 1009 | ], 1010 | "title": "Local time", 1011 | "type": "timeseries" 1012 | }, 1013 | { 1014 | "datasource": { 1015 | "type": "prometheus", 1016 | "uid": "${datasource}" 1017 | }, 1018 | "description": "", 1019 | "fieldConfig": { 1020 | "defaults": { 1021 | "color": { 1022 | "mode": "palette-classic" 1023 | }, 1024 | "custom": { 1025 | "axisCenteredZero": false, 1026 | "axisColorMode": "text", 1027 | "axisLabel": "", 1028 | "axisPlacement": "auto", 1029 | "barAlignment": 0, 1030 | "drawStyle": "line", 1031 | "fillOpacity": 0, 1032 | "gradientMode": "none", 1033 | "hideFrom": { 1034 | "legend": false, 1035 | "tooltip": false, 1036 | "viz": false 1037 | }, 1038 | "lineInterpolation": "linear", 1039 | "lineWidth": 1, 1040 | "pointSize": 5, 1041 | "scaleDistribution": { 1042 | "type": "linear" 1043 | }, 1044 | "showPoints": "never", 1045 | "spanNulls": false, 1046 | "stacking": { 1047 | "group": "A", 1048 | "mode": "none" 1049 | }, 1050 | "thresholdsStyle": { 1051 | "mode": "off" 1052 | } 1053 | }, 1054 | "mappings": [], 1055 | "min": 0, 1056 | "thresholds": { 1057 | "mode": "absolute", 1058 | "steps": [ 1059 | { 1060 | "color": "green", 1061 | "value": null 1062 | }, 1063 | { 1064 | "color": "red", 1065 | "value": 80 1066 | } 1067 | ] 1068 | }, 1069 | "unit": "ms" 1070 | }, 1071 | "overrides": [] 1072 | }, 1073 | "gridPos": { 1074 | "h": 11, 1075 | "w": 12, 1076 | "x": 12, 1077 | "y": 39 1078 | }, 1079 | "id": 67, 1080 | "links": [], 1081 | "options": { 1082 | "legend": { 1083 | "calcs": [ 1084 | "min", 1085 | "mean", 1086 | "max", 1087 | "lastNotNull" 1088 | ], 1089 | "displayMode": "table", 1090 | "placement": "right", 1091 | "showLegend": true 1092 | }, 1093 | "tooltip": { 1094 | "mode": "multi", 1095 | "sort": "desc" 1096 | } 1097 | }, 1098 | "pluginVersion": "9.2.6", 1099 | "targets": [ 1100 | { 1101 | "datasource": { 1102 | "type": "prometheus", 1103 | "uid": "${datasource}" 1104 | }, 1105 | "editorMode": "code", 1106 | "expr": "sum(rate(kafka_network_requestmetrics_remotetimems{instance=~\"$instance\"}[$__rate_interval])) by (instance) >= 0", 1107 | "format": "time_series", 1108 | "hide": false, 1109 | "intervalFactor": 1, 1110 | "legendFormat": "{{kubernetes_pod_name}}", 1111 | "range": true, 1112 | "refId": "A" 1113 | } 1114 | ], 1115 | "title": "Remote time (acks=all)", 1116 | "type": "timeseries" 1117 | }, 1118 | { 1119 | "datasource": { 1120 | "type": "prometheus", 1121 | "uid": "${datasource}" 1122 | }, 1123 | "description": "", 1124 | "fieldConfig": { 1125 | "defaults": { 1126 | "color": { 1127 | "mode": "palette-classic" 1128 | }, 1129 | "custom": { 1130 | "axisCenteredZero": false, 1131 | "axisColorMode": "text", 1132 | "axisLabel": "", 1133 | "axisPlacement": "auto", 1134 | "barAlignment": 0, 1135 | "drawStyle": "line", 1136 | "fillOpacity": 0, 1137 | "gradientMode": "none", 1138 | "hideFrom": { 1139 | "legend": false, 1140 | "tooltip": false, 1141 | "viz": false 1142 | }, 1143 | "lineInterpolation": "linear", 1144 | "lineWidth": 1, 1145 | "pointSize": 5, 1146 | "scaleDistribution": { 1147 | "type": "linear" 1148 | }, 1149 | "showPoints": "never", 1150 | "spanNulls": false, 1151 | "stacking": { 1152 | "group": "A", 1153 | "mode": "none" 1154 | }, 1155 | "thresholdsStyle": { 1156 | "mode": "off" 1157 | } 1158 | }, 1159 | "mappings": [], 1160 | "min": 0, 1161 | "thresholds": { 1162 | "mode": "absolute", 1163 | "steps": [ 1164 | { 1165 | "color": "green", 1166 | "value": null 1167 | }, 1168 | { 1169 | "color": "red", 1170 | "value": 80 1171 | } 1172 | ] 1173 | }, 1174 | "unit": "ms" 1175 | }, 1176 | "overrides": [] 1177 | }, 1178 | "gridPos": { 1179 | "h": 11, 1180 | "w": 12, 1181 | "x": 0, 1182 | "y": 50 1183 | }, 1184 | "id": 68, 1185 | "links": [], 1186 | "options": { 1187 | "legend": { 1188 | "calcs": [ 1189 | "min", 1190 | "mean", 1191 | "lastNotNull" 1192 | ], 1193 | "displayMode": "table", 1194 | "placement": "right", 1195 | "showLegend": true, 1196 | "sortBy": "Mean", 1197 | "sortDesc": true 1198 | }, 1199 | "tooltip": { 1200 | "mode": "multi", 1201 | "sort": "desc" 1202 | } 1203 | }, 1204 | "pluginVersion": "9.2.6", 1205 | "targets": [ 1206 | { 1207 | "datasource": { 1208 | "type": "prometheus", 1209 | "uid": "${datasource}" 1210 | }, 1211 | "editorMode": "code", 1212 | "expr": "sum(rate(kafka_network_requestmetrics_responsequeuetimems{instance=~\"$instance\"}[$__rate_interval])) by (instance) >= 0", 1213 | "format": "time_series", 1214 | "hide": false, 1215 | "intervalFactor": 1, 1216 | "legendFormat": "{{kubernetes_pod_name}}", 1217 | "range": true, 1218 | "refId": "A" 1219 | }, 1220 | { 1221 | "datasource": { 1222 | "type": "prometheus", 1223 | "uid": "${datasource}" 1224 | }, 1225 | "editorMode": "code", 1226 | "expr": "sum(rate(kafka_network_requestmetrics_requestqueuetimems{instance=~\"$instance\"}[$__rate_interval])) by (instance) >= 0", 1227 | "format": "time_series", 1228 | "hide": true, 1229 | "intervalFactor": 1, 1230 | "legendFormat": "{{kubernetes_pod_name}}", 1231 | "range": true, 1232 | "refId": "B" 1233 | } 1234 | ], 1235 | "title": "Response queue time", 1236 | "type": "timeseries" 1237 | }, 1238 | { 1239 | "datasource": { 1240 | "type": "prometheus", 1241 | "uid": "${datasource}" 1242 | }, 1243 | "description": "", 1244 | "fieldConfig": { 1245 | "defaults": { 1246 | "color": { 1247 | "mode": "palette-classic" 1248 | }, 1249 | "custom": { 1250 | "axisCenteredZero": false, 1251 | "axisColorMode": "text", 1252 | "axisLabel": "", 1253 | "axisPlacement": "auto", 1254 | "barAlignment": 0, 1255 | "drawStyle": "line", 1256 | "fillOpacity": 0, 1257 | "gradientMode": "none", 1258 | "hideFrom": { 1259 | "legend": false, 1260 | "tooltip": false, 1261 | "viz": false 1262 | }, 1263 | "lineInterpolation": "linear", 1264 | "lineWidth": 1, 1265 | "pointSize": 5, 1266 | "scaleDistribution": { 1267 | "type": "linear" 1268 | }, 1269 | "showPoints": "never", 1270 | "spanNulls": false, 1271 | "stacking": { 1272 | "group": "A", 1273 | "mode": "none" 1274 | }, 1275 | "thresholdsStyle": { 1276 | "mode": "off" 1277 | } 1278 | }, 1279 | "mappings": [], 1280 | "min": 0, 1281 | "thresholds": { 1282 | "mode": "absolute", 1283 | "steps": [ 1284 | { 1285 | "color": "green", 1286 | "value": null 1287 | }, 1288 | { 1289 | "color": "red", 1290 | "value": 80 1291 | } 1292 | ] 1293 | }, 1294 | "unit": "ms" 1295 | }, 1296 | "overrides": [] 1297 | }, 1298 | "gridPos": { 1299 | "h": 11, 1300 | "w": 12, 1301 | "x": 12, 1302 | "y": 50 1303 | }, 1304 | "id": 66, 1305 | "links": [], 1306 | "options": { 1307 | "legend": { 1308 | "calcs": [ 1309 | "min", 1310 | "mean", 1311 | "max", 1312 | "lastNotNull" 1313 | ], 1314 | "displayMode": "table", 1315 | "placement": "right", 1316 | "showLegend": true, 1317 | "sortBy": "Max", 1318 | "sortDesc": true 1319 | }, 1320 | "tooltip": { 1321 | "mode": "multi", 1322 | "sort": "desc" 1323 | } 1324 | }, 1325 | "pluginVersion": "9.2.6", 1326 | "targets": [ 1327 | { 1328 | "datasource": { 1329 | "type": "prometheus", 1330 | "uid": "${datasource}" 1331 | }, 1332 | "editorMode": "code", 1333 | "expr": "sum(rate(kafka_network_requestmetrics_responsesendtimems{instance=~\"$instance\"}[$__rate_interval])) by (instance) >= 0", 1334 | "format": "time_series", 1335 | "hide": false, 1336 | "intervalFactor": 1, 1337 | "legendFormat": "{{kubernetes_pod_name}}", 1338 | "range": true, 1339 | "refId": "A" 1340 | } 1341 | ], 1342 | "title": "Response send time", 1343 | "type": "timeseries" 1344 | }, 1345 | { 1346 | "collapsed": true, 1347 | "gridPos": { 1348 | "h": 1, 1349 | "w": 24, 1350 | "x": 0, 1351 | "y": 61 1352 | }, 1353 | "id": 47, 1354 | "panels": [ 1355 | { 1356 | "datasource": { 1357 | "type": "prometheus", 1358 | "uid": "$datasource" 1359 | }, 1360 | "fieldConfig": { 1361 | "defaults": { 1362 | "color": { 1363 | "mode": "palette-classic" 1364 | }, 1365 | "custom": { 1366 | "axisCenteredZero": false, 1367 | "axisColorMode": "text", 1368 | "axisLabel": "", 1369 | "axisPlacement": "auto", 1370 | "barAlignment": 0, 1371 | "drawStyle": "line", 1372 | "fillOpacity": 10, 1373 | "gradientMode": "none", 1374 | "hideFrom": { 1375 | "legend": false, 1376 | "tooltip": false, 1377 | "viz": false 1378 | }, 1379 | "lineInterpolation": "linear", 1380 | "lineWidth": 1, 1381 | "pointSize": 5, 1382 | "scaleDistribution": { 1383 | "type": "linear" 1384 | }, 1385 | "showPoints": "never", 1386 | "spanNulls": false, 1387 | "stacking": { 1388 | "group": "A", 1389 | "mode": "none" 1390 | }, 1391 | "thresholdsStyle": { 1392 | "mode": "off" 1393 | } 1394 | }, 1395 | "mappings": [], 1396 | "thresholds": { 1397 | "mode": "absolute", 1398 | "steps": [ 1399 | { 1400 | "color": "green", 1401 | "value": null 1402 | }, 1403 | { 1404 | "color": "red", 1405 | "value": 80 1406 | } 1407 | ] 1408 | }, 1409 | "unit": "bytes" 1410 | }, 1411 | "overrides": [ 1412 | { 1413 | "matcher": { 1414 | "id": "byName", 1415 | "options": "Usage %" 1416 | }, 1417 | "properties": [ 1418 | { 1419 | "id": "custom.drawStyle", 1420 | "value": "bars" 1421 | }, 1422 | { 1423 | "id": "custom.fillOpacity", 1424 | "value": 100 1425 | }, 1426 | { 1427 | "id": "color", 1428 | "value": { 1429 | "fixedColor": "#6d1f62", 1430 | "mode": "fixed" 1431 | } 1432 | }, 1433 | { 1434 | "id": "custom.lineWidth", 1435 | "value": 0 1436 | }, 1437 | { 1438 | "id": "unit", 1439 | "value": "percentunit" 1440 | }, 1441 | { 1442 | "id": "min", 1443 | "value": 0 1444 | }, 1445 | { 1446 | "id": "max", 1447 | "value": 1 1448 | } 1449 | ] 1450 | } 1451 | ] 1452 | }, 1453 | "gridPos": { 1454 | "h": 10, 1455 | "w": 12, 1456 | "x": 0, 1457 | "y": 62 1458 | }, 1459 | "id": 51, 1460 | "links": [], 1461 | "options": { 1462 | "legend": { 1463 | "calcs": [ 1464 | "mean", 1465 | "lastNotNull", 1466 | "max", 1467 | "min" 1468 | ], 1469 | "displayMode": "list", 1470 | "placement": "bottom", 1471 | "showLegend": false 1472 | }, 1473 | "tooltip": { 1474 | "mode": "multi", 1475 | "sort": "none" 1476 | } 1477 | }, 1478 | "pluginVersion": "9.0.3", 1479 | "repeat": "memarea", 1480 | "repeatDirection": "h", 1481 | "targets": [ 1482 | { 1483 | "datasource": { 1484 | "uid": "$datasource" 1485 | }, 1486 | "editorMode": "code", 1487 | "expr": "sum(jvm_memory_bytes_used{instance=~\"$instance\"}) by (instance)", 1488 | "format": "time_series", 1489 | "intervalFactor": 1, 1490 | "legendFormat": "{{ pod }}", 1491 | "range": true, 1492 | "refId": "A" 1493 | }, 1494 | { 1495 | "datasource": { 1496 | "type": "prometheus", 1497 | "uid": "$datasource" 1498 | }, 1499 | "editorMode": "code", 1500 | "expr": "sum(jvm_memory_bytes_max{instance=~\"$instance\"}) by (instance)", 1501 | "hide": false, 1502 | "range": true, 1503 | "refId": "B" 1504 | } 1505 | ], 1506 | "title": "Утилизация памяти JVM", 1507 | "type": "timeseries" 1508 | }, 1509 | { 1510 | "datasource": { 1511 | "type": "prometheus", 1512 | "uid": "$datasource" 1513 | }, 1514 | "fieldConfig": { 1515 | "defaults": { 1516 | "color": { 1517 | "mode": "palette-classic" 1518 | }, 1519 | "custom": { 1520 | "axisCenteredZero": false, 1521 | "axisColorMode": "text", 1522 | "axisLabel": "", 1523 | "axisPlacement": "auto", 1524 | "barAlignment": 0, 1525 | "drawStyle": "line", 1526 | "fillOpacity": 10, 1527 | "gradientMode": "none", 1528 | "hideFrom": { 1529 | "legend": false, 1530 | "tooltip": false, 1531 | "viz": false 1532 | }, 1533 | "lineInterpolation": "linear", 1534 | "lineWidth": 1, 1535 | "pointSize": 5, 1536 | "scaleDistribution": { 1537 | "type": "linear" 1538 | }, 1539 | "showPoints": "never", 1540 | "spanNulls": false, 1541 | "stacking": { 1542 | "group": "A", 1543 | "mode": "none" 1544 | }, 1545 | "thresholdsStyle": { 1546 | "mode": "off" 1547 | } 1548 | }, 1549 | "mappings": [], 1550 | "thresholds": { 1551 | "mode": "absolute", 1552 | "steps": [ 1553 | { 1554 | "color": "green", 1555 | "value": null 1556 | }, 1557 | { 1558 | "color": "red", 1559 | "value": 80 1560 | } 1561 | ] 1562 | }, 1563 | "unit": "bytes" 1564 | }, 1565 | "overrides": [ 1566 | { 1567 | "matcher": { 1568 | "id": "byName", 1569 | "options": "Usage %" 1570 | }, 1571 | "properties": [ 1572 | { 1573 | "id": "custom.drawStyle", 1574 | "value": "bars" 1575 | }, 1576 | { 1577 | "id": "custom.fillOpacity", 1578 | "value": 100 1579 | }, 1580 | { 1581 | "id": "color", 1582 | "value": { 1583 | "fixedColor": "#6d1f62", 1584 | "mode": "fixed" 1585 | } 1586 | }, 1587 | { 1588 | "id": "custom.lineWidth", 1589 | "value": 0 1590 | }, 1591 | { 1592 | "id": "unit", 1593 | "value": "percentunit" 1594 | }, 1595 | { 1596 | "id": "min", 1597 | "value": 0 1598 | }, 1599 | { 1600 | "id": "max", 1601 | "value": 1 1602 | } 1603 | ] 1604 | } 1605 | ] 1606 | }, 1607 | "gridPos": { 1608 | "h": 10, 1609 | "w": 12, 1610 | "x": 12, 1611 | "y": 62 1612 | }, 1613 | "id": 53, 1614 | "links": [], 1615 | "options": { 1616 | "legend": { 1617 | "calcs": [ 1618 | "mean", 1619 | "lastNotNull", 1620 | "max", 1621 | "min" 1622 | ], 1623 | "displayMode": "list", 1624 | "placement": "bottom", 1625 | "showLegend": false 1626 | }, 1627 | "tooltip": { 1628 | "mode": "multi", 1629 | "sort": "none" 1630 | } 1631 | }, 1632 | "pluginVersion": "9.0.3", 1633 | "repeatDirection": "h", 1634 | "targets": [ 1635 | { 1636 | "datasource": { 1637 | "uid": "$datasource" 1638 | }, 1639 | "editorMode": "code", 1640 | "expr": "sum(jvm_memory_pool_bytes_max{instance=~\"$instance\"}) by (instance)", 1641 | "format": "time_series", 1642 | "intervalFactor": 1, 1643 | "legendFormat": "{{ pod }}", 1644 | "range": true, 1645 | "refId": "A" 1646 | }, 1647 | { 1648 | "datasource": { 1649 | "type": "prometheus", 1650 | "uid": "$datasource" 1651 | }, 1652 | "editorMode": "code", 1653 | "expr": "sum(jvm_memory_pool_bytes_used{instance=~\"$instance\"}) by (instance)", 1654 | "hide": false, 1655 | "range": true, 1656 | "refId": "B" 1657 | } 1658 | ], 1659 | "title": "Memory pool JVM", 1660 | "type": "timeseries" 1661 | }, 1662 | { 1663 | "datasource": { 1664 | "type": "prometheus", 1665 | "uid": "$datasource" 1666 | }, 1667 | "fieldConfig": { 1668 | "defaults": { 1669 | "color": { 1670 | "mode": "palette-classic" 1671 | }, 1672 | "custom": { 1673 | "axisCenteredZero": false, 1674 | "axisColorMode": "text", 1675 | "axisLabel": "", 1676 | "axisPlacement": "auto", 1677 | "barAlignment": 0, 1678 | "drawStyle": "line", 1679 | "fillOpacity": 10, 1680 | "gradientMode": "none", 1681 | "hideFrom": { 1682 | "legend": false, 1683 | "tooltip": false, 1684 | "viz": false 1685 | }, 1686 | "lineInterpolation": "linear", 1687 | "lineWidth": 1, 1688 | "pointSize": 5, 1689 | "scaleDistribution": { 1690 | "type": "linear" 1691 | }, 1692 | "showPoints": "never", 1693 | "spanNulls": false, 1694 | "stacking": { 1695 | "group": "A", 1696 | "mode": "none" 1697 | }, 1698 | "thresholdsStyle": { 1699 | "mode": "off" 1700 | } 1701 | }, 1702 | "mappings": [], 1703 | "thresholds": { 1704 | "mode": "absolute", 1705 | "steps": [ 1706 | { 1707 | "color": "green", 1708 | "value": null 1709 | }, 1710 | { 1711 | "color": "red", 1712 | "value": 80 1713 | } 1714 | ] 1715 | }, 1716 | "unit": "s" 1717 | }, 1718 | "overrides": [ 1719 | { 1720 | "matcher": { 1721 | "id": "byName", 1722 | "options": "Usage %" 1723 | }, 1724 | "properties": [ 1725 | { 1726 | "id": "custom.drawStyle", 1727 | "value": "bars" 1728 | }, 1729 | { 1730 | "id": "custom.fillOpacity", 1731 | "value": 100 1732 | }, 1733 | { 1734 | "id": "color", 1735 | "value": { 1736 | "fixedColor": "#6d1f62", 1737 | "mode": "fixed" 1738 | } 1739 | }, 1740 | { 1741 | "id": "custom.lineWidth", 1742 | "value": 0 1743 | }, 1744 | { 1745 | "id": "unit", 1746 | "value": "percentunit" 1747 | }, 1748 | { 1749 | "id": "min", 1750 | "value": 0 1751 | }, 1752 | { 1753 | "id": "max", 1754 | "value": 1 1755 | } 1756 | ] 1757 | } 1758 | ] 1759 | }, 1760 | "gridPos": { 1761 | "h": 9, 1762 | "w": 24, 1763 | "x": 0, 1764 | "y": 72 1765 | }, 1766 | "id": 52, 1767 | "links": [], 1768 | "options": { 1769 | "legend": { 1770 | "calcs": [ 1771 | "mean", 1772 | "lastNotNull", 1773 | "max", 1774 | "min" 1775 | ], 1776 | "displayMode": "table", 1777 | "placement": "bottom", 1778 | "showLegend": true 1779 | }, 1780 | "tooltip": { 1781 | "mode": "multi", 1782 | "sort": "none" 1783 | } 1784 | }, 1785 | "pluginVersion": "9.0.3", 1786 | "repeatDirection": "h", 1787 | "targets": [ 1788 | { 1789 | "datasource": { 1790 | "uid": "$datasource" 1791 | }, 1792 | "editorMode": "code", 1793 | "expr": "sum(increase(jvm_gc_collection_seconds_sum{instance=~\"$instance\"}[$__rate_interval])) by (instance)", 1794 | "format": "time_series", 1795 | "intervalFactor": 1, 1796 | "legendFormat": "{{ pod }}", 1797 | "range": true, 1798 | "refId": "A" 1799 | } 1800 | ], 1801 | "title": "Время работы GC", 1802 | "type": "timeseries" 1803 | } 1804 | ], 1805 | "title": "JVM", 1806 | "type": "row" 1807 | } 1808 | ], 1809 | "refresh": false, 1810 | "schemaVersion": 37, 1811 | "style": "dark", 1812 | "tags": [], 1813 | "templating": { 1814 | "list": [ 1815 | { 1816 | "current": { 1817 | "selected": false, 1818 | "text": "Prometheus", 1819 | "value": "Prometheus" 1820 | }, 1821 | "hide": 0, 1822 | "includeAll": false, 1823 | "label": "Datasource", 1824 | "multi": false, 1825 | "name": "datasource", 1826 | "options": [], 1827 | "query": "prometheus", 1828 | "queryValue": "", 1829 | "refresh": 1, 1830 | "regex": "/Prometheus/", 1831 | "skipUrlSync": false, 1832 | "type": "datasource" 1833 | }, 1834 | { 1835 | "current": { 1836 | "selected": true, 1837 | "text": [ 1838 | "All" 1839 | ], 1840 | "value": [ 1841 | "$__all" 1842 | ] 1843 | }, 1844 | "datasource": { 1845 | "type": "prometheus", 1846 | "uid": "${datasource}" 1847 | }, 1848 | "definition": "label_values(kafka_cluster_partition_insyncreplicascount, instance)", 1849 | "hide": 0, 1850 | "includeAll": true, 1851 | "label": "Instance", 1852 | "multi": true, 1853 | "name": "instance", 1854 | "options": [], 1855 | "query": { 1856 | "query": "label_values(kafka_cluster_partition_insyncreplicascount, instance)", 1857 | "refId": "StandardVariableQuery" 1858 | }, 1859 | "refresh": 1, 1860 | "regex": "", 1861 | "skipUrlSync": false, 1862 | "sort": 1, 1863 | "type": "query" 1864 | }, 1865 | { 1866 | "current": { 1867 | "selected": true, 1868 | "text": [ 1869 | "All" 1870 | ], 1871 | "value": [ 1872 | "$__all" 1873 | ] 1874 | }, 1875 | "datasource": { 1876 | "type": "prometheus", 1877 | "uid": "${datasource}" 1878 | }, 1879 | "definition": "label_values(kafka_network_requestmetrics_responsesendtimems, request)", 1880 | "hide": 0, 1881 | "includeAll": true, 1882 | "label": "API request", 1883 | "multi": true, 1884 | "name": "request", 1885 | "options": [], 1886 | "query": { 1887 | "query": "label_values(kafka_network_requestmetrics_responsesendtimems, request)", 1888 | "refId": "StandardVariableQuery" 1889 | }, 1890 | "refresh": 1, 1891 | "regex": "", 1892 | "skipUrlSync": false, 1893 | "sort": 1, 1894 | "type": "query" 1895 | } 1896 | ] 1897 | }, 1898 | "time": { 1899 | "from": "now-30m", 1900 | "to": "now" 1901 | }, 1902 | "timepicker": {}, 1903 | "timezone": "Europe/Moscow", 1904 | "title": "Kafka: Performance", 1905 | "uid": "kafka-performance", 1906 | "version": 2, 1907 | "weekStart": "" 1908 | } -------------------------------------------------------------------------------- /grafana/dashboards/003-kafka-producers-and-consumers.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": { 7 | "type": "datasource", 8 | "uid": "grafana" 9 | }, 10 | "enable": true, 11 | "hide": true, 12 | "iconColor": "rgba(0, 211, 255, 1)", 13 | "name": "Annotations & Alerts", 14 | "target": { 15 | "limit": 100, 16 | "matchAny": false, 17 | "tags": [], 18 | "type": "dashboard" 19 | }, 20 | "type": "dashboard" 21 | } 22 | ] 23 | }, 24 | "description": "", 25 | "editable": true, 26 | "fiscalYearStartMonth": 0, 27 | "gnetId": 7589, 28 | "graphTooltip": 1, 29 | "links": [ 30 | { 31 | "asDropdown": false, 32 | "icon": "external link", 33 | "includeVars": false, 34 | "keepTime": false, 35 | "tags": [ 36 | "Apache Kafka" 37 | ], 38 | "targetBlank": false, 39 | "title": "Dashboards", 40 | "tooltip": "", 41 | "type": "dashboards", 42 | "url": "" 43 | } 44 | ], 45 | "liveNow": false, 46 | "panels": [ 47 | { 48 | "collapsed": false, 49 | "gridPos": { 50 | "h": 1, 51 | "w": 24, 52 | "x": 0, 53 | "y": 0 54 | }, 55 | "id": 21, 56 | "panels": [], 57 | "title": "Overview", 58 | "type": "row" 59 | }, 60 | { 61 | "datasource": { 62 | "type": "prometheus", 63 | "uid": "${datasource}" 64 | }, 65 | "fieldConfig": { 66 | "defaults": { 67 | "color": { 68 | "mode": "palette-classic" 69 | }, 70 | "custom": { 71 | "axisCenteredZero": false, 72 | "axisColorMode": "text", 73 | "axisLabel": "", 74 | "axisPlacement": "auto", 75 | "barAlignment": 0, 76 | "drawStyle": "line", 77 | "fillOpacity": 0, 78 | "gradientMode": "none", 79 | "hideFrom": { 80 | "legend": false, 81 | "tooltip": false, 82 | "viz": false 83 | }, 84 | "lineInterpolation": "linear", 85 | "lineWidth": 1, 86 | "pointSize": 5, 87 | "scaleDistribution": { 88 | "type": "linear" 89 | }, 90 | "showPoints": "never", 91 | "spanNulls": true, 92 | "stacking": { 93 | "group": "A", 94 | "mode": "none" 95 | }, 96 | "thresholdsStyle": { 97 | "mode": "off" 98 | } 99 | }, 100 | "mappings": [], 101 | "thresholds": { 102 | "mode": "absolute", 103 | "steps": [ 104 | { 105 | "color": "green", 106 | "value": null 107 | }, 108 | { 109 | "color": "red", 110 | "value": 80 111 | } 112 | ] 113 | }, 114 | "unit": "short" 115 | }, 116 | "overrides": [] 117 | }, 118 | "gridPos": { 119 | "h": 10, 120 | "w": 12, 121 | "x": 0, 122 | "y": 1 123 | }, 124 | "id": 19, 125 | "interval": "1m", 126 | "links": [], 127 | "options": { 128 | "legend": { 129 | "calcs": [ 130 | "lastNotNull", 131 | "max" 132 | ], 133 | "displayMode": "table", 134 | "placement": "right", 135 | "showLegend": true, 136 | "width": 480 137 | }, 138 | "tooltip": { 139 | "mode": "multi", 140 | "sort": "none" 141 | } 142 | }, 143 | "pluginVersion": "9.0.3", 144 | "targets": [ 145 | { 146 | "datasource": { 147 | "type": "prometheus", 148 | "uid": "${datasource}" 149 | }, 150 | "editorMode": "code", 151 | "expr": "sum(delta(kafka_topic_partition_current_offset{instance=~'$instance',topic=~\"$topic\"}[$__rate_interval])) by (topic, partition) >= 0", 152 | "format": "time_series", 153 | "intervalFactor": 1, 154 | "legendFormat": "{{topic}}#{{partition}}", 155 | "range": true, 156 | "refId": "A" 157 | } 158 | ], 159 | "title": "Produced messages rate", 160 | "type": "timeseries" 161 | }, 162 | { 163 | "datasource": { 164 | "type": "prometheus", 165 | "uid": "${datasource}" 166 | }, 167 | "fieldConfig": { 168 | "defaults": { 169 | "color": { 170 | "mode": "palette-classic" 171 | }, 172 | "custom": { 173 | "axisCenteredZero": false, 174 | "axisColorMode": "text", 175 | "axisLabel": "", 176 | "axisPlacement": "auto", 177 | "barAlignment": 0, 178 | "drawStyle": "line", 179 | "fillOpacity": 0, 180 | "gradientMode": "none", 181 | "hideFrom": { 182 | "legend": false, 183 | "tooltip": false, 184 | "viz": false 185 | }, 186 | "lineInterpolation": "linear", 187 | "lineWidth": 1, 188 | "pointSize": 5, 189 | "scaleDistribution": { 190 | "type": "linear" 191 | }, 192 | "showPoints": "never", 193 | "spanNulls": true, 194 | "stacking": { 195 | "group": "A", 196 | "mode": "none" 197 | }, 198 | "thresholdsStyle": { 199 | "mode": "off" 200 | } 201 | }, 202 | "mappings": [], 203 | "thresholds": { 204 | "mode": "absolute", 205 | "steps": [ 206 | { 207 | "color": "green", 208 | "value": null 209 | }, 210 | { 211 | "color": "red", 212 | "value": 80 213 | } 214 | ] 215 | }, 216 | "unit": "short" 217 | }, 218 | "overrides": [] 219 | }, 220 | "gridPos": { 221 | "h": 10, 222 | "w": 12, 223 | "x": 12, 224 | "y": 1 225 | }, 226 | "id": 18, 227 | "interval": "1m", 228 | "links": [], 229 | "options": { 230 | "legend": { 231 | "calcs": [ 232 | "lastNotNull", 233 | "max" 234 | ], 235 | "displayMode": "table", 236 | "placement": "right", 237 | "showLegend": true, 238 | "width": 480 239 | }, 240 | "tooltip": { 241 | "mode": "multi", 242 | "sort": "none" 243 | } 244 | }, 245 | "pluginVersion": "9.0.3", 246 | "targets": [ 247 | { 248 | "datasource": { 249 | "type": "prometheus", 250 | "uid": "${datasource}" 251 | }, 252 | "editorMode": "code", 253 | "expr": "sum(delta(kafka_consumergroup_current_offset{instance=~'$instance',topic=~\"$topic\",consumergroup=~\"$consumergroup\"}[$__rate_interval])) by (consumergroup, topic, partition) >= 0", 254 | "format": "time_series", 255 | "intervalFactor": 1, 256 | "legendFormat": "{{topic}}#{{partition}} ({{consumergroup}})", 257 | "range": true, 258 | "refId": "A" 259 | } 260 | ], 261 | "title": "Consumed messages rate", 262 | "type": "timeseries" 263 | }, 264 | { 265 | "datasource": { 266 | "type": "prometheus", 267 | "uid": "${datasource}" 268 | }, 269 | "fieldConfig": { 270 | "defaults": { 271 | "color": { 272 | "mode": "palette-classic" 273 | }, 274 | "custom": { 275 | "axisCenteredZero": false, 276 | "axisColorMode": "text", 277 | "axisLabel": "", 278 | "axisPlacement": "auto", 279 | "barAlignment": 0, 280 | "drawStyle": "line", 281 | "fillOpacity": 0, 282 | "gradientMode": "none", 283 | "hideFrom": { 284 | "legend": false, 285 | "tooltip": false, 286 | "viz": false 287 | }, 288 | "lineInterpolation": "linear", 289 | "lineWidth": 1, 290 | "pointSize": 5, 291 | "scaleDistribution": { 292 | "type": "linear" 293 | }, 294 | "showPoints": "never", 295 | "spanNulls": true, 296 | "stacking": { 297 | "group": "A", 298 | "mode": "none" 299 | }, 300 | "thresholdsStyle": { 301 | "mode": "off" 302 | } 303 | }, 304 | "mappings": [], 305 | "min": 0, 306 | "thresholds": { 307 | "mode": "absolute", 308 | "steps": [ 309 | { 310 | "color": "green", 311 | "value": null 312 | }, 313 | { 314 | "color": "red", 315 | "value": 80 316 | } 317 | ] 318 | }, 319 | "unit": "short" 320 | }, 321 | "overrides": [] 322 | }, 323 | "gridPos": { 324 | "h": 11, 325 | "w": 24, 326 | "x": 0, 327 | "y": 11 328 | }, 329 | "id": 12, 330 | "links": [], 331 | "options": { 332 | "legend": { 333 | "calcs": [ 334 | "lastNotNull", 335 | "max" 336 | ], 337 | "displayMode": "table", 338 | "placement": "right", 339 | "showLegend": true, 340 | "width": 480 341 | }, 342 | "tooltip": { 343 | "mode": "multi", 344 | "sort": "desc" 345 | } 346 | }, 347 | "pluginVersion": "9.0.3", 348 | "targets": [ 349 | { 350 | "datasource": { 351 | "type": "prometheus", 352 | "uid": "${datasource}" 353 | }, 354 | "editorMode": "code", 355 | "expr": "sum(kafka_consumergroup_lag{instance=\"$instance\",topic=~\"$topic\",consumergroup=~\"$consumergroup\"}) by (consumergroup, topic, partition) ", 356 | "format": "time_series", 357 | "instant": false, 358 | "interval": "", 359 | "intervalFactor": 1, 360 | "legendFormat": "{{ topic }}#{{ partition}} ({{consumergroup}})", 361 | "refId": "A" 362 | } 363 | ], 364 | "title": "Consumer group lag", 365 | "type": "timeseries" 366 | } 367 | ], 368 | "refresh": false, 369 | "schemaVersion": 37, 370 | "style": "dark", 371 | "tags": [ 372 | "Apache Kafka" 373 | ], 374 | "templating": { 375 | "list": [ 376 | { 377 | "current": { 378 | "selected": false, 379 | "text": "Prometheus", 380 | "value": "Prometheus" 381 | }, 382 | "hide": 0, 383 | "includeAll": false, 384 | "label": "Datasource", 385 | "multi": false, 386 | "name": "datasource", 387 | "options": [], 388 | "query": "prometheus", 389 | "queryValue": "", 390 | "refresh": 1, 391 | "regex": "/Prometheus/", 392 | "skipUrlSync": false, 393 | "type": "datasource" 394 | }, 395 | { 396 | "current": { 397 | "selected": false, 398 | "text": "kafka-exporter", 399 | "value": "kafka-exporter" 400 | }, 401 | "datasource": { 402 | "type": "prometheus", 403 | "uid": "${datasource}" 404 | }, 405 | "definition": "label_values(kafka_consumergroup_current_offset, job)", 406 | "hide": 2, 407 | "includeAll": false, 408 | "label": "Job", 409 | "multi": false, 410 | "name": "job", 411 | "options": [], 412 | "query": { 413 | "query": "label_values(kafka_consumergroup_current_offset, job)", 414 | "refId": "StandardVariableQuery" 415 | }, 416 | "refresh": 1, 417 | "regex": "", 418 | "skipUrlSync": false, 419 | "sort": 0, 420 | "tagValuesQuery": "", 421 | "tagsQuery": "", 422 | "type": "query", 423 | "useTags": false 424 | }, 425 | { 426 | "current": { 427 | "selected": false, 428 | "text": "kafka-exporter:9308", 429 | "value": "kafka-exporter:9308" 430 | }, 431 | "datasource": { 432 | "type": "prometheus", 433 | "uid": "${datasource}" 434 | }, 435 | "definition": "label_values(kafka_consumergroup_current_offset{job=~\"$job\"}, instance)", 436 | "hide": 2, 437 | "includeAll": false, 438 | "label": "Instance", 439 | "multi": false, 440 | "name": "instance", 441 | "options": [], 442 | "query": { 443 | "query": "label_values(kafka_consumergroup_current_offset{job=~\"$job\"}, instance)", 444 | "refId": "StandardVariableQuery" 445 | }, 446 | "refresh": 1, 447 | "regex": "", 448 | "skipUrlSync": false, 449 | "sort": 0, 450 | "tagValuesQuery": "", 451 | "tagsQuery": "", 452 | "type": "query", 453 | "useTags": false 454 | }, 455 | { 456 | "current": { 457 | "selected": true, 458 | "text": [ 459 | "All" 460 | ], 461 | "value": [ 462 | "$__all" 463 | ] 464 | }, 465 | "datasource": { 466 | "type": "prometheus", 467 | "uid": "${datasource}" 468 | }, 469 | "definition": "label_values(kafka_topic_partition_current_offset{instance='$instance',topic!='__consumer_offsets',topic!='--kafka'}, topic)", 470 | "hide": 0, 471 | "includeAll": true, 472 | "label": "Topic", 473 | "multi": true, 474 | "name": "topic", 475 | "options": [], 476 | "query": { 477 | "query": "label_values(kafka_topic_partition_current_offset{instance='$instance',topic!='__consumer_offsets',topic!='--kafka'}, topic)", 478 | "refId": "StandardVariableQuery" 479 | }, 480 | "refresh": 1, 481 | "regex": "", 482 | "skipUrlSync": false, 483 | "sort": 1, 484 | "tagValuesQuery": "", 485 | "type": "query", 486 | "useTags": false 487 | }, 488 | { 489 | "current": { 490 | "selected": true, 491 | "text": [ 492 | "All" 493 | ], 494 | "value": [ 495 | "$__all" 496 | ] 497 | }, 498 | "datasource": { 499 | "type": "prometheus", 500 | "uid": "${datasource}" 501 | }, 502 | "definition": "label_values(kafka_consumergroup_lag{instance='$instance',topic!='__consumer_offsets',topic!='--kafka',topic=~\"$topic\"}, consumergroup)", 503 | "hide": 0, 504 | "includeAll": true, 505 | "label": "Consumer group", 506 | "multi": true, 507 | "name": "consumergroup", 508 | "options": [], 509 | "query": { 510 | "query": "label_values(kafka_consumergroup_lag{instance='$instance',topic!='__consumer_offsets',topic!='--kafka',topic=~\"$topic\"}, consumergroup)", 511 | "refId": "StandardVariableQuery" 512 | }, 513 | "refresh": 1, 514 | "regex": "", 515 | "skipUrlSync": false, 516 | "sort": 1, 517 | "tagValuesQuery": "", 518 | "type": "query", 519 | "useTags": false 520 | } 521 | ] 522 | }, 523 | "time": { 524 | "from": "now-30m", 525 | "to": "now" 526 | }, 527 | "timepicker": { 528 | "refresh_intervals": [ 529 | "5s", 530 | "10s", 531 | "30s", 532 | "1m", 533 | "5m", 534 | "15m", 535 | "30m", 536 | "1h", 537 | "2h", 538 | "1d" 539 | ], 540 | "time_options": [ 541 | "5m", 542 | "15m", 543 | "1h", 544 | "6h", 545 | "12h", 546 | "24h", 547 | "2d", 548 | "7d", 549 | "30d" 550 | ] 551 | }, 552 | "timezone": "Europe/Moscow", 553 | "title": "Kafka: Producers & Consumers", 554 | "uid": "kafka-consumers-and-producers", 555 | "version": 1, 556 | "weekStart": "" 557 | } -------------------------------------------------------------------------------- /grafana/provisioning/dashboards/default.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: Default 5 | folder: Kafka 6 | type: file 7 | options: 8 | path: /var/lib/grafana/dashboards -------------------------------------------------------------------------------- /grafana/provisioning/datasources/datasource.yaml: -------------------------------------------------------------------------------- 1 | # config file version 2 | apiVersion: 1 3 | 4 | datasources: 5 | - name: Prometheus 6 | type: prometheus 7 | access: proxy 8 | orgId: 1 9 | url: http://prometheus:9090 10 | editable: false -------------------------------------------------------------------------------- /images/sbermarket-tech-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /jmx-exporter/kafka.yml: -------------------------------------------------------------------------------- 1 | lowercaseOutputName: true 2 | 3 | rules: 4 | # Special cases and very specific rules 5 | - pattern : kafka.server<>Value 6 | name: kafka_server_$1_$2 7 | type: GAUGE 8 | labels: 9 | clientId: "$3" 10 | topic: "$4" 11 | partition: "$5" 12 | - pattern : kafka.server<>Value 13 | name: kafka_server_$1_$2 14 | type: GAUGE 15 | labels: 16 | clientId: "$3" 17 | broker: "$4:$5" 18 | - pattern : kafka.coordinator.(\w+)<>Value 19 | name: kafka_coordinator_$1_$2_$3 20 | type: GAUGE 21 | 22 | # Generic per-second counters with 0-2 key/value pairs 23 | - pattern: kafka.(\w+)<>Count 24 | name: kafka_$1_$2_$3_total 25 | type: COUNTER 26 | labels: 27 | "$4": "$5" 28 | "$6": "$7" 29 | - pattern: kafka.(\w+)<>Count 30 | name: kafka_$1_$2_$3_total 31 | type: COUNTER 32 | labels: 33 | "$4": "$5" 34 | - pattern: kafka.(\w+)<>Count 35 | name: kafka_$1_$2_$3_total 36 | type: COUNTER 37 | 38 | - pattern: kafka.server<>([a-z-]+) 39 | name: kafka_server_quota_$3 40 | type: GAUGE 41 | labels: 42 | resource: "$1" 43 | clientId: "$2" 44 | 45 | - pattern: kafka.server<>([a-z-]+) 46 | name: kafka_server_quota_$4 47 | type: GAUGE 48 | labels: 49 | resource: "$1" 50 | user: "$2" 51 | clientId: "$3" 52 | 53 | # Generic gauges with 0-2 key/value pairs 54 | - pattern: kafka.(\w+)<>Value 55 | name: kafka_$1_$2_$3 56 | type: GAUGE 57 | labels: 58 | "$4": "$5" 59 | "$6": "$7" 60 | - pattern: kafka.(\w+)<>Value 61 | name: kafka_$1_$2_$3 62 | type: GAUGE 63 | labels: 64 | "$4": "$5" 65 | - pattern: kafka.(\w+)<>Value 66 | name: kafka_$1_$2_$3 67 | type: GAUGE 68 | 69 | # Emulate Prometheus 'Summary' metrics for the exported 'Histogram's. 70 | # 71 | # Note that these are missing the '_sum' metric! 72 | - pattern: kafka.(\w+)<>Count 73 | name: kafka_$1_$2_$3_count 74 | type: COUNTER 75 | labels: 76 | "$4": "$5" 77 | "$6": "$7" 78 | - pattern: kafka.(\w+)<>(\d+)thPercentile 79 | name: kafka_$1_$2_$3 80 | type: GAUGE 81 | labels: 82 | "$4": "$5" 83 | "$6": "$7" 84 | quantile: "0.$8" 85 | - pattern: kafka.(\w+)<>Count 86 | name: kafka_$1_$2_$3_count 87 | type: COUNTER 88 | labels: 89 | "$4": "$5" 90 | - pattern: kafka.(\w+)<>(\d+)thPercentile 91 | name: kafka_$1_$2_$3 92 | type: GAUGE 93 | labels: 94 | "$4": "$5" 95 | quantile: "0.$6" 96 | - pattern: kafka.(\w+)<>Count 97 | name: kafka_$1_$2_$3_count 98 | type: COUNTER 99 | - pattern: kafka.(\w+)<>(\d+)thPercentile 100 | name: kafka_$1_$2_$3 101 | type: GAUGE 102 | labels: 103 | quantile: "0.$4" 104 | 105 | # Generic gauges for MeanRate Percent 106 | # Ex) kafka.server<>MeanRate 107 | - pattern: kafka.(\w+)<>MeanRate 108 | name: kafka_$1_$2_$3_percent 109 | type: GAUGE 110 | - pattern: kafka.(\w+)<>Value 111 | name: kafka_$1_$2_$3_percent 112 | type: GAUGE 113 | - pattern: kafka.(\w+)<>Value 114 | name: kafka_$1_$2_$3_percent 115 | type: GAUGE 116 | labels: 117 | "$4": "$5" -------------------------------------------------------------------------------- /prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 5s 3 | evaluation_interval: 5s 4 | 5 | scrape_configs: 6 | - job_name: 'kafka-exporter' 7 | metrics_path: /metrics 8 | static_configs: 9 | - targets: [ 'kafka-exporter:9308' ] 10 | 11 | - job_name: 'kafka' 12 | metrics_path: /metrics 13 | static_configs: 14 | - targets: [ 'kafka-1:5556', 'kafka-2:5556', 'kafka-3:5556' ] --------------------------------------------------------------------------------