You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dolphinscheduler.apache.org by gi...@apache.org on 2021/12/10 10:19:51 UTC

[dolphinscheduler-website] branch asf-site updated: Automated deployment: b0832040d42b7fe3e0fca699f8ce47f147926978

This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/dolphinscheduler-website.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new 1b1810c  Automated deployment: b0832040d42b7fe3e0fca699f8ce47f147926978
1b1810c is described below

commit 1b1810cf7730661135c32be0b58a8eefcbfb5b76
Author: github-actions[bot] <gi...@users.noreply.github.com>
AuthorDate: Fri Dec 10 10:19:46 2021 +0000

    Automated deployment: b0832040d42b7fe3e0fca699f8ce47f147926978
---
 build/{blog.7e2316c.js => blog.57566d5.js} |   2 +-
 en-us/blog/index.html                      |   2 +-
 zh-cn/blog/YouZan-case-study.html          | 177 +++++++++++++++++++++++++++++
 zh-cn/blog/YouZan-case-study.json          |   6 +
 zh-cn/blog/index.html                      |   4 +-
 5 files changed, 187 insertions(+), 4 deletions(-)

diff --git a/build/blog.7e2316c.js b/build/blog.57566d5.js
similarity index 71%
rename from build/blog.7e2316c.js
rename to build/blog.57566d5.js
index 28be7ed..93e6b0e 100644
--- a/build/blog.7e2316c.js
+++ b/build/blog.57566d5.js
@@ -1 +1 @@
-webpackJsonp([2],{1:function(e,t){e.exports=React},2:function(e,t){e.exports=ReactDOM},400:function(e,t,n){e.exports=n(401)},401:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function l(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("functi [...]
\ No newline at end of file
+webpackJsonp([2],{1:function(e,t){e.exports=React},2:function(e,t){e.exports=ReactDOM},400:function(e,t,n){e.exports=n(401)},401:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function l(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("functi [...]
\ No newline at end of file
diff --git a/en-us/blog/index.html b/en-us/blog/index.html
index 60b531b..5e3c345 100644
--- a/en-us/blog/index.html
+++ b/en-us/blog/index.html
@@ -16,7 +16,7 @@
   <script src="//cdn.jsdelivr.net/npm/react-dom@15.6.2/dist/react-dom.min.js"></script>
   <script>window.rootPath = '';</script>
   <script src="/build/vendor.0581428.js"></script>
-  <script src="/build/blog.7e2316c.js"></script>
+  <script src="/build/blog.57566d5.js"></script>
   <script>
     var _hmt = _hmt || [];
     (function() {
diff --git a/zh-cn/blog/YouZan-case-study.html b/zh-cn/blog/YouZan-case-study.html
new file mode 100644
index 0000000..51454d3
--- /dev/null
+++ b/zh-cn/blog/YouZan-case-study.html
@@ -0,0 +1,177 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+  <meta name="keywords" content="YouZan-case-study">
+  <meta name="description" content="YouZan-case-study">
+  <title>YouZan-case-study</title>
+  <link rel="shortcut icon" href="/img/favicon.ico">
+  <link rel="stylesheet" href="/build/vendor.e328afe.css">
+  <link rel="stylesheet" href="/build/blog.md.fd8b187.css">
+</head>
+<body>
+  <div id="root"><div class="blog-detail-page" data-reactroot=""><header class="header-container header-container-dark"><div class="header-body"><a href="/zh-cn/index.html"><img class="logo" src="/img/hlogo_white.svg"/></a><div class="search search-dark"><span class="icon-search"></span></div><span class="language-switch language-switch-dark">En</span><div class="header-menu"><img class="header-menu-toggle" src="/img/system/menu_white.png"/><div><ul class="ant-menu whiteClass ant-menu-li [...]
+<p>编者按:在不久前的 Apache  DolphinScheduler Meetup 2021 上,有赞大数据开发平台负责人 宋哲琦 带来了平台调度系统从 Airflow 迁移到 Apache  DolphinScheduler 的方案设计思考和生产环境实践。</p>
+<p>这位来自浙江杭州的 90 后年轻人自 2019 年 9 月加入有赞,在这里从事数据开发平台、调度系统和数据同步组件的研发工作。刚入职时,有赞使用的还是同为 Apache 开源项目的 Airflow,但经过调研和生产环境测试,有赞决定切换到 DolphinScheduler。</p>
+<p>有赞大数据开发平台如何利用调度系统?为什么决定重新选型为 Apache DolphinScheduler ?让我们跟着他的分享来一探究竟。</p>
+<h2>有赞大数据开发平台(DP平台)</h2>
+<p>作为一家零售科技 SaaS 服务商,有赞的使命是助力线上商家开店,通过社交营销和拓展全渠道零售业务,搭建数据产品和数字化解决方案,为驱动商家数字增长提供更好的 SaaS 能力。</p>
+<p>目前,有赞在数据中台的支撑下已经建立了比较完整的数字产品矩阵:</p>
+<p><a href="https://imgpp.com/image/ZbGED"><img src="https://imgpp.com/images/2021/12/10/68d4bd3ba305f91cf.md.jpg" alt="68d4bd3ba305f91cf.md.jpg"></a></p>
+<p>为了支持日益增长的数据处理业务需求,有赞建立了大数据开发平台(以下简称 DP 平台)。这是一个大数据离线开发平台,提供用户大数据任务开发所需的环境、工具和数据。</p>
+<p><a href="https://imgpp.com/image/ZbZbN"><img src="https://imgpp.com/images/2021/12/10/8102f512534d0984a.md.jpg" alt="8102f512534d0984a.md.jpg"></a></p>
+<p>有赞大数据开发平台架构</p>
+<p>有赞大数据开发平台主要由基础组件层、任务组件层、调度层、服务层和监控层五个模块组成。其中,服务层主要负责作业的生命周期管理,基础组件层和任务组件层主要包括大数据开发平台依赖的中间件及大数据组件等基础环境。DP 平台的服务部署主要采用主从模式,Master 节点支持 HA。调度层是在 Airflow 的基础上进行二次开发,监控层对调度集群进行全方位监控和预警。</p>
+<h3>1 调度层架构设计</h3>
+<p><a href="https://imgpp.com/image/ZbiQL"><img src="https://imgpp.com/images/2021/12/10/9f5a07ad20fdbb0d9.md.jpg" alt="9f5a07ad20fdbb0d9.md.jpg"></a></p>
+<p>有赞大数据开发平台调度层架构设计</p>
+<p>2017 年,我们团队在17年的时候调研了当时的主流的调度系统,最终决定采用 Airflow(1.7)作为 DP 的任务调度模块。根据业务场景实际需求,架构设计方面,我们采用了Airflow + Celery + Redis + MySQL的部署方案,Redis 作为调度队列,通过 Celery 实现任意多台 worker 分布式部署。</p>
+<p>在调度节点 HA 设计上,众所周知,Airflow 在 schedule 节点上存在单点问题,为了实现调度的高可用,DP 平台采用了 Airflow Scheduler Failover Controller 这个开源组件,新增了一个 Standby 节点,会周期性地监听 Active 节点的健康情况,一旦发现 Active 节点不可用,则 Standby 切换为 Active,从而保证 schedule 的高可用。</p>
+<h3>2 Worker节点负载均衡策略</h3>
+<p>另外,由于不同任务占据资源不同,为了更有效地利用资源,DP 平台按照 CPU 密集/内存密集区分任务类型,并安排在不同的 celery 队列配置不同的 slot,保证每台机器 CPU/ 内存使用率保持在合理范围内。</p>
+<h2>调度系统升级选型</h2>
+<p>自 2017 年有赞大数据平台 1.0 版本正式上线以来,我们已经在 2018 年 100% 完成了数据仓库迁移的计划,2019 年日调度任务量达 30000+,到 2021 年,平台的日调度任务量已达到 60000+。伴随着任务量的剧增,DP 的调度系统也面临了许多挑战与问题。</p>
+<h3>1 Airflow 的痛点</h3>
+<ol>
+<li>深度二次开发,脱离社区版本,升级成本高;</li>
+<li>Python 技术栈,维护迭代成本高;</li>
+<li>性能问题:</li>
+</ol>
+<p><a href="https://imgpp.com/image/Zb8yu"><img src="https://imgpp.com/images/2021/12/10/134be4c7b422b89889.md.jpg" alt="134be4c7b422b89889.md.jpg"></a></p>
+<p>Airflow 的 schedule loop 如上图所示,本质上是对 DAG 的加载解析,将其生成 DAG round 实例执行任务调度。Airflow 2.0 之前的版本是单点 DAG 扫描解析到数据库,这就导致业务增长 Dag 数量较多时,scheduler loop 扫一次 Dag folder 会存在较大延迟(超过扫描频率),甚至扫描时间需要 60-70 秒,严重影响调度性能。</p>
+<ol start="4">
+<li>稳定性问题:</li>
+</ol>
+<p>Airflow Scheduler Failover Controller 本质还是一个主从模式,standby 节点通过监听 active进程是否存活来判断是否切换,如之前遇到 deadlock 阻塞进程的情况,则会被忽略,进而导致调度故障发生。在生产环境中发生过类似问题后,我们经过排查后发现了问题所在,虽然 Airflow 1.10 版本已经修复了这个问题,但在主从模式下,这个在生产环境下不可忽视的问题依然会存在。</p>
+<p>考虑到以上几个痛点问题,我们决定对 DP 平台的调度系统进行重新选型。</p>
+<p>在调研对比过程中,Apache DolphinScheduler 进入了我们的视野。同样作为 Apache 顶级开源调度组件项目,我们性能、部署、功能、稳定性和可用性、社区生态等角度对原调度系统和 DolphinScheduler 进行了综合对比。</p>
+<p>以下为对比分析结果:</p>
+<p><a href="https://imgpp.com/image/ZbaBs"><img src="https://imgpp.com/images/2021/12/10/14dfca6d4f7730a5f2.md.jpg" alt="14dfca6d4f7730a5f2.md.jpg"></a></p>
+<p>Airflow VS DolphinScheduler</p>
+<h3>1 DolphinScheduler 价值评估</h3>
+<p><a href="https://imgpp.com/image/ZbVrt"><img src="https://imgpp.com/images/2021/12/10/156249f11dc4ada0d4.md.jpg" alt="156249f11dc4ada0d4.md.jpg"></a></p>
+<p>如上图所示,经过对 DolphinScheduler 价值评估,我们发现其在相同的条件下,吞吐性能是原来的调度系统的 2 倍,而 2.0 版本后 DolphinScheduler 的性能还会有更大幅度的提升,这一点让我们非常兴奋。</p>
+<p>此外,在部署层面,DolphinScheduler 采用的 Java 技术栈有利于ops标准化部署流程,简化发布流程、解放运维人力,且支持Kubernetes、Docker 部署,扩展性更强。</p>
+<p>在功能新增上,因为我们在使用过程中比较注重任务依赖配置,而 DolphinScheduler 有更灵活的任务依赖配置,时间配置粒度细化到了时、天、周、月,使用体验更好。另外,DolphinScheduler 的调度管理界面易用性更好,同时支持 worker group 隔离。作为一个分布式调度,DolphinScheduler 整体调度能力随集群规模线性增长,而且随着新特性任务插件化的发布,可自定义任务类型这一点也非常吸引人。</p>
+<p>从稳定性与可用性上来说,DolphinScheduler 实现了高可靠与高可扩展性,去中心化的多 Master 多 Worker 设计架构,支持服务动态上下线,自我容错与调节能力更强。</p>
+<p>另外一个重要的点是,经过数月的接触,我们发现 DolphinScheduler 社区整体活跃度较高,经常会有技术交流,技术文档比较详细,版本迭代速度也较快。</p>
+<p>综上所述,我们决定转向 DolphinScheduler。</p>
+<h2>DolphinScheduler 接入方案设计</h2>
+<p>决定接入 DolphinScheduler 之后,我们梳理了平台对于新调度系统的改造需求。</p>
+<p>总结起来,最重要的是要满足以下几点:</p>
+<ol>
+<li>用户使用无感知,平台目前的用户数有 700-800,使用密度大,希望可以降低用户切换成本;</li>
+<li>调度系统可动态切换,生产环境要求稳定性大于一切,上线期间采用线上灰度的形式,希望基于工作流粒度,实现调度系统动态切换;</li>
+<li>测试与发布的工作流配置需隔离,目前任务测试和发布有两套配置文件通过 GitHub维护,线上调度任务配置需要保证数据整个确性和稳定性,需要两套环境进行隔离。</li>
+</ol>
+<p>针对以上三点,我们对架构进行了重新设计。</p>
+<h3>1 架构设计</h3>
+<ol>
+<li>保留现有前端界面与DP API;</li>
+<li>重构调度管理界面,原来是嵌入 Airflow 界面,后续将基于 DolphinScheduler 进行调度管理界面重构;</li>
+<li>任务生命周期管理/调度管理等操作通过 DolphinScheduler API 交互;
+利用 Project 机制冗余工作流配置,实现测试、发布的配置隔离。</li>
+</ol>
+<p><a href="https://imgpp.com/image/Zbsda"><img src="https://imgpp.com/images/2021/12/10/176ca073b653e27d44.md.jpg" alt="176ca073b653e27d44.md.jpg"></a></p>
+<p>改造方案设计</p>
+<p>架构设计完成之后,进入改造阶段。我们对 DolphinScheduler 的工作流定义、任务执行流程、工作流发布流程都进行了改造,并进行了一些关键功能补齐。</p>
+<ul>
+<li>工作流定义状态梳理</li>
+</ul>
+<p><a href="https://imgpp.com/image/Zbvzd"><img src="https://imgpp.com/images/2021/12/10/18e9d13314731833e1.md.jpg" alt="18e9d13314731833e1.md.jpg"></a></p>
+<p>我们首先梳理了 DolphinScheduler 工作流的定义状态。因为 DolphinScheduler 工作的定义和定时管理会区分为上下线状态, 但 DP平台上两者的状态是统一的,因此在任务测试和工作流发布流程中,需要对 DP到DolphinScheduler 的流程串联做相应的改造。</p>
+<ul>
+<li>任务执行流程改造</li>
+</ul>
+<p>首先是任务测试流程改造。在切换到 DolphinScheduler 之后,所有的交互都是基于DolphinScheduler API 来进行的,当在 DP 启动任务测试时,会在 DolphinScheduler 侧生成对应的工作流定义配置,上线之后运行任务,同时调用 DolphinScheduler 的日志查看结果,实时获取日志运行信息。</p>
+<p><a href="https://imgpp.com/image/Zb6Q0"><img src="https://imgpp.com/images/2021/12/10/1954c93c34d86390df.jpg" alt="1954c93c34d86390df.jpg"></a></p>
+<ul>
+<li>工作流发布流程改造</li>
+</ul>
+<p>其次,针对工作流上线流程,切换到 DolphinScheduler 之后,主要是对工作流定义配置和定时配置,以及上线状态进行了同步。</p>
+<p><a href="https://imgpp.com/image/Zbx2b"><img src="https://imgpp.com/images/2021/12/10/207714f8c5060a9162.md.jpg" alt="207714f8c5060a9162.md.jpg"></a></p>
+<p>通过这两个核心流程的改造。工作流的原数据维护和配置同步其实都是基于 DP master来管理,只有在上线和任务运行时才会到调度系统进行交互,基于这点,DP 平台实现了工作流维度下的系统动态切换,以便于后续的线上灰度测试。</p>
+<h3>2 功能补全</h3>
+<p>此外,DP 平台还进行了一些功能补齐。首先是任务类型的适配。</p>
+<ul>
+<li>任务类型适配</li>
+</ul>
+<p>目前,DolphinScheduler 平台已支持的任务类型主要包含数据同步类和数据计算类任务,如Hive SQL 任务、DataX 任务、Spark 任务等。因为任务的原数据信息是在 DP 侧维护的,因此 DP 平台的对接方案是在 DP 的 master 构建任务配置映射模块,将 DP 维护的 task 信息映射到 DP 侧的 task,然后通过 DolphinScheduler 的 API 调用来实现任务配置信息传递。</p>
+<p><a href="https://imgpp.com/image/Z8fNH"><img src="https://imgpp.com/images/2021/12/10/1.md.png" alt="1.md.png"></a></p>
+<p>因为 DolphinScheduler 已经支持部分任务类型 ,所以只需要基于 DP 平台目前的实际使用场景对 DolphinScheduler 相应任务模块进行定制化改造。而对于 DolphinScheduler 未支持的任务类型,如Kylin任务、算法训练任务、DataY任务等,DP 平台也计划后续通过 DolphinScheduler 2.0 的插件化能力来补齐。</p>
+<h3>3 改造进度</h3>
+<p>因为 DP 平台上 SQL 任务和同步任务占据了任务总量的 80% 左右,因此改造重点都集中在这几个任务类型上,目前已基本完成 Hive SQL 任务、DataX 任务以及脚本任务的适配改造以及迁移工作。</p>
+<p><a href="https://imgpp.com/image/Z8dgm"><img src="https://imgpp.com/images/2021/12/10/2.md.png" alt="2.md.png"></a></p>
+<h3>4 功能补齐</h3>
+<ul>
+<li>Catchup 机制实现调度自动回补</li>
+</ul>
+<p>DP 在实际生产环境中还需要一个核心能力,即基于 Catchup 的自动回补和全局补数能力。</p>
+<p>Catchup 机制在 DP 的使用场景,是在调度系统异常或资源不足,导致部分任务错过当前调度出发时间,当恢复调度后,会通过Catchup 自动补齐未被触发的调度执行计划。</p>
+<p>以下三张图是一个小时级的工作流调度执行的信息实例。</p>
+<p>在图 1 中,工作流在 6 点准时调起,每小时调一次,可以看到在 6 点任务准时调起并完成任务执行,当前状态也是正常调度状态。</p>
+<p><a href="https://imgpp.com/image/Z8IkI"><img src="https://imgpp.com/images/2021/12/10/11d6e4adb990afe71.md.png" alt="11d6e4adb990afe71.md.png"></a>
+图1</p>
+<p>图 2 显示在 6 点完成调度后,一直到 8 点期间,调度系统出现异常,导致 7 点和 8点该工作流未被调起。</p>
+<p><a href="https://imgpp.com/image/Z8X64"><img src="https://imgpp.com/images/2021/12/10/242413a5dcd1c029e.md.png" alt="242413a5dcd1c029e.md.png"></a>
+图2</p>
+<p>图 3 表示当 9 点恢复调度之后,因为 具有 Catchup 机制,调度系统会自动回补之前丢失的执行计划,实现调度的自动回补。</p>
+<p><a href="https://imgpp.com/image/Z8tq8"><img src="https://imgpp.com/images/2021/12/10/3.md.png" alt="3.md.png"></a>
+图3</p>
+<p>此机制在任务量较大时作用尤为显著,当 Schedule 节点异常或核心任务堆积导致工作流错过调度出发时间时,因为系统本身的容错机制可以支持自动回补调度任务,所以无需人工手动补数重跑。</p>
+<p>同时,这个机制还应用在了 DP 的全局补数能力中。</p>
+<ul>
+<li>跨 Dag 全局补数</li>
+</ul>
+<p><a href="https://imgpp.com/image/Z8BtU"><img src="https://imgpp.com/images/2021/12/10/4.md.png" alt="4.md.png"></a>
+DP 平台跨 Dag 全局补数流程</p>
+<p>全局补数在有赞的主要使用场景,是用在核心上游表产出中出现异常,导致下游商家展示数据异常时。这种情况下,一般都需要系统能够快速重跑整个数据链路下的所有任务实例。</p>
+<p>DP 平台目前是基于 Clear 的功能,通过原数据的血缘解析获取到指定节点和当前调度周期下的所有下游实例,再通过规则剪枝策略过滤部分无需重跑的实例。获取到这些实际列表之后,启动 clear down stream 的清除任务实例功能,再利用 Catchup 进行自动回补。</p>
+<p>这个流程实际上是通过 Clear 实现上游核心的全局重跑,自动补数的优势就在于可以解放人工操作。</p>
+<p>因为跨 Dag 全局补数能力在生产环境中是一个重要的能力,我们计划在 DolphinScheduler 中进行补齐。</p>
+<h2>现状&amp;规划&amp;展望</h2>
+<h3>1 DolphinScheduler 接入现状</h3>
+<p>DP 平台目前已经在测试环境中部署了部分 DolphinScheduler 服务,并迁移了部分工作流。</p>
+<p>对接到 DolphinScheduler API 系统后,DP 平台在用户层面统一使用 admin 用户,因为其用户体系是直接在 DP master 上进行维护,所有的工作流信息会区分测试环境和正式环境。</p>
+<p><a href="https://imgpp.com/image/Zb7rA"><img src="https://imgpp.com/images/2021/12/10/25.md.jpg" alt="25.md.jpg"></a>
+DolphinScheduler 工作流定义列表</p>
+<p><a href="https://imgpp.com/image/ZbQlk"><img src="https://imgpp.com/images/2021/12/10/26.md.jpg" alt="26.md.jpg"></a></p>
+<p><a href="https://imgpp.com/image/ZbodC"><img src="https://imgpp.com/images/2021/12/10/27.md.jpg" alt="27.md.jpg"></a>
+DolphinScheduler 2.0工作流任务节点展示</p>
+<p>DolphinScheduler 2.0 整体的 UI 交互看起来更加简洁,可视化程度更高,我们计划直接升级至 2.0 版本。</p>
+<h3>2 接入规划</h3>
+<p>目前 ,DP 平台还处于接入 DolphinScheduler 的灰度测试阶段,计划于今年 12 月进行工作流的全量迁移,同时会在测试环境进行分阶段全方位测试或调度性能测试和压力测试。确定没有任何问题后,我们会在来年 1 月进行生产环境灰度测试,并计划在 3 月完成全量迁移。</p>
+<p><a href="https://imgpp.com/image/Zb0z6"><img src="https://imgpp.com/images/2021/12/10/28.md.jpg" alt="28.md.jpg"></a></p>
+<h3>3 对 DolphinScheduler 的期待</h3>
+<p>未来,我们对 DolphinScheduler 最大的期待是希望 2.0 版本可以实现任务插件化。</p>
+<p><a href="https://imgpp.com/image/Z8Oae"><img src="https://imgpp.com/images/2021/12/10/5.md.png" alt="5.md.png"></a></p>
+<p>目前,DP 平台已经基于 DolphinScheduler 2.0实现了告警组件插件化,可在后端定义表单信息,并在前端自适应展示。</p>
+<p>“ 希望 DolphinScheduler 插件化的脚步可以更快一些,以更好地快速适配我们的定制化任务类型。
+——有赞大数据开发平台负责人 宋哲琦”</p>
+</section><footer class="footer-container"><div class="footer-body"><div><h3>联系我们</h3><h4>有问题需要反馈?请通过以下方式联系我们。</h4></div><div class="contact-container"><ul><li><img class="img-base" src="/img/emailgray.png"/><img class="img-change" src="/img/emailblue.png"/><a href="/zh-cn/community/development/subscribe.html"><p>邮件列表</p></a></li><li><img class="img-base" src="/img/twittergray.png"/><img class="img-change" src="/img/twitterblue.png"/><a href="https://twitter.com/dolphinschedule"><p>Twitt [...]
+  <script src="//cdn.jsdelivr.net/npm/react@15.6.2/dist/react-with-addons.min.js"></script>
+  <script src="//cdn.jsdelivr.net/npm/react-dom@15.6.2/dist/react-dom.min.js"></script>
+  <script>window.rootPath = '';</script>
+  <script src="/build/vendor.0581428.js"></script>
+  <script src="/build/blog.md.6f0aa52.js"></script>
+  <script>
+    var _hmt = _hmt || [];
+    (function() {
+      var hm = document.createElement("script");
+      hm.src = "https://hm.baidu.com/hm.js?4e7b4b400dd31fa015018a435c64d06f";
+      var s = document.getElementsByTagName("script")[0];
+      s.parentNode.insertBefore(hm, s);
+    })();
+  </script>
+  <!-- Global site tag (gtag.js) - Google Analytics -->
+  <script async src="https://www.googletagmanager.com/gtag/js?id=G-899J8PYKJZ"></script>
+  <script>
+    window.dataLayer = window.dataLayer || [];
+    function gtag(){dataLayer.push(arguments);}
+    gtag('js', new Date());
+
+    gtag('config', 'G-899J8PYKJZ');
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/zh-cn/blog/YouZan-case-study.json b/zh-cn/blog/YouZan-case-study.json
new file mode 100644
index 0000000..0847129
--- /dev/null
+++ b/zh-cn/blog/YouZan-case-study.json
@@ -0,0 +1,6 @@
+{
+  "filename": "YouZan-case-study.md",
+  "__html": "<h2>从 Airflow 到 Apache DolphinScheduler,有赞大数据开发平台的调度系统演进</h2>\n<p>编者按:在不久前的 Apache  DolphinScheduler Meetup 2021 上,有赞大数据开发平台负责人 宋哲琦 带来了平台调度系统从 Airflow 迁移到 Apache  DolphinScheduler 的方案设计思考和生产环境实践。</p>\n<p>这位来自浙江杭州的 90 后年轻人自 2019 年 9 月加入有赞,在这里从事数据开发平台、调度系统和数据同步组件的研发工作。刚入职时,有赞使用的还是同为 Apache 开源项目的 Airflow,但经过调研和生产环境测试,有赞决定切换到 DolphinScheduler。</p>\n<p>有赞大数据开发平台如何利用调度系统?为什么决定重新选型为 Apache DolphinScheduler ?让我们跟着他的分享来一探究竟。</p>\n<h2>有赞大数据开发平台(DP平台)</h2>\n<p>作为一家零售科技 SaaS 服务商,有赞的使命是助 [...]
+  "link": "/dist/zh-cn/blog/YouZan-case-study.html",
+  "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/index.html b/zh-cn/blog/index.html
index 17cb975..9a707b5 100644
--- a/zh-cn/blog/index.html
+++ b/zh-cn/blog/index.html
@@ -11,12 +11,12 @@
   <link rel="stylesheet" href="/build/blog.acc2955.css">
 </head>
 <body>
-  <div id="root"><div class="blog-list-page" data-reactroot=""><header class="header-container header-container-dark"><div class="header-body"><a href="/zh-cn/index.html"><img class="logo" src="/img/hlogo_white.svg"/></a><div class="search search-dark"><span class="icon-search"></span></div><span class="language-switch language-switch-dark">En</span><div class="header-menu"><img class="header-menu-toggle" src="/img/system/menu_white.png"/><div><ul class="ant-menu whiteClass ant-menu-ligh [...]
+  <div id="root"><div class="blog-list-page" data-reactroot=""><header class="header-container header-container-dark"><div class="header-body"><a href="/zh-cn/index.html"><img class="logo" src="/img/hlogo_white.svg"/></a><div class="search search-dark"><span class="icon-search"></span></div><span class="language-switch language-switch-dark">En</span><div class="header-menu"><img class="header-menu-toggle" src="/img/system/menu_white.png"/><div><ul class="ant-menu whiteClass ant-menu-ligh [...]
   <script src="//cdn.jsdelivr.net/npm/react@15.6.2/dist/react-with-addons.min.js"></script>
   <script src="//cdn.jsdelivr.net/npm/react-dom@15.6.2/dist/react-dom.min.js"></script>
   <script>window.rootPath = '';</script>
   <script src="/build/vendor.0581428.js"></script>
-  <script src="/build/blog.7e2316c.js"></script>
+  <script src="/build/blog.57566d5.js"></script>
   <script>
     var _hmt = _hmt || [];
     (function() {