写点什么

超大规模大数据集群管理平台的架构优化及实践

  • 2022-12-15
    江苏
  • 本文字数:5044 字

    阅读完需:约 17 分钟

导读:本文介绍了针对现网超大规模(3500+节点)大数据集群管理平台出现的页面卡顿、渲染速度慢、查询性能差等问题,通过创建数据汇聚层、对缓存配置优化等方式进行架构优化的方案及实践,供大家参考。

1 背景

Apache Ambari 作为一款基于 Web 的工具,提供 Apache Hadoop 集群的管理,运维,监控的功能。基于此组件开发的 BC-HControl 集群管理平台在项目上得到了广泛的应用。在实际业务场景中,当集群节点规模达到了 3500 节点,用户在操作页面时会产生明显的卡顿情况,页面渲染速度慢等问题。同时,在执行某个简单操作时都会出现 30s 甚至更长的请求响应。用户无法正常管理和使用当前的管理平台。受限于 Apache Ambari 的基础架构,在实际场景中出现了如下性能问题: 

1、前端页面在频繁的数据更新下,返回数据量大,重排、重绘频率非常高,直接导致页面卡死的状况。

2、后端接口过于冗余,同一个接口往往返回较大,未区分使用场景,导致页面加载的接口数据量远大于实际所需情况。

由此带来的性能问题导致操作体验较差,需要对其架构进行优化。

2 优化思路

根据实际场景分析,优化大致分为以下几个点:

(1)使用先进的前端框架重写 web 页面,降低原先的重排和重绘,提升页面的性能。

解决问题:前端页面数据更新频繁,渲染慢的问题。

(2)使用反向推送以及按需加载数据的方法,数据最小化返回前端。

解决问题:接口数据复杂庞大,页面使用频繁无区分。由此提出数据汇聚层和前端页面优化的整体方案。

3 数据汇聚层方案

汇聚层作用: 

(1)组件状态同步,消息汇聚,配置管理及更新,服务操作任务分发。 

(2)使用缓存对状态等信息进行存储及接口加速。 

(3)提供按需拉取数据的接口方式,同时提供反向推送数据的功能。 

方法及使用技术: 

(1)使用 Graphql 构建功能接口,提供按需拉取数据的方式。返回字段前端自定义。 

(2)使用 Graphql 订阅功能,结合 webSockets 技术,反向推送 message。 

(3)使用高性能 nosql 数据进行状态信息临时存储,部分信息缓存优先,并定时从 BC-HControl 中获取数据进行存储; 

组件选用:

(1)基于 Nodejs 开发,优异的高并发性能; 

(2)使用 MongoDB 数据库,文档存储,优异的查询性能; 

(3)Apollo Graphql,用于搭建 Graphql 的官方推荐方案;

基于数据汇聚层的建立,选用先进的前端框架重写前端页面,对数据的获取及渲染进行优化。

4 优化及实践

4.1 缓存配置

进行多类型数据缓存:

async syncCommonData(    dataMetas: DataMeta[],    hcAddr: string,    config: Record<string, unknown>,    storeType: string,  ) {    const commonRequest = Lo.map(dataMetas, (dataMeta) => {      if (dataMeta.options) {        return lastValueFrom(          this.httpService.request(            Lo.assign(              {                url: new URL(dataMeta.url, hcAddr).toString(),                auth: config.auth,                headers: config.headers,              },              dataMeta.options,            ),          ),        ).catch((err) => err.response);      } else {        return lastValueFrom(          this.httpService.get(            new URL(dataMeta.url, hcAddr).toString(),            config,          ),        ).catch((err) => err.response);      }    });    await Promise.all(commonRequest).then((resps) => {      Lo.forEach(resps, (resp, index) => {        const dataMeta = dataMetas[index];        const data = Lo.get(resp.data, dataMetas[index].dataKey);        switch (storeType) {          case 'configurationStore':            this.configurationModel              .updateOne({ _id: dataMeta['key'] }, data, { upsert: true })              .then();            break;          case 'cacheStore':            this.cache.set(              dataMeta['key'],              data,              dataMeta.ttl                ? { ttl: dataMeta.ttl, version: dataMetas[index].version }                : null,            );            break;          default:            return data;        }      });    });  }
复制代码

多类型状态信息存储 mongodb:

实现效果:利用缓存,页面快速拉取状态,同时减少与底层 HC server 的交互,达到页面的快速响应。

4.2 数据字段自定义拉取

GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。

基于 Graphql,向前端提供一套按需拉取数据的接口规范。数据字段的返回由前端需求者自定义拉取,实现数据返回体的最小化。

提供 DOCS 接口信息:

按需指定查询字段,获取最小返回体:

实现效果:将原先复杂庞大的数据返回体接口和不同场景的前端展示需求解绑,替换成由前端指定数据返回字段的接口规范,实现数据的最小传输,同时减少前端渲染压力。

4.3Graphql 订阅实现消息推送

页面中,服务状态,组件指标实时刷新,若由前端实时请求,会导致请求非常频繁,同时无法做到实时感知。

基于这种情况,后端利用 Graphql 订阅,同时配合 webSockets 技术,主动向前端推送信息。

@Subscription((returns) => SubscriptionType, {    resolve: async function (payload) {      return {        subscriptionType: payload.subscriptionType,      };    },    // 根据前端的传值,只监听特定事件    filter: (payload, variables) => {      return payload.subscriptionType === variables.subscriptionType;    },    description:      '配置订阅,当每次后台同步配置,发现有变动时,会主动推送配置信息',  })  GlobalSubscription(@Args('subscriptionType') subscriptionType: string) {    return this.pubSub.asyncIterator('GlobalSubscription');  }
复制代码

实现效果:减少了页面的请求压力,将特定需要的数据进行后台推送,做到了实时感知。

4.4Cookie 认证实现任务分发

基于动态 Cookie 认证,打通汇聚层到达 BC-HControl 的路径,实现任务的分发及认证。

async getLatestCookie(    currentCookie: string,    userName: string,    password: string,    clusterIp: string,  ) {    const clusterInfoUrl = `/api/v1/clusters`;    if (!clusterIp.endsWith('/')) {      clusterIp = clusterIp + '/';    }    const config = {      headers: {        referer: clusterIp,      },      config: {},    };    if (currentCookie) {      // 使用当前cookie发起请求,查看cookie是否失效      config['headers']['Cookie'] = currentCookie;      const request = lastValueFrom(        this.httpService.get(          new URL(clusterInfoUrl, clusterIp).toString(),          config,        ),      ).catch((err) => err.response);      const resps = await Promise.all([request]);      if (        resps &&        resps[0] &&        resps[0]['status'] >= 200 &&        resps[0]['status'] <= 299      ) {        // 成功直接返回当前cookie        return currentCookie;      } else if (resps && resps[0] && resps[0]['status'] === 403) {        // 当返回码为403时,则表示cookie失效,则重新请求,并刷新cookie        const latestCookie = await this.sendRequestWithOutCookie(          clusterInfoUrl,          userName,          password,          clusterIp,        );        const cookies = await this.getCookieByParams(userName, clusterIp);        if (cookies && cookies.length) {          // 更新cookie          await this.updateCookieInfo(cookies[0], latestCookie);        } else {          // 保存cookie          await this.saveCookieInfo(userName, clusterIp, latestCookie);        }        return latestCookie;      } else {        this.logger.error(          `valication error: userName = {${userName}}, clusterIp = {${clusterIp}}`,        );        throw new Error(          `验证cookie失败: userName = {${userName}}, clusterIp = {${clusterIp}}`,        );      }    } else {      const latestCookie = await this.sendRequestWithOutCookie(        clusterInfoUrl,        userName,        password,        clusterIp,      );      // 保存cookie      await this.saveCookieInfo(userName, clusterIp, latestCookie);      return latestCookie;    }  }
复制代码

在分发任务或者获取最新数据时,校验 BC-HControl server 的登录 Cookie 动态变化情况,若不通过则操作取消,若通过则下发任务。

实现效果:完成 BC-HControl 的认证,保持数据一致性,打通任务下发的路径。

4.5 调优

在实际的性能测试中,针对 3500 节点的模拟环境场景,根据上述的架构调整,页面性能仍未达标,若干页面渲染耗时达到 30s 左右,如下图:

此种情况很明显未达到预期的效果,因此需要继续优化。经过分析后,我们针对如下几点进行了优化调整:

(1) 原因:前端在处理逻辑时获取的数据量过大,导致渲染时间过长,经过分析,前端很多功能在处理一些中间状态,例如“获取某一个组件当前的状态”,“获取所有组件的状态”,然后根据这些数据再请求最终需要的信息。

优化:将很多中间状态参数的处理过程放到后端处理,前端尽量以“最少参数”、“最快过程”的规范去请求参数。此处极大的减少了页面需要发起的请求数以及返回数据。

(2) 原因:接⼝请求会带上“_typename”这个参数,后端返回给前端也会返回⼤量的 _typename 参数,导致接口大小迅速膨胀。

优化:由于该功能是 Apollo Graphql 自带的,关闭即可。优化后,数据量显著下降:

最终根据上述的调优,页面渲染时间降低了 90%以上,达到了预期的目标:

5 总结

1、基于 Graphql 实现数据的按需拉取及主动推送,减少页面的重排和重绘,提升页面的渲染效率。

效果:数据量在 5M 以上的接口都降低到 kb 级别。

2、基于 mongodb,nodejs 实现数据的缓存机制,提供优异的查询性能。

效果:页面渲染效率从 30s 降低到 3s 左右,时间降低 90%以上。

3、页面重写,利用数据汇聚层的接口,对页面进行重构。

效果:新接口将功能融合,原先前端调用的接口实现的中间处理逻辑,都由后端接口处理,前端只需提供相关参数,接口数量降低 50%,同一时间调用的接口数据降低 60%。

6 展望:BC-HControl federation 方案

遗留问题:后端在大集群的情况下,单节点架构的 BC-HControl Server 负载较高。操作原子性导致操作需要在全部动作完成之后再进行下一步,从而导致 pending 状态耗时长。

后续展望中希望实现 BC-HControl federation 方案:

总体方案介绍:

(1)将原有由一个 BC-HControl server 管理的超大规模节点集群【一般指管理 2000 节点以上的情况】分成若干小分组,即将原来由一个 BC-HControl server 管理的节点拆分成由多个 BC-HControl server 进行管控,其中集群中的 Master 节点逻辑上全部集中于 Manager 分组,用于对集群的实际管理。

(2)基于多个 BC-HControl server 之上构建一个数据汇聚层,一个 Web view 层。

(3)数据汇聚层:将多个 BC-HControl server 的数据进行汇聚,统一为整体对外提供服务。

(4)Web view:与底层实现的多个 BC-HControl server 进行逻辑隔离。提供统一的操作页面。在操作上,多个 HC server 作为一个整体对外提供服务,用户只能感知到一个整体的逻辑集群。

利用 BC-HControl federation 方案实现超大规模节点的分散治理,减轻单个 BC-HControl server 的负担压力,实现横向扩展。同时,该方案也需要解决如下问题:

(1)多个 BC-HControl server 间的状态一致性如何保证。

(2)任务操作后的反馈和回滚如何解决。

(3)Manager 分组和其他普通分组上形成的差异化配置问题如何解决。

(4)服务重装情况下,涉及到的数据如何清洗。

7 参考链接

  • https://graphql.cn/

  • https://docs.nestjs.cn/9/graphql

  • https://ambari.apache.org/

用户头像

移动云,5G时代你身边的智慧云 2019-02-13 加入

移动云大数据产品团队,在移动云上提供云原生大数据分析LakeHouse,消息队列Kafka/Pulsar,云数据库HBase,弹性MapReduce,数据集成与治理等PaaS服务。 微信公众号:人人都学大数据

评论

发布
暂无评论
超大规模大数据集群管理平台的架构优化及实践_移动云大数据_InfoQ写作社区