Agent 模块
功能
- 从Etcd上发现服务日志路径,采集日志数据发送至kafka
- 从Kafka消费日志数据,按规则调整日志数据,发送至Elasticsearch
技术栈
github.com/hpcloud/tail v1.0.0
github.com/IBM/sarama v1.43.2
github.com/segmentio/kafka-go v0.4.47
github.com/olivere/elastic/v7 v7.0.32
Tail
hpcloud/tail 是一个 GitHub 项目,但实际上它是指向一个名为 gtail 的工具的仓库。
gtail 是一个高级的日志文件尾部跟踪工具,它扩展了标准 Unix/Linux tail 命令的功能,提供了更多灵活性和功能。
常用参数:
ReOpen: true, //true则文件被删掉阻塞等待新建该文件,false则文件被删掉时程序结束
Follow: true, //true则一直阻塞并监听指定文件,false则一次读完就结束程序
MustExist: true, //true则没有找到文件就报错并结束,false则没有找到文件就阻塞保持住
Location: &tail.SeekInfo{ //指定开始读取的位置
Whence: 2, // 1: 跳到文件的开始位置 2: 跳到文件最后,不读取文件里原有的内容,从新加入的开始读
Offset: 0, //Whence之后,再偏移n个字符开始读,偏移量大于一行内容时换行继续计算
},
Poll: true, //使用Linux的Poll函数,poll的作用是把当前的文件指针挂到等待队列
Kafka
Apache Kafka 是一个分布式流处理平台,用于构建实时数据管道和流式应用程序。
Q: 消息传递语义
1. 至少一次(At Least Once)
至少一次传递语义意味着消息至少被发送一次,但也可能被发送多次。这是 Kafka 默认的行为,即只要消息被发送到了 Broker,就会被保存下来,并且至少会被消费者消费一次。
2. 最多一次(At Most Once)
最多一次传递语义意味着消息要么被发送一次,要么根本不发送。在这种情况下,如果在消息发送或接收过程中出现故障,消息可能会丢失。
3. 精确一次(Exactly Once)
精确一次传递语义意味着消息恰好被发送一次,既不会丢失也不会重复。这是最严格的语义,也是最难实现的。
Q: IBM/sarama如何保证生产者传递语义, 精确一次
错误处理, 重试机制, acks配置, 超时配置
saramaConfig := sarama.NewConfig()
saramaConfig.Producer.RequiredAcks = sarama.WaitForAll // 等待所有副本确认
saramaConfig.Net.MaxOpenRequests = 1 // 设置为 1 是幂等性生产者的必需配置
saramaConfig.Producer.Idempotent = true // 启用幂等性
saramaConfig.Producer.Retry.Max = 10 // 最多重试10次
saramaConfig.Producer.Timeout = 10 * time.Second // 设置超时时间
saramaConfig.Producer.Return.Successes = true // 成功发送时返回
saramaConfig.Producer.Return.Errors = true // 返回失败的生产请求
saramaConfig.ClientID = "agent-producer" // 设置客户端ID
saramaConfig.Version = sarama.V3_4_0_0 // 设置Kafka版本
Q: 消费者消息传递语义, 精确一次
saramaC := sarama.NewConfig()
saramaC.Version = sarama.V3_4_0_0 // 使用与你的 Kafka 集群兼容的版本
saramaC.Consumer.Offsets.AutoCommit.Enable = false // 禁止自动提交, 使用手动提交
saramaC.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategySticky // 使用粘性分配策略
Q: 如何保证消费幂等性
Kafka同一个分区,不能同时被消费者组里的多个消费者消费
1. 业务逻辑中的幂等性
在处理每条消息之前,你需要检查是否已经处理过这条消息。这可以通过存储已处理消息的标识符来实现,例如使用数据库中的唯一键或者缓存系统中的键值对。
2. 手动偏移量提交(不推荐,线上一般是使用自动提交)
手动控制偏移量的提交。这意味着在消息处理成功之后,你明确地告诉消费者提交偏移量。
Q: 如何保证消费顺序?
1. 设置一个分区,这样可以保证消费顺序,但是失去了拓展性个性能
2. 设置消息key,将相同的key发送到同一个分区
Q: 如何发送同一个主题同一个分区
1. 默认分区策略
消息键(Message Key): 如果消息携带了一个键(key),那么默认分区器会计算这个键的哈希值,然后对分区数量取模来决定具体的分区号。这确保了具有相同键的消息会被发送到同一个分区中。
无键消息: 如果消息没有键,那么默认分区器会轮询(Round-Robin)的方式选择分区。这意味着消息将依次发送到每个分区,从而达到负载均衡的效果。
2. 自定义分区策略
除了默认的分区策略外,Kafka 允许用户自定义分区逻辑。自定义分区器必须实现 org.apache.kafka.clients.producer.Partitioner 接口。
Q: 为什么选择 github.com/IBM/sarama v1.43.2, 而不是github.com/segmentio/kafka-go v0.4.47
不支持幂等性Idempotent,不支持重试(自己for循环)
select(), poll() 和 epoll()
poll()、select() 和 epoll() 都是 Linux 中用于实现 I/O 多路复用的技术,它们允许一个进程监控多个文件描述符,并且只有当某些描述符准备好进行读写操作时才唤醒进程。以下是这三种方法的简要比较:
select()
select() 是最早的多路复用函数之一。它使用一个 fd_set 数据结构来管理一组文件描述符。它的优点是简单易用,支持多种类型的文件描述符,如套接字、管道等。缺点包括:
存在 FD_SETSIZE 的限制,即可以监控的文件描述符数量有限制(通常为1024)。
效率较低,因为内核需要线性扫描所有注册的描述符。
poll()
poll() 使用基于链表的结构来管理文件描述符,因此理论上没有文件描述符的数量限制(受限于系统的最大文件句柄数)。poll() 相对于 select() 的优势在于:
没有最大文件描述符数目的限制。
描述符的添加和移除相对容易。
但是,poll() 仍然存在每次调用都需要将所有描述符从用户空间复制到内核空间的问题,这在大量描述符的情况下可能成为性能瓶颈。
epoll()
epoll() 是 Linux 2.6 内核引入的一个 I/O 多路复用接口,它是对 poll() 的改进。epoll 提供了更高的性能和灵活性,主要特点包括:
使用边缘触发(Edge Triggered)模式,这使得 epoll 能够更高效地工作在高并发环境下。
使用高效的事件通知机制,通过内核和用户空间之间的高效通信减少不必要的上下文切换。
支持快速添加/删除文件描述符,以及修改文件描述符的状态。
epoll 的事件模型更强大,支持水平触发(Level Triggered)和边缘触发两种模式。
总结
选择哪种多路复用技术取决于具体的使用场景:
如果你的应用程序只需要简单的多路复用功能,并且不会监控大量的文件描述符,那么 select() 或者 poll() 就足够用了。
如果你需要处理大量并发连接,并且希望获得更好的性能,那么 epoll() 是更好的选择。
对于现代的高性能网络服务器,epoll() 通常是首选,因为它提供了最好的性能和最丰富的特性。
vim /etc/docker/daemon.json