平台开发要关注事项

平台开发与项目或者一般的基于特定项目应用开发要求不尽一致,项目讲究的是快速上线。在快速开发的过程中难免有欠考虑的情况,但可以通过快速迭代,人肉运维,客户管理等方式维护项目。于此相比,平台开发就没这么幸运了。之所以成为平台开发,目标就是从单一的项目/应用中解脱出来,通过统一的方式完成业务需求,避免重复造轮子。平台开发有以下特点:

  1. 生态复杂性:平台开发通常要对接较为复杂的上下游系统,如我们的大数据应用平台就需要考虑不同引擎的兼容性,统一引擎不同版本的兼容下,上下游系统的版本,集群安全性,多集群等情况

  2. 应用开发多态多性: 用户在业务开发时有个人偏好,有的喜欢自己通过代码完成业务需求,有的则是习惯使用SQL,有的则喜欢通过托拉拽配置的方式完成业务

  3. 数据格式多样性:不同行业,同一行业的不同业务场景下的数据格式,以及数据处理后的存储格式,压缩,存储组件等均具有不同形式。

  4. 租户式管理:平台的使用者通常不是单个人,单个团队,其中即涉及到资源共享,又牵涉包括硬件,软件等平台的资源管理。

平台开发的选型参考

平台应用开发的多样性对底层平台的开发提出了严峻的挑战,笔者在流处理平台开发过程中也有不少经验教训,在此进行梳理以供参考。

  • 组件选型

    不同于一般项目的组件选型只考虑支撑项目即可,以流处理平台为例,一个流处理平台需要考虑如下几个方面:

    1. 时延敏感性

      对时延的敏感性很大程度上决定了底层计算框架的选型。如更低的时延则要求实时处理的框架(如Flink),如果不敏感则Spark也是一个强有力的计算框架竞争者。当然不同业务场景复杂性也可能对不同的计算框架都有需求

    2. 消息一致性要求

      鉴于流数据的无限性、硬件/网络可能的异常,精确计算始终是流数据劣势。对数据准确性敏感的场景则需要上下游组件都能分布式支持一致性语义。

    3. 上下游生态

      比如你的消息中心是选择Kafka,RabbitMQ,ZeroMQ亦或是Pulsar,即需要考虑上下游已有的生态组件、也需要考虑后续的业务发展(如多租户)

  • API选择

    以流处理平台为例,当前主要的流处理引擎都支持低级API和高级API:

    • 低级API通常指的是底层的map,fliter等类型的java/scala级别的api接口,该接口功能完备,开发定制方便但不利于灵活定义语义,
    • 高级API通常指的是Table/SQL级别的sql语句,接接口具有语义清晰,易上手,但难易支撑所有低级API业务场景,调优复杂
  • 版本选择

    在选择组件之后也涉及到组件版本的选择,这其中要考虑

    • 已存在的组件版本的兼容

    • 平台版本升级工作量

    • 业务需求

      不同的版本对特定场景的支持略有差异,如Kafka在0.9之后才支持kerberos安全环境,0.11之后版本才支持事务操作

平台开发的踩坑经验

配置动态化

​ 大数据栈的组件丰富,组件间的交互众多,进程在不同的节点运行,通常基础的配置我们容易识别容易设置,但也有许多配置在开发过程中我们难以识别,这时候就需要在开发过程中预留后门,在生产需要时有办法对参数进行设置,一个好的平台配置设计应当具有如下特征:

 1. 所有相关组件的参数可配置
 2. 平台自定义参数当具备默认值
 3. 用户设置参数值自动覆盖底层组件默认值

业务无侵入原则

​ 在开发的过程中,不能侵入业务侧的操作结果,即使是生产中有意义的添加也不能直接强加给业务侧。而应当通过平台高级参数开关的方式提供给应用侧。

如下代码就有多处违反以上原则:

// 在完成业务逻辑处理后,在dataFrame中添加时间戳列
val newDataFrame = dataFrame.withColumn(eventTime, functions.lit(time))
    newDataFrame
      .write
            // 在调用write接口时,无法设置相关的option
      .mode(write_mode)
      .format(outputFormat)
            // 在输出结果路径中添加一级时间戳的目录
      .save(path + s"/$eventTime=$time")
 

相关的逻辑应修改如下:

// 在输出的结果中是否添加时间列设置开关,默认应当设置为false
val newDataFrame =
  if (useEventTime.toBoolean) {
    dataFrame.withColumn(eventTime, functions.lit(time))
  } else {
    dataFrame
  }
})
// 在输出的目录中是否添加时间目录设置开关,默认应当为false
val timePath =
  if (useEventTimePath.toBoolean)
    path + s"/$eventTime=$time"
  else path + ""
var hdfsProps: collection.mutable.Map[String, String] = ...

    coalesceDataFrame
      .write
// 设置输出相关的参数
      .options(hdfsProps)
      .mode(write_mode)
      .format(outputFormat)
      .save(timePath)

合理的日志打印

一个好的平台不仅仅需要考虑平台的易用,也需要考虑平台的易维护性。当业务运行异常时,能够较为简单对问题进行定界,也就是区分是平台本身的问题还是应用运行问题。
在开发过程中,我们可以进行debug等操作,但在很多生产环境中,通常不运行我们这么操作。这就要求我们:

1. 代码中需要打印合理的日志,特别的关键逻辑中要详细打印相关日志。
2. 服务的所有进程可以动态调整日志打印级别。

你可能感兴趣的:(平台开发要关注事项)