写点什么

麒麟云体验优化之 Pod 列表展示优化

作者:麒麟云
  • 2023-08-11
    湖南
  • 本文字数:5540 字

    阅读完需:约 18 分钟

麒麟云体验优化之Pod列表展示优化

在 Kubernetes 的应用环境中,展示资源列表(例如 Pod 列表)是一项用户常用的操作,其主要目的是将集群中的资源(如 Pod)整合并以列表的形式展示在用户界面上。然而,随着集群规模的扩大和资源数量的增长,资源列表展示可能会遭遇一系列挑战,例如数据处理量过大、内存使用过多、数据更新速度缓慢以及页面流畅度下降等问题。这些问题都可能对用户的体验造成显著影响。实际上,经过测试,我们发现,如果直接采用 Kubernetes 原生接口未经压缩调用来展示 3000 个 Pod,所需要的时间超过 1.2 秒,再经时间数据处理、渲染,用户实际等待时间可超过 5 秒,用户负面体验影响很大。有调查报告显示,每个网址网页加载时间延长 1 秒,频率减少 11%,客户满意度下降 16%,用户满意度下降 7%。麒麟云团队对资源列表展示的方法进行了深入研究和优化,经测试优化后在同等数据量的资源列表的展示时间仅需要 18 毫秒,优化大约 66.67 倍。

1 方案设计与优化

1.1 方案调研

麒麟云团队对业界的主流云原生产品列表展示方法进行了深入研究,并得出了以下四种主要实施方案:

  • 后端分页方案:在此方案中,后端负责数据的分页、排序和过滤操作。前端只需传递参数,并通过 websocket 获取后续更新的数据,同时只缓存当前页面的数据。这种方案的优势在于其列表加载速度快,每次传输的数据量小。然而,由于它没有缓存所有列表数据,所以在切换页面、进行搜索或排序时需要重新发送请求,可能会出现明显的 Loading 效果。此外,这种方法会对服务器产生较大压力,因为它需要后端处理分页和过滤任务。

  • 数据一次性加载方案:在此方案中,前端一次性向后端请求加载所有数据,而前端则负责数据的分页、排序和过滤。这种方案通过使用 Kubernetes 原生接口,代码逻辑相对简单。然而,其缺点在于,由于一次性加载所有数据,当数据量大时,等待和加载时间将会变长。

  • 数据分段加载方案:在此方案中,前后端都不进行排序和分页,而是通过使用 limit 参数来多次向后端获取所有数据。这种方案的优点在于其初次加载速度快,同时由于使用 Kubernetes 原生接口,代码逻辑简单。然而,这种方案的默认是无序的,与用户常用的按时间倒序排列的习惯不符。此外,用户的列表数据将会持续变化,可能导致用户的操作被中断,且难以立即找到需要的数据,对用户友好度较低。

  • 数据分块加载方案:此方案按照模块组进行数据缓存。在模块内跳转时,可以直接从缓存中读取数据,显示速度快。然而,当从不同的模块组中进行调整时,反应可能会变慢。另外,由于所有数据都被缓存,因此需要建立多个 websocket 来进行数据更新,这可能会对浏览器、服务器都产生较大压力。

1.2 优化方案

在深入探讨后,麒麟云团队确定了对列表展示进行优化的几个主要方向,包括:

  • 首页数据的快速展示。

  • 无感换页和实时更新。

  • 高效处理大数据集。

我们的解决方案选择利用后端加速 API 获取首页数据,随后使用 Server-SentEvents(SSE)技术通过 Kubernetes 原生接口获取所有数据,并在前端进行分页和排序,最后重新展示并动态更新。


首先,我们使用加速接口获取首页数据进行快速展示,然后采用 Fetch 长链接高速解析,获取所有数据。在完成列表展示之后,我们监听数据流的操作更新,并执行局部更新。这种方式能实现无延迟翻页,显著提升效率,同时避免了后端分页多次请求接口的问题。


此外,我们对首页快速展示接口进行了容错处理。即使该接口未部署、报错或超时,页面依然能够通过原生接口高效、迅速地获取并展示数据给用户。这种优化方法确保了即使在面临网络或系统故障时,用户体验依然得以保持。


图 1 优化流程图

2 首页数据的快速展示

在云原生环境进行列表展示时,通常我们会使用前端分页。但是,如果资源数据集数量过大,展示大型列表可能导致应用程序响应变慢,甚至导致应用程序崩溃。由于列表拥有数据分页功能,每个页面只需加载部分数据,这大大提高了效率。我们的优化策略是,先通过后端加速接口获取排序后的首页数据(由于原生 KubernetesAPI 返回的数据未经排序),以防止在获取所有数据后首页数据不一致而被刷新,同时将换页按钮禁用(此时仅首页数据加载完成),在用户查看和操作首页数据时,通过长连接获取所有数据。


第一次访问列表时,排序方式是固定的,按照时间降序、命名空间降序、名称降序的顺序进行排序。这使得后端可以缓存有序列表,查询时无需进行额外的操作,直接返回即可。所以,我们使用 GoFrame 的 SortedArray 来存放有序列表,并通过 client-goInformer 的 EventHandler 来更新有序列表:


但是 client-go 中的 Infomer 仅监控了 Kubernetes 资源,而在实际环境中可能还存在自定义资源。通常情况下,要监控自定义资源,需要使用相应的 Informer,然而这样做会导入大量的包,并且有些自定义资源可能没有提供相应的 Informer 供我们调用。为了解决这个问题,我们参考了 client-go 中的 sharedInformerFactory,并使用 DynamicClient 的 List 和 Watch 功能,实现了一个支持任意类型资源的 sharedInformerFactory。并且通过该 sharedInformerFactory 监控 customresourcedefinitions 资源,实现了任意类型的资源添加到集群中时,自动缓存该资源的有序列表:


认证和鉴权是非常重要的。最初,我们使用 apiserver 默认的 DelegatingAuthenticationOptions 和 DelegatingAuthorizationOptions 完成认证和鉴权,这对于一般的 Kubernetes 扩展应用已经足够,但在这个扩展应用的使用场景中(短时间内只会调用一次该应用),认证和鉴权很少能命中缓存,如果没有命中缓存,就会调用 kube-apiserver 相关接口,从而导致一次请求中认证和鉴权消耗了大量时间。为了解决这个问题,我们导入了 kube-apiserver 中的相关代码来完成认证和鉴权,这样大幅降低了认证和鉴权所需的时间。


加速接口仅提供单页数据(数量可指定)和总数量,这种方案的最大优点在于,它不受所有数据的数量级限制,可以准确且快速地响应请求,返回所需的数据。


然而,需要注意的是,加速接口需要额外部署,对于没有部署加速接口的环境,我们采用 Kubernetes 原生接口获取数量,并异步使用 Fetch 长链接读取数据的方式来进行列表展示。由于非加速 API 获取数据的速度较慢,因此在非加速 API 刚刚获取数据的时候,如果 Fetch 长链接已经读取到了所有的数据并完成了数据格式的转换,那么在非加速 API 获取到列表总数量后,我们就可以立即更新列表数据(确保最快速度展示列表)。

3 无感换页和实时更新

在云原生环境中,如果展示列表仅使用前端分页,那么数据处理量过大会造成展示列表延迟过高,如果展示列表仅使用后端分页那么切换页码、搜索或排序时需要重新发送请求,可能会出现明显的 Loading 效果。此外,因为这种方法它需要后端处理分页和过滤任务会对服务器产生较大压力,所以我们在后端加速 API 获取首页数据后使用长连接获取全部列表数据进行前端分页。而且当多人同时在线操作数据时,不能保证数据实时性,需要使用实时更新技术。在这种背景下,我们结合了 Fetch 和 SSE(Server-SentEvents)两种技术,对资源检索过程进行全面优化,旨在提升检索速度。


Fetch 是一种 JavaScript 函数,它被设计用来创建 HTTP 请求并获取服务器的响应。基于 Promise 架构的 Fetch 提供了一种比传统的 XMLHttpRequestAPI 更为强大和灵活的方式。在前端数据处理中,Fetch 是实现分块读取和并行处理数据的核心。


另一方面,SSE 是一种 HTTP 协议的扩展,它允许服务器主动发送事件给客户端。作为一种传统轮询技术的替代方案,SSE 能在不增加客户端负担的情况下实现服务器和客户端间的实时数据传输。尽管 SSE 和长轮询、短轮询都是基于 HTTP 协议,但它们的工作方式却有所不同。SSE 并不是发送一次性数据包,而是发送一个持续的数据流。在这种模式下,服务器会不断地向客户端发送新的数据流,而客户端会保持连接并持续接收来自服务器的新数据流。因此,SSE 能够以一种流信息的方式实现长时间的数据下载,从而实现实时的数据传输。


图 2 SSE 优化流程图

在此次的 SSE 连接建立过程中,我们利用了一条持久连接(长连接)来完成列表数据的获取及其后续的更新操作,这大大降低了服务器的压力。实现的步骤如下:


  1. 首先,我们使用 Fetch 函数建立持久连接。在建立的连接中,我们通过调用 body 对象的 getReader()方法,得到了一个流读取器。

  2. 其次,我们调用`await reader.read()`方法,等待每一段数据被读取完毕。我们将读取到的数据块收集到一个名为 chunks 的数组中。

  3. 最后,我们利用解码器将流数据解码成字符串。


在上述操作中,我们已经获得了解码后的部分列表数据,如图所示:


解码后的数据以换行符(\n)作为结束标识,形成了一串拼接字符串。我们将这串字符串存放在一个累加器中,然后使用 split 函数根据换行符将其分割,生成一个名为 lines 的列表。然后,我们将 lines 的内容累加到名为 listdata 的操作数组中。

由于数据以数据流格式存在,因此我们可能会拿到一些不完整的数据。但是,这些不完整的部分在分割后必然在数组的末尾。我们可以通过 pop()函数将这些不完整的行留在累加器中,等待接收到更多的数据进行处理。然后,我们将完整的部分进行格式化处理,并判断 listdata 数组的大小是否大于等于我们预期获取的总数。如果满足条件,那么我们就将这些数据展示在列表中同时解除换页按钮禁用。


但是,当长连接接收的数据量过大时,可能会导致接口获取完整数据的响应时间过长,换页按钮会被禁用,从而影响用户体验。因此,我们引入了超时更新机制以适应大数据量推送的情况:如果在规定的时间内还没有读取到所有数据,我们将使用定时器先展示一遍列表,同时长连接仍会继续读取数据。当长连接接收到全部数据后,我们将再次更新列表,从而确保在大数据量推送的情况下仍能快速展示列表。这样,我们可以实现无感换页,提高效率,避免后端分页需要多次请求接口。

在累加器中,只会存放未经过处理的数据。当列表的全部数据都展示出来后,累加器接收到的数据就全部是需要进行列表更新的数据。此时,我们可以改变判断条件,监听数据流的更新操作,并对新接收到的数据进行局部更新。这样就保证了数据的实时性,实现实时更新的效果。

综上,我们使用 SSE 技术和 Fetch 的长连接来接收服务器发送的数据流,并将完整的数据分块传输和高速解析,在进行数据格式化处理后进行前端分页和排序同时监听资源数据变化,实现了无感换页和实时更新。

4 高效处理大数据集

在列表数据获取过程中,直接从接口获得的数据往往不适合直接用于列表展示,而需要进一步的数据处理,例如格式转换。当面临大量数据时,处理时间可能会变得很长,甚至卡死前端页面,用户体验很差。考虑到 JavaScript 的特性,它是单线程的,即使有多个子线程同时加载,也只有一个主线程暴露出来。在主线程中,通常需要在所有数据读取完成后才能进行数据格式化处理。然而,为了更好地利用多核 CPU 的计算能力,如果我们能够在一个可控线程中独立进行数据处理,那么在处理大量数据时,我们就能大大提高列表展示的效率。


HTML5 提供了 WebWorker 标准,这是一种 JavaScript 的多线程解决方案,它允许在浏览器后台执行 JavaScript,而不会阻塞主线程。子线程完全受主线程控制,可以通过将部分 JavaScript 代码放在单独的线程中运行,使主线程更专注于处理用户交互,从而提高网页的响应速度和平滑性。WebWorker 与主线程之间通过消息传递系统进行通信,可以避免数据同步的问题。

图 3 数据流+webworker 优化原理

在数据流模式下,相比于获取完整数据后再进行处理的方式,我们可以实时地一段一段接收并处理数据,这样既可以节省处理时间,也可以在数据处理完成后立即将其从内存中释放,从而节省内存空间。因此,当数据量较大时,数据流处理可以明显提高列表展示的性能。配合 SSE 的数据流模式,我们从块处理和 WebWorker 的角度进行了深度优化。


具体的操作如下:我们使用 reader 读取块数据,读取到块数据后将数据进行分割。这样,数组的末尾就会存放不完整的单条数据。我们将完整的数据交给 worker 处理,同时释放内存,而不完整的数据则留在累加器中,等待接收下一块数据。

同时,我们为每一条长连接建立了一个 WebWorker。主线程负责读取数据,而 worker 则异步处理需要大量计算的格式化原始数据,并拼接处理后的元数据。线程之间通过 postMessage 进行数据通信。通过使用 WebWorker,我们可以不再阻塞主线程的数据读取,从而加快页面展示速度。

5 优化效果

对于云原生 Web 列表展示性能的优化,经过测试,结果显示在多个方面都取得了显著的提升。以下是以云原生平台中 3000 条配置数据为例,从页面加载效果、多次接口调用时间、数据压缩效果和整体时间等方面展示的优化效果。

5.1 整体性能对比

优化前后的性能对比结果如下:

图 4 优化前后对比图

根据测试数据显示,经过优化后,列表展示的各项指标,包括平均首次加载时间、平均全部数据获取时间、平均切换页码时间、平均接口调用时间等,相较于原生方案均有大幅度提升,优化效果显著。

5.2 加载效果直观对比

优化前,使用原生接口的页面加载效果(从其他页面切换到有 3000+条数据的配置页面,并翻页):


优化后,页面加载效果(从其他页面切换到有 3000+条数据的配置页面,并翻页):

这种显著的改善证明了我们的优化措施的有效性。


银河麒麟云原生操作系统是麒麟软件面向云原生场景,为支撑云原生应用而全新打造的一款操作系统产品。云原生应用对操作系统有更多的功能需求和特殊的性能需求,如容器编排,集群调度,服务发现,负载均衡,弹性伸缩,资源隔离,快速容器创建,低延迟容器网络等。同时,在云原生应用编排领域,kubernetes 已成为业界的事实标准。银河麒麟云原生操作系统采用"内核+操作系统+kubernetes"的联合设计思想,在传统操作系统接口外,为云原生应用提供 kubernetes 扩展 API,支持操作系统集群粒度统一纳管,支持一致性发布和原子回滚。在容器调度,容器网络,容器运行时等关键组件进行深入优化,致力打造国产平台容器性能标杆。银河麒麟云原生操作系统已运用于银行和证券领域。


用户头像

麒麟云

关注

打造国产平台容器性能标杆 2023-06-13 加入

银河麒麟云原生操作系统面向云原生场景,致力打造国产平台容器性能标杆。采用“Kernel+OS+ Kubernetes”的联合设计思想,在传统操作系统接口外,为云原生应用提供kubernetes 扩展API,现已运用于银行和证券等领域。

评论

发布
暂无评论
麒麟云体验优化之Pod列表展示优化_Kubernetes_麒麟云_InfoQ写作社区