七猫日志接收系统之架构设计(下)
前言
七猫日志接收系统系列文章将会向大家介绍七猫日志接收系统及相关的埋点 SDK,总共分为四篇:
- 七猫日志接收系统之架构设计(上)
- 七猫日志接收系统之架构设计(下)(本篇)
- 七猫日志接收系统之客户端埋点 SDK
- 七猫日志接收系统之服务端埋点 SDK
本文为系列的第二篇,我们将基于当前的 v4 版本详细介绍七猫日志接收系统架构,并从日志处理效率、系统的高可用以及成本控制等方面进行架构设计分析。同时随着七猫的继续壮大,日志接收系统必将迎来越来越多的挑战,我们也会对后续的迭代版本进行规划与展望。
架构总览说明
上篇中,我们为了让架构清晰明了,在对历史各版本的架构图示进行说明时,做了一定程度的简化,下面我们给出七猫日志接收系统 v4 架构更完整的图示:
注:淡红色部分为系统所依赖的相关辅助系统。
辅助系统
在图上方淡红色部分,我们看到日志接收系统依赖了诸如埋点系统、Nacos、Redis 集群和其它三方服务,这些系统对于日志接收服务功能的完整性是不可或缺的。
埋点系统:七猫埋点系统是产品和运营人员完成创建、注销埋点事件,设置埋点事件属性等操作的后台系统。日志处理服务会定期从埋点系统同步埋点事件的配置信息,并将配置缓存在内存中,在处理日志数据(日志补全、日志校验、事件校验等)时,可以快速获取这些埋点配置信息。
Nacos:Nacos 是阿里云开源的一个用于构建云原生应用的动态服务发现、动态配置管理和服务管理平台,其动态配置管理功能,消除了配置变更时重新部署应用和服务的需要。目前七猫各业务线都有使用它来进行服务配置管理,在日志接收系统中,不同项目的日志使用的校验器和解析器(见下文),就是通过 Nacos 的配置管理功能实现动态开启和关闭。
Redis:我们使用 Redis 集群储存了七猫所有不同设备标识的映射关系,日志处理服务在进行设备归因等逻辑时,通过实时读写 Redis,可以高效的对设备进行归因,完成新老设备判断、设备映射关系更新等操作。
三方服务:日志接收系统中集成了七猫自己开发的 IP 解析服务,它可以做到对 IPv4 和 IPv6 两种 IP 的高效解析,给下游的 IP 归因等功能提供了基础。对 IP 解析实现感兴趣推荐参看本站之前发布的文章:IP 库 - IP 段生成 Trie 树过程的剪枝实现。
核心数据流
架构图示中间部分是日志处理的核心数据流程,可以细化如下图所示:
流程主要包括:
- 客户端和服务端等各端的埋点 SDK 收集、存储并通过 HTTP API 上报日志数据;
- SLB 将请求均衡到 Envoy 网关集群的不同 Pod 上;
- Envoy 网关 Pod 接收请求,并根据路由将日志转发到具体的日志处理服务;
- 日志处理服务首先对请求权限进行认证,随后对日志进行数据解密、基本信息补全、日志过滤、IP 解析、埋点事件解析校验、设备归因等一系列处理,然后将其写入不同目录下的磁盘文件;
- Filebeat 实例读取指定的文件目录下的文件,并将日志数据写入下游 Kafka 集群;
- 下游业务系统从 Kafka 消费日志,进行具体的业务处理。
架构设计分析
日志接收系统作为基础服务,承载了埋点日志的所有流量,是下游市场媒体归因(可参看本站文章:媒体推广业务架构演进)、广告分析、用户数据分析、大数据系统及数据报表等核心业务的入口,我们需要在保证日志处理服务的效率和系统高可用的同时,尽可能的降低成本,下面我们从这三个方面来看整体架构设计上我们是如何考虑的。
日志处理效率
使用 Go 语言开发
在上篇文章提到,我们最原始的日志接收系统使用 PHP 开发的,后来随着上报请求的增多,日志体量的增长,PHP 在并发能力上处理也逐渐接近瓶颈,在进行重构时我们选择了用 Go 语言实现。选择 Go 的原因很简单,它有着:
- 简单易学,社区活跃
- 代码风格统一易读
- 原生和轻量级的 goroutine
- 基于 channel 并发处理机制
- 标准库和三方库丰富
- 以及优越的性能表现
- 开发效率高
等特性,是编写云原生大流量服务的不二之选。目前 Go 也是我们七猫后端服务的主要开发语言,经过这几年来的使用摸索,我们后端团队已经积累了非常丰富的经验。
使用缓存进行加速
众所周知,增加缓存是提升处理效率的最快方式,所以我们在日志接收系统中大量使用缓存,包括 Redis 缓存和内存缓存。
- 对于设备信息映射关系和设备基本的活跃信息等数据,我们使用 Redis 作为缓存;
- 对于从埋点后台同步过来的埋点配置信息,我们以内存缓存的方式存储,并通过协程来定时更新。
我们在对日志进行处理时,直接使用缓存来提高处理效率,使用图示如下:
通过并发提高效率
日志接收系统接收来自不同端的各类日志,而我们“永远不要相信客户端的传值”,对于上报的日志,我们需要对日志字段做解析、校验和过滤,以防止客户端无限制的上报事件,而不同的项目对日志校验和过滤规则不尽相同。
因此我们根据这些规则抽象出了不同的解析器和校验器,并通过配置为不同的项目指定需要的校验器与解析器。我们抽象出的解析器和校验器针对日志的不同字段处理,为了提高处理速度,在日志进行解析和校验的时候开启 goroutine 并发操作,在所有协程完成处理后,才进行下一步的操作,示意图如下:
系统高可用
网关优化升级
上篇中提到,因为随着业务发展,日志上报越来越多,一段时间后 Ingress(Nginx)在大流量的冲击下,RT(P99) 较高,请求成功率在高峰时段达跌落到 98% 左右。通过运维同事的调研,我们引入了 Envoy(MSE 云原生网关:是基于 Envoy,做了深度优化的云上服务)网关,并将 k8s 的网络插件从 Flannel 换成了 Terway,成功地解决了这个问题,扛住了大流量的冲击。
我们优化前的网关架构如下(Nigix 和 Flannel 网络插件):
优化后网关架构的如下(使用 Envoy 和 Terway 网络插件):
Terway 与 Flannel 对比:
对比项 | Terway | Flannel |
性能 | Pod 地址即为 VPC 中地址,无 NAT 损耗支持独占 ENI 模式,几乎无损。 | 配合阿里云 VPC 路由,Pod 地址为虚拟地址,存在 NAT 转换损耗。 |
安全 | 支持使用网络策略 Network Policy。 | 不支持使用网络策略 Network Policy。 |
地址管理 | 无需按节点分配地址段,随用随分配,地址无浪费。支持为 Pod 配置固定 IP 及独立虚拟交换机、安全组。 | 节点维度划分地址段,大规模集群下地址浪费多。 |
SLB | SLB 后端直接对接 Pod,支持业务无中断升级。 | SLB 后端不能直接对接 Pod,需要通过 NodePort 转发。 |
来源:使用 Terway 网络插件 。
日志落盘及异步解耦
日志接收系统接收并处理日志之后,如何将数据传递给下游呢?如果我们直接调用下游提供的 RPC 接口,势必会影响日志处理效率,在高峰时给下游服务过大的压力,同时当下游服务出现故障时,导致日志数据丢失。所以我们使用日志落盘 + Filebeat + Kafka 等组件实现了日志持久化和异步解耦。
- 首先在处理完日志之后,会将日志写入磁盘文件,保证接收到的日志持久化,在出现问题时能够回放日志;
- 然后在每台主机上都部署 Filebeat 实例,由它实现对日志文件的监听并转发写入 Kafka;
- 最后下游服务通过订阅消费 Kafka Topic 的方式,进行业务处理。
这样处理虽然链路会变长,但能够保证日志的完整性,降低日志丢失的风险,从而提升了系统整体的高可用性。
成本控制
日志分流
在上篇中可以看到,旧版本架构下的的日志并没有进行任何分离处理,所有的客户端埋点聚合日志都经过下游归因系统、回传系统等处理后,写入新的 Kafka 再由大数据系统消费,最终生成实时报表以及写入数仓。分流前数据图示:
然而在归因等系统中实际业务需要的日志仅占很小一部分(大约 10%),大部分的日志(如阅读行为、广告等)都不会进行业务处理,而是直接透传,造成资源浪费。为了减少成本,我们在日志接收系统中根据事件对日志进行了分流处理。分流后数据图示:
进行日志分流后,日志接收系统将归因系统,回传系统等不需要的其他事件日志(约 90%)写入另外的 Kafka Topic 直接供大数据系统消费,而剩下的 10% 日志由归因、回传等业务系统与大数据一起直接消费。整体链路得到的有效缩短,运维效率得到了提升,同时也节约了资源。资源节约比例:
资源列表 | 节省比例 |
归因等相关服务器资源 | 50%~60% |
大数据相关 Kafka 资源 | 20%~30% |
大数据相关计算资源 | 10%~20% |
滚动删除
架构图中 Cronjob 用于对日志文件进行定期清除,最开始我们定时的清除日志(日志存储周期为三天,每日凌晨三点低峰时段进行清除操作),经过这几年的运行下来,我们发现整个日志系统是很稳定的,即使遇到问题,一般也能较快的进行解决修复,日志储存三天磁盘占用过高,我们通过计算和评估,决定将存储周期改为 48 小时,后续会进一步缩减到 36 小时,而且由一天清理一次,并改为滚动删除的方式。下图为保留三天定时删除和保留 48 小时滚动删除的硬盘使用情况对比:
其中蓝线为保留 3 天的定时删除方式,橙线为滚动删除保留 48 小时的方式。可以看出,将删除方式改为删除 48 小时且滚动删除时:
- 可以明显减少硬盘的存储压力,硬盘的存储水位一直保持相对稳定;
- 可以明显瞬时硬盘的 IO 压力,不会在某一时刻删除大量文件。
经过计算,使用滚动删除保留 48 小时的删除方式时,预计节约 25%左右的资源成本。
展望与规划
高可用性提升
为了进一步提升系统高可用,我们考虑未来可以引入应用多活的架构,如下:
多可用区部署,1 个可用区 1 个网关,1 个 K8s,整体由 ACK ONE 统一管理。在某一分区出现问题时,可将流量全部转移至另一个分区。
埋点事件分级
由于有些埋点事件对于我们整个系统上下游中非常重要,我们考虑对埋点事件重要程度进行分级。对于这些高优先级事件的日志,各端增加一些特殊处理,以保证高优先级事件一定能被日志系统接收,比如:
- 客户端对于高优先级事件可以选择分开上报,在网络环境不佳时,本地优先缓存高优先级事件日志存储,同时增加此类事件上报失败的重试次数等;
- 日志接收系统可以针对高优先级事件做更强的一致性保证(比如单独发送 Kafka 后再落盘,增加重试等)。
部署方式优化
当前 Filebeat 的部署方式如下:
每个 Filebeat 以守护进程的形式部署在每台机器上,它需要监听当前机器上所有 Pod 产生的日志文件,文件数较多。若想要修改 Filebeat 配置,或者升级,重启 Filebeat 时,按照当前的文件数,Filebeat 会有大量时间消耗在扫描元数据上,使得该机器上的日志暂时积压,影响系统内的日志时效性。
所以可以考虑将 Filebeat 的部署 Pod 化,如下图所示:
将 Filebeat 与业务服务部署在同一个 Pod 内,Filebeat 直接监听 Pod 产出的日志文件(监听 Pod 内目录,底层日志文件实际上还是会落在硬盘)
这样部署主要有以下两个好处:
- Filebeat 监听文件数显著减少,减少每个 Filebeat 的 metadata 大小,大大提升 Filebeat 启动速度;
- 部署粒度更细,更加方便灰度操作,比如升级 Filebeat 版本,修改 Filebeat 配置等。出现问题,影响范围也仅仅是单 Pod 级别,而不是整台机器。
总结
我们向大家详尽的介绍了七猫日志接收系统整体的架构设计,并从日志处理效率、系统的高可用以及成本控制等方面进行了设计上的分析,同时提出了一下优化方向。在后续文章中,我们还会给大家带来关于客户端埋点设计的分析,敬请期待。