需求背景
每个业务系统都有自己的⽇志,当业务系统出现问题时,需要通过查找⽇志信息来定位和解决问题。 当业务系统服务器⽐较少时,登陆到服务器上查看即可满⾜。但当系统机器规模巨⼤,登陆到服务器上查看⼏乎不现实(分布式的系统,⼀个系统部署在⼗⼏甚至几十台服务器上)
解决方案
把服务器上的⽇志实时收集,统⼀存储到中⼼系统。 再对这些⽇志建⽴索引,通过搜索即可快速找到对应的⽇志记录。 通过提供⼀个界⾯友好的web⻚⾯实现⽇志展示与检索。
业界方案
有早期的ELK到现在的EFK。ELK在每台服务器上部署logstash,比较重量级,所以演化成客户端部署filebeat的EFK,由filebeat收集向logstash中写数据,最后落地到elasticsearch,通过kibana界面进行日志检索。
优缺点
优点:
现成的解决方案,直接拿过来用,能够实现日志收集与检索。
缺点:
运维成本⾼,每增加⼀个⽇志收集项,都需要⼿动修改配置。
监控缺失,⽆法准确获取logstash的状态。⽆法做到定制化开发与维护。
⽆法做到定制化开发与维护。
准备开发环境
为了快速开发,这边提供一个docker-compose.yaml文件,直接启动。
docker-compose.yaml内容如下:
version: "3"
networks:
app-kafka:
driver: bridge
services:
zookeeper:
container_name: zookeeper
image: zookeeper:3.4.14
restart: always
networks:
- app-kafka
kafka:
container_name: kafka
image: bitnami/kafka:2.4.0
restart: always
environment:
- KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
- ALLOW_PLAINTEXT_LISTENER=yes
# 后面三条是暴露给外网使用
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
- KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,PLAINTEXT_HOST://:29092
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
ports:
- 127.0.0.1:9092:9092
# 外网使用29092进行访问
- 127.0.0.1:29092:29092
networks:
- app-kafka
Kafka库的基本操作
我们需要先把两者启动,先启动Zookeeper,再启动Kafka
启动ZooKeeper:./bin/zkServer.sh start
启动kafka:./bin/kafka-server-start.sh ./config/server.properties
我这里使用的是Golang1.14最新版本,并且开启了Go Module, 所以下载的包都会整合在go.mod下
这里使用sarama对kafka进行操作
Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据,具有高性能、持久化、多副本备份、横向扩展等特点。本文介绍了如何使用Go语言发送和接收kafka消息。
Go语言中连接kafka使用第三方库:github.com/Shopify/sarama。
##下载及安装
go get github.com/Shopify/sarama
注意事项:
sarama v1.20之后的版本加入了zstd压缩算法,需要用到cgo,在Windows平台编译时会提示类似如下错误:
# github.com/DataDog/zstd
exec: "gcc":executable file not found in %PATH%
所以在Windows平台请使用v1.19版本的sarama。
连接kafka发送消息
package main
import (
"fmt"
"github.com/Shopify/sarama"
)
// 基于sarama第三方库开发的kafka client
func main() {
config := sarama.NewConfig()
config.Producer.RequiredAcks = sarama.WaitForAll // 发送完数据需要leader和follow都确认
config.Producer.Partitioner = sarama.NewRandomPartitioner // 新选出一个partition
config.Producer.Return.Successes = true // 成功交付的消息将在success channel返回
// 构造一个消息
msg := &sarama.ProducerMessage{}
msg.Topic = "web_log"
msg.Value = sarama.StringEncoder("this is a test log")
// 连接kafka
client, err := sarama.NewSyncProducer([]string{"192.168.1.7:9092"}, config)
if err != nil {
fmt.Println("producer closed, err:", err)
return
}
defer client.Close()
// 发送消息
pid, offset, err := client.SendMessage(msg)
if err != nil {
fmt.Println("send msg failed, err:", err)
return
}
fmt.Printf("pid:%v offset:%v\n", pid, offset)
}
连接kafka消费消息
package main
import (
"fmt"
"github.com/Shopify/sarama"
)
// kafka consumer
func main() {
consumer, err := sarama.NewConsumer([]string{"127.0.0.1:9092"}, nil)
if err != nil {
fmt.Printf("fail to start consumer, err:%v\n", err)
return
}
partitionList, err := consumer.Partitions("web_log") // 根据topic取到所有的分区
if err != nil {
fmt.Printf("fail to get list of partition:err%v\n", err)
return
}
fmt.Println(partitionList)
for partition := range partitionList { // 遍历所有的分区
// 针对每个分区创建一个对应的分区消费者
pc, err := consumer.ConsumePartition("web_log", int32(partition), sarama.OffsetNewest)
if err != nil {
fmt.Printf("failed to start consumer for partition %d,err:%v\n", partition, err)
return
}
defer pc.AsyncClose()
// 异步从每个分区消费信息
go func(sarama.PartitionConsumer) {
for msg := range pc.Messages() {
fmt.Printf("Partition:%d Offset:%d Key:%v Value:%v", msg.Partition, msg.Offset, msg.Key, msg.Value)
}
}(pc)
}
}