Lightflus:云原生流计算框架,Demo 版本正式发布!


theme: cyanosis


Introduction

Hi all,这是我首次在腾讯云开发者上发文章,先简单介绍下我自己吧,我叫 Jason Thon,魔都一枚小开发,喜欢撸猫撸狗打电玩,练习时长两年半,擅长各种 Bug 制作技巧与 Debug 手艺。

在这篇文章里,我将介绍一款与我一同练习时长一年,擅长流处理的新型云原生计算框架 Lightflus。在我的公众号文章: Lightflus: 云原生流处理框架 中,小范围地宣传了一下这款框架,并被 Rust 中文社区转发,一时得到了一点关注度。也因此有了动力继续做下去。

Lightflus 如我的文章中所言,是基于我对现代数据栈的思考,尝试去面向未来数据计算框架开发的一款框架。其目标要解决的问题是主流流计算框架如 Flink 所没有解决的:

  1. Cloud-Native:因为历史原因,目前 Flink 并没有真的做到 Cloud-Native;
  2. 上手门槛高:Flink 需要很专业的团队才能玩得转,不是所有公司都有 BAT 这样的开发资源的;
  3. 云中立:Flink 在商业上不是云中立的,客户只能在某云上使用 Flink 的云版本。这就导致 许多用户怕被云厂商绑定,只能去做各种重复建设的活,浪费大量时间和资源在一件人效比极低的事情上。而 Lightflus 与云无关,理论上什么云环境都能用,用户可以将 Lightflus 非常简单地部署到自己的公有云或私有云环境中;
  4. Pay-as-you-go:因为沉重的历史包袱,Flink 实现 SaaS 化比较艰难,用户需要付很多额外的钱在一个对主营业务没那么重要的东西上。但 Lightflus 模块化和轻量化的设计,SaaS 化的路径就会简单很多;

Lightflus 尝试通过技术创新,来解决如上的问题。简单来讲,Lightflus 给开发者搭了个云端的流计算“开发环境”,让他们能够更加简单(便宜)地去开发流数据的价值。在未来 Cloud IDE 成熟后,会进一步释放 Lightflus 在 DataOps 上的价值;

Lightflus Demo 的功能一览

在 Demo 版本中,Lightflus 将提供如下的 features:

  1. 仅支持 Typescript API
  2. 一个小巧的 Rust 编写的 Runtime;
  3. 支持 mapreduceflatMapfilter 算子,在 1.0.1-alpha 版本中支持 window 算子;
  4. 提供如下 Data Source 的支持:Kafka
  5. 提供如下 Data Sink 的支持:MySQLRedisKafka
  6. 支持 Docker 部署;

由于计划中的 CLI 工具在 demo 里并不提供,因此发布时间比计划的提前了。此外,Demo版本及其之后的release版本都会提供社区支持,大家有问题都可以在Gitter上提出来。

Github 与社区

欢迎大家到 Github 去试用下 Lightflus,我们非常希望能得到各位的任何关于产品的建议!

再谈谈社区,其实我是非常欢迎任何人来做 Lightflus 的 contributor 的,但社区是有规范的(包括 PR、提 issue 等),这一块我会逐步完善起来。对于开源生态,我都是拥抱的态度,我会让 Lightflus 加速融入到开源生态中。

下面让我们来详细领略下 Lightflus 这款新型流计算框架吧~

Lightflus 的技术架构

应用架构

Lightflus 的整体应用架构如下图

image.png

Lightflus 本质是个分布式实时 DAG 计算引擎,它从指定的流数据源(如 Kafka、MQTT 等)消费数据,经过 User-Defined 的 DAG 计算出结果后,写入指定的数据存储层(如 MySQL,Redis,ElasticSearch 等)或数据管道(如 Kafka)中。

技术架构

image.png

Lightflus 由 Master 和 Worker 节点组成,其中 Master 节点有 ApiServer 和 Coordinator 两个服务;

  • ApiServer 主要负责与 Client 端或 CLI 工具的交互,提供标准的 REST Api 接口;
  • Coordinator 负责集群管理、Checkpoint 管理、动态扩缩容管理等;
  • Worker 是主要的计算负载并负责与数据源 Connection 、计算中间结果的 Dispatch、状态管理等;计算图会被构建成一个 Actor System,通过 Channel 在 Actor 间同步计算结果;

API

Lightflus 采用 Typescirpt 作为 API 的开发语言,这么做有几个目的:

  1. Typescirpt 是类型安全的,可以避免很多类型错误,还能够规定 Dataflow 算子转换的输入输出结构,减轻 Runtime 的负担;
  2. Typescript 可以有两种编译中间产物:JavaScriptWebAssembly,而两者的 Runtime 现在正逐步融合,在未来,用户可以根据自己的需求来决定 Lightflus 使用哪种编译中间产物;
  3. Typescript 在写法上与 Java 差异不太大(当然,Typescript 玩法更加丰富),现有的数据开发工程师也能很容易转过来;
  4. Typescript 是 web 的语言,天生支持云端化;

Runtime

Lightflus 的核心计算引擎是 Rust 编写的,这么做的目的是:

  1. Rust 能很大程度上保证内存安全,并且拥有接近 C/C++ 的性能;
  2. Rust 生态内有成熟的技术可以作为 Typescirpt 运行的底层引擎;
  3. Rust 在工程化上更加成熟,能很好地模块化,还能通过软件工程来有效管理 Lightflus 的代码产出质量;

核心计算引擎采用 Actor 大规模并行计算模型,这种并行计算模型经过时间充分的检验,证明是目前大规模并行流处理最适合的计算模型,在可扩展性、高可用、容错上也都有成熟的解决方案。Lightflus 是站在巨人的肩膀上起步的,产品能够更快地到达较高的成熟度

Let's Try Lightflus!

本地部署 Standalone 集群

在 Flink 的世界里,即使是最简单的流任务(如 word count),想要在线上运转起来,都需要折腾不少时间。虽然 Flink 社区努力把 Flink 的使用门槛降到最低,但你想让它哪怕在本地环境正常工作,依旧需要花费不少力气。

而 Lightflus 就非常简单了,只需要执行一行命令

代码语言:shell
复制
docker-compose up

docker-compose.yml 文件配置如下:

代码语言:yaml
复制
  coordinator:
    image: lightflus/coordinator:v1.0.0-alpha
    hostname: coordinator
    ports:
      - '8791:8791'
    environment:
      RUST_LOG: INFO
      WORKER_1: worker
    depends_on:
      - worker
    healthcheck:
      test:
        [
          "CMD",
          "/lightflus/runtime/tools/healthcheck",
          "-TARGET",
          "localhost:8791",
          "-METHOD",
          "/coordinator.CoordinatorApi/Probe",
          "-SERVICE",
          "0",
          "-PROBE",
          "1"
        ]
      interval: 3s
      timeout: 5s
      retries: 3
      start_period: 5s
  worker:
    image: lightflus/worker:v1.0.0-alpha
    hostname: worker
    ports:
      - '8792:8792'
    environment:
      RUST_LOG: INFO
    healthcheck:
      test:
        [
          "CMD",
          "/lightflus/runtime/tools/healthcheck",
          "-TARGET",
          "localhost:8792",
          "-METHOD",
          "/worker.TaskWorkerApi/Probe",
          "-SERVICE",
          "1",
          "-PROBE",
          "0"
        ]
      interval: 2s
      timeout: 1s
      retries: 3
      start_period: 5s
  apiserver:
    image: lightflus/apiserver:v1.0.0-alpha
    environment:
      RUST_LOG: INFO
      LIGHTFLUS_COORDINATOR_URI: http://coordinator:8791
    ports:
      - "8080:8080"
    depends_on:
      - coordinator

等待 docker 完成镜像拉取和服务启动、检测的过程后,Lightflus Standalone 就能成功启动了,这个时间你还可以喝杯咖啡养养神。

开始编写流任务

我们从最简单也是最常被拿来当例子的 Word Count 来看我们如何编写和部署 Lightflus 的流任务;

准备

由于 Lightflus API 依赖 NodeJS 环境和 Typescript 编译器,因此首先要安装 NodeJS,通过 npm 安装 typescript 依赖,具体可以参考微软的文档;

环境准备

我们需要启动如下的服务

  1. kafka
  2. redis

我在这里提供一份 docker-compose.yml 文件供大家参考:

代码语言:yaml
复制
version: "3.9"
services:
  zookeeper:
    image: 'bitnami/zookeeper:latest'
    ports:
      - '2181:2181'
    environment:
      - ALLOW_ANONYMOUS_LOGIN=yes
  kafka:
    image: 'bitnami/kafka:latest'
    ports:
      - '9092:9092'
    environment:
      - KAFKA_BROKER_ID=1
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
      - ALLOW_PLAINTEXT_LISTENER=yes
    depends_on:
      - zookeeper
  redis:
    image: 'bitnami/redis:latest'
    ports:
      - '6379:6379'
    environment:
      - ALLOW_EMPTY_PASSWORD=yes

执行命令:docker-compose up -f /path/to/docker-compose.yml 启动服务;

在 Lightflus 的 Github 项目仓库里,我准备了一份完整的 docker-compose.yml 文件,如有需要,大家可以直接 clone 仓库后在项目根目录启动即可;

初始化项目

创建一个 nodejs 项目,准备好 typescript 开发环境后,下载 lightflus-api 依赖:

代码语言:shell
复制
npm i lightflus-api

并初始化 tsconfig.json 文件:

代码语言:shell
复制
npx tsc --init

编写代码

在准备好后,我们开始编写 word count 的代码,代码非常简单:

代码语言:typescript
复制
// wordCount example
async function wordCount(ctx: ExecutionContext) {

// 从 kafka 中获取字符串流
let source = Kafka
.builder()
.brokers(["localhost:9092"])
// 消费的 topic 为 topic
.topic("topic")
// groupId 为 word_count
.group("word_count")
// 反序列化的类型
.build<string>(undefined, typeof "");

// 将统计的结果存储在 Redis 里
let sink = Redis.new<{ t0: number, t1: string }>()
.host("localhost")
.keyExtractor((v) => v.t1)
.valueExtractor((v) => v.t0.toString());

// 创建一个 Dataflow
let stream = source.createFlow(ctx);

// 设计 Dataflow 的结构

await stream.flatMap(value => value.split(" ").map(v => {
return { t0: 1, t1: v };
}))
.keyBy(v => v.t1)
.reduce((v1, v2) => {
return { t1: v1.t1, t0: v1.t0 + v2.t0 };
})
// 将 Dataflow 计算的结果写入 redis sink 里
.sink(sink)
// 执行
.execute();
}

// 我们指定这个任务的 id 和所属的 namespace
wordCount(ExecutionContext.new("wordCount", "default")).then();

保存该段代码为文件 wordCount.ts

部署流任务

首先设置 Lightflus 服务地址的环境变量

代码语言:shell
复制
export LIGHTFLUS_ENDPOINT=localhost:8080

我们可以直接通过 nodejs 来编译上面的代码:

代码语言:shell
复制
yarn tsc -p .

然后运行编译后的文件

代码语言:shell
复制
node wordCount.js

这样我们就成功将 word count 任务部署到 Lightflus 本地集群上了。

让 Dataflow 跑起来!

接下来一个重要的步骤就是让刚刚部署的 word count 任务跑起来。

我们发送一条数据到 Kafka 对应的 topic 里:

代码语言:text
复制
hello hello hello world world world

如果一切安好,那么我们将在 Redis 里找到数据:

代码语言:shell
复制
redis> GET hello
"3"

redis> GET world
"3"

如果此时再发送这个数据到 Kafka 里,状态就会更新为:

代码语言:shell
复制
redis> GET hello
"6"

redis> GET world
"6"

Notes

  • 由于本地 standalone 模式默认使用内存存储的状态管理器,因此在 Lightflus 集群关闭后,所有状态都会被抹除;

与 Flink 的对比

我无意于在 demo 阶段就让 Lightflus 和 Flink 去 battle。坦白地说,目前的 Lightflus 不论在技术成熟度和功能完成度上,与 Flink 的差距是巨大的。但产品总要对比,我也不能免俗。我就先从功能完成度上让 Lightflus Demo 和 Flink PK 下,结果见下表:

功能

Lightflus

Flink

Notes

flatMap 算子

支持

支持

map 算子

支持

支持

filter 算子

支持

支持

reduce 算子

支持

支持

window 算子

支持

支持

v1.0.0-alpha 版本还不支持 window 算子,我们将在 v1.0.1-alpha 版本发布;Lightflus 目前仅计划支持三种基于 EventTime 的 window 算子:FixedSlidingSession

keyBy 算子

支持

支持

join 算子

暂不支持

支持

计划将在 release 1.0 版本支持

存储

暂不支持

支持,但不成熟

Lightflus 一个大的方向就是存算一体化,我们的一个大的 roadmap 就是为 Lightflus 开发一个存储引擎来提供给用户更多的价值

Data Source 的支持

支持 kafka,目前不支持自己编写 Source Connector

官方支持 kafka,支持自己编写 Source Connector

Lightflus 将在 release 1.0 支持 MQTT 的 source

Data Sink 的支持

支持 kafkaRedisMySQL,目前不支持自己编写 Sink Connector

官方支持 kafka ElasticSearch 等,支持自己编写 Sink Connector

Lightflus 将在 release 1.0 版本支持 ElasticSearch 的 sink

机器学习

暂不支持

支持

暂时没有计划,做不做还需要讨论下

StreamSQL

暂不支持

支持

在支持存储这个 Roadmap 里的,但具体何时发布还没有计划

Docker 支持

Docker 支持

支持

k8s 支持

暂不支持

支持

Lightflus 将在 release 1.0 版本开始支持 k8s 部署

可以看到,单从功能完成度上看,现阶段的 Lightflus 就比 Flink 差了不少,而在技术成熟度上,Lightflus 更是与 Flink 差了十条华盛顿大街。我们来看一下技术成熟度上 Lightflus 和 Flink 到底差了多少:

技术特性

Lightflus

Flink

Notes

Checkpoint 容错

暂不支持

支持

将在 release 1.0 版本支持

keyBy 分区并行计算

暂不支持

支持

我们计划在 release 1.x 版本里支持

Dynamic Scale

暂不支持

有第三方支持

暂时没有计划

Backpressure Metric

暂不支持

支持

计划将在 release 1.x 版本支持

算子并行度设置

暂不支持

支持

目前没有计划

定制化算子的能力

暂不支持

支持

目前没有计划

流批一体

将在demo版本支持,但很不成熟

支持,相对比较成熟

Lightflus 另一个大的 roadmap 就是支持流批一体

高吞吐、低延迟

没有数据支撑

有数据支撑

计划在发布 release 版本后公布测试数据

可以说是惨不忍睹了……对于一个刚起步不到一年的项目,这是意料之中的结果,我不想宣传说 Lightflus 比 Flink 强多少(比如很多新出来的云原生开源数据系统都会用各种夸张的宣传来显示自己有多牛逼)。再说 Flink 好歹有数据支撑自己的宣传,Lightflus 现在都没有数据,想吹我也很难吹啊(

这时有人肯定会反问:既然 Flink 不论在功能还是技术上都比 Lightflus 要强,为什么我还要用 Lightflus 呢?

我们可以从非技术的角度看,Lightflus 比 Flink 最大的优势就是简单、成本低,云中立,无需配置专业的团队。一个水平即使没那么高的程序员,只要熟悉 Docker 和 k8s 基本的操作,也能很快部署 Lightflus 到云环境中然后直接就能在本地编写流计算任务。随着 Lightflus 在功能上逐步完善、技术上逐步成熟,这些优势给用户带来的价值会呈指数级别上升。这种价值不单单是技术上的,也是在团队管理和业务目标上的,相比技术上的价值,后两者我认为其实在现实中更为重要;试想一下,你作为一家创业公司的 CTO,当你需要引入实时计算技术解决业务问题时,你是考虑增加一个团队来搭建 Flink 基础架构(很可能最后发现这些基建都没啥太大价值),大幅提高成本和管理复杂度,还是直接让现在的团队接手,通过 Lightflus 快速上线一个版本呢?

In The Future

Lightflus 目前有两个大的 Roadmap。第一个是支持流批一体,应该说这是目前分布式计算框架的基本趋势,Lightflus 在产品规划上也必然是往这个方向努力的;第二个则是支持原生的存储,目前 Flink 也正在做这件事,但总的来讲流存储/流数仓整体还处于初期阶段。当然,因为 Flink 的 底层功能相对已经比较成熟了,他们可以很快在这些功能上搭起一个流数仓的产品。Lightflus 从规划上看,必然也是要完善基础功能后才会去发力存储的,这个是一个比较长远的规划。

All in All

总得来讲呢,Lightflus 因为起步较晚,目前还处于完善基础功能的阶段。但用户的使用是任何软件前进路上最大的动力,也因此我们会重视每一个前来试用 Lightflus 的用户的建议。我们也会不断去迭代 Lightflus,让它能不断逼近一个优秀的计算框架。