将 Syslog 与 Kinesis 集成:预期使用 Firehose

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

Kinesis Firehose 发布 之后,越来越多的人希望将 Kinesis 与日志系统集成(以加快/简化将日志摄取到 S3 和 Redshift 中)。这是将 syslog-ng 与 Kinesis 集成来解决该问题的方法。

首先,让我们看一下 syslog-ng 配置。在 syslog-ng 配置中,您将源连接到目标:


 source s_sys {
    udp(ip(127.0.0.1) port(514));
};

destination d_test { file("/var/log/monetate/test.log" perm(0644) template("$MSGONLY\n") template_escape(no) ); };

destination d_fact_kinesis { tcp("localhost" port(4242) template("$MSGONLY\n") template_escape(no) ); }; log { source(s_sys); destination(d_test); destination(d_kinesis); };


在这个例子中,我们有一个事件源:UDP,我们有两个目的地:一个文件和一个 TCP。我们在最后一条 log 语句中将它们连接在一起。使用此配置,syslog-ng 通过 UDP 接收的任何数据包都将写入 TCP 端口和文件。文件是我们的备份策略,以防万一Kinesis出问题了,我们也会把事件持久化到本地。 TCP 是我们通往 Kinesis 的路径

在 TCP 方面,我们可以轻松地用 Java 实现一个简单的 TCP 服务器,它从 syslog-ng 接收消息并通过 KPL 将它们发送出去。在 Monetate,我们已经做到了这一点。 (我会看看我们是否可以开源解决方案)。 然而 ,了解 syslog-ng 流量控制 非常重要,更具体地说,如果/当 Kinesis 开始备份时,我们将如何处理背压。

让我们考虑一下我们如何支持这一点。

这是显示整个流程的视觉效果:

在序列中的每个点,我们需要确保我们准备好处理背压。具体来说,当下一个下游组件拒绝接受额外数据时,我们需要验证每个组件的行为。

例如,如果我们的小型 TCP 服务器(“网桥”)盲目地接受来自 TCP 流的数据包,那么当 Kinesis 变慢时,我们将快速 OoM JVM。因此,在“桥”中,我们需要在上游施加背压,以防止这种情况发生。更具体地说,我们需要停止消耗 TCP 流,并希望 syslog-ng 做出适当的反应。


幸运的是,syslog-ng 已经用他们的“输出缓冲区”解决了这种情况,如上所示。输出缓冲区用于存储要传送到目的地的消息。如果该缓冲区达到最大大小,则目标将“断开连接”,以免系统日志崩溃。您可以通过 syslog-ng 中的 log_fifo_size 设置来控制缓冲区的最大大小。

在这种情况下,不同的目的地类型有不同的行为,我们测试了其中的一些。首先,我们考虑使用 syslog-ng 中的“程序”目标在 syslog 和我们的桥接进程之间建立一个直通管道。这很有效,但在备份队列的情况下,syslog-ng 会终止程序并重新生成一个新进程。这不太理想。我们还考虑过使用 UDP。这也很有效,并且实际上消除了背压情况,因为 UDP 是“即发即弃”的。然而,在重负载下,我们注意到数据包丢失,这意味着我们正在丢失事件。


最后,我们决定使用 TCP。使用 TCP,我们不会悄无声息地丢失消息,syslog-ng 也不会不断地重新启动我们的进程。有了这个决定,我们需要确认 syslog-ng 的行为。为此,我们需要监控 syslog-ng。这个方便的花花公子命令行正是这样做的:


 source s_sys {
    udp(ip(127.0.0.1) port(514));
};

destination d_test { file("/var/log/monetate/test.log" perm(0644) template("$MSGONLY\n") template_escape(no) ); };

destination d_fact_kinesis { tcp("localhost" port(4242) template("$MSGONLY\n") template_escape(no) ); }; log { source(s_sys); destination(d_test); destination(d_kinesis); };


在监控系统日志时,我们测试了两种故障场景。第一个测试了一个死桥(没有 TCP 侦听器)。在这种情况下,syslog-ng 将无法连接。我们需要确保 syslog-ng 在这种情况下表现良好。为了对此进行测试,我们在 40 秒内通过系统运行了 100K 条消息(>2K/秒)。在监控 syslog-ng 时,我们看到它试图重新连接:


 source s_sys {
    udp(ip(127.0.0.1) port(514));
};

destination d_test { file("/var/log/monetate/test.log" perm(0644) template("$MSGONLY\n") template_escape(no) ); };

destination d_fact_kinesis { tcp("localhost" port(4242) template("$MSGONLY\n") template_escape(no) ); }; log { source(s_sys); destination(d_test); destination(d_kinesis); };


所有消息都进入了我们的日志文件(哈利路亚!),这意味着断开的 TCP 连接对日志文件目标没有影响。此外,syslog-ng 不断重试建立 TCP 连接,所以当进程确实恢复时,它会重新连接!好的!从上面的输出中需要注意的一件非常重要的事情是“time_reopen”。事实证明,这是一个 全局配置选项 ,它告诉 syslog-ng 等待多长时间,直到重新尝试建立连接。所以,这个行为是可靠的——我们实际上可以配置如果/当我们失去桥接过程时,事情会变得多么嘈杂。

对于最终测试,我们需要查看当我们从 Java 进程本身应用背压时会发生什么。在这种情况下,syslog-ng 可以连接,但 java 进程拒绝读取流/套接字。为此,我们运行了相同的 100K 测试,但暂停了 java 进程中的消耗以模拟 Kinesis 减速。再一次,我们看到了好东西……所有消息都进入了日志文件,这次我们看到来自 syslog-ng 的消息表明输出缓冲区(fifo 队列)已满:


 source s_sys {
    udp(ip(127.0.0.1) port(514));
};

destination d_test { file("/var/log/monetate/test.log" perm(0644) template("$MSGONLY\n") template_escape(no) ); };

destination d_fact_kinesis { tcp("localhost" port(4242) template("$MSGONLY\n") template_escape(no) ); }; log { source(s_sys); destination(d_test); destination(d_kinesis); };


日志消息告诉你一切,在队列填满后,它会将消息丢弃在地板上,直到它可以重新建立套接字。

所以,你已经知道了……要将 syslog 连接到 Kinesis,我建议使用 TCP 输出目标,并在该目标和 Kinesis Producer Library (KPL) 之间使用一些粘合代码。 (再一次,我会看看我们是否可以开源其中的一些)只是要小心,并将反压应用于 syslog-ng。

根据我们的实验,syslog-ng 可以 支持这一点