├── kafka ├── zk1 │ ├── data │ │ └── .gitkeep │ └── log │ │ └── .gitkeep ├── zk2 │ ├── data │ │ └── .gitkeep │ └── log │ │ └── .gitkeep ├── zk3 │ ├── data │ │ └── .gitkeep │ └── log │ │ └── .gitkeep ├── kfk1 │ └── data │ │ └── .gitkeep ├── kfk2 │ └── data │ │ └── .gitkeep ├── kfk3 │ └── data │ │ └── .gitkeep └── docker-compose.yml ├── .env ├── gopher.png ├── test └── ab_post_test.json ├── .gitignore ├── src ├── docker-compose.yml ├── consume │ ├── go.mod │ ├── vendor │ │ └── modules.txt │ ├── dockerfile │ ├── main.go │ └── go.sum └── produce │ ├── go.mod │ ├── dockerfile │ ├── vendor │ └── modules.txt │ ├── go.sum │ └── main.go ├── LICENSE ├── Makefile └── README.md /kafka/zk1/data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kafka/zk1/log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kafka/zk2/data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kafka/zk2/log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kafka/zk3/data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kafka/zk3/log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kafka/kfk1/data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kafka/kfk2/data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /kafka/kfk3/data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | GOPROXY=https://goproxy.io 2 | KFKADDR=kfk1:19092 -------------------------------------------------------------------------------- /gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErikJiang/kafka_cluster_example/HEAD/gopher.png -------------------------------------------------------------------------------- /test/ab_post_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "text": "Hi guys! This is kafka cluster example!" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Ignore all files in kafka/ & src/ 15 | kafka/kfk1/data/* 16 | kafka/kfk2/data/* 17 | kafka/kfk3/data/* 18 | kafka/zk1/data/* 19 | kafka/zk1/log/* 20 | kafka/zk2/data/* 21 | kafka/zk2/log/* 22 | kafka/zk3/data/* 23 | kafka/zk3/log/* 24 | src/produce/vendor/* 25 | src/consume/vendor/* 26 | 27 | # Except for .gitkeep & modules.txt 28 | !.gitkeep 29 | !modules.txt -------------------------------------------------------------------------------- /src/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | produce: 4 | build: ./produce 5 | container_name: produce 6 | ports: 7 | - "9000:9000" 8 | environment: 9 | LISTEN_ADDRESS: '0.0.0.0:9000' 10 | KAFKA_BROKERS: 'kfk1:19092,kfk2:29092,kfk3:39092' 11 | KAFKA_TOPIC: 'foo' 12 | 13 | consume1: 14 | build: ./consume 15 | container_name: consume1 16 | environment: 17 | KAFKA_BROKERS: 'kfk1:19092,kfk2:29092,kfk3:39092' 18 | KAFKA_CONSUMER_GROUP_ID: 'consumer-group' 19 | KAFKA_TOPIC: 'foo' 20 | 21 | consume2: 22 | build: ./consume 23 | container_name: consume2 24 | environment: 25 | KAFKA_BROKERS: 'kfk1:19092,kfk2:29092,kfk3:39092' 26 | KAFKA_CONSUMER_GROUP_ID: 'consumer-group' 27 | KAFKA_TOPIC: 'foo' 28 | 29 | networks: 30 | default: 31 | external: 32 | name: kafka_default -------------------------------------------------------------------------------- /src/consume/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ErikJiang/kafka_tutorial/src/consume 2 | 3 | require ( 4 | github.com/DataDog/zstd v1.3.5 // indirect 5 | github.com/Shopify/sarama v1.20.1 6 | github.com/Shopify/toxiproxy v2.1.4+incompatible // indirect 7 | github.com/bsm/sarama-cluster v2.1.15+incompatible 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/eapache/go-resiliency v1.1.0 // indirect 10 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect 11 | github.com/eapache/queue v1.1.0 // indirect 12 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect 13 | github.com/onsi/ginkgo v1.7.0 // indirect 14 | github.com/onsi/gomega v1.4.3 // indirect 15 | github.com/pierrec/lz4 v2.0.5+incompatible // indirect 16 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect 17 | github.com/rs/zerolog v1.11.0 18 | github.com/urfave/cli v1.20.0 19 | gopkg.in/yaml.v2 v2.2.2 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 ERIK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/consume/vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/DataDog/zstd v1.3.5 2 | github.com/DataDog/zstd 3 | # github.com/Shopify/sarama v1.20.1 4 | github.com/Shopify/sarama 5 | # github.com/bsm/sarama-cluster v2.1.15+incompatible 6 | github.com/bsm/sarama-cluster 7 | # github.com/davecgh/go-spew v1.1.1 8 | github.com/davecgh/go-spew/spew 9 | # github.com/eapache/go-resiliency v1.1.0 10 | github.com/eapache/go-resiliency/breaker 11 | # github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 12 | github.com/eapache/go-xerial-snappy 13 | # github.com/eapache/queue v1.1.0 14 | github.com/eapache/queue 15 | # github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db 16 | github.com/golang/snappy 17 | # github.com/pierrec/lz4 v2.0.5+incompatible 18 | github.com/pierrec/lz4 19 | github.com/pierrec/lz4/internal/xxh32 20 | # github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a 21 | github.com/rcrowley/go-metrics 22 | # github.com/rs/zerolog v1.11.0 23 | github.com/rs/zerolog/log 24 | github.com/rs/zerolog 25 | github.com/rs/zerolog/internal/cbor 26 | github.com/rs/zerolog/internal/json 27 | # github.com/urfave/cli v1.20.0 28 | github.com/urfave/cli 29 | -------------------------------------------------------------------------------- /src/produce/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ErikJiang/kafka_tutorial/src/produce 2 | 3 | require ( 4 | github.com/DataDog/zstd v1.3.5 // indirect 5 | github.com/Shopify/sarama v1.20.1 6 | github.com/Shopify/toxiproxy v2.1.4+incompatible // indirect 7 | github.com/eapache/go-resiliency v1.1.0 // indirect 8 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect 9 | github.com/eapache/queue v1.1.0 // indirect 10 | github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 // indirect 11 | github.com/gin-gonic/gin v1.3.0 12 | github.com/golang/protobuf v1.2.0 // indirect 13 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect 14 | github.com/json-iterator/go v1.1.5 // indirect 15 | github.com/mattn/go-isatty v0.0.4 // indirect 16 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 17 | github.com/modern-go/reflect2 v1.0.1 // indirect 18 | github.com/pierrec/lz4 v2.0.7+incompatible // indirect 19 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect 20 | github.com/rs/zerolog v1.11.0 21 | github.com/stretchr/testify v1.3.0 // indirect 22 | github.com/ugorji/go/codec v0.0.0-20190128213124-ee1426cffec0 // indirect 23 | github.com/urfave/cli v1.20.0 24 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc // indirect 25 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect 26 | golang.org/x/sys v0.0.0-20190114130336-2be517255631 // indirect 27 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect 28 | gopkg.in/go-playground/validator.v8 v8.18.2 // indirect 29 | gopkg.in/yaml.v2 v2.2.2 // indirect 30 | ) 31 | -------------------------------------------------------------------------------- /src/consume/dockerfile: -------------------------------------------------------------------------------- 1 | ############################ 2 | # STEP 1 构建可执行文件 3 | ############################ 4 | 5 | # 指定 GO 版本号 6 | ARG GO_VERSION=1.11.1 7 | 8 | # 指定构建环境 9 | FROM golang:${GO_VERSION}-alpine3.7 AS builder 10 | 11 | # china aliyun mirrors 12 | RUN echo "http://mirrors.aliyun.com/alpine/v3.7/main/" > /etc/apk/repositories 13 | 14 | # ca-certificates is required to call HTTPS endpoints. 15 | # tzdata is required to time zone info. 16 | RUN apk update && apk upgrade && apk add --no-cache ca-certificates tzdata && update-ca-certificates 17 | 18 | # 创建用户 appuser 19 | RUN adduser -D -g '' appuser 20 | 21 | # 复制源码并指定工作目录 22 | RUN mkdir -p /src/app 23 | COPY . /src/app 24 | WORKDIR /src/app 25 | 26 | # 为 go build 设置环境变量: 27 | # * CGO_ENABLED=0 表示构建一个静态链接的可执行程序 28 | # * GOOS=linux GOARCH=amd64 表示指定linux 64位的运行环境 29 | # * GOPROXY=https://goproxy.io 指定代理地址 30 | # * GOFLAGS=-mod=vendor 在执行 `go build` 强制查看 `/vendor` 目录 31 | ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor 32 | 33 | # 构建可执行文件 34 | RUN go build -a -installsuffix cgo -ldflags="-w -s" -o /src/app/consume 35 | 36 | ############################ 37 | # STEP 2 构建镜像 38 | ############################ 39 | 40 | # 指定最小镜像源 41 | FROM scratch AS final 42 | 43 | # 设置系统语言 44 | ENV LANG en_US.UTF-8 45 | 46 | # 从 builder 中导入时区信息 47 | COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo 48 | 49 | # 从 builder 中导入证书 50 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 51 | 52 | # 从 builder 中导入用户及组相关文件 53 | COPY --from=builder /etc/passwd /etc/passwd 54 | 55 | # 将构建的可执行文件复制到新镜像中 56 | COPY --from=builder /src/app/consume /consume 57 | 58 | # 运行 59 | ENTRYPOINT [ "/consume" ] 60 | -------------------------------------------------------------------------------- /src/produce/dockerfile: -------------------------------------------------------------------------------- 1 | ############################ 2 | # STEP 1 构建可执行文件 3 | ############################ 4 | 5 | # 指定 GO 版本号 6 | ARG GO_VERSION=1.11.1 7 | 8 | # 指定构建环境 9 | FROM golang:${GO_VERSION}-alpine3.7 AS builder 10 | 11 | # china aliyun mirrors 12 | RUN echo "http://mirrors.aliyun.com/alpine/v3.7/main/" > /etc/apk/repositories 13 | 14 | # ca-certificates is required to call HTTPS endpoints. 15 | # tzdata is required to time zone info. 16 | RUN apk update && apk upgrade && apk add --no-cache ca-certificates tzdata && update-ca-certificates 17 | 18 | # 创建用户 appuser 19 | RUN adduser -D -g '' appuser 20 | 21 | # 复制源码并指定工作目录 22 | RUN mkdir -p /src/app 23 | COPY . /src/app 24 | WORKDIR /src/app 25 | 26 | # 为 go build 设置环境变量: 27 | # * CGO_ENABLED=0 表示构建一个静态链接的可执行程序 28 | # * GOOS=linux GOARCH=amd64 表示指定linux 64位的运行环境 29 | # * GOPROXY=https://goproxy.io 指定代理地址 30 | # * GOFLAGS=-mod=vendor 在执行 `go build` 强制查看 `/vendor` 目录 31 | ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor 32 | 33 | # 构建可执行文件 34 | RUN go build -a -installsuffix cgo -ldflags="-w -s" -o /src/app/produce 35 | 36 | ############################ 37 | # STEP 2 构建镜像 38 | ############################ 39 | 40 | # 指定最小镜像源 41 | FROM scratch AS final 42 | 43 | # 设置系统语言 44 | ENV LANG en_US.UTF-8 45 | 46 | # 从 builder 中导入时区信息 47 | COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo 48 | 49 | # 从 builder 中导入证书 50 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 51 | 52 | # 从 builder 中导入用户及组相关文件 53 | COPY --from=builder /etc/passwd /etc/passwd 54 | 55 | # 将构建的可执行文件复制到新镜像中 56 | COPY --from=builder /src/app/produce /produce 57 | 58 | # 端口申明 59 | EXPOSE 9000 60 | 61 | # 运行 62 | ENTRYPOINT [ "/produce" ] 63 | -------------------------------------------------------------------------------- /src/produce/vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/DataDog/zstd v1.3.5 2 | github.com/DataDog/zstd 3 | # github.com/Shopify/sarama v1.20.1 4 | github.com/Shopify/sarama 5 | # github.com/davecgh/go-spew v1.1.0 6 | github.com/davecgh/go-spew/spew 7 | # github.com/eapache/go-resiliency v1.1.0 8 | github.com/eapache/go-resiliency/breaker 9 | # github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 10 | github.com/eapache/go-xerial-snappy 11 | # github.com/eapache/queue v1.1.0 12 | github.com/eapache/queue 13 | # github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 14 | github.com/gin-contrib/sse 15 | # github.com/gin-gonic/gin v1.3.0 16 | github.com/gin-gonic/gin 17 | github.com/gin-gonic/gin/binding 18 | github.com/gin-gonic/gin/json 19 | github.com/gin-gonic/gin/render 20 | # github.com/golang/protobuf v1.2.0 21 | github.com/golang/protobuf/proto 22 | # github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db 23 | github.com/golang/snappy 24 | # github.com/json-iterator/go v1.1.5 25 | github.com/json-iterator/go 26 | # github.com/mattn/go-isatty v0.0.4 27 | github.com/mattn/go-isatty 28 | # github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd 29 | github.com/modern-go/concurrent 30 | # github.com/modern-go/reflect2 v1.0.1 31 | github.com/modern-go/reflect2 32 | # github.com/pierrec/lz4 v2.0.7+incompatible 33 | github.com/pierrec/lz4 34 | github.com/pierrec/lz4/internal/xxh32 35 | # github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a 36 | github.com/rcrowley/go-metrics 37 | # github.com/rs/zerolog v1.11.0 38 | github.com/rs/zerolog/log 39 | github.com/rs/zerolog 40 | github.com/rs/zerolog/internal/cbor 41 | github.com/rs/zerolog/internal/json 42 | # github.com/ugorji/go/codec v0.0.0-20190128213124-ee1426cffec0 43 | github.com/ugorji/go/codec 44 | # github.com/urfave/cli v1.20.0 45 | github.com/urfave/cli 46 | # golang.org/x/sys v0.0.0-20190114130336-2be517255631 47 | golang.org/x/sys/unix 48 | # gopkg.in/go-playground/validator.v8 v8.18.2 49 | gopkg.in/go-playground/validator.v8 50 | # gopkg.in/yaml.v2 v2.2.2 51 | gopkg.in/yaml.v2 52 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include .env 2 | 3 | PROJECTNAME=$(shell basename "$(PWD)") 4 | 5 | all: help 6 | 7 | ## vendor Auto generate go vendor dir. 8 | .PHONY: vendor 9 | vendor: 10 | @echo "auto generate vendor dir ..."; 11 | export GOPROXY=$(GOPROXY); cd src/produce/; go mod vendor; 12 | export GOPROXY=$(GOPROXY); cd src/consume/; go mod vendor; 13 | 14 | ## up Docker compose up for src. 15 | .PHONY: up 16 | up: 17 | @echo "docker compose up ..."; 18 | docker-compose -f src/docker-compose.yml up -d; 19 | 20 | ## down Docker compose down for src. 21 | .PHONY: down 22 | down: 23 | @echo "docker compose down ..."; 24 | docker-compose -f src/docker-compose.yml down; 25 | 26 | ## ps Docker compose ps for src. 27 | .PHONY: ps 28 | ps: 29 | @echo "docker compose ps ..."; 30 | docker-compose -f src/docker-compose.yml ps; 31 | 32 | ## logs Docker compose logs for src. 33 | .PHONY: logs 34 | logs: 35 | @echo "docker compose logs ..."; 36 | docker-compose -f src/docker-compose.yml logs -f; 37 | 38 | ## clean Clean up docker images for src. 39 | .PHONY: clean 40 | clean: 41 | @echo "docker image clean ..."; 42 | docker image prune -f; 43 | docker rmi src_produce src_consume1 src_consume2 -f; 44 | 45 | ## test Apache benchmark test for src. 46 | .PHONY: test 47 | test: 48 | @echo "apache benchmark test ..."; 49 | ab -n100 -c10 -T application/json -p test/ab_post_test.json http://127.0.0.1:9000/api/v1/data; 50 | 51 | ## kafka-up Docker compose up for kafka services. 52 | .PHONY: kafka-up 53 | kafka-up: 54 | @echo "docker compose up for kafka ..."; 55 | docker-compose -f kafka/docker-compose.yml up -d; 56 | 57 | ## kafka-down Docker compose down for kafka services. 58 | .PHONY: kafka-down 59 | kafka-down: 60 | @echo "docker compose down for kafka ..."; 61 | docker-compose -f kafka/docker-compose.yml down; 62 | 63 | ## kafka-clean Clean up log and data files for kafka services. 64 | .PHONY: kafka-clean 65 | kafka-clean: 66 | @echo "kafka dir clean ..."; 67 | cd kafka/kfk1/data/; ls|grep -v .gitkeep|xargs rm -rf; 68 | cd kafka/kfk2/data/; ls|grep -v .gitkeep|xargs rm -rf; 69 | cd kafka/kfk3/data/; ls|grep -v .gitkeep|xargs rm -rf; 70 | cd kafka/zk1/data/; ls|grep -v .gitkeep|xargs rm -rf; 71 | cd kafka/zk1/log/; ls|grep -v .gitkeep|xargs rm -rf; 72 | cd kafka/zk2/data/; ls|grep -v .gitkeep|xargs rm -rf; 73 | cd kafka/zk2/log/; ls|grep -v .gitkeep|xargs rm -rf; 74 | cd kafka/zk3/data/; ls|grep -v .gitkeep|xargs rm -rf; 75 | cd kafka/zk3/log/; ls|grep -v .gitkeep|xargs rm -rf; 76 | 77 | ## kafka-test Check running state of the kafka service. 78 | .PHONY: kafka-test 79 | kafka-test: 80 | @echo "check kafka run status ..."; 81 | kafkacat -L -b $(KFKADDR); 82 | 83 | ## help print this help message and exit. 84 | .PHONY: help 85 | help: Makefile 86 | @echo "" 87 | @echo "Choose a command run in "$(PROJECTNAME)":" 88 | @echo "" 89 | @echo "Usage: make [target]" 90 | @echo "" 91 | @echo "Valid target values are:" 92 | @echo "" 93 | @sed -n 's/^## //p' $< -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :beetle: kafka_cluster_example 2 | 3 |

4 | 5 |

6 |

7 | 8 | 9 |

10 | 11 | --- 12 | 13 | ### 项目获取: 14 | ``` sh 15 | $ git clone --depth=1 https://github.com/ErikJiang/kafka_cluster_example.git 16 | ``` 17 | 18 | ### 项目依赖工具: 19 | * [Make](https://www.gnu.org/software/make/) 20 | * [Kafkacat](https://github.com/edenhill/kafkacat) 21 | * [ApacheBench](https://httpd.apache.org/docs/2.4/programs/ab.html) 22 | 23 | ### 支持 make 构建: 24 | ``` 25 | $ make 26 | 27 | Choose a command run in kafka_cluster_example: 28 | 29 | Usage: make [target] 30 | 31 | Valid target values are: 32 | 33 | vendor Auto generate go vendor dir. 34 | up Docker compose up for src. 35 | down Docker compose down for src. 36 | ps Docker compose ps for src. 37 | logs Docker compose logs for src. 38 | clean Clean up docker images for src. 39 | test Apache benchmark test for src. 40 | kafka-up Docker compose up for kafka services. 41 | kafka-down Docker compose down for kafka services. 42 | kafka-clean Clean up log and data files for kafka services. 43 | kafka-test Check running state of the kafka service. 44 | help print this help message and exit. 45 | ``` 46 | 47 | --- 48 | 49 | ### 1. 配置 hosts 域名 50 | 使用 `ifconfig -a` 查看本地IP地址; 51 | 52 | 配置 /etc/hosts 文件,将域名 kfk1、kfk2、kfk3 映射到当前本地 IP 地址,例如; 53 | 54 | ``` sh 55 | # 假设本地IP为: 192.168.0.166 56 | 192.168.0.166 kfk1 kfk2 kfk3 57 | ``` 58 | 59 | ### 2. 构建 Kafka 集群 60 | 61 | ``` sh 62 | # docker compose 构建方式: 63 | $ docker-compose -f kafka/docker-compose.yml up -d 64 | 65 | # 或使用 make 构建方式: 66 | $ make kafka-up 67 | ``` 68 | 69 | 如若在构建下载过程中,出现等待连接超时,可尝试在 docker 的 `daemon.json` 中添加注册镜像: 70 | ``` json 71 | { 72 | "registry-mirrors":["https://docker.mirrors.ustc.edu.cn"] 73 | } 74 | ``` 75 | 76 | 若构建完成,可使用 kafkacat 检测服务是否正常运行: 77 | ``` sh 78 | # 直接进行检测验证: 79 | $ kafkacat -L -b kfk1:19092 80 | 81 | # 或使用 make 方式验证: 82 | $ make kafka-test 83 | ``` 84 | 85 | ### 3. 构建 Produce & Consume 服务 86 | 87 | 为 produce 和 consume 生成 vendor 依赖: 88 | ``` sh 89 | $ make vendor 90 | ``` 91 | 92 | 构建 Produce & Consume Docker 服务 93 | ``` sh 94 | # 使用 docker compose 直接构建方式: 95 | $ docker-compose -f src/docker-compose.yml up -d 96 | 97 | # 或者使用 make 构建方式: 98 | $ make up 99 | ``` 100 | 101 | ### 4. 最终测试 102 | 103 | 使用 ApacheBench 进行并发测试(并发数为10,总计100个请求): 104 | ``` sh 105 | # 直接使用 ab 命令进行测试: 106 | $ ab -n100 -c10 -T application/json -p test/ab_post_test.json http://127.0.0.1:9000/api/v1/data 107 | 108 | # 或者使用 make 方式测试: 109 | $ make test 110 | ``` 111 | 112 | --- 113 | 114 | > 文档详见:[Wiki](https://github.com/ErikJiang/kafka_cluster_example/wiki) -------------------------------------------------------------------------------- /kafka/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | zk1: 4 | image: confluentinc/cp-zookeeper:5.1.0 5 | container_name: zk1 6 | ports: 7 | - "12181:12181" 8 | environment: 9 | ZOOKEEPER_SERVER_ID: 1 10 | ZOOKEEPER_CLIENT_PORT: 12181 11 | ZOOKEEPER_TICK_TIME: 2000 12 | ZOOKEEPER_INIT_LIMIT: 5 13 | ZOOKEEPER_SYNC_LIMIT: 2 14 | ZOOKEEPER_SERVERS: zk1:12888:13888;zk2:22888:23888;zk3:32888:33888 15 | volumes: 16 | - ./zk1/data:/var/lib/zookeeper/data 17 | - ./zk1/log:/var/lib/zookeeper/log 18 | 19 | zk2: 20 | image: confluentinc/cp-zookeeper:5.1.0 21 | container_name: zk2 22 | ports: 23 | - "22181:22181" 24 | environment: 25 | ZOOKEEPER_SERVER_ID: 2 26 | ZOOKEEPER_CLIENT_PORT: 22181 27 | ZOOKEEPER_TICK_TIME: 2000 28 | ZOOKEEPER_INIT_LIMIT: 5 29 | ZOOKEEPER_SYNC_LIMIT: 2 30 | ZOOKEEPER_SERVERS: zk1:12888:13888;zk2:22888:23888;zk3:32888:33888 31 | volumes: 32 | - ./zk2/data:/var/lib/zookeeper/data 33 | - ./zk2/log:/var/lib/zookeeper/log 34 | 35 | zk3: 36 | image: confluentinc/cp-zookeeper:5.1.0 37 | container_name: zk3 38 | ports: 39 | - "32181:32181" 40 | environment: 41 | ZOOKEEPER_SERVER_ID: 3 42 | ZOOKEEPER_CLIENT_PORT: 32181 43 | ZOOKEEPER_TICK_TIME: 2000 44 | ZOOKEEPER_INIT_LIMIT: 5 45 | ZOOKEEPER_SYNC_LIMIT: 2 46 | ZOOKEEPER_SERVERS: zk1:12888:13888;zk2:22888:23888;zk3:32888:33888 47 | volumes: 48 | - ./zk3/data:/var/lib/zookeeper/data 49 | - ./zk3/log:/var/lib/zookeeper/log 50 | 51 | kfk1: 52 | image: confluentinc/cp-kafka:5.1.0 53 | container_name: kfk1 54 | ports: 55 | - "19092:19092" 56 | expose: 57 | - "19092" 58 | depends_on: 59 | - zk1 60 | - zk2 61 | - zk3 62 | environment: 63 | KAFKA_BROKER_ID: 1 64 | KAFKA_ZOOKEEPER_CONNECT: zk1:12181,zk2:22181,zk3:32181 65 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kfk1:19092 66 | volumes: 67 | - ./kfk1/data:/var/lib/kafka/data 68 | 69 | kfk2: 70 | image: confluentinc/cp-kafka:5.1.0 71 | container_name: kfk2 72 | ports: 73 | - "29092:29092" 74 | expose: 75 | - "29092" 76 | depends_on: 77 | - zk1 78 | - zk2 79 | - zk3 80 | environment: 81 | KAFKA_BROKER_ID: 2 82 | KAFKA_ZOOKEEPER_CONNECT: zk1:12181,zk2:22181,zk3:32181 83 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kfk2:29092 84 | volumes: 85 | - ./kfk2/data:/var/lib/kafka/data 86 | 87 | kfk3: 88 | image: confluentinc/cp-kafka:5.1.0 89 | container_name: kfk3 90 | ports: 91 | - "39092:39092" 92 | expose: 93 | - "39092" 94 | depends_on: 95 | - zk1 96 | - zk2 97 | - zk3 98 | environment: 99 | KAFKA_BROKER_ID: 3 100 | KAFKA_ZOOKEEPER_CONNECT: zk1:12181,zk2:22181,zk3:32181 101 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kfk3:39092 102 | volumes: 103 | - ./kfk3/data:/var/lib/kafka/data 104 | -------------------------------------------------------------------------------- /src/consume/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "os/signal" 7 | "sort" 8 | "strings" 9 | "sync" 10 | "syscall" 11 | 12 | "github.com/Shopify/sarama" 13 | "github.com/bsm/sarama-cluster" 14 | "github.com/rs/zerolog/log" 15 | "github.com/urfave/cli" 16 | ) 17 | 18 | func main() { 19 | app := cli.NewApp() 20 | app.Name = "kafka Tutorial Consume Commandline" 21 | app.Usage = "Run Consume" 22 | app.Version = "1.0.0" 23 | app.Flags = args() 24 | sort.Sort(cli.FlagsByName(app.Flags)) 25 | app.Action = action 26 | err := app.Run(os.Args) 27 | if err != nil { 28 | log.Error().Msgf("error: %v", err) 29 | } 30 | log.Debug().Msgf("args: %v", os.Args) 31 | } 32 | 33 | // args 命令行参数定义 34 | func args() []cli.Flag { 35 | return []cli.Flag{ 36 | cli.StringFlag{ 37 | Name: "kafka-brokers, kb", 38 | Value: "kfk1:19092,kfk2:29092,kfk3:39092", 39 | Usage: "Kafka brokers in comma separated value", 40 | EnvVar: "KAFKA_BROKERS", 41 | }, 42 | cli.StringFlag{ 43 | Name: "kafka-consumer-group, kcg", 44 | Value: "consumer-group", 45 | Usage: "Kafka consumer group", 46 | EnvVar: "KAFKA_CONSUMER_GROUP_ID", 47 | }, 48 | cli.StringFlag{ 49 | Name: "kafka-topic, kt", 50 | Value: "hello", 51 | Usage: "Kafka topic to push", 52 | EnvVar: "KAFKA_TOPIC", 53 | }, 54 | } 55 | } 56 | 57 | // action 创建 Kafka 生产者并启动路由服务 58 | func action(c *cli.Context) error { 59 | log.Info().Msg("kafka tutorial consume.") 60 | log.Info().Msg("(c) Erik 2019") 61 | 62 | brokerUrls := c.String("kafka-brokers") 63 | topic := c.String("kafka-topic") 64 | consumerGroup := c.String("kafka-consumer-group") 65 | 66 | log.Info().Msgf("kafka-brokers: %s", brokerUrls) 67 | log.Info().Msgf("kafka-topic: %s", topic) 68 | log.Info().Msgf("kafka-consumer-group: %s", consumerGroup) 69 | 70 | wg := &sync.WaitGroup{} 71 | wg.Add(1) 72 | go clusterConsumer(wg, strings.Split(brokerUrls, ","), []string{topic}, consumerGroup) 73 | wg.Wait() 74 | 75 | return nil 76 | } 77 | 78 | // 支持brokers cluster的消费者 79 | func clusterConsumer(wg *sync.WaitGroup, brokers, topics []string, groupID string) { 80 | defer wg.Done() 81 | config := cluster.NewConfig() 82 | config.Consumer.Return.Errors = true 83 | config.Group.Return.Notifications = true 84 | config.Version = sarama.V2_1_0_0 85 | config.Consumer.Offsets.Initial = sarama.OffsetNewest 86 | 87 | // 初始化消费者 88 | consumer, err := cluster.NewConsumer(brokers, groupID, topics, config) 89 | if err != nil { 90 | log.Debug().Msgf("%s: sarama.NewSyncProducer err, message=%s \n", groupID, err) 91 | return 92 | } 93 | defer consumer.Close() 94 | 95 | // 捕获终止中断信号触发程序退出 96 | signals := make(chan os.Signal, 1) 97 | signal.Notify(signals, os.Interrupt, syscall.SIGTERM) 98 | 99 | // 消费错误信息 100 | go func() { 101 | for err := range consumer.Errors() { 102 | log.Debug().Msgf("%s:Error: %s\n", groupID, err.Error()) 103 | } 104 | }() 105 | 106 | // 消费通知信息 107 | go func() { 108 | for ntf := range consumer.Notifications() { 109 | log.Debug().Msgf("%s:Rebalanced: %v \n", groupID, ntf) 110 | } 111 | }() 112 | 113 | // 消费信息及监听信号 114 | var successes int 115 | Loop: 116 | for { 117 | select { 118 | case msg, ok := <-consumer.Messages(): 119 | if ok { 120 | value := struct { 121 | Text string `form:"text" json:"text"` 122 | }{} 123 | err := json.Unmarshal(msg.Value, &value) 124 | if err != nil { 125 | log.Error().Msgf("consume message json format error, %v", err) 126 | break Loop 127 | } 128 | log.Debug().Msgf("GroupID: %s, Topic: %s, Partition: %d, Offset: %d, Key: %s, Value: %s", 129 | groupID, msg.Topic, msg.Partition, msg.Offset, msg.Key, value.Text) 130 | consumer.MarkOffset(msg, "") // 标记信息为已处理 131 | successes++ 132 | } 133 | case <-signals: 134 | break Loop 135 | } 136 | } 137 | log.Debug().Msgf("%s consume %d messages", groupID, successes) 138 | } 139 | -------------------------------------------------------------------------------- /src/consume/go.sum: -------------------------------------------------------------------------------- 1 | github.com/DataDog/zstd v1.3.5 h1:DtpNbljikUepEPD16hD4LvIcmhnhdLTiW/5pHgbmp14= 2 | github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= 3 | github.com/Shopify/sarama v1.20.1 h1:Bb0h3I++r4eX333Y0uZV2vwUXepJbt6ig05TUU1qt9I= 4 | github.com/Shopify/sarama v1.20.1/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 5 | github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= 6 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 7 | github.com/bsm/sarama-cluster v2.1.15+incompatible h1:RkV6WiNRnqEEbp81druK8zYhmnIgdOjqSVi0+9Cnl2A= 8 | github.com/bsm/sarama-cluster v2.1.15+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= 12 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 13 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= 14 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 15 | github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= 16 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 17 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 18 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 19 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 20 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 21 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= 22 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 23 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 24 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 25 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 26 | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= 27 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 28 | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= 29 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 30 | github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= 31 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 32 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= 33 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 34 | github.com/rs/zerolog v1.11.0 h1:DRuq/S+4k52uJzBQciUcofXx45GrMC6yrEbb/CoK6+M= 35 | github.com/rs/zerolog v1.11.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 36 | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= 37 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 38 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= 39 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 40 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 41 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 42 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= 43 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 44 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 45 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 46 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 47 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 48 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 49 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 50 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 51 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 52 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 53 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 54 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 55 | -------------------------------------------------------------------------------- /src/produce/go.sum: -------------------------------------------------------------------------------- 1 | github.com/DataDog/zstd v1.3.5 h1:DtpNbljikUepEPD16hD4LvIcmhnhdLTiW/5pHgbmp14= 2 | github.com/DataDog/zstd v1.3.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= 3 | github.com/Shopify/sarama v1.20.1 h1:Bb0h3I++r4eX333Y0uZV2vwUXepJbt6ig05TUU1qt9I= 4 | github.com/Shopify/sarama v1.20.1/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 5 | github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= 6 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= 10 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 11 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= 12 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 13 | github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= 14 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 15 | github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74 h1:FaI7wNyesdMBSkIRVUuEEYEvmzufs7EqQvRAxfEXGbQ= 16 | github.com/gin-contrib/sse v0.0.0-20190125020943-a7658810eb74/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 17 | github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs= 18 | github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= 19 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 20 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 21 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= 22 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 23 | github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= 24 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 25 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 26 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 27 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 29 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 30 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 31 | github.com/pierrec/lz4 v2.0.7+incompatible h1:CbATlhU1ACG9h9JIvjWcdRjNOyxpSe/zZww7SPj8ruk= 32 | github.com/pierrec/lz4 v2.0.7+incompatible/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= 33 | github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 34 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 35 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 36 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= 37 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 38 | github.com/rs/zerolog v1.11.0 h1:DRuq/S+4k52uJzBQciUcofXx45GrMC6yrEbb/CoK6+M= 39 | github.com/rs/zerolog v1.11.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 40 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 41 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 42 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 43 | github.com/ugorji/go v1.1.2 h1:JON3E2/GPW2iDNGoSAusl1KDf5TRQ8k8q7Tp097pZGs= 44 | github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= 45 | github.com/ugorji/go/codec v0.0.0-20190128213124-ee1426cffec0 h1:Q3Bh5Dwzek5LreV9l86IftyLaexgU1mag9WNntbAW9c= 46 | github.com/ugorji/go/codec v0.0.0-20190128213124-ee1426cffec0/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= 47 | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= 48 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 49 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc h1:Yx9JGxI1SBhVLFjpAkWMaO1TF+xyqtHLjZpvQboJGiM= 50 | golang.org/x/net v0.0.0-20190110200230-915654e7eabc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 51 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= 52 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 53 | golang.org/x/sys v0.0.0-20190114130336-2be517255631 h1:g/5trXm6f9Tm+ochb21RlFNnF63lt+elB9hVBqtPu5Y= 54 | golang.org/x/sys v0.0.0-20190114130336-2be517255631/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 56 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 57 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= 58 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 59 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= 60 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 61 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 62 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 63 | -------------------------------------------------------------------------------- /src/produce/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/sha1" 6 | "encoding/hex" 7 | "encoding/json" 8 | "fmt" 9 | "net/http" 10 | "os" 11 | "os/signal" 12 | "sort" 13 | "strings" 14 | "syscall" 15 | "time" 16 | 17 | "github.com/Shopify/sarama" 18 | "github.com/gin-gonic/gin" 19 | "github.com/rs/zerolog/log" 20 | "github.com/urfave/cli" 21 | ) 22 | 23 | func main() { 24 | app := cli.NewApp() 25 | app.Name = "kafka Tutorial Produce Commandline" 26 | app.Usage = "Run Produce" 27 | app.Version = "1.0.0" 28 | app.Flags = args() 29 | sort.Sort(cli.FlagsByName(app.Flags)) 30 | app.Action = action 31 | app.Run(os.Args) 32 | } 33 | 34 | // args 命令行参数定义 35 | func args() []cli.Flag { 36 | return []cli.Flag{ 37 | cli.StringFlag{ 38 | Name: "listen-address, la", 39 | Value: "0.0.0.0:9000", 40 | Usage: "Listen address for api", 41 | EnvVar: "LISTEN_ADDRESS", 42 | }, 43 | cli.StringFlag{ 44 | Name: "kafka-brokers, kb", 45 | Value: "kfk1:19092,kfk2:29092,kfk3:39092", 46 | Usage: "Kafka brokers in comma separated value", 47 | EnvVar: "KAFKA_BROKERS", 48 | }, 49 | cli.StringFlag{ 50 | Name: "kafka-topic, kt", 51 | Value: "hello", 52 | Usage: "Kafka topic to push", 53 | EnvVar: "KAFKA_TOPIC", 54 | }, 55 | } 56 | } 57 | 58 | // action 创建 Kafka 生产者并启动路由服务 59 | func action(c *cli.Context) error { 60 | log.Info().Msg("kafka tutorial produce.") 61 | log.Info().Msg("(c) Erik 2019") 62 | 63 | listenAddr := c.String("listen-address") 64 | kafkaBrokers := c.String("kafka-brokers") 65 | topic := c.String("kafka-topic") 66 | 67 | log.Info().Msgf("listen-address: %s", listenAddr) 68 | log.Info().Msgf("kafka-brokers: %s", kafkaBrokers) 69 | log.Info().Msgf("kafka-topic: %s", topic) 70 | 71 | brokerUrls := strings.Split(kafkaBrokers, ",") 72 | 73 | config := sarama.NewConfig() 74 | 75 | config.Producer.RequiredAcks = sarama.WaitForAll 76 | 77 | config.Producer.Partitioner = sarama.NewRandomPartitioner 78 | 79 | config.Producer.Return.Successes = true 80 | 81 | config.Producer.Return.Errors = true 82 | 83 | config.Version = sarama.V2_1_0_0 84 | 85 | log.Info().Msg("start make topic") 86 | err := createTopic(config, brokerUrls[0], topic) 87 | if err != nil { 88 | log.Error().Msgf("%v", err) 89 | return err 90 | } 91 | 92 | log.Info().Msg("start make producer") 93 | producer, err := sarama.NewAsyncProducer(brokerUrls, config) 94 | if err != nil { 95 | log.Error().Msgf("%v", err) 96 | return err 97 | } 98 | defer producer.AsyncClose() 99 | 100 | log.Info().Msgf("starting server at %s", listenAddr) 101 | errChan := make(chan error, 1) 102 | go func(p sarama.AsyncProducer) { 103 | errChan <- httpServer(p, topic, listenAddr) 104 | }(producer) 105 | 106 | var signalChan = make(chan os.Signal, 1) 107 | signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) 108 | 109 | Loop: 110 | for { 111 | select { 112 | case succ := <-producer.Successes(): 113 | log.Info().Msgf("success: offset: %d, timestamp: %s, partitions: %d", 114 | succ.Offset, succ.Timestamp.String(), succ.Partition) 115 | case fail := <-producer.Errors(): 116 | log.Error().Err(fail).Msg("fail while produce message, exiting...") 117 | break Loop 118 | case <-signalChan: 119 | log.Info().Msg("got an interrupt, exiting...") 120 | break Loop 121 | case err := <-errChan: 122 | log.Error().Err(err).Msg("error while runing api, exiting...") 123 | break Loop 124 | } 125 | } 126 | return nil 127 | } 128 | 129 | // createTopic 创建 Topic 130 | func createTopic(config *sarama.Config, brokerURL, topicName string) error { 131 | broker := sarama.NewBroker(brokerURL) 132 | broker.Open(config) 133 | yes, err := broker.Connected() 134 | if err != nil { 135 | log.Error().Msgf("broker connect fail, %v", err) 136 | return err 137 | } 138 | log.Debug().Msgf("broker connect status: %v", yes) 139 | 140 | topicDetail := &sarama.TopicDetail{ 141 | NumPartitions: 2, 142 | ReplicationFactor: 1, 143 | ConfigEntries: make(map[string]*string), 144 | } 145 | 146 | topicDetails := make(map[string]*sarama.TopicDetail) 147 | topicDetails[topicName] = topicDetail 148 | 149 | request := sarama.CreateTopicsRequest{ 150 | Timeout: time.Second * 15, 151 | TopicDetails: topicDetails, 152 | } 153 | 154 | response, err := broker.CreateTopics(&request) 155 | if err != nil { 156 | log.Error().Msgf("create topics fail, %v", err) 157 | return err 158 | } 159 | log.Debug().Msgf("response length: %d", len(response.TopicErrors)) 160 | for key, val := range response.TopicErrors { 161 | log.Debug().Msgf("Key is %s", key) 162 | log.Debug().Msgf("Val is %#v", val.Err.Error()) 163 | log.Debug().Msgf("ValMsg is %#v", val.ErrMsg) 164 | } 165 | log.Info().Msgf("create topics response: %v", response) 166 | broker.Close() 167 | return nil 168 | } 169 | 170 | // httpServer 启动 HTTP 服务 171 | func httpServer(producer sarama.AsyncProducer, topicName, listenAddr string) error { 172 | gin.SetMode(gin.ReleaseMode) 173 | 174 | router := gin.New() 175 | router.POST("/api/v1/data", func(ctx *gin.Context) { 176 | parent := context.Background() 177 | defer parent.Done() 178 | 179 | form := &struct { 180 | Text string `form:"text" json:"text"` 181 | }{} 182 | 183 | err := ctx.ShouldBindJSON(form) 184 | if err != nil { 185 | ctx.JSON(http.StatusBadRequest, map[string]interface{}{ 186 | "error": map[string]interface{}{ 187 | "message": fmt.Sprintf("error while bind request param: %s", err.Error()), 188 | }, 189 | }) 190 | ctx.Abort() 191 | return 192 | } 193 | formInBytes, err := json.Marshal(form) 194 | if err != nil { 195 | ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ 196 | "error": map[string]interface{}{ 197 | "message": fmt.Sprintf("error while marshalling json: %s", err.Error()), 198 | }, 199 | }) 200 | ctx.Abort() 201 | return 202 | } 203 | 204 | // send message to kafka 205 | msg := &sarama.ProducerMessage{ 206 | Topic: topicName, 207 | Key: sarama.StringEncoder(MakeSha1(form.Text)), 208 | Value: sarama.ByteEncoder(formInBytes), 209 | Timestamp: time.Now(), 210 | } 211 | producer.Input() <- msg 212 | 213 | ctx.JSON(http.StatusOK, map[string]interface{}{ 214 | "success": true, 215 | "message": "success push data into kafka", 216 | "data": form, 217 | }) 218 | }) 219 | return router.Run(listenAddr) 220 | } 221 | 222 | // MakeSha1 计算字符串的 sha1 hash 值 223 | func MakeSha1(source string) string { 224 | sha1Hash := sha1.New() 225 | sha1Hash.Write([]byte(source)) 226 | return hex.EncodeToString(sha1Hash.Sum(nil)) 227 | } 228 | --------------------------------------------------------------------------------