限时领|《AI 百问百答》专栏课+实体书(包邮)! 了解详情
写点什么

用 Airflow 实现 EMR 集群的动态启停并通过 Livy 远程提交任务

  • 2020-02-27
  • 本文字数:9214 字

    阅读完需:约 30 分钟

用 Airflow 实现 EMR 集群的动态启停并通过 Livy 远程提交任务

互联网行业每天都有大量的日志生成,需要在固定时间段对数据进行 ETL 工作。用户常规的做法是启动一组长期运行的 EMR 集群,配置远程提交任务的服务器,结合自身的任务调度系统定期提交任务,但集群执行完成任务之后会闲置,造成不必要的开销。另一种方法是在需要执行任务的时候启动集群,任务完成之后关闭集群,但因为每次启动集群后,主节点与核心节点的 IP 都会发生分变化,导致每次都需要重新配置提交任务的服务器,造成额外的工作负担。本文介绍了一种通过 Apache Airflow 任务调度系统动态启停 Amazon EMR 集群的方法,并通过 EMR 内置的 Livy 远程提交作业,这样可以节省大量的成本并且无需进行过多的额外配置。

1. 相关技术介绍

在开始之前,请先对以下技术进行简单了解。

1.1 Apache Airflow

Apache Airflow 是一款开源的任务调度系统,用户通过创建 DAG(有向无环图)来定义任务的流程,图中的每个节点就是需要执行的任务,不同 DAG 之间的任务可以相互依赖。通过 Airflow 我们可以定时执行脚本,并且它提供了 web 界面供用户可视化地查看任务执行情况。

1.2 Apache Livy

Apache Livy 是 Hadoop 生态圈中提供远程提交任务功能的应用程序。它以 Rest API 的方式提供了 Session 与 Batches 两种集群执行任务的方法。Session 指的是将集群需要执行的代码写在对 Livy 请求中,目前支持 spark、pyspark、sparkr 与 sql 等四种方式与集群交互。Bathches 指的是将代码存放在指定位置,在请求中提供路径,让集群执行代码。例如将 jar 包存放在 S3 上,在请求 Livy 的时候提供 jar 包的路径,从而让集群直接执行 jar 包,好处是无需在集群上配置执行代码所需的依赖。

2. 演练

通过本文示例,我将向您展示如何实现以下方案:


基于开源调度工具 Airflow 编排提交 Spark Jobs 到 EMR 做批处理,Job 开始之前启动 EMR 集群,对集群节点采用 Spot 实例,所有 Job 结束后关闭 EMR 集群。

2.1 流程架构图与过程简介


(1)在一台 EC2 上配置 Airflow;


(2)定义 Airflow 工作流,其中包含创建集群,Spark 任务步骤与终止集群等任务;


(3)向 Livy 提交任务;


(4)EMR 从 S3 中读取数据,对数据进行处理完成之后重新写入 S3;


(5)工作完成,终止集群。

2.2 前提条件

(1)本文示例所使用的区域 us-east-1;


(2)在该区域创建一台 EC2,并确保与 EC2 绑定的 IAM Role 有 EMR 集群的 Full Access;


(3)拥有创建 EMR 集群所需的默认角色:EMR_DefaultRole 与 EMR_EC2_DefaultRole;


(4)创建 S3 桶,下载 jar 包 spark-examples_2.11-2.4.4 和数据集 emrdata.txt,并上传至 s3。

2.3 实现过程

2.3.1 在 EC2 上配置 Airflow

(1)登陆 EC2,安装 Airflow 已经相关依赖


Python


sudo yum update -ysudo yum install -y python-pip gcc mysql-devel python-devel mysqlsudo pip install mysql-pythonsudo yum install -y python3sudo pip3 install boto3sudo pip3 install requests
# 安装Airflowsudo pip install apache-airflowsudo pip install 'apache-airflow[celery]'airflow initdb
复制代码


(2)创建 RDS for Mysql 数据库供 Airflow 使用,对数据库性能要求不高,因此使用默认配置即可



(3)更改 airflow.cfg 配置文件,并测试是否能打开 Airflow 的 web 页面


Python


cd airflowvim airflow.cfg
# 找到sql_alchemy_conn等参数所在位置,替换为创建的数据库信息sql_alchemy_conn = mysql://admin:12345678@database-for-airflow.cdtwa5j4xten.us-east-1.rds.amazonaws.com/airflowdb
# Exit vim, Update Airflow Databaseairflow initdb
# 配置celery相关参数vim airflow.cfg
# 找到executor位置,将执行器设置为celery,可保证不相互依赖的任务可以并行执行executor = CeleryExecutor
# 找到broker_url与result_backend参数的位置broker_url = sqla+mysql://admin:12345678@database-for-airflow.cdtwa5j4xten.us-east-1.rds.amazonaws.com:3306/airflowdbresult_backend = db+mysql://admin:12345678@database-for-airflow.cdtwa5j4xten.us-east-1.rds.amazonaws.com:3306/airflowdb
# 开启airflow的webserver,在网页上输入EC2的DNS,查看是否能打开网页(注意打开安全组,并且如果本地连上的是公司的vpn,可能会出现无法打开网页的情况)airflow webserver -p 8080 &
# 启动workerairflow worker &
# 启动flower,可对worker中的任务进行可视化(要看到网页注意打开5555端口)airflow flower &
复制代码


 
复制代码

2.3.2 定义工作流

现定义如下两个 Airflow 的 DAG:


dag_transform_calpi



(1)create_emr_cluster:创建 EMR 集群;


Python


# -*- coding: UTF-8 -*-
import boto3import time
emr_client = boto3.client('emr', region_name='us-east-1')
# 定义集群名称,集群名称不要与当前运行的集群重名name = 'emr-cluster'
# 定义instance,可自定义实例的数量与类型intances = { 'InstanceGroups': [ { 'Market': 'SPOT', 'InstanceRole': 'MASTER', 'InstanceType': 'm4.xlarge', 'InstanceCount': 1, }, { 'Market': 'SPOT', 'InstanceRole': 'CORE', 'InstanceType': 'm4.xlarge', 'InstanceCount': 2, } ], 'KeepJobFlowAliveWhenNoSteps': True}
# 定义集群中的应用applications = [ { 'Name': 'Hadoop' }, { 'Name': 'Pig' }, { 'Name': 'Livy' }, { 'Name': 'Hive' }, { 'Name': 'Hue' }, { 'Name': 'Spark' }]
if __name__ == '__main__':
# 创建emr集群 emr_client.run_job_flow( Name=name, ReleaseLabel='emr-5.12.0', Instances=intances, Applications=applications, JobFlowRole='EMR_EC2_DefaultRole', ServiceRole='EMR_DefaultRole')
# 持续发送请求,直到创建的集群状态处于Waiting为止 flag = True while flag: time.sleep(20) r = emr_client.list_clusters(ClusterStates=['WAITING']) for i in r['Clusters']: if i['Name'] == name: flag = False
复制代码


(2)create_livy_session:创建 Livy 会话;


Python


# -*- coding: UTF-8 -*-import requestsimport jsonimport pprintimport boto3
# 获取集群的DNS,其中name为你的集群名称name = 'emr-cluster'emr_client = boto3.client('emr', region_name='us-east-1')r = emr_client.list_clusters(ClusterStates=['WAITING'])for i in r['Clusters']: if i['Name'] == name: cluster_id = i['Id']r = emr_client.describe_cluster(ClusterId=cluster_id)emr_dns = r['Cluster']['MasterPublicDnsName']
# livy_host为配置在emr集群上livy的url,无需修改代码livy_host = 'http://' + emr_dns + ':8998'data = {'kind': 'pyspark'}headers = {'Content-Type': 'application/json'}r = requests.post(livy_host + '/sessions', data=json.dumps(data), headers=headers)pprint.pprint(r.json())
复制代码


(3)sleep:等待会话创建完成;


(4)calpi:以 batches 的方式执行 spark 任务计算 pi 值;


Python


# -*- coding: UTF-8 -*-import requestsimport jsonimport textwrapimport pprintimport boto3
# 获取执行jar包任务的livy batch的url,其中name为你的集群名称name = 'emr-cluster'emr_client = boto3.client('emr', region_name='us-east-1')r = emr_client.list_clusters(ClusterStates=['WAITING'])for i in r['Clusters']: if i['Name'] == name: cluster_id = i['Id']r = emr_client.describe_cluster(ClusterId=cluster_id)emr_dns = r['Cluster']['MasterPublicDnsName']batch_url = 'http://' + emr_dns + ':8998/batches'headers = {'Content-Type': 'application/json'}
# 提交任务data = {"file": "s3://xiaoyj/emr/spark-examples_2.11-2.4.4.jar", "className": "org.apache.spark.examples.SparkPi"}r = requests.post(batch_url, data=json.dumps(data), headers=headers)pprint.pprint(r.json())
复制代码


(5)query_completed:外部任务,依赖于第二个 DAG(dag_query),即等待查询完成之后,执行下一个任务;


(6)终止集群。


Python


# -*- coding: UTF-8 -*-import boto3import time
# 终止集群,其中name为你的集群名称name = 'emr-cluster'emr_client = boto3.client('emr', region_name='us-east-1')flag = Truewhile flag: time.sleep(120) r = emr_client.list_clusters(ClusterStates=['WAITING']) for i in r['Clusters']: if i['Name'] == name: emr_client.terminate_job_flows(JobFlowIds=[i['Id']]) flag = False
复制代码


dag_query



(1)sleep_completed:外部任务,依赖于第一个 DAG(dag_transform_calpi),即等待 Livy 会话执行下一个任务;


(2)transform:对之前上传到 S3 上的文本文件进行聚合、转换;


Python


# -*- coding: UTF-8 -*-
import requestsimport jsonimport textwrapimport pprintimport boto3
# 获取提交任务的livy_url,其中name为你的集群名称name = 'emr-cluster'emr_client = boto3.client('emr', region_name='us-east-1')r = emr_client.list_clusters(ClusterStates=['WAITING'])for i in r['Clusters']: if i['Name'] == name: cluster_id = i['Id']r = emr_client.describe_cluster(ClusterId=cluster_id)emr_dns = r['Cluster']['MasterPublicDnsName']livy_url = 'http://' + emr_dns + ':8998/sessions/0/statements'headers = {'Content-Type': 'application/json'}
# 提交任务,data中的code为在emr中执行的代码,对s3中的文件进行转化操作,完成后将结果存放回s3作为中间结果data = { 'code': textwrap.dedent(""" import json sc._jsc.hadoopConfiguration().set('fs.s3a.endpoint', 's3-us-east-2.amazonaws.com') text_file = sc.textFile("s3a://xiaoyj/emr/emrdata.txt") text_file = text_file.map(lambda x: x.split('::')) text_file = text_file.map(lambda x: (int(x[0]), x[1:])) text_file = text_file.groupByKey().map(lambda x: (x[0], list(x[1]))) text_file = text_file.sortByKey() text_file = text_file.map(lambda x: {x[0]: x[1]}) text_file = text_file.map(lambda x: json.dumps(x)) text_file.coalesce(1).saveAsTextFile("s3a://xiaoyj/emr/middle_result") print("Transform Complete!") """)}r = requests.post(livy_url, data=json.dumps(data), headers=headers)pprint.pprint(r.json())
复制代码


(3)check_s3:检查 S3 中是否有上一步生成的中间结果;


Python


# -*- coding: UTF-8 -*-
import boto3import time
# 轮询s3,确认transform任务是否执行完成(即s3中是否有middle_result文件生成),name为你的s3桶名称name = 'xiaoyj's3_client = boto3.client('s3', region_name='us-east-1')flag = Truewhile flag: time.sleep(60) r = s3_client.list_objects(Bucket=name) for i in r['Contents']: if i['Key'] == 'emr/middle_result/part-00000': flag = False
复制代码


(4)query:对上一步生成的中间结果进行查询。


Python


# -*- coding: UTF-8 -*-
import requestsimport jsonimport textwrapimport pprintimport boto3
# 获取提交任务的livy_url,其中name为你的集群名称name = 'emr-cluster'emr_client = boto3.client('emr', region_name='us-east-1')r = emr_client.list_clusters(ClusterStates=['WAITING'])for i in r['Clusters']: if i['Name'] == name: cluster_id = i['Id']r = emr_client.describe_cluster(ClusterId=cluster_id)emr_dns = r['Cluster']['MasterPublicDnsName']livy_url = 'http://' + emr_dns + ':8998/sessions/0/statements'headers = {'Content-Type': 'application/json'}
# 提交任务,data中的code为在emr中执行的代码,对s3中的文件进行转化操作,完成后将结果存放回s3作为中间结果data = { 'code': textwrap.dedent(""" import json from pyspark.sql import HiveContext, Row hiveCtx = HiveContext(sc) input = hiveCtx.read.json("s3a://xiaoyj/emr/middle_result/part-00000") input.registe rTempTable("tbn") result = hiveCtx.sql("SELECT size(`9`) from tbn") result = result.rdd.map(lambda row: row[0]) result.coalesce(1).saveAsTextFile("s3a://xiaoyj/emr/result") print("Search Complete!") """)}r = requests.post(livy_url, data=json.dumps(data), headers=headers)pprint.pprint(r.json())
复制代码

2.3.3 创建 Airflow 工作流

(1)在 airflow 文件夹中创建 dags 文件夹,并进入到文件夹中;


(2)定义工作流(注意开头的 # — coding: UTF-8 –不要省略,并且 bash_command 需替换为自己任务所在的路径);


Python


vim dag_transform_calpi.py
# -*- coding: UTF-8 -*-
from airflow import DAGfrom airflow.operators.bash_operator import BashOperatorfrom datetime import datetime, timedeltafrom airflow.sensors.external_task_sensor import ExternalTaskSensor
default_args = { 'owner': 'Airflow', 'depends_on_past': False, 'start_date': datetime.now().replace(microsecond=0), 'email': ['756044579@qq.com'], 'email_on_failure': False, 'email_on_retry': False, 'retries': 0, 'retry_delay': timedelta(minutes=5), # 'queue': 'bash_queue', # 'pool': 'backfill', # 'priority_weight': 10, # 'end_date': datetime(2016, 1, 1),}
dag = DAG('dag_transform_calpi', default_args=default_args, schedule_interval=timedelta(days=1))
# 创建emr集群t0 = BashOperator( task_id='create_emr_cluster', bash_command='python3 /Users/xiaoyj/Desktop/emr_poc/create_emr_cluster.py', dag=dag)
# 创建livy的会话t1 = BashOperator( task_id='create_livy_session', bash_command='python3 /Users/xiaoyj/Desktop/emr_poc/create_session.py', dag=dag)
# 等待会话创建完成t2 = BashOperator( task_id='sleep', bash_command='sleep 20', dag=dag)
# 计算pi值t3 = BashOperator( task_id='calpi', bash_command='python3 /Users/xiaoyj/Desktop/emr_poc/calpi.py', dag=dag)
# 终止emr集群t4 = BashOperator( task_id='terminate_cluster', bash_command='python3 /Users/xiaoyj/Desktop/emr_poc/terminate_cluster.py', dag=dag)
# dag_query中的spark sql任务external_task = ExternalTaskSensor( external_task_id='query', task_id='query_completed', external_dag_id='dag_query', dag=dag)
# 定义airflow的有向无环图t0 >> t1t1 >> t2t2 >> t3external_task >> t4
复制代码


Python


vim dag_query.py # -*- coding: UTF-8 -*-from airflow import DAGfrom airflow.operators.bash_operator import BashOperatorfrom datetime import datetime, timedeltafrom airflow.sensors.external_task_sensor import ExternalTaskSensor
default_args = { 'owner': 'Airflow', 'depends_on_past': False, 'start_date': datetime.now().replace(microsecond=0), 'email': ['756044579@qq.com'], 'email_on_failure': False, 'email_on_retry': False, 'retries': 0, 'retry_delay': timedelta(minutes=5), # 'queue': 'bash_queue', # 'pool': 'backfill', # 'priority_weight': 10, # 'end_date': datetime(2016, 1, 1),}
dag = DAG('dag_query', default_args=default_args, schedule_interval=timedelta(days=1))
# 对s3上的文本文件进行转化操作t0 = BashOperator( task_id='transform', bash_command='python3 /Users/xiaoyj/Desktop/emr_poc/transform.py', dag=dag)
# 轮询s3,查看中间结果是否生成t1 = BashOperator( task_id='check_s3', bash_command='python3 /Users/xiaoyj/Desktop/emr_poc/check_s3.py', dag=dag)
# spark sql任务t2 = BashOperator( task_id='query', bash_command='python3 /Users/xiaoyj/Desktop/emr_poc/query.py', dag=dag)
# dag_transform_calpi中的sleep任务external_task = ExternalTaskSensor( external_task_id='sleep', task_id='sleep_completed', external_dag_id='dag_transform_calpi', dag=dag)external_task >> t0t0 >> t1t1 >> t2
复制代码



(3)重制Airflow数据库;
Python

复制代码


airflow resetdb



(4)启动Airflow,-s为当前日期,-e是结束日期,均设置为当日的日期(若工作流执行失败并想重头开始执行工作,需要先执行airflow resetdb)
Python

复制代码


airflow backfill dag_transform_calpi -s 2019-12-02 -e 2019-12-02 & airflow backfill dag_query -s 2019-12-02 -e 2019-12-02



## 3. 展示
(1)打开AWS EMR控制台,可以观察到集群正在创建;
[](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy5.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy5.jpg)
(2)待集群创建完成后,获取主节点DNS,并打开网页;
[](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy6.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy6.jpg)
(3)观察到Livy上并行提交了两个任务分别是spark对文本的tansform操作和jar包计算pi值的任务;
[](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy7.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy7.jpg)
(4)pi值计算完成;
[](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy8.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy8.jpg)
(5)待Transform任务完成,Spark SQL任务开始执行;
[](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy9.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy9.jpg)
(6)执行完成后可以在s3上可以看到Transform任务生成的middle result和Spark SQL任务生成的最终结果;
[](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy10.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy10.jpg)
(7)下载middle_result中的文件,可以看到聚合结果;
[](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy11.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy11.jpg)
(8)下载result中的文件,可以查看到最终结果(统计编号为9的列表中包含53组数据,-1表示其他json文件没有编号为9的;
[](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy12.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy12.jpg)
(9)任务执行完毕,发现集群自动终止;
[](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy13.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy13.jpg)
(10)再查看远程服务器上Airflow的web界面,发现两个dag已经执行完毕。
[](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy14.png)](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy14.jpg)
## 4. 总结
本文展现了如何使用Airflow启动EMR集群,并通过Livy远程提交任务,在任务完成后终止集群。成本节省主要体现在两个方面:1)每天在需要执行ETL工作时启动集群,任务执行完成后终止集群,因此不会出现空闲的集群;2)EMR可以配合Spot实例使用,从而节省更多的成本。另一个好处是使用Livy无需额外配置远程提交任务的服务器,并且EMR集成了Livy的一键安装,造成了极大的方便。
## 本篇作者
<footer> ![](https://s3.cn-north-1.amazonaws.com.cn/awschinablog/Author/xiaoyj.jpg)
### [](https://amazonaws-china.com/cn/blogs/china/tag/%E8%82%96%E5%85%83%E5%90%9B/)
AWS解决方案架构师,负责基于AWS云计算方案的架构咨询和设计实现,同时致力于数据分析与AI的研究与应用。</footer>
**作者介绍:**翟羽翔,AWS解决方案架构师,负责基于AWS云计算方案的架构咨询和设计实现,同时致力于数据湖的应用和推广。
**本文转载自AWS技术博客。**
**原文链接:**https://amazonaws-china.com/cn/blogs/china/dynamic-start-stop-of-emr-cluster-with-airflow-and-remote-submission-of-tasks-via-livy/
复制代码


2020-02-27 16:301054

评论 1 条评论

发布
用户头像
2021-08-02 08:53
回复
没有更多了
发现更多内容

时序数据库破局开放探讨

YMatrix 超融合数据库

物联网 时序数据库 超融合数据库 数据库架构选型 YMatrix

演讲实录|OpenMLDB 与阿里云 MaxCompute 生态集成

第四范式开发者社区

人工智能 数据库 开源 时序数据库 特征

React 之 Context 的变迁与背后实现

冴羽

JavaScript 源码分析 前端 前端框架 React

华为侯金龙:能源流与信息流融合,共建绿色低碳城市

极客天地

YMatrix:超融合数据库如何在泵车智能运维场景实现 One for All 价值

YMatrix 超融合数据库

智能运维 三一重工 超融合数据库 智能化运维 YMatrix

前端培训学习前景怎么样

小谷哥

数据生态第四弹 | OpenMLDB Hive Connector,架构起数据仓库到特征工程的生态桥梁

第四范式开发者社区

人工智能 机器学习 数据库 开源 特征

【前端相关】服务端渲染和客户端渲染的比较

No8g攻城狮

CSS css3 前端 js 前端框架

对于Getaverse,灵魂绑定和去中心化身份是什么?

Geek_Web3

Web3 Daily 去中心化信任 #区块链# did

flutter系列之:如丝般顺滑的SliverAppBar

程序那些事

flutter 程序那些事

FLStudio21.0.0水果官方中文版发布功能介绍

茶色酒

FLStudio21.0.0

重磅 | 九科信息入选创新型中小企业(原深圳市专精特新企业)

九科Ninetech

2022-12-12:有n个城市,城市从0到n-1进行编号。小美最初住在k号城市中 在接下来的m天里,小美每天会收到一个任务 她可以选择完成当天的任务或者放弃该任务 第i天的任务需要在ci号城市完成,

福大大架构师每日一题

算法 rust 福大大

架构实战营 2-5 微信红包分析随堂测验

西山薄凉

「架构实战营」

喜报 | 秒云获评2022(第二届)“金信通”金融科技创新应用优秀案例

MIAOYUN

金融科技 解决方案 信创

iQOO11 国内安卓首发背景音过滤,人声更突显,通话更隐私

极客天地

学习java开发技术应该如何入手

小谷哥

卡塔尔世界杯出现了半自动越位识别技术、动作轨迹捕捉等黑科技。

汀丶人工智能

12月日更 12月月更 世界杯黑科技

KCL - 让 Kubernetes 资源清单管理更容易

Peefy

编程 Serverless Kubernetes #开源 #DevOps

中台+低代码 企业数字化转型新范式

力软低代码开发平台

“智造新未来”欧比护理智造总部奠基仪式

联营汇聚

北京同仁堂两大名牌品种亮相帝都

联营汇聚

Verilog 时延与过程结构

芯动大师

Verilog语法 Verilog延时 Verilog过程结构

Flink核心组件

穿过生命散发芬芳

flink 12月月更

2022年11月中国汽车智能网联月度观察

易观分析

汽车 智能网联

在成都培训web前端哪有比较好的机构

小谷哥

Java培训一般需要多长时间?

小谷哥

Java开发技术很难吗?

小谷哥

云计算的六大核心技术,你了解多少?

Finovy Cloud

云技术 云渲染

RocketMQ 在网易云音乐的实践

Apache RocketMQ

RocketMQ 消息

JDK自带命令优化

@下一站

代码优化 12月日更 12月月更 jvm优化 java程序优化

用 Airflow 实现 EMR 集群的动态启停并通过 Livy 远程提交任务_行业深度_AWS_InfoQ精选文章