一、背景
1.1 背景介绍
我们常常会收到客户反馈的问题,在业务高峰时容易出现分析查询慢和卡顿的情况。造成这种现象的原因是查询引擎(trino)在业务高峰时资源瓶颈,查询只能排队等待。也采取过增加资源的手段来缓解这一现状。但是由于架构的耦合性,很多程序部署相互依赖,扩展比较困难。在业务低谷时资源利用率非常低。
这种方式在一定程度上缓解了查询慢的问题,但是需要付出更多的人力和服务器成本。希望通过架构的演进实现按需弹性资源,增强架构的扩展性。在提高查询效率的同时平衡服务器成本。
二、设计思路
整体方案设计围绕Trino查询引擎资源瓶颈问题来展开,并且需要兼顾到服务器成本。让Trino的架构具备资源弹性能力,在业务高峰时动态弹性扩资源,在资源负载低时释放资源。做到动态按需按量使用资源。
现有的部署方式是同一个节点可能会部署多个程序。同节点和跨节点的程序与程序之间又相互有依赖关系,这对于架构的扩展非常难。想要实现查询引擎的弹性能力,在架构上就需要做一些调整。这里借鉴了一些微服务架构的设计方法。单一职责原则,服务化设计,程序独立运维部署。
将Trino从原有的架构体系中分离出来,独立部署,独立维护和扩展。原有的架构通过配置切换即可使用到新的查询引擎的能力。新老查询引擎可以并存,按照架构实际使用效果逐步将老的查询引擎缩减直至完全下线。
实现计算资源弹性有两种方式: 一种是直接利用云厂商的节点伸缩组策略,另一种是利用容器服务的高效弹性伸缩能力
节点伸缩组的优势在于架构简单,部署和维护方便。可以和现有的架构风格很好的兼容和融合(都是采用虚拟机的部署方式)。不足之处在于弹性的维度比较单一只支持CPU或内存的指标进行弹性伸缩。弹性效率不足大概需要3min30s左右才能弹性好资源。
弹性容器的云服务的优势在于可以无缝对接自动化CI/CD工具。可以更加细粒度,更多方式的对资源进行调度和控制。k8s的HPA也很方便的支持多种类型的监控数据作为弹性指标。而且弹性速度也很快约,可以较好的满足计算资源弹性需求。
在ToB的业务,服务器权限都在客户那边。我们通常只有比较有限的权限和访问路径去排查问题和更新升级维护业务系统版本。部署架构的简洁性也是我们一个重要参考维度。Serverless模式比较符合我们的期望。
弹性能力的设计大体方向是利用云厂商的Serverless容器服务作为基础设施。然后根据实际业务需求进行调整和设计。云原生的架构通常不是孤立的存在。在Trino从原有的架构体系中分离出来,使用云原生的方式重新设计,就必然需要把服务治理的内容给补全(服务网关,服务编排,监控,日志和配置等)。查询引擎云原生化才能真正有效运行起来。
三、实现落地
总体方案包括CI/CD,资源调度,服务治理和可观测性。篇幅有限就挑一些容易比较有价值模块来描述。
3.1 弹性容器资源
预期能通过自定义的监控数据作为弹性伸缩指标,在超过阈值时弹性资源副本,低于阈值时缩减资源数量。始终能确保资源是按需调度和分配的。
Kubernetes的HPA支持CPU,内存维度的监控数据作为弹性指标。但是对自定义弹性指标支持不太好。而我们的实际场景是需要根据一些特定的业务监控数据(例如 : 消息队列积压数,请求处理等待时间等)来触发扩缩。
我们利用Kubernetes API Aggregator的特性,支持将第三方的服务注册到Kubernetes API。这样就能实现直接通过Kubernetes API访问到外部服务。外部服务作为数据源,那就有了更好的灵活性和扩展性了。具体落地时我们使用的是prometheus作为监控数据源,prometheus-adapeter作为扩展服务注册到Kubernetes API。整体逻辑架构图如下:
补充: 定时扩缩容(CronHPA)
我们使用了HPA和CronHPA相结合的方式,来应对不同的场景。负载缓慢增长的场景使用HPA根据业务指标进行控制。而在瞬时高负载场景下使用CronHPA,根据业务繁忙与空闲规律,提前自动调度好资源。在业务高峰之前扩资源,在业务空闲时释放资源。CronHPA极大缩短了应用冷启动时间。
3.2 服务接入
Kubernetes抽象了一层网络供pod相互访问,外部无法访问。外部想要访问Kubernetes通常会采用NodePort、LoadBalancer和Ingress等方式。因为k8s Serverless云服务的特殊性,底层Kubernetes工作节点是封装屏蔽的。所以选择通过云服务LoadBalancer将所有的请求转发到ingress-nginx-controller。再通过 ingress配置具体的路由转发规则。
3.3 镜象仓库
在镜像仓库的设计也是经历三次改进,有很多问题也无法提前预判。基本上是在实际的问题和场景中一步一步迭代演进的。镜像仓库一直围绕解决的问题是保证国内和国外的客户获取镜像的过程稳定可靠。在设计时也希望确保镜像同步和分发的一致性和统一管理。
阶段一: 能提供基本的镜像分发能力。
存在问题:
a) 海外获取镜像非常慢
阶段二: 国内和海外提供镜像分发能力,稳定运行了很长一段时间。
也暴露了两个问题:
a) 国内访问DockerHub服务 网络延时和不稳定,经常造成弹性容器无法顺利启动。
b) DockerHub 自身对镜像获取频次和速率有限制,超过阈值就会服务不响应。
阶段三: 国内和海外提供稳定的镜像分发能力
a) DNS就近解析,国内解析到国内的镜像仓库,海外解析海外的镜像仓库
b) 镜像优先推送到国内仓库,然后由国内镜像仓库同步到海外和DockerHub
c) DockerHub依然保留作为备份,在异常时迅速切换使用
3.4 镜像加速
查询引擎Trino的镜像大概1.8G左右。在弹性资源后,获取镜像的过程非常耗时。从外部来看就会发现弹性应用一直处于等待状态。用户可能需要等待很久,弹性出来的服务都处于ready状态时,查询体验才会提升。
在调研设计镜像加速时,我们结合各自云厂商的服务能力。比如在阿里云上使用ImageCache CRD的方式做ECI的镜像缓存。
在腾讯云上有镜像缓存服务,不过需要手动在云控制台配置。这对于我们ToB的私有化部署的可能造成很多的不便。也是非常感谢腾讯云的何猛老师,在听说了我们的诉求后,迅速协调研发资源帮忙推动CRD方式的镜像加速服务的上线。
AWS云平台比较特殊,k8s serverless 服务Fargate,有CPU规格限制。我们使用的是EKS弹性节点方式。弹性节点比弹性容器要慢非常多,大概2~3min才能处于可用状态。在和AWS共同探讨合作,决定采用Bottlerocket的AMI,然后制作快照(提前将镜像缓存到快照)。然后使用Karpenter去管理和控制节点的弹性伸缩。弹性节点速度从3min左右 优化到55s左右。
3.5 节点组
业务场景和资源使用方式的不同,用同一种方式来管理。可能会造成资源调度的灵活性不足,扩展难和资源浪费等问题。我们在设计时抽象一层节点组来管理不同类型的资源。节点组维度配置资费模式(包年包月/按量计费/Spot竞价实例),资费规格,芯片架构和区域。整合了云平台中多种实例类型,根据应用的特性调度不同类型的资源。所有节点组都会被统一管理,根据业务场景、资源规模进行调整优化。在应用性能和服务器成本之间达成一个平衡。
(1) 多种实例规格型号混用
云厂商提供的资源规格非常丰富,但是要合理搭配和使用才能花更小的成本产生更大的价值。得益于Kubernetes的很强的兼容性和适配性。节点组可以让各种架构,厂商,规格,型号和版本的资源并存。不同的业务应用调度分配适合的资源类型。
比如有一些常规应用希望使用x86的服务器,二另外一些查询型应用希望使用ARM的服务器。希望在同一套架构混合使用。通常来说ARM的服务器要比X86便宜一些约10%。而另外一些对安全和性能要求苛刻的应用,可以独立分配适量的资源。
(2) 资费模式混合
同一套架构为不同类型和使用场景的资源配置差异化的资费模式。例如,常驻留资源使用包年包月,弹性资源使用按量计费模式。程序的不同特性也可以选择是否采用Spot竞价模式。
(3) 多区域调度资源
同一个区域可能会出现特定型号资源存量不足,导致无法申请到资源。Trino云原生方案结合云服务功能打通各区域网络,将资源统一调度和使用整合到一个Kubernetes集群中。这样可以大幅提高资源申请成功率,确保在业务高峰时有充足的资源可供使用。
3.6 成本优化的思考
云厂商的资源使用资费模式有很多种类型(包年包月 / 按量计费 / Spot竞价实例 / 预留实例券 / 节省计划)。根据业务实际需求和场景综合使用可以节省比较多成本。
长驻留资源比较适合包年包月,弹性资源部分因为是按需只在高负载时使用尽可能的使用按量计费模式。但是如果有按量计费的时间每天超过8h,则需要重新计算成本。相同资源规格和使用时长计算的资费成本,按量计费比包年包月要贵一些。如果业务运行实例是无状态且偶发的中断对业务的影响可以忽略,则可以使用Spot竞价实例模式。竞价计费模式通常比正常的资费要便宜很多,最低可达1折。如果能利用好这种计费模式节省的服务器成本将相当可观。
利用节点组概念,来区分不同的资费类型。将不同的应用调度到不同的节点组。产生的资费也是按照不同节点组的实际使用情况来计算的。多种资源模式混合使用最大化的优化成本结构。
问题汇总
三、不足与期望
目前的方案基本满足了大数据查询引擎资源弹性的诉求。不过也还面临着一些问题和挑战。
3.1 冷启动问题
Trino给现有的资源分配大查询任务,负载变高。新弹性出来的资源无法分摊本次计算压力。后续的查询任务才能分配到新资源。这会造成本次查询慢的问题依然没有很好的解决。目前也是希望能实现按照资源使用历史来预测提前分配资源来缓解冷启动问题。
3.2 资源分配异常问题
k8s HPA频繁扩缩容造成,计费pod数量超过配置的Max最大值。这可能会造成资源浪费,很多扩充出来的资源利用率非常低。后续也会作力在资源更精确的控制上,确保资源的生命周期管理有序有效合理。
四、客户价值
在上线云原生方案后基本实现了在业务上实现了资源隔离,多业务并行运行相互不影响。也可以很方便的支持更多业务和个性化需求的扩展。云原生方案的弹性伸缩能力,可以保证按需使用资源。在月度硬件成本上下降了40%左右.服务的查询响应速度也提升50%左右。整个运维和治理过程也大大简化,方便后续的维护和管理。
五、后记
在架构的调研演进时,肯定会碰到各种各样的奇奇怪怪的问题。保持心态开放多和云厂商的技术人员保持密切的沟通,一个方案尽可能多的与不同的人讨论,避免认知盲区。很有可能针对某个场景的问题已经有了比较成熟和完备的解决方案了。而因为你没接触过而采用更费时费力的手段去应对。尽可能多的去验证实际测试,没上线、没测试验证,你永远不知道有多少坑在等着你。