Go实时读取日志并写入kafka

3 篇文章 0 订阅
2 篇文章 0 订阅

kafka

kafka, 是一种高吞吐率, 多分区, 多副本, 基于发布订阅的分布式消息系统, 支持海量数据传递

  • 高吞吐量, 低延迟: 每秒可以处理几十万条消息, 延迟最低只有几毫秒, 每个主题可以分多个分区, 消费组可对不同分区进行操作
  • 可扩展性: 集群支持热扩展
  • 持久化, 可靠性: 消息被持久化到本地磁盘, 且支持数据备份防止丢失
  • 容错性: 允许集群中节点失败(若副本数量为n, 则允许n-1个节点失败)
  • 高并发: 支持数千个客户端同时读写
安装与部署
  1. 下载安装包 (前提已安装 JDK)
cd /usr/local
wget https://archive.apache.org/dist/kafka/3.0.1/kafka_2.13-3.0.1.tgz
  1. 解压
tar -zxvf kafka_2.13-3.0.1.tgz
  1. 启动zookeeper
    由于kafka是基于zookeeper做集群节点维护和管理的, 会把节点信息存到zk中, 所以要先启动zookeeper, kafka有自带的zookeeper
cd kafka3.0.1/
# 修改zookeeper配置文件
vim ./config/zookeeper.properties
# 修改数据持久化存储路径
dataDir=/opt/tmp/zookeeper
# 保存并退出

启动zk

./bin/zookeeper-server-start.sh ./config/zookeeper.properties
  1. 启动kafka
    修改kafka配置文件
vim ./config/server0.properties
# 搭建集群时的唯一标识
broker.id=0
# 部署的机器 IP 和对外提供服务的端口, 默认9092
#listeners=PLAINTEXT://:9092
# 修改数据文件路径
log.dirs=/opt/kafka/kafka-logs
# 连接 zookeeper
zookeeper.connect=192.168.10.100:2181
# 保存并退出

启动

./bin/kafka-server-start.sh -daemon ./config/server.properties
  1. 客户端登录zk, 查看节点信息
./bin/zookeeper-shell.sh 47.98.100.76:2181 ls /
# 查看kafka节点
./bin/zookeeper-shell.sh 47.98.100.76:2181 ls /brokers/ids/0

实现

在这里插入图片描述

conf 为配置文件目录
  1. config.ini
[kafka]
addr=192.168.100.76:9092
topic=chao2022
[tail]
filename=./log/my.log
  1. config.go
    配置文件结构体, 将配置文件信息映射到该结构体, 方便获取值
package conf

type Cfg struct {
	KafkaCfg `ini:"kafka"`
	TailCfg  `ini:"tail"`
}

type KafkaCfg struct {
	Addr  string `ini:"addr"`
	Topic string `ini:"topic"`
}

type TailCfg struct {
	Filename string `ini:"filename"`
}
kafka 专门往kafka写数据的模块
  1. kafka.go
package kafka

import (
	"fmt"
	"github.com/Shopify/sarama"
)

var (
	producer sarama.SyncProducer
	err error
)

// Init 初始化 client
func Init(addr []string) error {
	config := sarama.NewConfig()
	config.Producer.RequiredAcks = sarama.WaitForAll
	config.Producer.Partitioner = sarama.NewHashPartitioner // 设置选择分区的策略为Hash
	config.Producer.Return.Successes = true // 成功交付的消息将在success channel返回

	// 生产者
	producer, err = sarama.NewSyncProducer(addr, config)
	if err != nil {
		return err
	}

	return nil
}

func SendMsg(topic, key, value string) error {

	// 构造一个消息
	msg := &sarama.ProducerMessage{}
	msg.Topic = topic // 指定主题Topic
	msg.Value = sarama.StringEncoder(value) // 消息内容
	msg.Key = sarama.StringEncoder(key) // 设置key

	// 分区ID, 偏移量
	pid, offset, err := producer.SendMessage(msg)
	if err != nil {
		fmt.Println("send msg failed, err:", err)
		return err
	}
	fmt.Printf("pid:%v offset:%v\n", pid, offset)
	return nil
}

producer设置ack参数

  • WaitForLocal: 消息同步到master之后返回ack信号,否则抛异常使应用程序感知到并在业务中进行重试发送。这种方式一定程度保证了消息的可靠性,producer等待broker确认信号的时延也不高。
  • WaitForAll: 消息同步到master且同步到所有follower之后返回ack信号,否则抛异常使应用程序感知到并在业务中进行重试发送。这样设置,在更大程度上保证了消息的可靠性,缺点是producer等待broker确认信号的时延比较高。

订阅kafka的消费者如何按照消息顺序写入mysql, Kafka的消息在一个partition中是有序的,所以只要确保发给某个人的消息都在同一个partition中即可

  • 全局一个partition, 这个最简单,但是在kafka中一个partition对应一个线程,所以这种模型下Kafka的吞吐是个问题。
  • 多个partition手动指定, 生产消息的时候,除了Topic和Value,我们可以通过手动指定partition,比如总共有10个分区,我们根据用户ID取余,这样发给同一个用户的消息,每次都到1个partition里面去了,消费者写入mysql中的时候,自然也是有序的。但是,因为分区总数是写死的,万一Kafka的分区数要调整呢?那不得重新编译代码?所以这个方式不够优美。
  • 多个partition自动计算, kafka客户端为我们提供了这种支持。在初始化的时候,设置选择分区的策略为Hash, 然后在生成消息之前,设置消息的Key值, Kafka客户端会根据Key进行Hash,我们通过把接收用户ID作为Key,这样就能让所有发给某个人的消息落到同一个分区了,也就有序了。
tail 读取日志文件的模块
package tail

import (
	"github.com/hpcloud/tail"
)

var (
	tailObj *tail.Tail
)
// Init 初始化
func Init(path string) error {
	cfg := tail.Config{
		ReOpen: true, // 重新打开, 在单个日志文件写满做切隔时, 重新打开新一个文件
		Follow: true, // 是否跟随
		Location: &tail.SeekInfo{ // 从文件的哪个位置开始读
			Offset: 0,
			Whence: 2,
		},
		MustExist: false,
		Poll: true,
	}

	t, err := tail.TailFile(path, cfg)
	if err != nil {
		return err
	}
	tailObj = t
	return nil
}

func Read() chan *tail.Line{
	return tailObj.Lines
}

main
package main

import (
	"fmt"
	"logagent/conf"
	"logagent/kafka"
	"logagent/tail"
	"gopkg.in/ini.v1"
	"time"
)

var (
	cfg conf.Cfg
	err error
)

func main() {
	// 0. 初始化配置文件
	err = ini.MapTo(&cfg, "./conf/config.ini")
	if err != nil {
		fmt.Println("init config failed: ", err.Error())
		return
	}

	// 1. 初始化kafka连接
	addr := []string{cfg.KafkaCfg.Addr}
	err = kafka.Init(addr)
	if err != nil {
		fmt.Println("init kafka failed: ", err.Error())
		return
	}
	fmt.Println("init kafka success.")

	// 2. 打开日志文件读取
	filename := cfg.TailCfg.Filename
	err = tail.Init(filename)
	if err != nil {
		fmt.Println("init tail failed: ", err.Error())
		return
	}
	fmt.Println("init tail success.")

	// 3. 执行
	run()
}

func run() {
	topic := cfg.KafkaCfg.Topic
	key := "long"
	// 将日志发送到kafka
	for {
		select {
		case line := <-tail.Read():
			// 有日志写入kafka
			err = kafka.SendMsg(topic, key, line.Text)
			if err != nil {
				fmt.Println("kafka send failed: ", err.Error())
			}
		default:
			time.Sleep(time.Second)
		}
	}
}

  1. 首先要初始化配置文件, 解析.ini文件, 并将数据映射到结构体
  2. 初始化kafka, 创建连接, 得到一个生产者
  3. 初始化读取文件
  4. 循环读取日志, 并发送到kafka中

效果

编译启动
在这里插入图片描述
利用kafka终端工具, 开启一个消费者

./kafka-console-consumer.sh --bootstrap-server 192.168.100.76:9092 --from-beginning  --topic chao2022

写入日志到my.log
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲代码der

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值