数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯

科技学院

科技百科

科技书籍

网站大全

软件大全

「深度学习福利」大神带你进阶工程师,立即查看>>> # default_exp china # 上面一行用于nbdev中声明本模块的名称。必须是notebook的第一个Cell的第一行。
china_balance 描述:抗击新冠病毒(7)- 全国治愈与新增病例平衡分析。 功能:载入data/china.csv文件,进行绘图输出和分析。 模块:使用JupyterLab、Python、nbdev等完成。用到的Python模块包括: re,正则表达式解析。 json,JSON格式解析。 pandas,数据表格分析。 数据来源: http://www.nhc.gov.cn/xcs/yqtb/list_gzbd.shtml 源码-https://github.com/openthings/anti2020ncov 参考: JupyterLab-数据实验室 文学式编程-nbdev入门教程 Pandas快速入门 更多参考: https://my.oschina.net/u/2306127?q=pandas 抗击新冠病毒(1)-开源软件与数据项目 抗击新冠病毒(2)-基于Jupyter+nbdev的数据分析 抗击新冠病毒(3)-探索在线数据资源 抗击新冠病毒(4)-获取并保存在线数据 抗击新冠病毒(5)-使用pandas进行数据分析 抗击新冠病毒(6)-全国总体情况分析 import pandas as pd import numpy as np import matplotlib.pyplot as plt from matplotlib.font_manager import * china = pd.read_csv("./data/china.csv") china = china.sort_values(by='日期') china_t = china china_t['平衡']=china['新增确诊']-(china['新增治愈']+china['新增死亡'])
趋势图绘制 # 绘制增加量趋势图 def drawb(dfx): myfont = FontProperties(fname='/usr/share/fonts/truetype/arphic/ukai.ttc',size=24) fig=plt.figure(figsize=(48,12), dpi=250) p1=fig.add_subplot(1,1,1) p1.set_xticklabels(dfx['日期'], rotation=15, fontsize='small',fontproperties=myfont) #显示数据。 p1.plot(dfx['日期'],dfx['平衡'],color='blue',linewidth=3,label='平衡【新增确诊-(新增治愈+新增死亡)】') p1.plot(dfx['日期'],dfx['新增确诊'],color='#BB0000',linewidth=1,label='新增确诊') p1.plot(dfx['日期'],dfx['现有确诊'],color='red',linewidth=1,label='现有确诊') p1.plot(dfx['日期'],dfx['新增治愈'],color='green',linewidth=1,label='新增治愈') p1.bar(dfx['日期'],dfx['新增死亡'],color='black',label='新增死亡') plt.title(u'全国病例(COVID-2019)增减数量平衡-2020年01-02月,数据来源:http://www.nhc.gov.cn/xcs/yqtb/list_gzbd.shtml',fontproperties=myfont) plt.legend(loc=0,ncol=1,prop=myfont) plt.grid(True) plt.gcf().autofmt_xdate() plt.show() def drawb1(dfx): myfont = FontProperties(fname='/usr/share/fonts/truetype/arphic/ukai.ttc',size=24) fig=plt.figure(figsize=(48,12), dpi=250) p1=fig.add_subplot(1,1,1) p1.set_xticklabels(dfx['日期'], rotation=15, fontsize='small',fontproperties=myfont) #显示数据。 p1.plot(dfx['日期'],dfx['平衡'],color='blue',linewidth=3,label='平衡【新增确诊-(新增治愈+新增死亡)】') plt.title(u'全国病例(COVID-2019)增减数量平衡-2020年01-02月,数据来源:http://www.nhc.gov.cn/xcs/yqtb/list_gzbd.shtml',fontproperties=myfont) plt.legend(loc=0,ncol=1,prop=myfont) plt.grid(True) plt.gcf().autofmt_xdate() plt.show()

全国病例(COVID-2019)数量平衡-2020年01-02月 drawb(china)
drawb1(china)
大数据
2020-02-19 22:19:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
事件时间与水印
所谓事件时间,就是Flink DataStream中的数据元素自身带有的、在其实际发生时记录的时间戳,具有业务含义,并与系统时间独立。很显然,由于外部系统产生的数据往往不能及时、按序到达Flink系统,所以事件时间比处理时间有更强的不可预测性。为了能够准确地表达事件时间的处理进度,就必须用到水印。
Flink水印的本质是DataStream中的一种特殊元素,每个水印都携带有一个时间戳。当时间戳为T的水印出现时,表示事件时间t <= T的数据都已经到达,即水印后面应该只能流入事件时间t > T的数据。也就是说,水印是Flink判断迟到数据的标准,同时也是窗口触发的标记。
为了形象地说明水印的作用,参考一下下面的图,是一个乱序的基于事件时间的数据流示例。
https://www.ververica.com/blog/how-apache-flink-enables-new-streaming-applications-part-1 图中的方框就是数据元素,其中的数字表示事件时间,W(x)就表示时间戳是x的水印,并有长度为4个时间单位的滚动窗口。假设时间单位为秒,可见事件时间为2、3、1s的元素都会进入区间为[1s, 4s]的窗口,而事件时间为7s的元素会进入区间为[5s, 8s]的窗口。当水印W(4)到达时,表示已经没有t <= 4s的元素了,[1s, 4s]窗口会被触发并计算。同理,水印W(9)到达时,[5s, 8s]窗口会被触发并计算,以此类推。
不过图中暂时没有示出迟到数据。如果事件时间为6的元素出现在W(9)后面,就算是迟到了。迟到数据的处理后面再说。
上面的示例只有一个并行度,那么在有多个并行度的情况下,就会有多个流产生水印,窗口触发时该采用哪个水印呢?答案是所有流入水印中时间戳最小的那个。来自官方文档的图能够说明问题。
https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/event_time.html 容易理解,如果所有流入水印中时间戳最小的那个都已经达到或超过了窗口的结束时间,那么所有流的数据肯定已经全部收齐,就可以安全地触发窗口计算了。
提取事件时间、产生水印
上面说了这么多,那么事件时间是如何从数据中提取的,水印又是如何产生的呢?Flink提供了统一的DataStream.assignTimestampsAndWatermarks()方法来提取事件时间并同时产生水印,毕竟它们在处理过程中是紧密联系的。
assignTimestampsAndWatermarks()方法接受的参数类型有AssignerWithPeriodicWatermarks和AssignerWithPunctuatedWatermarks两种,分别对应周期性水印和打点(即由事件本身的属性触发)水印,它们的类图如下所示。
周期性水印
顾名思义,使用AssignerWithPeriodicWatermarks时,水印是周期性产生的。该周期默认为200ms,也能通过ExecutionConfig.setAutoWatermarkInterval()方法来指定新的周期。
由类图容易看出,我们需要通过实现extractTimestamp()方法来提取事件时间,实现getCurrentWatermark()方法产生水印。但好在Flink已经提供了3种内置的实现类,所以我们直接用就可以了,省事。
AscendingTimestampExtractor 总说话口干舌燥的(?),还是看代码吧。 public abstract long extractAscendingTimestamp(T element); [@Override](https://my.oschina.net/u/1162528) public final long extractTimestamp(T element, long elementPrevTimestamp) { final long newTimestamp = extractAscendingTimestamp(element); if (newTimestamp >= this.currentTimestamp) { this.currentTimestamp = newTimestamp; return newTimestamp; } else { violationHandler.handleViolation(newTimestamp, this.currentTimestamp); return newTimestamp; } } [@Override](https://my.oschina.net/u/1162528) public final Watermark getCurrentWatermark() { return new Watermark(currentTimestamp == Long.MIN_VALUE ? Long.MIN_VALUE : currentTimestamp - 1); }
AscendingTimestampExtractor产生的时间戳和水印必须是单调非递减的,用户通过覆写extractAscendingTimestamp()方法抽取时间戳。如果产生了递减的时间戳,就要使用名为MonotonyViolationHandler的组件处理异常,有两种方式:打印警告日志(默认)和抛出RuntimeException。
单调递增的事件时间并不太符合实际情况,所以AscendingTimestampExtractor用得不多。
BoundedOutOfOrdernessTimestampExtractor 它的出镜率就非常高了。还是看代码先。 public BoundedOutOfOrdernessTimestampExtractor(Time maxOutOfOrderness) { if (maxOutOfOrderness.toMilliseconds() < 0) { throw new RuntimeException("Tried to set the maximum allowed " + "lateness to " + maxOutOfOrderness + ". This parameter cannot be negative."); } this.maxOutOfOrderness = maxOutOfOrderness.toMilliseconds(); this.currentMaxTimestamp = Long.MIN_VALUE + this.maxOutOfOrderness; } public abstract long extractTimestamp(T element); [@Override](https://my.oschina.net/u/1162528) public final Watermark getCurrentWatermark() { long potentialWM = currentMaxTimestamp - maxOutOfOrderness; if (potentialWM >= lastEmittedWatermark) { lastEmittedWatermark = potentialWM; } return new Watermark(lastEmittedWatermark); } [@Override](https://my.oschina.net/u/1162528) public final long extractTimestamp(T element, long previousElementTimestamp) { long timestamp = extractTimestamp(element); if (timestamp > currentMaxTimestamp) { currentMaxTimestamp = timestamp; } return timestamp; }
如名字所述,BoundedOutOfOrdernessTimestampExtractor产生的时间戳和水印是允许“有界乱序”的,构造它时传入的参数maxOutOfOrderness就是乱序区间的长度,而实际发射的水印为通过覆写extractTimestamp()方法提取出来的时间戳减去乱序区间,相当于让水印把步调“放慢一点”。这是Flink为迟到数据提供的第一重保障。
当然,乱序区间的长度要根据实际环境谨慎设定,设定得太短会丢较多的数据,设定得太长会导致窗口触发延迟,实时性减弱。
IngestionTimeExtractor @Override public long extractTimestamp(T element, long previousElementTimestamp) { final long now = Math.max(System.currentTimeMillis(), maxTimestamp); maxTimestamp = now; return now; } @Override public Watermark getCurrentWatermark() { final long now = Math.max(System.currentTimeMillis(), maxTimestamp); maxTimestamp = now; return new Watermark(now - 1); }
IngestionTimeExtractor基于当前系统时钟生成时间戳和水印,其实就是Flink三大时间特征里的摄入时间了。
打点水印
打点水印比周期性水印用的要少不少,并且Flink没有内置的实现,那么就写个最简单的栗子吧。 sourceStream.assignTimestampsAndWatermarks(new AssignerWithPunctuatedWatermarks() { @Nullable @Override public Watermark checkAndGetNextWatermark(UserActionRecord lastElement, long extractedTimestamp) { return lastElement.getUserId().endsWith("0") ? new Watermark(extractedTimestamp - 1) : null; } @Override public long extractTimestamp(UserActionRecord element, long previousElementTimestamp) { return element.getTimestamp(); } });
AssignerWithPunctuatedWatermarks适用于需要依赖于事件本身的某些属性决定是否发射水印的情况。我们实现checkAndGetNextWatermark()方法来产生水印,产生的时机完全由用户控制。上面例子中是收取到用户ID末位为0的数据时才发射。
还有三点需要提醒:
不管使用哪种方式产生水印,都不能过于频繁。因为Watermark对象是会全部流向下游的,也会实打实地占用内存,水印过多会造成系统性能下降。 水印的生成要尽量早,一般是在接入Source之后就产生,或者在Source经过简单的变换(map、filter等)之后产生。 如果需求方对事件时间carry的业务意义并不关心,可以直接使用处理时间,简单方便。 迟到数据处理
如上所述,水印的乱序区间能够保证一些迟到数据不被丢弃,但是乱序区间往往不很长,那些真正迟到了的数据该怎么办呢?有两种方法来兜底,可以说是Flink为迟到数据提供的第二重保障。
窗口允许延迟
Flink提供了WindowedStream.allowedLateness()方法来设定窗口的允许延迟。也就是说,正常情况下窗口触发计算完成之后就会被销毁,但是设定了允许延迟之后,窗口会等待allowedLateness的时长后再销毁。在该区间内的迟到数据仍然可以进入窗口中,并触发新的计算。当然,窗口也是吃资源大户,所以allowedLateness的值要适当。给个完整的代码示例如下。 sourceStream.assignTimestampsAndWatermarks( new BoundedOutOfOrdernessTimestampExtractor(Time.seconds(30)) { private static final long serialVersionUID = 1L; @Override public long extractTimestamp(UserActionRecord record) { return record.getTimestamp(); } } ) .keyBy("platform") .window(TumblingEventTimeWindows.of(Time.minutes(1))) .allowedLateness(Time.seconds(30)) .aggregate(new ViewAggregateFunc(), new ViewSumWindowFunc()) // ......
allowedLateness机制实际上就是DataFlow模型中的回填(backfill)策略的实现。对于滑动窗口和滚动窗口是累积(accumulating)策略,对于会话窗口则是累积与回撤(accumulating & retracting)策略。之前讲DataFlow模型时提到过,不废话了。
侧输出迟到数据
侧输出(side output)是Flink的分流机制。迟到数据本身可以当做特殊的流,我们通过调用WindowedStream.sideOutputLateData()方法将迟到数据发送到指定OutputTag的侧输出流里去,再进行下一步处理(比如存到外部存储或消息队列)。代码如下。 // 侧输出的OutputTag OutputTag lateOutputTag = new OutputTag<>("late_data_output_tag"); sourceStream.assignTimestampsAndWatermarks( new BoundedOutOfOrdernessTimestampExtractor(Time.seconds(30)) { private static final long serialVersionUID = 1L; @Override public long extractTimestamp(UserActionRecord record) { return record.getTimestamp(); } } ) .keyBy("platform") .window(TumblingEventTimeWindows.of(Time.minutes(1))) .allowedLateness(Time.seconds(30)) .sideOutputLateData(lateOutputTag) // 侧输出 .aggregate(new ViewAggregateFunc(), new ViewSumWindowFunc()) // ...... // 获取迟到数据并写入对应Sink stream.getSideOutput(lateOutputTag).addSink(lateDataSink);
The End
作者:LittleMagic 链接: https://www.jianshu.com/p/c612e95a5028 声明:本号所有文章除特殊注明,都为原创,公众号读者拥有优先阅读权,未经作者本人允许不得转载,否则追究侵权责任。
关注我的公众号,后台回复【JAVAPDF】获取200页面试题! 5万人关注的大数据成神之路,不来了解一下吗? 5万人关注的大数据成神之路,真的不来了解一下吗? 5万人关注的大数据成神之路,确定真的不来了解一下吗?
欢迎您关注 《大数据成神之路》
大数据
2020-02-19 21:58:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
先跟大家分享一个好消息!即日起,Apache Flink 社区微信公众号 Ververica 正式更名为 Flink 中文社区 并由 Apache Flink PMC 成员进行维护,是国内唯一的 Flink 社区官方微信公众号,详细信息请见次条「声明」。
在去年的一年中,Flink 中文社区共发布文章 144 篇,通过提供 Flink 技术原理剖析、上手实操、多场景下的最佳实践以及社区的最新资讯等帮助大家更好的理解、使用 Flink。
同时,我们也发现当前社区除文章、直播教程、Meetup 外还缺少一个清晰的图谱让大家了解 Flink 完整的技术体系与学习路径。因此,社区整理了这样一份知识图谱,由 Apache Flink Committer 执笔,四位 PMC 成员审核,将 Flink 9 大技术版块详细拆分,突出重点内容并搭配全面的学习素材。看完这份图谱,才算真的搞懂 Flink!

▽ Flink 知识图谱概览 ▽
如何获取?
关注「Flink 中文社区」微信公众号,后台回复关键字“图谱”即可下载 PDF 版本,内含大量补充链接,一键点击即可查看相关素材!
最实用的知识图谱
1.内容全面 ,将 Flink 所涉及的技术内容划分为 9 大版块,每部分内容进行详细分解,并提供学习路径及深入了解的学习素材。 Streaming Processing Concepts(common concepts for stream processing) Architecture State Management DataStream Libraries Table API & SQL Deployment and Operations Debugging and Monitoring Ecosystem Use Cases
2.层次分明 ,将各部分技术内容中的基础入门知识进行标示,重点突出,帮你找到清晰的学习路径。
3.方便实用 ,每个知识点附带补充说明的链接与最佳学习素材,可及时进行深度探索,有助于理解消化。
4.强大的拓展阅读资料配置 ,整合了社区全年输出的技术文章、系列直播教程、线下 Meetup 及 Flink Forward Asia 的精华内容,一图在手,学好 Flink 不用愁!
各版块知识点详解 Streaming Processing Concepts(common concepts for stream processing)
Architecture
State Management
DataStream
Libraries
Table API & SQL
Deployment and Operations
Debugging and Monitoring
Ecosystem
Use Cases
查看更多:https://yq.aliyun.com/articles/744741?utm_content=g_1000104494
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-19 15:14:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
研究表明,用户最满意的打开网页时间,是在 2 秒以下。用户能够忍受的最长等待时间在 6~8 秒之间。就是说,8 秒是一个临界值,如果你的网站打开速度在 8 秒以上,那么你将失去大部分用户。研究显示,如果等待 12 秒以后,网页还是没有载入,那么 99% 以上的用户会选择关闭网页。
Google 做过一个试验,10 条搜索结果的页面载入时间需要 0.4 秒,显示 30 条搜索结果的页面载入时间需要 0.9 秒,结果后者使得 Google 总的流量和收入减少了 20%。Google 地图上线的时候,首页大小有 100KB,后来下降到 70~80KB。结果,流量在第一个星期上升了 10%,接下来的 3 个星期又再上升了 25%。Amazon 的统计也显示了相近的结果,首页打开时间每增加 100 毫秒,网站销售量会减少 1%。
以上数据说明了一个非常重要的问题,如果你的网站速度如果超过 2s 以上,那么你的客户可能在流失和离你而去了。这一点对于电商网站尤其重要,打开速度慢,那么将造成转化率降低,损失将会大量增加。
网站的访问速度受到很多因素影响。服务器的带宽、服务器的 cpu、内存等硬件配置、网站的代码质量、css 和 js 文件是否合并成了一个请求、用户的访问地理位置、用户的线路、数据库语句是否高效等等。影响一个网站速度的因素非常多,需要针对每一项因素做优化。
随着云计算的普及,cdn 逐渐随着云计算的普及而普及。而且随着云计算技术的发展,很多大厂商开发了很多底层系统,将这些服务直接打包用来出售。例如 cdn 服务,随着这几年技术的发展,cdn 的价格已经越来越便宜。如果自己逐一去优化网站中每个性能瓶颈,那么将耗费大量的时间。cdn 服务将整套服务打包,将全站的访问速度整体提升一个量级,不再需要你再做耗时耗力的优化工作,能够节约大量时间去做推广。今天就来介绍下 WordPress 使用 阿里云 cdn 的教程。
手把手教你 WordPress 使用阿里云 CDN 教程 让你的网站飞起来
添加域名
进入 域名管理 ,点击添加域名,如下图:
「加速域名」输入你要加速的域名,一般是你的二级域名:如:www.domain.com。 「业务类型」选择「图片小文件」。 「源站信息」选择「IP」,输入框输入自己源站的 IP 地址。 「端口」,如果源站是 http 的,则选择 80 端口,如果源站是 https 的, 需要选择 443 端口 ,否则会导致 cdn 不断的 301 重定向,导致网站无法访问。
添加完成后,界面提示如下:
配置 CNAME 进入 域名管理 ,复制域名的 CNAME 值。
进入 云解析 控制台,选择对应的域名,添加一条 CNAME 记录。
https 配置
如果源站是 https 的,还需要在 cdn 上进行 https 配置。
域名管理 控制台,选择对应的域名,选择「HTTPS 配置」。
勾选「HTTPS 安全加速」,如果你的 https 证书是在阿里云购买的,在「证书类型」选项,选择「云盾」,系统会自动将证书导入。关于如何购买阿里云 SSL 证书,可以参考 阿里云免费申请 SSL 证书 。因为我是阿里云购买的,所以直接选择了云盾。 「强制跳转」处,勾选 「HTTP -> HTTPS」。 勾选「HTTP/2」,勾选「TLS 版本控制」-> 「TLSv 1.3」。
缓存配置
缓存配置分为目录和文件后缀名 2 种方式,权重分为 1~99 等级,99 最大,1 最小,权重大会优先缓存。 如果控制台的缓存不配置,阿里云 cdn 默认缓存的时间是:(当前时间 - lastModified) * 0.1,取值范围为:10s~3600s 之间。
下图是我的站点配置方案,可以做参考:
我配置了根目录 / 的 7 天过期时间,是因为我的网站所有结构都是 1 级目录结构(www.domain.com/ur1/),无法根据 url 目录详细区分,如果你的站点可以区分 url 目录结构,可以配置的更加详细。
动态内容无需缓存,所以我对 wp-admin 目录缓存时间设置为 0。
控制台配置的缓存时间并不是绝对时间,假设你的 js 缓存时间是 3 个月,但是你的站点访问量非常少,cdn 会提前将内容过期,直到下次重新访问,缓存才会重新生效。
配置 Cache-Control
Cache-Control 是什么?Cache-Control 通用消息头字段,被用在 http 请求和响应中,通过指定指令来实现缓存机制。阿里云的 cdn 也是通过这个字段来判断是否缓存的,如果你的服务器容器的 Cache-Control 为 no-cache,那么阿里云 cdn 是无法进行缓存的。这个字段可以通过 chrome 浏览器的 Network 查看,如下图:
配置 cache - control 有 2 种方法,阿里云 cdn 控制台配置 HTTP 头、源站配置 Cache-Control。 第 1 种方法:阿里云 cdn 配置 HTTP 头,在阿里云 cdn 控制台进入「缓存配置」->「HTTP 头」添加参数:Cache-Control:public。
第 2 种方法:源站服务器添加 Cache-Control 配置,这里以 nginx 为例,打开 nginx 的 conf 配置文件,在 server 节点下添加如下代码: add_header Cache -Control public ;
关于阿里云 cdn 详细的解释可以参考 配置缓存过期时间 。
性能优化
性能优化主要勾选阿里云 cdn 控制台的 3 个选项。
页面优化,主要是去除 HTML 页面页面冗余内容,如注释以及重复的空白符,节省带宽,加快访问速度。 智能压缩,对静态文件类型进行 Gzip 压缩, 有效减少用户传输内容大小。 Brotli 压缩,Google 在 2015 年 9 月推出了无损压缩算法 Brotli。Brotli 通过变种的 LZ77 算法、Huffman 编码以及二阶文本建模等方式进行数据压缩,与其他压缩算法相比,它有着更高的压缩效率。开启后,查看 css、js 的 content-encoding 值为 br,就说明启用了 Brotli 压缩。
如何查看是否命中 CDN 缓存
首先在 dos 界面 ping 域名,查看返回信息,如果返回后缀为:w.kunlungr.com 的域名信息,说明 CNAME 的配置生效了。但是这并不能说明网站已经命中 cdn 缓存。
查看网站是否命中 cdn 缓存的方法。打开 Chrome 浏览器的 Network 选项卡,刷新网站,找到 Response Headers 的 x-cache 属性, 如果为 HIT 则表明命中缓存,如果为 MISS 则表明未命中缓存。
刷新缓存
如果在源站更新了内容,想要立刻生效,可以使用「刷新预热」功能。
效果如何?
我们花了大量的时间和精力做了 cdn 的配置和优化,那么效果到底如何呢?有没有可以量化的数据来验证呢?下面是我的统计数据图。
未配置 cdn 前的统计时间,平均时间大约为 1500 ms。
启用 cdn 后,时间降低为 130 ms 左右。
访问时间直接提升了 10 倍 以上,提升效果还是非常明显的。


查看更多:https://yq.aliyun.com/articles/744160?utm_content=g_1000104491
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-19 14:43:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
导读 : go modules 是 golang 1.11 新加的特性。如今 1.13 都已经发布了第 7 个小版本了,几乎所有大项目均已开始使用,这自然也包括 Kubernetes 生态中的众多项目。笔者在开发 OAM 相关项目的时候,却发现 modules 的各项功能看似简单,却并没有那么好用,于是便想给大家分享一下使用心得,希望大家也能在最短时间内学会 modules 的使用,避免踩坑。
modules 是什么?
简单说就是包管理,Golang 的包管理素来以混乱著称,以前是依赖 $GOPATH ,只要你的代码放在指定路径下就好了,完全没有“包管理”的概念。被社区吐槽了很久以后开始搞 vendor 机制,简单来说就是代码不光是可以放到指定路径,还可以放在项目自身路径的 vendor 文件夹。这个解决的问题是:你引用的代码包上游变更不会直接影响你的项目,这显然是开始关心“包版本”了。遗憾的是依旧没有解决包管理的问题,比如不同的包依赖了同一个包的不同版本怎么办?版本间代码冲突怎么办? vendor 机制并没有解决,于是围绕 vendor/ 社区就出了几十个包管理工具,一时间百花齐放、百家争鸣、各有所长,导致 golang 的包管理生态变得有些混乱。对这段历史感兴趣的可以阅读下笔者曾经写的文章 《Go 包管理的前世今生》 。
更有意思的是,在 go 官方社区看到包管理工具的乱象后,也做了个功能类似的工具 dep ,原理与其他各类依靠 vendor/ 机制的包管理工具类似,准备对包管理做统一。当大家对 dep 工具报以期望并纷纷开始切换到 dep 工具管理依赖包的时候,go 官方又发布了现在的 modules 机制,完全放弃了之前的 dep 工具与 vendor 机制。这样的操作在社区引起了巨大的争议,modules 与 go get 、 go build 等官方工具生态有很好的集成,官方的意图自然是希望抛开原有的历史包袱,通过全新的方式拯救世界。然而实际体验下来,却依旧不尽如人意。
总的来说大趋势已经是用 modules ,go1.13 也对 modules 机制做了不少工作。
言归正传,本文的目标是希望能用 5~10 分钟时间带您学会使用 go modules,然后通过 QA 的形式,描述一些常见的问题。如果希望详细理解相关内容,也可以参考 官方文档 。
初始化
modules 机制在 go 里面的子命令是 go mod 。
modules 机制是 go 1.11 才引入的,所以开始用之前先检查下自己的 go 版本 go version ,建议使用最新的 1.13 版本 ,涵盖了 module 机制相关的较多更新和功能。
在保证 go 版本至少在 1.11 或以上之后,就要开启一下环境变量 GO111MODULE ,启用 module 机制。 export GO111MODULE= on
或者设置为 auto 模式,这样在 GOPATH 路径下的项目可以不使用 module 机制。 export GO111MODULE= auto
建议加到 ~/.bashrc 、 ~/.zshrc 等配置中启动便自动生效。
如果您的项目之前已经用 modules 管理了,那么到此为止你本地的环境已经完成初始化了。
如果项目里之前没有使用 modules,切换过来也很简单,删除原先的 vendor 文件夹(保险起见可以移动到项目之外的地方),在项目里执行一下初始化命令即可。 go mod init [ module 名称]
包名跟以前一样,还是跟 go path 强关联的,比如我们的项目一般是在 http://github.com/oam-dev/oam-go-sdk ,那么你的包名就是这个了。 go mod init github.com/oam-dev/oam-go-sdk
初始化完成后就会看见项目里有个 go.mod 文件。
然后通过 go mod download 就可以下载所有原先 vendor 中的依赖。
日常包管理
使用了 go module 以后,你的许多命令就会与包管理集成,比如 go get 、 go build 、 go run 都会自动查找并在 go.mod 里面更新依赖。
所以按照 Go 官方团队的意思,一般情况下,你根本不用关心包管理的问题了。( 当然,这真的纯粹只是官方在 YY )
所以到这里你就已经学会 go modules 了,没超过5分钟吧?
FAQ
实际因为 go 官方对包管理重视的太晚,各种包都没有版本的概念,随随便便就会出现各种冲突,而且由于 go modules 实在是太自动了,所以就算你学会了怎么用 modules,最后还是会比较头疼。
下面我们就以 FAQ 的形式回答项目中遇到各种问题怎么办。
有些包由于特定网络原因无法访问怎么办?
Go module 加入了代理的机制,只要设置一个代理地址,就可以提供代理访问。阿里云就提供了这样一个 go 的代理 ,是完全免费的服务。公共代理仓库会代理并缓存go模块,你可以利用该代理来避免 DNS 污染或其他问题导致的模块拉取缓慢或失败的问题,加速你的项目构建。
设置的方式非常简单,只需要设置如下环境变量即可,执行命令: export GOPROXY=https: //mirrors.aliyun.com/goproxy/
go1.13 加入了 mirror 机制,可以通过 go env -w 设置 mirror,其实就是之前的 GOPROXY 做的更到位一些,执行命令: go env -w GOPROXY=https: //mi rrors.aliyun.com /goproxy/ ,direct
逗号后面可以增加多个 proxy,最后的 direct 则是在所有 proxy 都找不到的时候,直接访问,代理访问不到的私有仓库就可以正常使用了。
这个功能基本上是在家远程办公的必备工具了。
公司通过 gitlab 搭建了私有库,二方依赖库下载不下来怎么办?
这个几乎是最常见的问题,比较简单的解决方案是 hack 一下 git 配置: git config --global url."git@gitlab.your-company.com:/.git".insteadOf "https://gitlab.your-company.com//.git"
这个方案依赖你本地的 ~/.ssh/id_rsa , 这样你就可以正常 go get 了。
Dockerfile 中构建镜像怎么解决私有库的依赖包问题? 方案一:上述方式通过修改 git config ,却依赖你本地的 ~/.ssh/id_rsa ,在构建时可以通过 multistage-build 把私钥 add 到 stage 0 里面 build,然后用后面新的 stage 生成镜像,这样构建的镜像就不会包含私钥; 方案二: 更为安全的方式是,在每次构建 Docker 镜像之前,先在本地用 go mod vendor 把包缓存下来,在 Dockerfile 构建镜像过程中还是用 GOPATH 和 Vendor 机制来管理依赖。
依赖包怎么更新指定版本?
先查看版本: $ go list -m -versions rsc.io/sampler rsc.io/sampler v1 .0 .0 v1 .2 .0 v1 .2 .1 v1 .3 .0 v1 .3 .1 v1 .99 .99
再更新: $ go get rsc.io/sampler@v1. 3.1 go: finding rsc.io/sampler v1. 3.1 go: downloading rsc.io/sampler v1. 3.1 go: extracting rsc.io/sampler v1. 3.1 $ go test PASS ok example.com/hello 0 . 022 s
某些依赖包的地址变更导致无法找到了怎么办?
go 的依赖与项目名直接相关,这就导致如果我们使用了 github 上的项目,然后项目的维护人员突发奇想改个项目名称,就会导致所有依赖它的项目都无法找到依赖。
还好有 replace 的机制: replace golang.org/x/crypto v0.0.0-20190313024323 -a 1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323 -a 1f597ede03a
版本冲突怎么办?
这就要梳理版本了,是最没有捷径的。一个比较简单的办法是把所有 go.mod 里不需要指定版本的包全部删掉,仅指定必要的包版本,然后通过 go build 让项目自动构建依赖包的版本。
通过 go mod graph 可以查看具体依赖路径: $ go mod graph github.com/oam-dev/oam-go-sdk github.com/go-logr/logr@v0. 1.0 github.com/oam-dev/oam-go-sdk github.com/onsi/ginkgo@v1. 10.1 github.com/oam-dev/oam-go-sdk github.com/onsi/gomega@v1. 7.0 github.com/oam-dev/oam-go-sdk github.com/stretchr/testify@v1. 4.0 github.com/oam-dev/oam-go-sdk golang.org/x/net@v0. 0 . 0 - 20191004110552 - 13 f9640d40b9 github.com/oam-dev/oam-go-sdk k8s.io/api@v0. 17.0 github.com/oam-dev/oam-go-sdk k8s.io/apimachinery@v0. 17.0 github.com/oam-dev/oam-go-sdk k8s.io/client-go@v0. 17.0 github.com/oam-dev/oam-go-sdk sigs.k8s.io/controller-runtime@v0. 4.0 ...
左边是项目包,右边是被依赖的包和版本。
如果确实存在两个需要指定版本的包互相冲突,那就要做取舍,修改代码,升级或降级某个包了。
本地包如何引用?
如果在代码调试过程中,涉及到修改其他依赖项目代码,这时候就要引用本地包,也可以采用 replace 机制: require ( golang.org/x/crypto v0 .0 .0 ) replace golang.org/x/crypto v0 .0 .0 => ../crypto
后面这个就是个相对项目路径的本地依赖所在路径。
解决了上面的这些问题,基本上你就可以愉快的使用 module 功能啦。
go mod 命令一览
go mod 里还有一些其他功能,也在此列举,方便大家查阅:
最后
OAM(Open Application Model) 开放应用模型是阿里联合微软针对云原生应用的模型,第一次对“以应用为中心”的基础设施和构建规范进行了完整的阐述。应用管理者只要遵守这个规范,就可以编写出一个自包含、自描述的“应用定义文件”。
OAM 将应用划分为应用组件和应用特征两部分,应用组件是应用本身的逻辑,而应用特征则是云上的各种通用能力(如扩缩容、监控、灰度等等),大大提升了应用构建时模块化复用能力,将云上的各类资源和能力都转化为了标准化的可“声明”对象。
同时 OAM 强调关注点分离,通过标准化的模型将应用开发不同阶段的 API 进行分层,流程上先由研发定义应用组件,再由运维配置云上的各种策略,最后由基础设施团队统一提供各类模块化的能力。OAM 则在其中起着彼此协作的粘合剂作用,大大提高了应用交付的效率。
OAM 相关内容在 github 上完全开源,同时我们也为 Go 生态编写了 oam-go-sdk 方便快速实现 OAM。
目前,阿里巴巴团队正在上游贡献和维护这套技术,如果大家有什么问题或者反馈,也非常欢迎与我们在上游或者钉钉联系。

查看更多:https://yq.aliyun.com/articles/744783?utm_content=g_1000104490
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-19 14:36:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
PTS 支持导入脚本、使用场景模板、录制器和手动创建等方式快速发起压测。本文以手动创建方式,对某网站登录页快速发起压测为例,介绍如何使用 PTS 在一分钟内发起压测。
前提条件 您已开通 PTS 服务,请参见 开通 PTS 服务 。
操作步骤 登录 PTS 控制台 ,在概览页单击 快速压测 。
在 快速压测 对话框中选择 手动创建 。 在 创建压测 对话框中完成以下信息,并单击 确定 。
请求方法 :支持 GET、POST、PUT 和 DELETE。 URL地址 :待压测的网页地址,例如http://www.example.com/login。 压测模式 :支持并发模式和 RPS 模式。本示例中选择 并发模式 。 并发模式 :“并发”是指虚拟并发用户数,从业务角度,也可以理解为同时在线的用户数。 RPS 模式 :即“吞吐量模式”,通过设置每秒发出的请求数,从服务端的角度出发,直接衡量系统的吞吐能力,免去并发到 RPS 的繁琐转化,一步到位。 施压量级 :本示例中,设置压测量级为 10,压测时长为 1 分钟,表示压力值为 10 个并发,压测时长为 1 分钟。 并发模式下: 施压量级 :固定的压力值。 压测时长 :固定压力的持续时间。 RPS 模式下: 最大 RPS :最大压力值。 起始 RPS :起始压力值。 压测时长 :固定压力的持续时间。
配置完成后,PTS 将发起流量,进行测试。控制台将跳转到压测中页面。 压测结束后,从左侧导航栏,选择 压测报告 ,在报告列表 操作 列单击 查看报告 。您可以查看采样日志、压测相关的业务指标等,详情请参见 查看压测报告 和 查看采样日志 。
查看更多:https://help.aliyun.com/document_detail/70290.html
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-19 12:23:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
公司介绍
我们公司是北京蛙跳科技有限公司,成立于2019年。全新上线的短视频社区,即蛙跳视频APP,是一款垂直于青少年儿童才艺展示的短视频平台。用户在蛙跳视频APP上可以报名参加适合自己孩子的展示活动、帮助儿童寻找自己的学习榜样、发现适合的才艺培训机构。此外,用户还可通过上传视频、参与活动、分享、获赞等方式获得积分,兑换精美奖品。目前分为:才艺、秀场、综艺、商城四个版块。
业务痛点 存储空间成本:静态内容音频、视频需要存储等体量大存储空间成本高,服务器磁盘资源价格贵,读写性能低,存储得不到安全保障,若存储过程中出现坏道,将会造成不可逆损失。 CDN资源稀缺:传统CDN节点少,资费贵,首屏打开时间长,流畅程度低。 烧钱抢夺资源:如今群雄逐鹿,都看中视频行业的发展前景,竞争压力大,各大厂家都加烧钱拉浏览量,想从中脱颖而出。 国内带宽成本:相比于国外,国内带宽成本极高,流量费用,网络搭建费用,很多的直播APP最终入不敷出,导致项目破产。费用预算没有心理预估。
解决方案
图 1. 解决方案架构图:
方案细节 : 阿里云部署灵活一站式全球文娱活动互动直播,为全球文化娱乐传播行业我们公司提供低成本的直播转码、分发能力,助力我们公司快速进行全球范围内的活动直播。“直播+互动“的模式,通过社交传播,助力平台快速变。全景数据统计,丰富角度分析,我们公司画像描述助力业务拓展。 业务安全:多种直播加密技术,全方位的检测与防护,减少盗播、盗链风险。 SDK 开发者工具包:提供web播放器SDK、客户端播放器SDK、播放器SDK。多平台播放SDK方便快速接入,全网加速提供多屏下的极致播放体验。边缘推流优先将视频推流至最优CDN节点,保证用户访问的都是最佳的上行网络,减少因上行传输带来的卡顿、拉流缓慢的问题。
上云价值 系统架构优化促成了业务层可用性和稳定性的大幅度提升: 优化后的系统架构可承担更高的业务层访问量、处理更多埋点数据。随着业务的增加,运营或用户的查询响应时间也不会受到影响。 更多、更安全、更稳定的存储空间,更多的CDN提供更快的浏览体验、解决音视频播放的流畅度问题,完成快速接入。 另外,我们公司对安全性和保密性要求非常高,视频点播播放器可实现加密下载。 全新的架构方案性价比高,有效控制研发成本。 SDK 开发者工具包:提供产品级SDK,最快2小时接入,节省自行开发耗费的人力物力,业务筹备期开发降低我们公司开发和时间成本。 带读写分离的高可用服务:数据服务需要提供7 x 24的高可用服务,POLARDB的DB Server采用一主多读的集群架构,在提供服务高可用的同时,进一步降低了高可用服务的使用成本,同时数据库访问地址的管理更加便捷和高效。 我们公司非常满意,后续会购买请求包等增值服务,也会通过阿里云产品开展赛事直播媒体服务。
客户证言
“每次遇到对接技术问题阿里云的工程师快速响应,节省时间!非常感谢!”——蛙跳视频架构师孔助振
选用的产品 视频点播
视频点播(ApsaraVideo VoD,简称VoD)是集视频采集、编辑、上传、媒体资源管理、自动化转码处理(窄带高清TM)、视频审核分析、分发加速于一体的一站式音视频点播解决方案。


查看更多:https://help.aliyun.com/document_detail/148582.htm
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-17 10:51:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
公司介绍
我们公司是喜氏互联网科技,我们公司主要从事互联网科技、物联网科技、信息技术、智能科技专业领域内的技术开发云平台服务,云软件服务,票务服务。此次上云的是喜氏电子票据交易撮合平台,为我们公司的会员提供即时的不同区域和不同银行的各类信息,为持票人、票据机构、企业、银行建立发布及交易撮合服务,旨在帮助企业建立投融资渠道,降低票据交易成本。
业务痛点 我们公司平台服务的对象主要是企业和银行,在和银行对接数据时会受监管要求,所以我们安全性、稳定性和延迟都要达到要求。 高并发的场景下我们公司要保证业务的连续性及可用性,同时若缓存的数据较多时可以实时处理。 在我们公司的平台访问需要登录验证,对登录的账户需要做安全性确认,防止重要信息泄露。
解决方案
方案细节 : 融资票务平台服务的对象会涉及到持票人、票务机构、企业和银行,使用金融云机房稳定性更高、更安全;使用云盾验证码服务来验证账号登录信息是否准确。终端客户正常访问使用高可用SLB做前端业务的代理,同时使用验证码服务来验证账号,登录信息转到ECS做后端处理,同时高并发场景下的事物性消息可以使用MQ中间件做分布式处理。 我们公司有一些缓存的数据是通过Redis来处理,数据的读写是由RDS for MySQL做高可用的读写分离,银行机房侧和阿里云机房实现双活专线接入保证业务数据可以实时的读取,延迟和稳定性同时符合业务监管上的要求。
上云价值 票务平台上云、同时在高并发场景下利用SLB、MQ、Redis和RDS架构,解决了我们公司业务访问中断的问题。客户侧不会因此出现卡顿延迟的情况,保证平台正常运行。 此次业务上云也为之后我们公司在云上部署其他业务提供了很好的支持。
选用的产品 负载均衡SLB
负载均衡(Server Load Balancer)是将访问流量根据转发策略分发到后端多台云服务器(ECS实例)的流量分发控制服务。负载均衡扩展了应用的服务能力,增强了应用的可用性。
更多关于负载均衡SLB的介绍,参见 负载均衡产品详情页 。 云数据库 Redis 版
阿里云数据库Redis版是兼容开源Redis协议标准、提供内存加硬盘混合存储的数据库服务,基于高可靠双机热备架构及可平滑扩展的集群架构,可充分满足高吞吐、低延迟及弹性变配的业务需求。
更多关于云数据库 Redis 版的介绍,参见 云数据库 Redis 版产品详情页 。 消息队列 for Apache RocketMQ
消息队列 for Apache RocketMQ 是由阿里巴巴自研并捐赠给 Apache 基金会,并与开源社区共建的消息中间件,该产品服务于阿里巴巴集团已超过13 年,经过交易核心链路反复打磨与历年双十一高并发场景的严苛考验,是一个真正具备低延迟、高并发、高可用、高可靠,可支撑万亿级数据洪峰的分布式消息中间件。
更多关于消息队列的介绍,参见 消息队列产品详情页 。 云数据库 Redis 版
阿里云数据库Redis版是兼容开源Redis协议标准、提供内存加硬盘混合存储的数据库服务,基于高可靠双机热备架构及可平滑扩展的集群架构,可充分满足高吞吐、低延迟及弹性变配的业务需求。
更多关于云数据库 Redis 版的介绍,参见 云数据库 Redis 版产品详情页 。 高速通道
帮助不同网络环境间实现高速、稳定、安全的私网通信。物理专线连接实现云下IDC专线接入云上,提高网络拓扑灵活性和跨网通信质量;高速通道(ECC)基于阿里云智能接入网关的硬件能力和SD-WAN技术,为客户提供整合运营商物理专线的高可靠、高性能、低时延的一站式上云服务;对等连接实现云上跨地域/跨用户的VPC内网互通。
查看更多:https://help.aliyun.com/document_detail/148577.htm
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-17 10:49:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
函数计算是事件驱动的全托管计算服务。使用函数计算,您无需采购与管理服务器等基础设施,只需编写并上传代码。函数计算为您准备好计算资源,弹性地可靠地运行任务,并提供日志查询、性能监控和报警等功能。
借助函数计算,您可以快速构建任何类型的应用和服务,并且只需为任务实际消耗的资源付费。

工作流程
函数计算工作流程如下图所示:
编号说明
①. 开发者使用编程语言编写应用和服务。函数计算支持的开发语言请参考 开发语言列表 。
②. 开发者上传应用到函数计算。上传途径包括 函数计算控制台 ( 推荐 )、命令行工具 Fun (推荐) 、 API/SDK 以及 命令行工具 fcli 。
③. 触发函数执行。触发方式包括 OSS、API 网关、日志服务、表格存储以及函数计算 API/SDK 等。
④. 动态扩容以响应请求。函数计算可以根据用户请求量自动扩容,该过程对您和您的用户均透明无感知。
⑤. 根据函数的实际执行时间按量计费。函数执行结束后,可以通过账单来查看执行费用,收费粒度精确到 100 ms。更多详情,请参阅 计费方式 。
集成服务
函数计算以事件驱动的方式连接其他服务。借助这些方式,您可以构建弹性的、可靠的以及安全的应用和服务,甚至在数天内就能完成一套多媒体数据处理后端服务。当事件源触发事件时,我们会自动调用关联的函数处理事件。例如,对象存储(OSS)在新对象创建 / 删除事件(ObjectCreated/ObjectRemoved)时会自动触发函数处理。或者 API 网关在收到 HTTP 请求时自动触发函数处理请求。此外,函数还可以由 日志服务 或者 表格存储 等其他阿里云服务触发。
函数计算支持的事件源类型请参考文章 触发器列表 。
无服务器架构
假设您计划采购服务器开发一款短视频社交应用,那么您需要考虑很多的问题,例如: 如何构建和运维一套弹性的稳定的视频处理后端服务? 需要采购多少台服务器? 服务器采用什么规格? 如何配置网络和操作系统? 如何部署环境? 如何负载均衡? 如何动态伸缩? 如何升级配置? 如何应对服务器宕机? 如何应对用户请求峰值? 如何应对系统监控报警?
……
可喜的是,基础设施的云化,使您能快速调动和使用海量计算资源,无需担心如何短时间内获取合适规格的服务器。但当前云计算的抽象粒度大多在机器级别,要管理和使用这些计算资源仍然有不小的门槛和成本。阿里云函数计算为解决计算成本和效率问题而生,将计算服务的抽象粒度提高到了函数级别,打造无服务器概念的应用设计模式。
使用函数计算,你无需管理底层的基础设施,只需要将您的代码部署到函数计算,并以事件驱动的方式触发函数执行,服务就可以平稳运行。您无需再为环境部署、服务器扩容、服务器宕机等问题烦恼,函数计算提供弹性的扩容机制,并按量计费。此外,函数计算提供日志查询、性能监控和报警等功能,帮助您快速定位问题、排查故障。

查看更多:https://help.aliyun.com/document_detail/52895.html
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-17 10:41:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在win10 下 运行 hadoop api 代码,下载代码的时候报错 爆了这个错误 NativeIO$Windows.createFileWithMode0(Ljava/lang/String;JJJI)Ljava/io/FileDes /** * 拷贝HDFS文件到本地:下载 */ @Test public void copyToLocalFile() throws Exception { Path src = new Path("/hdfsapi/test/d.txt"); Path dst = new Path("C:\
大数据
2020-02-18 19:42:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
导读 :本文作者写这篇文章前前后后大概 2 个月的时间,全文大概 2w 字,建议收藏后阅读或者通过电脑阅读。
调度是一个非常广泛的概念,很多领域都会使用调度这个术语,在计算机科学中, 调度 就是一种将任务(Work)分配给资源的方法。任务可能是虚拟的计算任务,例如线程、进程或者数据流,这些任务会被调度到硬件资源上执行,例如:处理器 CPU 等设备。
图 1 - 调度系统设计精要
本文会介绍调度系统的常见场景以及设计过程中的一些关键问题,调度器的设计最终都会归结到一个问题上 — 如何对资源高效的分配和调度以达到我们的目的,可能包括对资源的合理利用、最小化成本、快速匹配供给和需求。
图 2 - 文章脉络和内容
除了介绍调度系统设计时会遇到的常见问题之外,本文还会深入分析几种常见的调度器的设计、演进与实现原理,包括操作系统的进程调度器,Go 语言的运行时调度器以及 Kubernetes 的工作负载调度器,帮助我们理解调度器设计的核心原理。
设计原理
调度系统其实就是调度器(Scheduler),我们在很多系统中都能见到调度器的身影,就像我们在上面说的,不止操作系统中存在调度器,编程语言、容器编排以及很多业务系统中都会存在调度系统或者调度模块。
这些调度模块的核心作用就是对有限的资源进行分配,以实现最大化资源的利用率或者降低系统的尾延迟,调度系统面对的就是资源的需求和供给不平衡的问题。
图 3 - 调度器的任务和资源
我们在这一节中将从多个方面介绍调度系统设计时需要重点考虑的问题,其中包括调度系统的需求调研、调度原理以及架构设计。
1. 需求调研
在着手构建调度系统之前,首要的工作就是进行详细的需求调研和分析,在这个过程中需要完成以下两件事: 调研调度系统的应用场景,深入研究场景中待执行的任务(Work)和能用来执行任务的资源(Resource)的特性; 分析调度系统的目的,可能是成本优先、质量优先、最大化资源的利用率等,调度目的一般都是动态的,会随着需求的变化而转变;
应用场景
调度系统应用的场景是我们首先需要考虑的问题,对应用场景的分析至关重要,我们需要深入了解当前场景下待执行任务和能用来执行任务的资源的特点。我们需要分析待执行任务的以下特征: 任务是否有截止日期,必须在某个时间点之前完成; 任务是否支持抢占,抢占的具体规则是什么; 任务是否包含前置的依赖条件; 任务是否只能在指定的资源上运行; ...
而用于执行任务的资源也可能存在资源不平衡,不同资源处理任务的速度不一致的问题。
资源和任务特点的多样性决定了调度系统的设计,我们在这里举几个简单的例子帮助各位读者理解调度系统需求分析的过程。
图 4 - Linux 操作系统
在操作系统的进程调度器中,待调度的任务就是线程,这些任务一般只会处于正在执行或者未执行(等待或者终止)的状态;而用于处理这些任务的 CPU 往往都是不可再分的,同一个 CPU 在同一时间只能执行一个任务,这是物理上的限制。简单总结一下,操作系统调度器的任务和资源有以下特性: 任务 —— Thread。状态简单:只会处于正在执行或者未被执行两种状态;优先级不同:待执行的任务可能有不同的优先级,在考虑优先级的情况下,需要保证不同任务的公平性; 资源 —— CPU 时间。资源不可再分:同一时间只能运行一个任务;
在上述场景中,待执行的任务是操作系统调度的基本单位 —— 线程,而可分配的资源是 CPU 的时间。Go 语言的调度器与操作系统的调度器面对的是几乎相同的场景,其中的任务是 Goroutine,可以分配的资源是在 CPU 上运行的线程。
图 5 - 容器编排系统 Kubernetes
除了操作系统和编程语言这种较为底层的调度器之外,容器和计算任务调度在今天也很常见,Kubernetes 作为容器编排系统会负责调取集群中的容器,对它稍有了解的人都知道,Kubernetes 中调度的基本单元是 Pod,这些 Pod 会被调度到节点 Node 上执行: 任务 —— Pod。优先级不同:Pod 的优先级可能不同,高优先级的系统 Pod 可以抢占低优先级 Pod 的资源;有状态:Pod 可以分为无状态和有状态,有状态的 Pod 需要依赖持久存储卷; 资源 —— Node。类型不同:不同节点上的资源类型不同,包括 CPU、GPU 和内存等,这些资源可以被拆分但是都属于当前节点;不稳定:节点可能由于突发原因不可用,例如:无网络连接、磁盘损坏等;
调度系统在生活和工作中都很常见,除了上述的两个场景之外,其他需要调度系统的场景包括 CDN 的资源调度、订单调度以及离线任务调度系统等。在不同场景中,我们都需要深入思考任务和资源的特性,它们对系统的设计起者指导作用。
调度目的
在深入分析调度场景后,我们需要理解调度的目的。我们可以将调度目的理解成机器学习中的成本函数(Cost function),确定调度目的就是确定成本函数的定义,调度理论一书中曾经介绍过常见的 调度目的 ,包含以下内容: 完成跨度(Makesapan) — 第一个到最后一个任务完成调度的时间跨度; 最大延迟(Maximum Lateness) — 超过截止时间最长的任务; 加权完成时间的和(Total weighted completion time)— 权重乘完成时间的总和; ...
这些都是偏理论的调度的目的,多数业务调度系统的调度目的都是优化与业务联系紧密的指标 — 成本和质量。如何在成本和质量之间达到平衡是需要仔细思考和设计的,由于篇幅所限以及业务场景的复杂,本文不会分析如何权衡成本和质量,这往往都是需要结合业务考虑的事情,不具有足够的相似性。
2. 调度原理
性能优异的调度器是实现特定调度目的前提,我们在讨论调度场景和目的时往往都会忽略调度的额外开销,然而调度器执行时的延时和吞吐量等指标在调度负载较重时是不可忽视的。本节会分析与调度器实现相关的一些重要概念,这些概念能够帮助我们实现高性能的调度器: 协作式调度与抢占式调度; 单调度器与多调度器; 任务分享与任务窃取;
协作式与抢占式
协作式(Cooperative)与抢占式(Preemptive)调度是操作系统中常见的多任务运行策略。这两种调度方法的定义完全不同: 协作式调度允许任务执行任意长的时间,直到任务主动通知调度器让出资源; 抢占式调度允许任务在执行过程中被调度器挂起,调度器会重新决定下一个运行的任务;
图 6 - 协作式调度与抢占式调度
任务的执行时间和任务上下文切换的额外开销决定了哪种调度方式会带来更好的性能。如下图所示,图 7 展示了一个协作式调度器调度任务的过程,调度器一旦为某个任务分配了资源,它就会等待该任务主动释放资源,图中 4 个任务尽管执行时间不同,但是它们都会在任务执行完成后释放资源,整个过程也只需要 4 次上下文的切换。
图 7 - 协作式调度
图 8 展示了抢占式调度的过程,由于调度器不知道所有任务的执行时间,所以它为每一个任务分配了一段时间切片。任务 1 和任务 4 由于执行时间较短,所以在第一次被调度时就完成了任务;但是任务 2 和任务 3 因为执行时间较长,超过了调度器分配的上限,所以为了保证公平性会触发抢占,等待队列中的其他任务会获得资源。在整个调度过程中,一共发生了 6 次上下文切换。
图 8 - 抢占式调度
如果部分任务的执行时间很长,协作式的任务调度会使部分执行时间长的任务饿死其他任务;不过如果待执行的任务执行时间较短并且几乎相同,那么使用协作式的任务调度能减少任务中断带来的额外开销,从而带来更好的调度性能。
因为多数情况下任务执行的时间都不确定,在协作式调度中一旦任务没有主动让出资源,那么就会导致其它任务等待和阻塞,所以调度系统一般都会以抢占式的任务调度为主,同时支持任务的协作式调度。
单调度器与多调度器
使用单个调度器还是多个调度器也是设计调度系统时需要仔细考虑的,多个调度器并不一定意味着多个进程,也有可能是一个进程中的多个调度线程,它们既可以选择在多核上并行调度、在单核上并发调度,也可以同时利用并行和并发提高性能。
图 9 - 单调度器调度任务和资源
不过对于调度系统来说,因为它做出的决策会改变资源的状态和系统的上下文进而影响后续的调度决策,所以单调度器的串行调度是能够精准调度资源的唯一方法。单个调度器利用不同渠道收集调度需要的上下文,并在收到调度请求后会根据任务和资源情况做出当下最优的决策。
随着调度器的不断演变,单调度器的性能和吞吐量可能会受到限制,我们还是需要引入并行或者并发调度来解决性能上的瓶颈,这时我们需要将待调度的资源分区,让多个调度器分别负责调度不同区域中的资源。
图 10 - 多调度器与资源分区
多调度器的并发调度能够极大提升调度器的整体性能,例如 Go 语言的调度器。Go 语言运行时会将多个 CPU 交给不同的处理器分别调度,这样通过并行调度能够提升调度器的性能。
上面介绍的两种调度方法都建立在需要精准调度的前提下,多调度器中的每一个调度器都会面对无关的资源,所以对于同一个分区的资源,调度还是串行的。
图 11 - 多调度器粗粒度调度
使用多个调度器同时调度多个资源也是可行的,只是可能需要牺牲调度的精确性 — 不同的调度器可能会在不同时间接收到状态的更新,这就会导致不同调度器做出不同的决策。负载均衡就可以看做是多线程和多进程的调度器,因为对任务和资源掌控的信息有限,这种粗粒度调度的结果很可能就是不同机器的负载会有较大差异,所以无论是小规模集群还是大规模集群都很有可能导致某些实例的负载过高。
工作分享与工作窃取
这一小节将继续介绍在多个调度器间重新分配任务的 两个调度范式 — 工作分享(Work Sharing)和工作窃取(Work Stealing)。独立的调度器可以同时处理所有的任务和资源,所以它不会遇到多调度器的任务和资源的不平衡问题。在多数的调度场景中,任务的执行时间都是不确定的,假设多个调度器分别调度相同的资源,由于任务的执行时间不确定,多个调度器中等待调度的任务队列最终会发生差异 — 部分队列中包含大量任务,而另外一些队列不包含任务,这时就需要引入任务再分配策略。
工作分享和工作窃取是完全不同的两种再分配策略。在工作分享中,当调度器创建了新任务时,它会将一部分任务分给其他调度器;而在工作窃取中,当调度器的资源没有被充分利用时,它会从其他调度器中窃取一些待分配的任务,如下图所示:
图 12 - 工作窃取调度器
这两种任务再分配的策略都为系统增加了额外的开销,与工作分享相比,工作窃取只会在当前调度器的资源没有被充分利用时才会触发,所以工作窃取引入的额外开销更小。工作窃取在生产环境中更加常用,Linux 操作系统和 Go 语言都选择了工作窃取策略。
3. 架构设计
本节将从调度器内部和外部两个角度分析调度器的架构设计,前者分析调度器内部多个组件的关系和做出调度决策的过程;后者分析多个调度器应该如何协作,是否有其他的外部服务可以辅助调度器做出更合理的调度决策。
调度器内部
当调度器收到待调度任务时,会根据采集到的状态和待调度任务的规格(Spec)做出合理的调度决策,我们可以从下图中了解常见调度系统的内部逻辑。
图 13 - 调度器做出调度决策
常见的调度器一般由两部分组成 — 用于收集状态的状态模块和负责做决策的决策模块。 状态模块
状态模块会从不同途径收集尽可能多的信息为调度提供丰富的上下文,其中可能包括资源的属性、利用率和可用性等信息。根据场景的不同,上下文可能需要存储在 MySQL 等持久存储中,一般也会在内存中缓存一份以减少调度器访问上下文的开销。 决策模块
决策模块会根据状态模块收集的上下文和任务的规格做出调度决策,需要注意的是做出的调度决策只是在当下有效,在未来某个时间点,状态的改变可能会导致之前做的决策不符合任务的需求,例如:当我们使用 Kubernetes 调度器将工作负载调度到某些节点上,这些节点可能由于网络问题突然不可用,该节点上的工作负载也就不能正常工作,即调度决策失效。
调度器在调度时都会通过以下的三个步骤为任务调度合适的资源: 通过优先级、任务创建时间等信息确定不同任务的调度顺序; 通过过滤和打分两个阶段为任务选择合适的资源; 不存在满足条件的资源时,选择牺牲的抢占对象。
图 14 - 调度框架
上图展示了常见调度器决策模块执行的几个步骤,确定优先级、对闲置资源进行打分、确定抢占资源的牺牲者,上述三个步骤中的最后一个往往都是可选的,部分调度系统不需要支持抢占式调度的功能。
调度器外部
如果我们将调度器看成一个整体,从调度器外部看架构设计就会得到完全不同的角度 — 如何利用外部系统增强调度器的功能。在这里我们将介绍两种调度器外部的设计,分别是多调度器和反调度器(Descheduler)。 多调度器
串行调度与并行调度一节已经分析了多调度器的设计,我们可以将待调度的资源进行分区,让多个调度器线程或者进程分别负责各个区域中资源的调度,充分利用多和 CPU 的并行能力。 反调度器
反调度器是一个比较有趣的概念,它能够移除决策不再正确的调度,降低系统中的熵,让调度器根据当前的状态重新决策。
图 15 - 调度器与反调度器
反调度器的引入使得整个调度系统变得更加健壮。调度器负责根据当前的状态做出正确的调度决策,反调度器根据当前的状态移除错误的调度决策,它们的作用看起来相反,但是目的都是为任务调度更合适的资源。
反调度器的使用没有那么广泛,实际的应用场景也比较有限。作者第一次发现这个概念是在 Kubernetes 孵化的 descheduler 项目中,不过因为反调度器移除调度关系可能会影响正在运行的线上服务,所以 Kubernetes 也只会在特定场景下使用。
操作系统
调度器是操作系统中的重要组件,操作系统中有进程调度器、网络调度器和 I/O 调度器等组件,本节介绍的是操作系统中的进程调度器。
有一些读者可能会感到困惑,操作系统调度的最小单位不是线程么,为什么这里使用的是进程调度。在 Linux 操作系统中,调度器调度的不是进程也不是线程,它调度的是 task_struct 结构体,该结构体既可以表示线程,也可以表示进程,而调度器会将进程和线程都看成任务,我们在这里先 说明 这一问题,避免读者感到困惑。我们会使用进程调度器这个术语,但是一定要注意 Linux 调度器中并不区分线程和进程。 Linux incorporates process and thread scheduling by treating them as one in the same. A process can be viewed as a single thread, but a process can contain multiple threads that share some number of resources (code and/or data).
接下来,本节会研究操作系统中调度系统的类型以及 Linux 进程调度器的演进过程。
1. 调度系统类型
操作系统会将进程调度器分成三种不同的类型,即长期调度器、中期调度器和短期调度器。这三种不同类型的调度器分别提供了不同的功能,我们将在这一节中依次介绍它们。
长期调度器
长期调度器(Long-Term Scheduler)也被称作任务调度器(Job Scheduler),它能够决定哪些任务会进入调度器的准备队列。当我们尝试执行新的程序时,长期调度器会负责授权或者延迟该程序的执行。长期调度器的作用是平衡同时正在运行的 I/O 密集型或者 CPU 密集型进程的任务数量: 如果 I/O 密集型任务过多,就绪队列中就不存在待调度的任务,短期调度器不需要执行调度,CPU 资源就会面临闲置; 如果 CPU 密集型任务过多,I/O 等待队列中就不存在待调度的任务,I/O 设备就会面临闲置;
长期调度器能平衡同时正在运行的 I/O 密集型和 CPU 密集型任务,最大化的利用操作系统的 I/O 和 CPU 资源。
中期调度器
中期调度器会将不活跃的、低优先级的、发生大量页错误的或者占用大量内存的进程从内存中移除,为其他的进程释放资源。
图 16 - 中期调度器
当正在运行的进程陷入 I/O 操作时,该进程只会占用计算资源,在这种情况下,中期调度器就会将它从内存中移除等待 I/O 操作完成后,该进程会重新加入就绪队列并等待短期调度器的调度。
短期调度器
短期调度器应该是我们最熟悉的调度器,它会从就绪队列中选出一个进程执行。进程的选择会使用特定的调度算法,它会同时考虑进程的优先级、入队时间等特征。因为每个进程能够得到的执行时间有限,所以短期调度器的执行十分频繁。
2. 设计与演进
本节将重点介绍 Linux 的 CPU 调度器,也就是短期调度器。Linux 的 CPU 调度器并不是从设计之初就是像今天这样复杂的,在很长的一段时间里(v0.01 ~ v2.4),Linux 的进程调度都由几十行的简单函数负责,我们先了解一下不同版本调度器的历史: 初始调度器 · v0.01 ~ v2.4。由几十行代码实现,功能非常简陋;同时最多处理 64 个任务; 调度器 · v2.4 ~ v2.6。调度时需要遍历全部任务当待执行的任务较多时,同一个任务两次执行的间隔很长,会有比较严重的饥饿问题; 调度器 · v2.6.0 ~ v2.6.22。通过引入运行队列和优先数组实现 的时间复杂度;使用本地运行队列替代全局运行队列增强在对称多处理器的扩展性;引入工作窃取保证多个运行队列中任务的平衡; 完全公平调度器 · v2.6.23 ~ 至今。引入红黑树和运行时间保证调度的公平性;引入调度类实现不同任务类型的不同调度策略;
这里会详细介绍从最初的调度器到今天复杂的完全公平调度器(Completely Fair Scheduler,CFS)的演变过程。
初始调度器
Linux 最初的进程调度器仅由 sched.h 和 sched.c 两个文件构成。你可能很难想象 Linux 早期版本使用只有几十行的 schedule 函数负责了 操作系统进程的调度 : void schedule(void) { int i,next,c; struct task_struct ** p; for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) { ... } while ( 1 ) { c = -1 ; next = 0 ; i = NR_TASKS; p = &task[NR_TASKS]; while (--i) { if (!*--p) continue ; if ( (*p) -> state == TASK_RUNNING && (*p)->counter > c) c = (*p) -> counter, next = i; } if (c) break ; for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) if (*p) (*p)->counter = ( (*p) -> counter >> 1 ) + (*p)->priority; } switch_to(next); }
无论是进程还是线程,在 Linux 中都被看做是 task_struct 结构体,所有的调度进程都存储在上限仅为 64 的数组中,调度器能够处理的进程上限也只有 64 个。
图 17 - 最初的进程调度器
上述函数会先唤醒获得信号的可中断进程,然后从队列倒序查找计数器 counter 最大的可执行进程,counter 是进程能够占用的时间切片数量,该函数会根据时间切片的值执行不同的逻辑: 如果最大的 counter 时间切片大于 0,调用汇编语言的实现的 switch_to 切换进程; 如果最大的 counter 时间切片等于 0,意味着所有进程的可执行时间都为 0,那么所有进程都会获得新的时间切片;
Linux 操作系统的计时器会每隔 10ms 触发一次 do_timer 将当前正在运行进程的 counter 减一,当前进程的计数器归零时就会重新触发调度。
O(n)调度器
调度器是 Linux 在 v2.4 ~ v2.6 版本使用的调度器,由于该调取器在最坏的情况下会遍历所有的任务,所以它调度任务的时间复杂度就是 。Linux 调度算法将 CPU 时间分割成了不同的时期(Epoch),也就是每个任务能够使用的时间切片。
我们可以在 sched.h 和 sched.c 两个文件中找到调度器的源代码。与上一个版本的调度器相比, 调度器的实现复杂了很多,该调度器会在 schedule 函数中 遍历 运行队列中的所有任务并调用 goodness 函数分别计算它们的权重获得下一个运行的进程: asmlinkage void schedule ( void ) { ... still_running_back: list_for_each(tmp, &runqueue_head) { p = list_entry(tmp, struct task_struct, run_list); if (can_schedule(p, this_cpu)) { int weight = goodness(p, this_cpu, prev->active_mm); if (weight > c) c = weight, next = p; } } ... }
在每个时期开始时,上述代码都会为所有的任务计算时间切片,因为需要执行 n 次,所以调度器被称作 调度器。在默认情况下,每个任务在一个周期都会分配到 200ms 左右的时间切片,然而这种调度和分配方式是 调度器的最大问题: 每轮调度完成之后就会陷入没有任务需要调度的情况,需要提升交互性能的场景会受到严重影响,例如:在桌面拖动鼠标会感觉到明显的卡顿; 每次查找权重最高的任务都需要遍历数组中的全部任务; 调度器 分配 的平均时间片大小为 210ms,当程序中包含 100 个进程时,同一个进程被运行两次的间隔是 21s,这严重影响了操作系统的可用性.
正是因为调度器存在了上述的问题,所以 Linux 内核在两个版本后使用新的 调度器替换该实现。
O(1)调度器
调度器在 v2.6.0 到 v2.6.22 的 Linux 内核中使用了四年的时间,它能够在常数时间内完成进程调度,你可以在sched.h 和 sched.c 中查看 调度器的源代码。因为实现和功能复杂性的增加,调度器的代码行数从 的 2100 行增加到 5000 行,它在调度器的基础上进行了如下的 改进 : 调度器支持了 时间复杂度的调度; 调度器支持了对称多处理(Symmetric multiprocessing,SMP)的扩展性; 调度器优化了对称多处理的亲和性。
数据结构
调度器通过运行队列 runqueue 和优先数组 prio_array 两个重要的数据结构实现了 的时间复杂度。每一个运行队列都持有两个优先数组,分别存储活跃的和过期的进程数组: struct runqueue { ... prio_array_t *active, *expired, arrays[ 2 ]; ... } struct prio_array { unsignedint nr_active; unsignedlong bitmap[BITMAP_SIZE]; struct list_head queue [MAX_PRIO]; };
优先数组中的 nr_active 表示活跃的进程数,而 bitmap 和 list_head 共同组成了如下图所示的数据结构:
图 18 - 优先数组
优先数组的 bitmap 总共包含 140 位,每一位都表示对应优先级的进程是否存在。图 17 中的优先数组包含 3 个优先级为 2 的进程和 1 个优先级为 5 的进程。每一个优先级的标志位都对应一个 list_head 数组中的链表。 调度器使用上述的数据结构进行如下所示的调度: 调用 sched_find_first_bit 按照优先级分配 CPU 资源; 调用 schedule 从链表头选择进程执行; 通过 schedule 轮训调度同一优先级的进程,该函数在每次选中待执行的进程后,将进程添加到队列的末尾,这样可以保证同一优先级的进程会依次执行(Round-Robin); 计时器每隔 1ms 会触发一次 scheduler_tick 函数,如果当前进程的执行时间已经耗尽,就会将其移入过期数组; 当活跃队列中不存在待运行的进程时,schedule 会交换活跃优先数组和过期优先数组;
上述的这些规则是 调度器运行遵守的主要规则,除了上述规则之外,调度器还需要支持抢占、CPU 亲和等功能,不过在这里就不展开介绍了。
本地运行队列
全局的运行队列是 调度器难以在对称多处理器架构上扩展的主要原因。为了保证运行队列的一致性,调度器在调度时需要获取运行队列的全局锁,随着处理器数量的增加,多个处理器在调度时会导致更多的锁竞争,严重影响调度性能。 调度器通过引入本地运行队列解决这个问题,不同的 CPU 可以通过 this_rq 获取绑定在当前 CPU 上的运行队列,降低了锁的粒度和冲突的可能性。 # define this_rq() (&__get_cpu_var(runqueues))
图 19 - 全局运行队列和本地运行队列
多个处理器由于不再需要共享全局的运行队列,所以增强了在对称对处理器架构上的扩展性,当我们增加新的处理器时,只需要增加新的运行队列,这种方式不会引入更多的锁冲突。
优先级和时间切片
调度器中包含两种不同的优先级计算方式,一种是静态任务优先级,另一种是动态任务优先级。在默认情况下,任务的静态任务优先级都是 0,不过我们可以通过系统调用 nice 改变任务的优先级; 调度器会奖励 I/O 密集型任务并惩罚 CPU 密集型任务,它会通过改变任务的静态优先级来完成优先级的动态调整,因为与用户交互的进程时 I/O 密集型的进程,这些进程由于调度器的动态策略会提高自身的优先级,从而提升用户体验。
完全公平调度器
完全公平调度器(Completely Fair Scheduler,CFS)是 v2.6.23 版本被合入内核的调度器,也是内核的默认进程调度器,它的目的是最大化 CPU 利用率和交互的 性能 。Linux 内核版本 v2.6.23 中的 CFS 由以下的多个文件组成: include/linux/sched.h kernel/sched_stats.h kernel/sched.c kernel/sched_fair.c kernel/sched_idletask.c kernel/sched_rt.c
通过 CFS 的名字我们就能发现,该调度器的能为不同的进程提供完全公平性。一旦某些进程受到了不公平的待遇,调度器就会运行这些进程,从而维持所有进程运行时间的公平性。这种保证公平性的方式与『水多了加面,面多了加水』有一些相似: 调度器会查找运行队列中受到最不公平待遇的进程,并为进程分配计算资源,分配的计算资源是与其他资源运行时间的差值加上最小能够运行的时间单位; 进程运行结束之后发现运行队列中又有了其他的进程受到了最不公平的待遇,调度器又会运行新的进程; ...
调度器算法不断计算各个进程的运行时间并依次调度队列中的受到最不公平对待的进程,保证各个进程的运行时间差不会大于最小运行的时间单位。
数据结构
虽然我们还是会延用运行队列这一术语,但是 CFS 的内部已经不再使用队列来存储进程了,cfs_rq 是用来管理待运行进程的新结构体,该结构体会使用红黑树(Red-black tree)替代链表: struct cfs_rq { struct load_weight load; unsignedlong nr_running; s64 fair_clock; u64 exec_clock; s64 wait_runtime; u64 sleeper_bonus; unsignedlong wait_runtime_overruns, wait_runtime_underruns; struct rb_root tasks_timeline; struct rb_node *rb_leftmost; struct rb_node *rb_load_balance_curr; struct sched_entity *curr; struct rq *rq; struct list_head leaf_cfs_rq_list; };
红黑树(Red-black tree)是平衡的 二叉搜索树 ,红黑树的增删改查操作的最坏时间复杂度为 ,也就是树的高度,树中最左侧的节点 rb_leftmost 运行的时间最短,也是下一个待运行的进程。 注:在最新版本的 CFS 实现中,内核使用虚拟运行时间 vruntime 替代了等待时间,但是基本的调度原理和排序方式没有太多变化。
调度过程
CFS 的调度过程还是由 schedule 函数完成的,该函数的执行过程可以分成以下几个步骤: 关闭当前 CPU 的抢占功能; 如果当前 CPU 的运行队列中不存在任务,调用 idle_balance 从其他 CPU 的运行队列中取一部分执行; 调用 pick_next_task 选择红黑树中优先级最高的任务; 调用 context_switch 切换运行的上下文,包括寄存器的状态和堆栈; 重新开启当前 CPU 的抢占功能。
CFS 的调度过程与 调度器十分类似,当前调度器与前者的区别只是增加了可选的工作窃取机制并改变了底层的数据结构。
调度类
CFS 中的调度类是比较有趣的概念,调度类可以决定进程的调度策略。每个调度类都包含一组负责调度的函数,调度类由如下所示的 sched_class 结构体表示: struct sched_class { struct sched_class *next; void (*enqueue_task) ( struct rq *rq, struct task_struct *p, int wakeup); void (*dequeue_task) ( struct rq *rq, struct task_struct *p, int sleep); void (*yield_task) ( struct rq *rq, struct task_struct *p); void (*check_preempt_curr) ( struct rq *rq, struct task_struct *p); struct task_struct * (*pick_next_task) ( struct rq *rq); void (*put_prev_task) ( struct rq *rq, struct task_struct *p); unsigned long (*load_balance) ( struct rq *this_rq, int this_cpu, struct rq *busiest, unsigned long max_nr_move, unsigned long max_load_move, struct sched_domain *sd, enum cpu_idle_type idle, int *all_pinned, int *this_best_prio) ; void (*set_curr_task) ( struct rq *rq); void (*task_tick) ( struct rq *rq, struct task_struct *p); void (*task_new) ( struct rq *rq, struct task_struct *p); };
调度类中包含任务的初始化、入队和出队等函数,这里的设计与面向对象中的设计稍微有些相似。内核中包含 SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE、SCHED_FIFO 和 SCHED_RR 调度类,这些不同的调度类分别实现了 sched_class 中的函数以提供不同的调度行为。
3. 小结
本节介绍了操作系统调度器的设计原理以及演进的历史,从 2007 年合入 CFS 到现在已经过去了很长时间,目前的 调度器 也变得更加复杂,社区也在不断改进进程调度器。
我们可以从 Linux 调度器的演进的过程看到主流系统架构的变化,最初几十行代码的调度器就能完成基本的调度功能,而现在要使用几万行代码来完成复杂的调度,保证系统的低延时和高吞吐量。
由于篇幅有限,我们很难对操作系统的调度器进行面面俱到的分析,你可以在 这里 找到作者使用的 Linux 源代码,亲自动手分析不同版本的进程调度器。
4. 延伸阅读 Understanding the Linux 2.6.8.1 CPU Scheduler CFS Scheduler Inside the Linux 2.6 Completely Fair Scheduler The Linux desktop may soon be a lot faster Modular Scheduler Core and Completely Fair Scheduler The Linux Scheduler: A Decade of Wasted Cores
Go 语言
Go 语言是诞生自 2009 年的编程语言,相信很多人对 Go 语言的印象都是语法简单,能够支撑高并发的服务。语法简单是编程语言的顶层设计哲学,而语言的高并发支持依靠的是运行时的调度器,这也是本节将要研究的内容。
对 Go 语言稍微有了解的人都知道, 通信顺序进程 (Communicating sequential processes,CSP)影响着 Go 语言的并发模型,其中的 Goroutine 和 Channel 分别表示实体和用于通信的媒介。
图 20 - Go 和 Erlang 的并发模型
『不要通过共享内存来通信,我们应该使用通信来共享内存』不只是 Go 语言鼓励的设计哲学,更为古老的 Erlang 语言其实也遵循了同样的设计,但是 Erlang 选择使用了 Actor 模型 ,我们在这里就不介绍 CSP 和 Actor 的区别和联系的,感兴趣的读者可以在推荐阅读和应引用中找到相关资源。
1. 设计与演进
今天的 Go 语言调度器有着非常优异的性能,但是如果我们回过头重新看 Go 语言的 v0.x 版本的调度器就会发现最初的调度器非常简陋,也无法支撑高并发的服务。整个调度器经过几个大版本的迭代才有了今天的优异性能。 单线程调度器 · 0.x - 源代码。只包含 40 多行代码;只能单线程调度,由 G-M 模型组成; 多线程调度器 · 1.0 - 源代码。引入了多线程调度;全局锁导致竞争严重; 任务窃取调度器 · 1.1 - 源代码。引入了处理器 P,构成了目前的 G-M-P 模型;在处理器 P 的基础上实现了基于工作窃取的调度器;在某些情况下,Goroutine 不会让出线程造成饥饿问题;时间过长的程序暂停(Stop-the-world,STW)会导致程序无法工作; 抢占式调度器 · 1.2 ~ 至今 - 源代码。实现基于信号的真抢占式调度;垃圾回收对栈进行扫描时会触发抢占调度;抢占的时间点不够多,还不能覆盖全部的边缘情况;通过编译器在函数调用时插入检查指令,实现基于协作的抢占式调度;GC 和循环可能会导致 Goroutine 长时间占用资源导致程序暂停;协作的抢占式调度器 - 1.2 ~ 1.13;抢占式调度器 - 1.14 ~ 至今; 非均匀存储访问调度器 · 提案。对运行时中的各种资源进行分区;实现非常复杂,到今天还没有提上日程;
除了多线程、任务窃取和抢占式调度器之外,Go 语言社区目前还有一个非均匀存储访问(Non-uniform memory access,NUMA)调度器的提案,将来有一天可能 Go 语言会实现这个调度器。在这一节中,我们将依次介绍不同版本调度器的实现以及未来可能会实现的调度器提案。
单线程调度器
Go 语言在 0.x 版本调度器中只包含表示 Goroutine 的 G 和表示线程的 M 两种结构体,全局也只有一个线程。我们可以在 clean up scheduler 提交中找到单线程调度器的源代码,在这时 Go 语言的 调度器 还是由 C 语言实现的,调度函数 schedule 中也只包含 40 多行代码 : static void scheduler(void) { G* gp; lock(&sched); if (gosave(&m->sched)){ lock(&sched); gp = m->curg; switch (gp->status){ case Grunnable: case Grunning: gp->status = Grunnable; gput(gp); break ; ... } notewakeup(&gp->stopped); } gp = nextgandunlock(); noteclear(&gp->stopped); gp->status = Grunning; m->curg = gp; g = gp; gogo(&gp->sched); }
该函数会遵循如下所示的过程执行: 获取调度器的全局锁; 调用 gosave 保存栈寄存器和程序计数器; 调用 nextgandunlock 获取下一个线程 M 需要运行的 Goroutine 并解锁调度器; 修改全局线程 m 上要执行的 Goroutine; 调用 gogo 函数运行最新的 Goroutine。
这个单线程调度器的唯一优点就是能跑,不过从这次提交中我们能看到 G 和 M 两个重要的数据结构,它建立了 Go 语言调度器的框架。
多线程调度器
Go 语言 1.0 版本在正式发布时就支持了多线程的调度器,与上一个版本完全不可用的调度器相比,Go 语言团队在这一阶段完成了从不可用到可用。我们可以在 proc.c 中找到 1.0.1 版本的调度器,多线程版本的调度函数 schedule 包含 70 多行代码,我们在这里保留了其中的核心逻辑: static void schedule(G *gp) { schedlock(); if (gp != nil) { gp->m = nil; uint32 v = runtime·xadd(&runtime·sched.atomic, -1 < maxgomaxprocs) runtime· throw ( "negative mcpu in scheduler" ); switch (gp->status){ case Grunning: gp->status = Grunnable; gput(gp); break ; case ...: } } else { ... } gp = nextgandunlock(); gp->status = Grunning; m->curg = gp; gp->m = m; runtime·gogo(&gp->sched, 0 ); }
整体的逻辑与单线程调度器没有太多区别,多线程调度器引入了 GOMAXPROCS 变量帮助我们控制程序中的最大线程数,这样我们的程序中就可能同时存在多个活跃线程。
多线程调度器的主要问题是调度时的锁竞争,Scalable Go Scheduler Design Doc 中对调度器做的 性能测试 发现 14% 的时间都花费在 runtime.futex 函数上,目前的调度器实现有以下问题需要解决: 全局唯一的调度器和全局锁,所有的调度状态都是中心化存储的,带来了锁竞争; 线程需要经常互相传递可运行的 Goroutine,引入了大量的延迟和额外开销; 每个线程都需要处理内存缓存,导致大量的内存占用并影响数据局部性(Data locality); 系统调用频繁阻塞和解除阻塞正在运行的线程,增加了额外开销。
这里的全局锁问题和 Linux 操作系统调度器在早期遇到的问题比较相似,解决方案也都大同小异。
任务窃取调度器
2012 年 Google 的工程师 Dmitry Vyukov 在 Scalable Go Scheduler Design Doc 中指出了现有多线程调度器的问题并在多线程调度器上提出了两个改进的手段: 在当前的 G-M 模型中引入了处理器 P; 在处理器 P 的基础上实现基于工作窃取的调度器。
基于任务窃取的 Go 语言调度器使用了沿用至今的 G-M-P 模型,我们能在 runtime: improved scheduler 提交中找到任务窃取调度器刚被实现时的源代码,调度器的 schedule 函数到现在反而更简单了: static void schedule ( void ) { G *gp; top: if (runtime·gcwaiting) { gcstopm(); goto top; } gp = runqget(m->p); if (gp == nil) gp = findrunnable(); ... execute(gp); } 如果当前运行时在等待垃圾回收,调用 gcstopm 函数; 调用 runqget 和 findrunnable 从本地的或者全局的运行队列中获取待执行的 Goroutine; 调用 execute 函数在当前线程 M 上运行 Goroutine。
当前处理器本地的运行队列中不包含 Goroutine 时,调用 findrunnable 函数会触发工作窃取,从其他的处理器的队列中随机获取一些 Goroutine。
运行时 G-M-P 模型中引入的处理器 P 是线程 M 和 Goroutine 之间的中间层,我们从它的结构体中就能看到 P 与 M 和 G 的关系: struct P { Lock; uint32 status; // one of Pidle/Prunning/... P* link; uint32 tick; // incremented on every scheduler or system call M* m; // back-link to associated M ( nil if idle) MCache* mcache; G** runq; int32 runqhead; int32 runqtail; int32 runqsize; G* gfree; int32 gfreecnt; };
处理器 P 持有一个运行队列 runq,这是由可运行的 Goroutine 组成的数组,它还反向持有一个线程 M 的指针。调度器在调度时会从处理器的队列中选择队列头的 Goroutine 放到线程 M 上执行。如下所示的图片展示了 Go 语言中的线程 M、处理器 P 和 Goroutine 的关系。
图 21 - G-M-P 模型
基于工作窃取的多线程调度器将每一个线程绑定到了独立的 CPU 上并通过不同处理器分别管理,不同处理器中通过工作窃取对任务进行再分配,提升了调度器和 Go 语言程序的整体性能,今天所有的 Go 语言服务的高性能都受益于这一改动。
抢占式调度器
对 Go 语言并发模型的修改提升了调度器的性能,但是在 1.1 版本中的调度器仍然不支持抢占式调度,程序只能依靠 Goroutine 主动让出 CPU 资源。Go 语言的调度器在 1.2 版本 中引入了基于协作的抢占式调度解决下面的 问题 : 单独的 Goroutine 可以一直占用线程运行,不会切换到其他的 Goroutine,造成饥饿问题; 垃圾回收需要暂停整个程序(Stop-the-world,STW),如果没有 抢占 可能需要等待几分钟的时间,导致整个程序无法工作。
然而 1.2 版本中实现的抢占式调度是基于协作的,在很长的一段时间里 Go 语言的调度器都包含一些无法被抢占的边缘情况,直到 1.14 才实现了基于信号的真抢占式调度解决部分问题。
基于协作的抢占式调度
我们可以在 proc.c 文件中找到引入抢占式调度后的调度器实现。Go 语言会在当前的分段栈机制上实现抢占式的调度,所有的 Goroutine 在函数调用时都有机会进入运行时检查是否需要执行抢占。基于协作的抢占是通过以下的多个提交实现的: runtime: mark runtime.goexit as nosplit runtime: add stackguard0 to G。为 Goroutine 引入 stackguard0 字段,当该字段被设置成 StackPreempt 时,Goroutine 会被抢占; runtime: introduce preemption function (not used for now)。引入抢占函数 preemptone 和 preemptall,这两个函数会设置 Goroutine 的 StackPreempt;引入抢占请求 StackPreempt; runtime: preempt goroutines for GC。在垃圾回收调用的 runtime·stoptheworld 中调用 preemptall 函数设置所有处理器上 Goroutine 的 StackPreempt;在 runtime·newstack 函数中增加抢占的代码,当 stackguard0 等于 StackPreempt 时触发调度器的抢占; runtime: preempt long-running goroutines。在系统监控中,如果一个 Goroutine 的运行时间超过 10ms,就会调用 retake 和 preemptone; runtime: more reliable preemption。修复 Goroutine 因为周期性执行非阻塞的 CGO 或者系统调用不会被抢占的问题。
从上述一系列的提交中,我们会发现 Go 语言运行时会在垃圾回收暂停程序、系统监控发现 Goroutine 运行超过 10ms 时提出抢占请求 StackPreempt;因为编译器会在函数调用中插入 runtime.newstack,所以函数调用时会通过 runtime.newstack 检查 Goroutine 的 stackguard0 是否为 StackPreempt 进而触发抢占让出当前线程。
这种做法没有带来运行时的过多额外开销,实现也相对比较简单,不过增加了运行时的复杂度,总体来看还是一种比较成功的实现。因为上述的抢占是通过编译器在特定时机插入函数实现的,还是需要函数调用作为入口才能触发抢占,所以这是一种协作式的抢占式调度。
基于信号的抢占式调度
协作的抢占式调度实现虽然巧妙,但是留下了很多的边缘情况,我们能在 runtime: non-cooperative goroutine preemption 中找到一些遗留问题: runtime: tight loops should be preemptible #10958 An empty for{} will block large slice allocation in another goroutine, even with GOMAXPROCS > 1 ? #17174 runtime: tight loop hangs process completely after some time #15442 ...
Go 语言在 1.14 版本中实现了非协作的抢占式调度,在实现的过程中我们对已有的逻辑进行重构并为 Goroutine 增加新的状态和字段来支持抢占。Go 团队通过下面提交的实现了这一功能,我们可以顺着提交的顺序理解其实现原理: runtime: add general suspendG/resumeG。挂起 Goroutine 的过程是在栈扫描时完成的,我们通过 runtime.suspendG 和 runtime.resumeG 两个函数重构栈扫描这一过程;调用 runtime.suspendG 函数时会将运行状态的 Goroutine 的 preemptStop 标记成 true;调用 runtime.preemptPark 函数可以挂起当前 Goroutine、将其状态更新成 _Gpreempted 并触发调度器的重新调度,该函数能够交出线程控制权; runtime: asynchronous preemption function for x86。在 x86 架构上增加异步抢占的函数 runtime.asyncPreempt 和 runtime.asyncPreempt2; runtime: use signals to preempt Gs for suspendG。支持通过向线程发送信号的方式暂停运行的 Goroutine;在 runtime.sighandler 函数中注册了 SIGURG 信号的处理函数 runtime.doSigPreempt;runtime.preemptM 函数可以向线程发送抢占请求; runtime: implement async scheduler preemption。修改 runtime.preemptone 函数的实现,加入异步抢占的逻辑。
目前的抢占式调度也只会在垃圾回收扫描任务时触发,我们可以梳理一下触发抢占式调度的过程: 程序启动时,在 runtime.sighandler 函数中注册了 SIGURG 信号的处理函数 runtime.doSigPreempt; 在触发垃圾回收的栈扫描时会调用 runtime.suspendG 函数挂起 Goroutine。将 _Grunning 状态的 Goroutine 标记成可以被抢占,即 preemptStop 设置成 true;调用 runtime.preemptM 函数触发抢占; runtime.preemptM 函数会调用 runtime.signalM 向线程发送信号 SIGURG; 操作系统会中断正在运行的线程并执行预先注册的信号处理函数 runtime.doSigPreempt; runtime.doSigPreempt 函数会处理抢占信号,获取当前的 SP 和 PC 寄存器并调用 runtime.sigctxt.pushCall; runtime.sigctxt.pushCall 会修改寄存器并在程序回到用户态时从 runtime.asyncPreempt 开始执行; 汇编指令 runtime.asyncPreempt 会调用运行时函数 runtime.asyncPreempt2; runtime.asyncPreempt2 会调用 runtime.preemptPark 函数; runtime.preemptPark 会修改当前 Goroutine 的状态到 _Gpreempted 并调用 runtime.schedule 让当前函数陷入休眠并让出线程,调度器会选择其他的 Goroutine 继续执行;
上述 9 个步骤展示了基于信号的抢占式调度的执行过程。我们还需要讨论一下该过程中信号的选择,提案根据以下的四个原因选择 SIGURG 作为触发异步抢占的 信号 : 该信号需要被调试器透传; 该信号不会被内部的 libc 库使用并拦截; 该信号可以随意出现并且不触发任何后果; 我们需要处理多个平台上的不同信号。
目前的抢占式调度也没有解决所有潜在的问题,因为 STW 和栈扫描时更可能出现问题,也是一个可以抢占的安全点(Safe-points),所以我们会在这里先加入 抢占功能 ,在未来可能会加入更多抢占时间点。
非均匀内存访问调度器
非均匀内存访问(Non-uniform memory access,NUMA)调度器目前只是 Go 语言的 提案 ,因为该提案过于复杂,而目前的调度器的性能已经足够优异,所以暂时没有实现该提案。该提案的原理就是通过拆分全局资源,让各个处理器能够就近获取本地资源,减少锁竞争并增加数据局部性。
在目前的运行时中,线程、处理器、网络轮训器、运行队列、全局内存分配器状态、内存分配缓存和垃圾收集器都是全局的资源。运行时没有保证本地化,也不清楚系统的拓扑结构,部分结构可以提供一定的局部性,但是从全局来看没有这种保证。
图 22 - Go 语言 NUMA 调度器
如上图所示,堆栈、全局运行队列和线程池会按照 NUMA 节点进行分区,网络轮训器和计时器会由单独的处理器持有。这种方式虽然能够利用局部性提高调度器的性能,但是本身的实现过于复杂,所以 Go 语言团队还没有着手实现这一提案。
2. 小结
Go 语言的调度器在最初的几个版本中迅速迭代,但是从 1.2 版本之后调度器就没有太多的变化,直到 1.14 版本引入了真正的抢占式调度解决了自 1.2 以来一直存在的问题。在可预见的未来,Go 语言的调度器还会进一步演进,增加抢占式调度的时间点减少存在的边缘情况。 本节内容选择《Go 语言设计与实现》一书中的 Go 语言调度器实现原理,你可以点击链接了解更多与 Go 语言设计与实现原理相关的内容。
3. 延伸阅读 How Erlang does scheduling Analysis of the Go runtime scheduler Go's work-stealing scheduler runtime: clean up async preemption loose ends The Go netpoller System Calls Make the World Go Round Linux Syscall Reference
Kubernetes
Kubernetes 是生产级别的容器调度和管理系统,在过去的一段时间中,Kubernetes 迅速占领市场,成为容器编排领域的实施标准。
图 23 - 容器编排系统演进
Kubernetes 是希腊语『舵手』的意思,它最开始由 Google 的几位软件工程师创立,深受公司内部 Borg 和 Omega 项目 的影响,很多设计都是从 Borg 中借鉴的,同时也对 Borg 的缺陷进行了改进,Kubernetes 目前是 Cloud Native Computing Foundation (CNCF) 的项目,也是很多公司管理分布式系统的 解决方案 。
调度器是 Kubernetes 的核心组件,它的主要功能是为待运行的工作负载 Pod 绑定运行的节点 Node。与其他调度场景不同,虽然资源利用率在 Kubernetes 中也非常重要,但是这只是 Kubernetes 关注的一个因素,它需要在容器编排这个场景中支持非常多并且复杂的业务需求,除了考虑 CPU 和内存是否充足,还需要考虑其他的领域特定场景,例如:两个服务不能占用同一台机器的相同端口、几个服务要运行在同一台机器上,根据节点的类型调度资源等。
这些复杂的业务场景和调度需求使 Kubernetes 调度器的内部设计与其他调度器完全不同,但是作为用户应用层的调度器,我们却能从中学到很多有用的模式和设计。接下来,本节将介绍 Kubernetes 中调度器的设计以及演变。
1. 设计与演进
Kubernetes 调度器的演变过程比较简单,我们可以将它的演进过程分成以下的两个阶段: 基于谓词和优先级的调度器 · v1.0.0 ~ v1.14.0 基于调度框架的调度器 · v1.15.0 ~ 至今
Kubernetes 从 v1.0.0 版本发布到 v1.14.0,总共 15 个版本一直都在使用谓词和优先级来管理不同的调度算法,知道 v1.15.0 开始引入调度框架(Alpha 功能)来重构现有的调度器。我们在这里将以 v1.14.0 版本的谓词和优先级和 v1.17.0 版本的调度框架分析调度器的演进过程。
谓词和优先级算法
谓词(Predicates)和优先级(Priorities)调度器是从 Kubernetes v1.0.0 发布时就存在的模式,v1.14.0 的最后实现与最开始的设计也没有太多区别。然而从 v1.0.0 到 v1.14.0 期间也引入了很多改进: 调度器扩展 · v1.2.0 - Scheduler extension。通过调用外部调度器扩展的方式改变调度器的决策; Map-Reduce 优先级算法 · v1.5.0 - MapReduce-like scheduler priority functions。为调度器的优先级算法支持 Map-Reduce 的计算方式,通过引入可并行的 Map 阶段优化调度器的计算性能; 调度器迁移 · v1.10.0 - Move scheduler code out of plugin directory。从 plugin/pkg/scheduler 移到 pkg/scheduler;kube-scheduler 成为对外直接提供的可执行文件;
谓词和优先级都是 Kubernetes 在调度系统中提供的两个抽象,谓词算法使用 FitPredicate 类型,而优先级算法使用 PriorityMapFunction 和 PriorityReduceFunction 两个类型: type FitPredicate func ( pod * v1 .Pod , meta PredicateMetadata , nodeInfo * schedulernodeinfo .NodeInfo ) ( bool , [] PredicateFailureReason , error ) type PriorityMapFunction func ( pod * v1 .Pod , meta interface {}, nodeInfo * schedulernodeinfo .NodeInfo ) ( schedulerapi .HostPriority , error ) type PriorityReduceFunction func ( pod * v1 .Pod , meta interface {}, nodeNameToInfo map [string] * schedulernodeinfo .NodeInfo , result schedulerapi .HostPriorityList ) error
因为 v1.14.0 也是作者刚开始参与 Kubernetes 开发的第一个版本,所以对当时的设计印象也非常深刻,v1.14.0 的 Kubernetes 调度器会使用 PriorityMapFunction 和 PriorityReduceFunction 这种 Map-Reduce 的方式计算所有节点的分数并从其中选择分数最高的节点。下图展示了,v1.14.0 版本中调度器的执行过程:
图 24 - 谓词和优先级算法
如上图所示,我们假设调度器中存在一个谓词算法和一个 Map-Reduce 优先级算法,当我们为一个 Pod 在 6 个节点中选择最合适的一个时,6 个节点会先经过谓词的筛选,图中的谓词算法会过滤掉一半的节点,剩余的 3 个节点经过 Map 和 Reduce 两个过程分别得到了 5、10 和 5 分,最终调度器就会选择分数最高的 4 号节点。
genericScheduler.Schedule 是 Kubernetes 为 Pod 选择节点的方法,我们省略了该方法中用于检查边界条件以及打点的代码: func (g *genericScheduler) Schedule(pod *v1.Pod, nodeLister algorithm.NodeLister) (result ScheduleResult, err error) { nodes, err := nodeLister.List() if err != nil { return result, err } iflen(nodes) == 0 { return result, ErrNoNodesAvailable } filteredNodes, failedPredicateMap, err := g.findNodesThatFit(pod, nodes) if err != nil { return result, err } ... priorityList, err := PrioritizeNodes(pod, g.nodeInfoSnapshot.NodeInfoMap, ..., g.prioritizers, filteredNodes, g.extenders) if err != nil { return result, err } host, err := g.selectHost(priorityList) return ScheduleResult{ SuggestedHost: host, EvaluatedNodes: len(filteredNodes) + len(failedPredicateMap), FeasibleNodes: len(filteredNodes), }, err } 从 NodeLister 中获取当前系统中存在的全部节点; 调用 genericScheduler.findNodesThatFit 方法并行执行全部的谓词算法过滤节点。谓词算法会根据传入的 Pod 和 Node 对节点进行过滤,这时会过滤掉端口号冲突、资源不足的节点;调用所有调度器扩展的 Filter 方法辅助过滤; 调用 PrioritizeNodes 函数为所有的节点打分。以 Pod 和 Node 作为参数并发执行同一优先级的 PriorityMapFunction;Pod 和优先级返回的 Node 到分数的映射为参数调用 PriorityReduceFunction 函数;调用所有调度器扩展的 Prioritize 方法;将所有分数按照权重相加后返回从 Node 到分数的映射; 调用 genericScheduler.selectHost 方法选择得分最高的节点。
这就是使用谓词和优先级算法时的调度过程,我们在这里省略了调度器的优先队列中的排序,出现调度错误时的抢占以及 Pod 持久存储卷绑定到 Node 上的过程,只保留了核心的调度逻辑。
调度框架
Kubernetes 调度框架是 Babak Salamat 和 Jonathan Basseri 2018 年提出的 最新调度器设计 ,这个提案明确了 Kubernetes 中的各个调度阶段,提供了设计良好的基于插件的接口。调度框架认为 Kubernetes 中目前存在调度(Scheduling)和绑定(Binding)两个循环: 调度循环在多个 Node 中为 Pod 选择最合适的 Node; 绑定循环将调度决策应用到集群中,包括绑定 Pod 和 Node、绑定持久存储等工作。
除了两个大循环之外,调度框架中还包含 QueueSort、PreFilter、Filter、PostFilter、Score、Reserve、Permit、PreBind、Bind、PostBind 和 Unreserve 11 个扩展点(Extension Point),这些扩展点会在调度的过程中触发,它们的运行顺序如下:
图 25 - Kubernetes 调度框架
我们可以将调度器中的 Scheduler.scheduleOne 方法作为入口分析基于调度框架的调度器实现,每次调用该方法都会完成一遍为 Pod 调度节点的全部流程,我们将该函数的执行过程分成调度和绑定两个阶段,首先是调度器的调度阶段: func (sched *Scheduler) scheduleOne(ctx context.Context) { fwk := sched.Framework podInfo := sched.NextPod() pod := podInfo.Pod state := framework.NewCycleState() scheduleResult, _ := sched.Algorithm.Schedule(schedulingCycleCtx, state , pod) assumedPod := podInfo.DeepCopy().Pod allBound, _ := sched.VolumeBinder.Binder.AssumePodVolumes(assumedPod, scheduleResult.SuggestedHost) if err != nil { return } if sts := fwk.RunReservePlugins(schedulingCycleCtx, state , assumedPod, scheduleResult.SuggestedHost); !sts.IsSuccess() { return } if err := sched.assume(assumedPod, scheduleResult.SuggestedHost); err != nil { fwk.RunUnreservePlugins(schedulingCycleCtx, state , assumedPod, scheduleResult.SuggestedHost) return } ... } 调用内部优先队列的 MakeNextPodFunc 返回的函数从队列中获取下一个等待调度的 Pod,用于维护等待 Pod 的队列会执行 QueueSort 插件; 调用 genericScheduler.Schedule 函数选择节点,该过程会执行 PreFilter、Filter、PostFilter、Score 四个扩展点的插件; 调用 framework.RunReservePlugins 函数运行 Reserve 插件用于保留资源并进入绑定阶段(绑定阶段运行时间较长,避免资源被抢占)。如果运行失败执行,调用 framework.RunUnreservePlugins 函数运行 Unreserve 插件。
因为每一次调度决策都会改变上下文,所以该阶段 Kubernetes 需要串行执行。而绑定阶段就是实现调度的过程了,我们会创建一个新的 Goroutine 并行执行绑定循环: func (sched *Scheduler) scheduleOne(ctx context.Context) { ... gofunc() { bindingCycleCtx, cancel := context.WithCancel(ctx) defer cancel() fwk.RunPermitPlugins(bindingCycleCtx, state , assumedPod, scheduleResult.SuggestedHost) if !allBound { sched.bindVolumes(assumedPod) } fwk.RunPreBindPlugins(bindingCycleCtx, state , assumedPod, scheduleResult.SuggestedHost) if err := sched.bind(bindingCycleCtx, assumedPod, scheduleResult.SuggestedHost, state ); err != nil { fwk.RunUnreservePlugins(bindingCycleCtx, state , assumedPod, scheduleResult.SuggestedHost) } else { fwk.RunPostBindPlugins(bindingCycleCtx, state , assumedPod, scheduleResult.SuggestedHost) } }() } 启动一个 Goroutine 并调用 framework.RunPermitPlugin 异步运行 Permit 插件,这个阶段可以用来实现批调度器; 调用 Scheduler.bindVolumes 将卷先绑定到 Node 上; 调用 Scheduler.bind 函数将 Pod 绑定到 Node 上完成调度,绑定的过程会执行 PreBind、Bind 和 PostBind 三个扩展点的插件。
目前的调度框架在 Kubernetes v1.17.0 版本中还是 Alpha 阶段,很多功能还不明确,为了支持更多、更丰富的场景,在接下来的几个版本还可能会做出很多改进,不过调度框架在很长的一段时间中都会是调度器的核心。
2. 小结
本节介绍了 Kubernetes 调度器从 v1.0.0 到最新版本中的不同设计,Kubernetes 调度器中总共存在两种不同的设计,一种是基于谓词和优先级算法的调度器,另一种是基于调度框架的调度器。
很多的业务调度器也需要从多个选项中选出最优的选择,无论是成本最低还是质量最优,我们可以考虑将调度的过程分成过滤和打分两个阶段为调度器建立合适的抽象,过滤阶段会按照需求过滤掉不满足需求的选项,打分阶段可能会按照质量、成本和权重对多个选项进行排序,遵循这种设计思路可以解决很多类似问题。
目前的 Kubernetes 已经通过调度框架详细地支持了多个阶段的扩展方法,几乎是调度器内部实现的最终形态了。不过随着调度器功能的逐渐复杂,未来可能还会遇到更复杂的调度场景,例如:多租户的调度资源隔离、多调度器等功能,而 Kubernetes 社区也一直都在为构建高性能的调度器而努力。
3. 延伸阅读 Scheduling Framework #624 Create a custom Kubernetes scheduler Scheduler extender Document
总结
从操作系统、编程语言到应用程序,我们在这篇文章中分析了 Linux、Go 语言和 Kubernetes 调度器的设计与实现原理,这三个不同的调度器其实有相互依赖的关系:
图 26 - 三层调度器
如上图所示,Kubernetes 的调度器依赖于 Go 语言的运行时调度器,而 Go 语言的运行时调度器也依赖于 Linux 的进程调度器,从上到下离用户越来越远,从下到上越来越关注具体业务。我们在最后通过两个比较分析一下这几个调度器的异同: Linux 进程调度器与 Go 语言调度器; 系统级调度器(Linux 和 Go)与业务调度器(Kubernetes)。
这是两种不同层面的比较,相信通过不同角度的比较能够让我们对调度器的设计有更深入的认识。
1. Linux 和 Go
首先是 Linux 和 Go 语言调度器,这两个调度器的场景都非常相似,它们最终都是要充分利用机器上的 CPU 资源,所以在实现和演进上有很多相似之处: 调度器的初始版本都非常简单,甚至很简陋,只能支持协作式的调度; 按照运行队列进行分区,通过工作窃取的方式平衡不同 CPU 或者线程上的运行队列; 最终都通过某些方式实现了基于信号的抢占式调度,不过 Go 语言的实现并不完善。
因为场景非常相似,所以它们的目的也非常相似,只是它们调度的任务粒度会有不同,Linux 进程调度器的最小调度单位是线程,而 Go 语言是 Goroutine,与 Linux 进程调度器相比,Go 语言在用户层建立新的模型,实现了另一个调度器,为使用者提供轻量级的调度单位来增强程序的性能,但是它也引入了很多组件来处理系统调用、网络轮训等线程相关的操作,同时组合多个不同粒度的任务导致实现相对复杂。
Linux 调度器的最终设计引入了调度类的概念,让不同任务的类型分别享受不同的调度策略以此来调和低延时和实时性这个在调度上两难的问题。
Go 语言的调度器目前刚刚引入了基于信号的抢占式调度,还有很多功能都不完善。除了抢占式调度之外,复杂的 NUMA 调度器提案也可能是未来 Go 语言的发展方向。
2. 系统和业务
如果我们将系统调度器和业务调度器进行对比的话,你会发现两者在设计差别非常大,毕竟它们处于系统的不同层级。系统调度器考虑的是极致的性能,所以它通过分区的方式将运行队列等资源分离,通过降低锁的粒度来降低系统的延迟;而业务调度器关注的是完善的调度功能,调度的性能虽然十分重要,但是一定要建立在满足特定调度需求之上,而因为业务上的调度需求往往都是比较复杂,所以只能做出权衡和取舍。
正是因为需求的不同,我们会发现不同调度器的演进过程也完全不同。系统调度器都会先充分利用资源,降低系统延时,随后在性能无法优化时才考虑加入调度类等功能满足不同场景下的调度,而 Kubernetes 调度器更关注内部不同调度算法的组织,如何同时维护多个复杂的调度算法,当设计了良好的抽象之后,它才会考虑更加复杂的多调度器、多租户等场景。
3. 最后
这种研究历史变化带来的快乐是很不同的,当我们发现代码发生变化的原因时也会感到欣喜,这让我们站在今天重新见证了历史上的决策,本文中的相应章节已经包含了对应源代码的链接,各位读者可以自行阅读相应内容,也衷心希望各位读者能够有所收获。

查看更多:https://yq.aliyun.com/articles/744477?utm_content=g_1000104318
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-18 14:28:03
「深度学习福利」大神带你进阶工程师,立即查看>>>
本文整理自《CNCF x Alibaba 云原生技术公开课》第 22 讲。
关注“阿里巴巴云原生”公众号,回复关键词 “入门” ,即可下载从零入门 K8s 系列文章 PPT。 导读 :有状态应用的部署交付向来都是应用运维领域的难点之一,常见的有状态需求如在磁盘持久化状态、每个机器需要独立且稳定的网络标识、发布顺序确定性等。针对这类问题 Kubernetes 提供了 StatefulSet 控制器,作为帮助有状态应用部署和在 K8s 环境落地的 Workload。
一、“有状态”需求
我们之前讲到过 Deployment 作为一个应用编排管理工具,它为我们提供了哪些功能?
如下图所示:
首先它支持定义一组 Pod 的期望数量,Controller 会为我们维持 Pod 的数量在期望的版本以及期望的数量; 第二它支持配置 Pod 发布方式,配置完成后 Controller 会按照我们给出的策略来更新 Pod,同时在更新的过程中,也会保证不可用 Pod 数量在我们定义的范围内; 第三,如果我们在发布的过程中遇到问题,Deployment 也支持一键来回滚。
可以简单地说, Deployment 认为:它管理的所有相同版本的 Pod 都是一模一样的副本。 也就是说,在 Deployment Controller 看来,所有相同版本的 Pod,不管是里面部署的应用还是行为,都是完全相同的。
这样一种能力对于无状态应用是支持满足的,如果我们遇到一些有状态应用呢?
需求分析
比如下图所示的一些需求:
以上的这些需求都是 Deployment 无法满足的,因此 Kubernetes 社区为我们提供了一个叫 StatefluSet 的资源,用来管理有状态应用。
StatefulSet:主要面向有状态应用管理的控制器
其实现在社区很多无状态应用也通过 StatefulSet 来管理,通过本文的学习,大家也会明白为什么我们将部分无状态应用也通过 StatefulSet 来管理。
如上图右侧所示,StatefulSet 中的 Pod 都是有序号的,从 0 开始一直到定义的 replica 数量减一。每个 Pod 都有独立的网络标识:一个 hostname、一块独立的 pvc 以及 pv 存储。这样的话,同一个 StatefulSet 下不同的 Pod,有不同的网络标识、有自己独享的存储盘,这就能很好地满足了绝大部分有状态应用的需求。
如上图右侧所示: 首先,每个 Pod 会有 Order 序号,会按照序号来创建,删除和更新 Pod; 其次,通过配置一个 headless Service,使每个 Pod 有一个唯一的网络标识 (hostname); 第三,通过配置 pvc 模板,就是 pvc template,使每个 Pod 有一块或者多块 pv 存储盘; 最后,支持一定数量的灰度发布。比如现在有三个副本的 StatefulSet,我们可以指定只升级其中的一个或者两个,更甚至是三个到新版本。通过这样的方式,来达到灰度升级的目的。
二、用例解读
StatefulSet 范例创建
上图左侧是一个 Service 的配置,我们通过配置 headless Service,其实想要达到的目标是:期望 StatefulSet 里面的 Pod 有独立的网络标识。这里的 Service name 叫 nginx。
上图右侧是一个 StatefulSet 的配置,在 spec 中有个 serviceName 也叫 nginx。通过这个 serviceName 来指定这个 StatefulSet 要对应哪一个 Service。
这个 spec 中还有其它几个很熟悉的字段,比如 selector 和 template。selector 是一个标签选择器,selector 定义的标签选择逻辑,必须匹配 template 中 metadata 中 labels 包含 app: nginx。在 template 中定义一个 nginx container,这个 container 用的 image 版本是 alpine 版本,对外暴露的 80 端口作为一个 web 服务。
最后,template.spec 里面定义了一个 volumeMounts,这个 volumeMounts 并不是来源于 spec 中的一个 Volumes,而是来自于 volumeClaimTemplates,也就是 pvc 模板。我们在 pvc 模板中定义了一个叫 www-storage 的 pvc 名称。这个 pvc 名称,我们也会写到 volumeMounts 作为一个 volume name,挂载到 /usr/share/nginx/html 这个目录下。通过这样的方式来达到每个 Pod 都有独立的一个 pvc,并且挂载到容器中对应目录的一个需求。
Service、StatefulSet 状态
通过将上文中的两个对象创建之后,我们可以通过 get 命令可以看到 Service nginx 资源已经创建成功。
同时可以通过查看 endpoints 看到,这个后端已经注册了三个 IP 和端口,这三个 IP 对应了 Pod 的 IP,端口对应了之前 spec 中配置的 80 端口。
最后通过 get sts (StatefulSet 缩写) nginx-web。从结果可以看到有一列叫做 READY,值为 3/3。分母 3 是 StatefulSet 中期望的数量,而分子 3 表示 Pod 已经达到期望 READY 的状态数量。
Pod、PVC 状态
下图中的 get pod 可以看到三个 Pod 的状态都是 Running 状态,并且已经 READY。它的 IP 就是前面看到的 endpoint 地址。
通过 get pvc 可以看到 NAME 那一列名称,前缀为 www-storage,中间是 nginx-web,后缀是一个序号。通过分析可以知道 www-storage 是 volumeClaimTemplates 中定义的 name,中间为 StatefulSet 定义的 name,末尾的序号对应着 Pod 的序号,也就是三个 PVC 分别被三个 Pod 绑定。通过这样一种方式,达到不同的 Pod 享有不同的 PVC;PVC 也会绑定相应的一个 PV, 来达到不同的 Pod 绑定不同 PV 的目的。
Pod 的版本
之前我们学到 Deployment 使用 ReplicaSet 来管理 Pod 的版本和所期望的 Pod 数量,但是在 StatefulSet 中,是由 StatefulSet Controller 来管理下属的 Pod,因此 StatefulSet 通过 Pod 的 label 来标识这个 Pod 所属的版本,这里叫 controller-revision-hash。这个 label 标识和 Deployment 以及 StatefulSet 在 Pod 中注入的 Pod template hash 是类似的。
如上图所示,通过 get pod 查看到 controller-revision-hash,这里的 hash 就是第一次创建 Pod 对应的 template 版本,可以看到后缀是 677759c9b8。这里先记录一下,接下来会做 Pod 升级,再来看一下 controller-revision-hash 会不会发生改变。
更新镜像
通过执行上图的命令,可以看到上图下方的 StatefulSet 配置中,已经把 StatefulSet 中的 image 更新到了 mainline 新版本。
查看新版本状态
通过 get pod 命令查询 Revision hash,可以看到三个 Pod 后面的 controller-revision-hash 都已经升级到了新的 Revision hash,后面变成了 7c55499668。通过这三个 Pod 创建的时间可以发现:序号为 2 的 Pod 创建的是最早的,之后是序号是 1 和 0。这表示在升级的过程中,真实的升级顺序为 2-1-0,通过这么一个倒序的顺序来逐渐把 Pod 升级为新版本,并且我们升级的 Pod,还复用了之前 Pod 使用的 PVC。所以之前在 PV 存储盘中的数据,仍然会挂载到新的 Pod 上。
上图右上方是在 StatefulSet 的 status 中看到的数据,这里有几个重要的字段: currentReplica :表示当前版本的数量 currentRevision: 表示当前版本号 updateReplicas :表示新版本的数量 updateRevision: 表示当前要更新的版本号
当然这里也能看到 currentReplica 和 updateReplica,以及 currentRevision 和 updateRevision 都是一样的,这就表示所有 Pod 已经升级到了所需要的版本。
三、操作演示
StatefulSet 编排文件
首先这里已经连接到了阿里云的一个集群,集群中有三个节点。
现在开始创建一个 StatefulSet 和对应的 Service,首先看一下对应的编排文件。
如上图中的例子所示,Service 对应的 nginx 对外暴露的 80 端口。StatefulSet 配置中 metadata 定义了 name 为 nginx-web;template 中的 containers 定义了镜像信息;最后定义了一个 volumeClaimTemplates 作为 PVC 模板。
开始创建
执行上面的命令后,我们就把 Service 和 StatefulSet 创建成功了。通过 get pod 可以看到首先创建的 Pod 序号为 0;通过 get pvc 可以看到序号为 0 的 PVC 已经和 PV 进行了绑定。
此时序号为 0 的 Pod 已经开始创建了,状态为 ContainerCreating。
当序号为 0 的 Pod 创建完成后,开始创建序号为 1 的 Pod,然后看到新的 PVC 也已经创建成功,紧接着是序号为 2 的 Pod。
可以看到每一个 Pod 创建之前,会先创建 PVC。PVC 创建完成后,Pod 从 Pending 状态和 PV 进行绑定,然后变成了 ContainerCreating,最后达到 Running。
查看状态
然后通过 kubectl get sts nginx-web -o yaml 查看 StatefulSet 的状态。
如上图所示,期望的 replicas 数量为 3 个,当前可用数量为 3 个,并且达到最新的版本。
接着来看一下 Service 和 endpoints,可以看到 Service 的 Port 为 80,endpoint 有三个对应的 IP 地址。
再来 get pod ,可以看到三个 Pod 对应了上面的 endpoints 的 IP 地址。
以上的操作结果为:三个 PVC 和三个 Pod 已经达到了所期望的状态,并且 StatefulSet 上报的 status 中,replicas 以及 currentReplicas 都为三个。
升级操作
这里重复说一下,kubectl set image 为声明镜像的固定写法;StatefulSet 表示自愿类型;nginx-web是资源名称;nginx=nginx:mainline,等号前面的 nginx 是我们在 template 中定义的 container 名称,后面的nginx:mainline是所期望更新的镜像版本。
通过上面的命令,已经成功的将 StatefulSet 中的镜像更新为新的版本。
通过 get pod 看一下状态,nginx-web-1,nginx-web-2 已经进入了 Running 状态。对应的 controller-revision-hash 已经是新的版本。那么 nginx-web-0 这个 Pod,旧的 Pod 已经被删除了,新 Pod 还在 Createing 状态。
再次查看一下状态,所有的 Pod 都已经 Running 状态了。
查看一下 StatefulSet 信息,目前 StatefulSet 中的 status 里定义的 currentRevision 已经更新到了新的版本,表示 StatefulSet 已经获取到的三个 Pod 都已经进入了新版本。
如何查看这三个 Pod 是否还复用了之前的网络标识和存储盘呢?
其实 headless Service 配置的 hostname 只是和 Pod name 挂钩的,所以只要升级后的 Pod 名称和旧的 Pod 名称相同,那么就可以沿用之前 Pod 使用的网络标识。
关于存储盘,由上图可以看到 PVC 的状态,它们的创建时间一直没有改变,还是第一次创建 Pod 时的时间,所以现在升级后的 Pod 使用的还是旧 Pod 中使用的 PVC。
比如可以查看其中的某一个 Pod,这个 Pod 里面同样有个声明的 volumes,这个 persistentVolumeClaim 里的名称 www-storage-nginx-web-0,对应着 PVC 列表中看到的序号为 0 的 PVC,之前是被旧的 Pod 所使用。升级过程中 Controller 删除旧 Pod,并且创建了一个同名的新 Pod,新的 Pod 仍然复用了旧 Pod 所使用的 PVC。
通过这种方式来达到升级前后,网络存储都能复用的目的。
四、架构设计
管理模式
StatefulSet 可能会创建三种类型的资源。 第一种资源:ControllerRevision
通过这个资源,StatefulSet 可以很方便地管理不同版本的 template 模板。
举个例子:比如上文中提到的 nginx,在创建之初拥有的第一个 template 版本,会创建一个对应的 ControllerRevision。而当修改了 image 版本之后,StatefulSet Controller 会创建一个新的 ControllerRevision,大家可以理解为每一个 ControllerRevision 对应了每一个版本的 Template,也对应了每一个版本的 ControllerRevision hash。其实在 Pod label 中定义的 ControllerRevision hash,就是 ControllerRevision 的名字。通过这个资源 StatefulSet Controller 来管理不同版本的 template 资源。 第二个资源:PVC
如果在 StatefulSet 中定义了 volumeClaimTemplates,StatefulSet 会在创建 Pod 之前,先根据这个模板创建 PVC,并把 PVC 加到 Pod volume 中。
如果用户在 spec 的 pvc 模板中定义了 volumeClaimTemplates,StatefulSet 在创建 Pod 之前,根据模板创建 PVC,并加到 Pod 对应的 volume 中。当然也可以在 spec 中不定义 pvc template,那么所创建出来的 Pod 就不会挂载单独的一个 pv。 第三个资源:Pod
StatefulSet 按照顺序创建、删除、更新 Pod,每个 Pod 有唯一的序号。
如上图所示,StatefulSet Controller 是 Owned 三个资源:ControllerRevision、Pod、PVC。
这里不同的地方在于,当前版本的 StatefulSet 只会在 ControllerRevision 和 Pod 中添加 OwnerReference,而不会在 PVC 中添加 OwnerReference。之前的系列文章中提到过,拥有 OwnerReference 的资源,在管理的这个资源进行删除的默认情况下,会关联级联删除下属资源。因此默认情况下删除 StatefulSet 之后,StatefulSet 创建的 ControllerRevision 和 Pod 都会被删除,但是 PVC 因为没有写入 OwnerReference,PVC 并不会被级联删除。
StatefulSet 控制器
上图为 StatefulSet 控制器的工作流程,下面来简单介绍一下整个工作处理流程。
首先通过注册 Informer 的 Event Handler(事件处理),来处理 StatefulSet 和 Pod 的变化。在 Controller 逻辑中,每一次收到 StatefulSet 或者是 Pod 的变化,都会找到对应的 StatefulSet 放到队列。紧接着从队列取出来处理后,先做的操作是 Update Revision,也就是先查看当前拿到的 StatefulSet 中的 template,有没有对应的 ControllerRevision。如果没有,说明 template 已经更新过,Controller 就会创建一个新版本的 Revision,也就有了一个新的 ControllerRevision hash 版本号。
然后 Controller 会把所有版本号拿出来,并且按照序号整理一遍。这个整理的过程中,如果发现有缺少的 Pod,它就会按照序号去创建,如果发现有多余的 Pod,就会按照序号去删除。当保证了 Pod 数量和 Pod 序号满足 Replica 数量之后,Controller 会去查看是否需要更新 Pod。也就是说这两步的区别在于,Manger pods in order 去查看所有的 Pod 是否满足序号;而后者 Update in order 查看 Pod 期望的版本是否符合要求,并且通过序号来更新。
Update in order 其更新过程如上图所示,其实这个过程比较简单,就是删除 Pod。删除 Pod 之后,其实是在下一次触发事件,Controller 拿到这个 success 之后会发现缺少 Pod,然后再从前一个步骤 Manger pod in order 中把新的 Pod 创建出来。在这之后 Controller 会做一次 Update status,也就是之前通过命令行看到的 status 信息。
通过整个这样的一个流程,StatefulSet 达到了管理有状态应用的能力。
扩容模拟
假设 StatefulSet 初始配置 replicas 为 1,有一个 Pod0。那么将 replicas 从 1 修改到 3 之后,其实我们是先创建 Pod1,默认情况是等待 Pod1 状态 READY 之后,再创建 Pod2。
通过上图可以看到每个 StatefulSet 下面的 Pod 都是从序号 0 开始创建的。因此一个 replicas 为 N 的 StatefulSet,它创建出来的 Pod 序号为 [0,N),0 是开曲线,N 是闭曲线,也就是当 N>0 的时候,序号为 0 到 N-1。
扩缩容管理策略
可能有的同学会有疑问:如果我不想按照序号创建和删除,那 StatefulSet 也支持其它的创建和删除的逻辑,这也就是为什么社区有些人把无状态应用也通过 StatefulSet 来管理。它的好处是它能拥有唯一的网络标识以及网络存储,同时也能通过并发的方式进行扩缩容。
StatefulSet.spec 中有个字段叫 podMangementPolicy 字段,这个字段的可选策略为 OrderedReady 和 Parallel,默认情况下为前者。
如我们刚才创建的例子,没有在 spec 中定义 podMangementPolicy。那么 Controller 默认 OrderedReady 作为策略,然后在 OrderedReady 情况下,扩缩容就严格按照 Order 顺序来执行,必须要等前面的 Pod 状态为 Ready 之后,才能扩容下一个 Pod。在缩容的时候,倒序删除,序号从大到小进行删除。
举个例子,上图右侧中,从 Pod0 扩容到 Pod0、Pod1、Pod2 的时候,必须先创建 Pod1,等 Pod1 Ready 之后再创建 Pod2。其实还存在一种可能性:比如在创建 Pod1 的时候,Pod0 因为某些原因,可能是宿主机的原因或者是应用本身的原因,Pod0 变成 NotReady 状态。这时 Controller 也不会创建 Pod2,所以不只是我们所创建 Pod 的前一个 Pod 要 Ready,而是前面所有的 Pod 都要 Ready 之后,才会创建下一个 Pod。上图中的例子,如果要创建 Pod2,那么 Pod0、Pod1 都要 ready。
另一种策略叫做 Parallel,顾名思义就是并行扩缩容,不需要等前面的 Pod 都 Ready 或者删除后再处理下一个。
发布模拟
假设这里的 StatefulSet template1 对应逻辑上的 Revision1,这时 StatefulSet 下面的三个 Pod 都属于 Revision1 版本。在我们修改了 template,比如修改了镜像之后,Controller 是通过倒序的方式逐一升级 Pod。上图中可以看到 Controller 先创建了一个 Revision2,对应的就是创建了 ControllerRevision2 这么一个资源,并且将 ControllerRevision2 这个资源的 name 作为一个新的 Revision hash。在把 Pod2 升级为新版本后,逐一删除 Pod0、Pod1,再去创建 Pod0、Pod1。
它的逻辑其实很简单,在升级过程中 Controller 会把序号最大并且符合条件的 Pod 删除掉,那么删除之后在下一次 Controller 在做 reconcile 的时候,它会发现缺少这个序号的 Pod,然后再按照新版本把 Pod 创建出来。
spec 字段解析
首先来看一下 spec 中前几个字段,Replica 和 Selector 都是我们比较熟悉的字段。 Replica 主要是期望的数量; Selector 是事件选择器,必须匹配 spec.template.metadata.labels 中定义的条件; Template:Pod 模板,定义了所要创建的 Pod 的基础信息模板; VolumeClaimTemplates:PVC 模板列表,如果在 spec 中定义了这个,PVC 会先于 Pod 模板 Template 进行创建。在 PVC 创建完成后,把创建出来的 PVC name 作为一个 volume 注入到根据 Template 创建出来的 Pod 中。
ServiceName:对应 Headless Service 的名字。当然如果有人不需要这个功能的时候,会给 Service 定一个不存在的 value,Controller 也不会去做校验,所以可以写一个 fake 的 ServiceName。但是这里推荐每一个 Service 都要配置一个 Headless Service,不管 StatefulSet 下面的 Pod 是否需要网络标识; PodMangementPolicy:Pod 管理策略。前面提到过这个字段的可选策略为 OrderedReady 和 Parallel,默认情况下为前者; UpdataStrategy:Pod 升级策略。这是一个结构体,下面再详细介绍; RevisionHistoryLimit:保留历史 ControllerRevision 的数量限制(默认为 10)。需要注意的一点是,这里清楚的版本,必须没有相关的 Pod 对应这些版本,如果有 Pod 还在这个版本中,这个 ControllerRevision 是不能被删除的。
升级策略字段解析
在上图右侧可以看到 StatefulSetUpdateStrategy 有个 type 字段,这个 type 定义了两个类型:一个是 RollingUpdate;一个是OnDelete。 RollingUpdate 其实跟 Deployment 中的升级是有点类似的,就是根据滚动升级的方式来升级; OnDelete 是在删除的时候升级,叫做禁止主动升级,Controller 并不会把存活的 Pod 做主动升级,而是通过 OnDelete 的方式。比如说当前有三个旧版本的 Pod,但是升级策略是 OnDelete,所以当更新 spec 中镜像的时候,Controller 并不会把三个 Pod 逐一升级为新版本,而是当我们缩小 Replica 的时候,Controller 会先把 Pod 删除掉,当我们下一次再进行扩容的时候,Controller 才会扩容出来新版本的 Pod。
在 RollingUpdateStatefulSetSetStrategy 中,可以看到有个字段叫 Partition。这个 Partition 表示滚动升级时,保留旧版本 Pod 的数量。很多刚结束 StatefulSet 的同学可能会认为这个是灰度新版本的数量,这是错误的。
举个例子:假设当前有个 replicas 为 10 的 StatefulSet,当我们更新版本的时候,如果 Partition 是 8,并不是表示要把 8 个 Pod 更新为新版本,而是表示需要保留 8 个 Pod 为旧版本,只更新 2 个新版本作为灰度。当 Replica 为 10 的时候,下面的 Pod 序号为 [0,9),因此当我们配置 Partition 为 8 的时候,其实还是保留 [0,7) 这 8个 Pod 为旧版本,只有 [8,9) 进入新版本。
总结一下,假设 replicas=N,Partition=M (M
五、本节总结
本文的主要内容就到此为止了,这里为大家简单总结一下: StatefulSet 是 Kubernetes 中常见的一种 Workload,其初始目标是面向有状态应用部署,但也支持部署无状态应用; 与 Deployment 不同,StatefulSet 是直接操作 Pod 来做扩缩容/发布,并没有通过类似 ReplicaSet 的其他 workload 来管控; StatefulSet 的特点是:支持每个 Pod 独享 PVC、有一个唯一网络标识,且在升级发布后还能复用 PVC 和网络标识;


查看更多:https://yq.aliyun.com/articles/744513?utm_content=g_1000104317
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-18 14:24:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
导读 : OAM 是阿里巴巴联合微软在社区推出的一款用于构建和交付云原生应用的标准规范,旨在通过全新的应用定义、运维、分发与交付模型,推动应用管理技术向“轻运维”的方向迈进,全力开启下一代云原生 DevOps 的技术革命。
背景
OAM 是阿里巴巴联合微软在社区推出的一款用于构建和交付云原生应用的标准规范,之前我们已经发布过一系列介绍文章,为方便大家查阅,链接和介绍如下: 《4 个概念,1 个动作,让应用管理变得更简单》 :具体而详实的介绍了 OAM 方方面面的细节; 《给 K8s API “做减法”:阿里巴巴云原生应用管理的挑战和实践》 :介绍了我们在探索出 OAM 之前的一些实践背景以及为什么会自然而然地设计出 OAM 这样的应用模型; 《OAM 加持下的 Kubernetes PaaS 应用管理实践》 :围绕目前常见的基于 Kubernetes 构建 PaaS 的各类解决方案,介绍了 OAM 如何将这些方案有机结合并最终统一,然后进一步的通过标准化模块化的组织,发挥生态的力量,使得彼此协作互惠互利成为可能; 《开放应用模型操作指南(一):云服务“一键接入” OAM 体系》 :以云资源为例,介绍了如何介入 OAM 体系的方法和实践。
在上面的几篇文章中,我们介绍了为什么云原生应用需要标准定义,以及 OAM 模型到底是什么样子的。今天则为大家介绍 OAM 本身有哪些价值,即回答为什么是使用 OAM 来作为应用标准模型。
AWS 构建 ECS CLI v2 的开发原则
本月初(2019 年 12 月),AWS 发布了 ECS CLI v2 ,这是自 2015 年发布 v1 以后,四年来首次发布的大版本更新,这次发布的 v2 版本命令行工具将更关注端到端的应用体验,即管理从源代码开发到应用部署的全方位应用交付流程。他们基于多年来收到的用户反馈总结了四条 CLI 的开发原则: 默认创建现代化的应用 。创建的现代化应用默认满足这几个特征:无服务化 (serverless),基础设施即代码 (infrastructure as code),可观测 (observable),安全 (secure); 用户应该考虑的是架构,而不是基础设施 。开发者构建微服务的时候,不应该指定 VPC、负载均衡配置亦或是复杂的 Pipeline 流程配置。开发者可以对云服务一无所知,但是他们应该制定应用到底属于哪种类型,即应用应该适配哪种架构,基础设施应该根据应用指定的架构自动匹配资源; 运维也应该是工作流的一部分 。应用的构建、开发、部署只是应用生命周期中由应用开发者负责的一部分。应用的全生命周期中还应该包含运维的部分,即问题排查和解决; 应用交付是持续的 。应用的升级变更也应该方便地集成到 CI/CD 系统中。
这几条原则与其说是 ECS CLI 的开发原则,不如说是用户的诉求,用户希望他们的应用是现代化的(或者说云原生化的);用户希望他们指定架构,而不是具体的基础设施资源;用户希望运维能力也被统一管理进应用的生命周期;用户希望应用的变更交付可以持续、透明、方便的对接并被 CI/CD 系统管理。
OAM 模型的价值
针对上述用户的诉求,我们一个个来看 OAM 是如何满足的,同时也能看出 OAM 在其中发挥的价值。
云原生化 OAM 应用定义是声明式的,即面向终态的,它的格式与 K8s 的 API 一致,可以与 K8s 的 CRD 无缝对接,直接作为 Custom Resource 的 Object 部署到 K8s; OAM 应用定义是自包含的,通过 OAM 定义的描述可以找到包含一个应用生命周期中方方面面所有的信息。
如下图所示,你可以看到运行 OAM 的一个应用配置,使用 K8s 的 API spec,完整包含了一个应用方方面面的资源。
平台无关、运行时无关
OAM 应用定义并不限定你底层的平台和实际运行时,你完全可以运行在 K8s 以外的平台,不管是 ECS、Docker、又或是 FaaS (Serverless),自然也不存在厂商锁定的问题。如果你的应用满足 Serverless 的条件,那么针对该应用的 OAM 描述,天然就可以运行在支持 OAM 规范的 Serverless 运行时。
在支持 OAM 的不同环境中,你便可以使用统一的应用描述,打造无差别的应用交付。就如下图所示,对应用户,他们只要描述统一的应用配置,便可以在不同的环境达到一致的应用体验。
基础设施即代码
云原生的普及很大程度上推动了基础设施即代码的实现,K8s 作为一个基础设施平台,通过声明式 API,让用户习惯了 通过 Yaml 文件描述需要的资源,这其实就是基础设施即代码的实现。 而 OAM 更进一步,还将原生 K8s 中没有包含的基础设施资源也统一定义起来,通过配置 OAM 规范的 yaml(代码)来使用基础设施。
如今阿里云上的 资源编排产品 ROS 的 OAM 实现就是这样一个典范,你完全可以通过 OAM 的配置拉起一个云上的基础设施资源。
让我们来实际看一个例子,为拉起一个 NAS 持久化存储,其中包含两个 ROS 的资源,分别为 NAS FileSystem 和 NAS MountTarget 。 apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: nasFileSystem annotations: version: v1.0.0 description: > component schematic that describes the nas filesystem. spec: workloadType: ros.aliyun.com/v1alpha1.NAS_FileSystem workloadSettings: ProtocolType: NFS StorageType: Performance Description: xxx expose: - name: fileSystemOut --- apiVersion: core.oam.dev/v1alpha1 kind: ComponentSchematic metadata: name: nasMountTarget annotations: version: v1.0.0 description: > component schematic that describes the nas filesystem. spec: workloadType: ros.aliyun.com/v1alpha1.NAS_MountTarget workloadSettings: NetworkType: VPC AccessGroupName: xxx FileSystemId: ${fileSystemOut.FileSystemId} consume: - name: fileSystemOut expose: - name: moutTargetOut --- apiVersion: core.oam.dev/v1alpha1 kind: ApplicationConfiguration metadata: name: nas-demo spec: components: - componentName: nasMountTarget traits: - name: DeletionPolicy properties: "Retain" - componentName: nasFileSystem traits: - name: DeletionPolicy properties: "Retain"
在定义中,你可以看到 NAS MountTarget 使用了 NAS FileSystem 输出的 FileSystemId,最终这个 yaml 会由 ROS 的 OAM Controller 翻译为 ROS 的资源栈模板文件,最终拉起云上的资源。
关心架构而不是基础设施
用户的诉求其实是应用的架构,而不是具体使用哪种基础设施资源。而 OAM 通过 "WorkloadType" 来解决这个诉求,通过描述一个应用的 WorkloadType 来定义应用的架构,这个 WorkloadType 可以是简单的无状态应用 "Server",表示应用可复制、可访问、并作为守护进程长久运行;也可以是一个数据库类型的应用 "RDS",对应启动一个云上的 RDS 实例。
用户的组件 "Component" 通过指定 "WorkloadType" 选择具体的架构模板,多个 Component 构成了完整的架构。
使用 OAM 应用定义让用户真正关心的是架构,而不是具体的基础设施。
如下图所示,OAM 的一个应用描述,用户指定它的应用需要一个外网访问能力,而不是指定一个 SLB,用户指定它的组件是数据库的。
运维能力管理
用户希望运维能力也是应用生命周期的一部分,而 OAM 正是如此,通过绑定 Trait,来定义一个 Component 所使用到的运维能力,从而把运维能力也加入到应用描述中,方便底层基础设施统一管理。
如下图所示,一个应用包含两部分组件,一个 web 服务和一个数据库, 数据库组件应该具有数据备份的能力,而 web 服务则可以被访问、可以弹性扩缩容。这些能力由 OAM 的解释器(即 OAM 的实现层)统一管理,从此运维能力绑定出现冲突也不再是烦恼。
透明化的集成
就像 Docker 镜像解决了长久以来开发、测试、生产环境不一致一样,统一而标准化的 OAM 应用描述也让不同系统之间的集成变得透明而标准化。
不同的角色关注点分离
OAM 也将原先 K8s All-in-one 的复杂 API 做了一定层次的解耦,分为应用研发(管理 Component)、应用运维(将 Component 组合并绑定 Trait 变成 AppConfig)、以及基础设施提供方(提供 OAM 的解释能力映射到实际的基础设施)三大角色,不同角色分工协作,从而整体简化单个角色关注的内容。使得不同角色可以更聚焦更专业的做好本角色的工作。
弹性可扩展
OAM 应用定义是弹性、可扩展的,你可以通过扩展 Workload 来定义不同类型的工作负载,你也可以通过自定义的 Trait 来描述你的运维能力,而且都可以与现有的 K8s 体系里面 CRD+Operator 的扩展方式完美结合。
模块化协作
OAM 通过关注点分离的思想,将应用分为研发、运维和基础设施三个层次,同时又为研发的 Workload 和运维的 Trait 提供了模块化协作的能力,大大提高了复用能力。
当模块化的 Workload 和 Trait 越来越多,就会形成这些组件的市场,用户可以在 CRD Registry 这样的注册中心,快速找到适合自己的应用的架构(Workload),以及自己需要使用的运维能力(Trait)。构建应用将越来越简单。
未来
相信应用的构建会越来越简单,基础设施的选择会根据用户的架构需求自动匹配,用户可以真正享受到云平台化的红利,快速复用已有的模块化能力,而 OAM 也将成为应用云原生化的必然选择。
目前,阿里巴巴团队正在上游贡献和维护这套技术,如果大家有什么问题或者反馈,也非常欢迎与我们在上游或者钉钉联系。


查看更多:https://yq.aliyun.com/articles/744417?utm_content=g_1000104315
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-18 13:43:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
前言
首先介绍下在本文出现的几个比较重要的概念: 函数计算(Function Compute) : 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费。函数计算更多信息 参考 。
Fun : Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算、API 网关、日志服务等资源。它通过一个资源配置文件(template.yml),协助您进行开发、构建、部署操作。Fun 的更多文档 参考 。
NAS : 阿里云文件存储NAS是一个可共享访问,弹性扩展,高可靠,高性能的分布式文件系统。在函数计算的场景中,由于其有代码包的限制,可以使用 NAS 存放一些不经常变动的文件,比如数据模型、静态资源等。 参考 。
ROS : 阿里云资源编排服务(ROS)助您简化云计算资源的管理。您可以遵循ROS定义的模板规范,在模板中定义所需云计算资源的集合及资源间依赖关系。ROS自动完成所有资源的创建和配置,实现自动化部署和运维。更多文档 参考 。
备注: 本文介绍的技巧需要 Fun 版本大于等于 3.4.0。
基本上所有的 faas 平台为了优化函数的冷启动,都会加入代码包的限制。阿里云 函数计算 (FC)也不例外。FC 要求压缩后的代码包大小不超过 50MB。由于用户的函数代码可能需要大量的依赖库,所以代码包很容易达到函数计算设定的阈值。

传统解决方式
基于平台硬性要求下,依然有很多途径来解决函数计算上传代码包大小受限的问题。下面列举 3 种常见的解决方案。 备注 : 包括不局限于以下方案。
1. 提交工单
提交函数计算工单,由后台人员为您账号开通上传限制白名单。但提交工单依然有代码包的限制。部署时代码包上传时间长,并且也增加了函数的冷启动时间,影响函数性能。 PS:通过使用 预留模式 可以完全去除冷启动,由于超出本文范围,这里不再阐述。
2. 利用 OSS 来实现
对代码包进行分类,除项目代码和少量依赖库可以在创建函数时上传到函数计算,用户将部分依赖库预先上传到 OSS,并在函数被触发执行时开始从 OSS 上加载依赖, 这类依赖的加载操作均可定义应用层冷启动,当加载依赖结束后,应用层冷启动才结束,函数的处理逻辑才开始执行,应用层冷启动的开销往往会导致毛刺的产生,影响函数的性能。您可以将从 OSS 加载代码包的代码逻辑放在 initializer 函数中。既可以解决函数计算对上传代码包的限制问题,又不影响函数的性能。但需要用户进行额外的操作。参考 使用 initializer 接口解决函数计算上传代码包大小受限问题 。
3. 基于文件系统 Nas
我们通过 NAS 存放一些体积比较大且不易变动的资源。这使得即使在依赖比较大的场景下依旧适用。NAS 在帮助函数计算解决大依赖问题的同时,由于其自身的配置也比较复杂,也增加了函数计算的使用难度。如何管理 Nas 资源、上传本地资源到Nas 以及服务级别的 Nas 相关配置参考 开发函数计算的正确姿势 —— 使用 Fun NAS 管理 NAS 资源 。
本文旨在介绍第四种方式 -- 使用 Fun 工具来解决函数计算对上传代码包的限制,使得部署步骤简单明了,不需关心额外的配置。同时也展现 Fun 工具对大依赖场景的顺滑体验,Let's go!
注 :已经获得 fc 支持的 runtime 有 nodejs6,nodejs8,nodejs10,python2.7,python3,java8,php7.2,dotnetcore2.1,custom。目前基于所有 fc 支持的 runtime,大依赖场景下除 php7.2 和 dotnetcore2.1 其他都支持。

环境准备
Fun 安装教程 可以直接在这里下载二进制版本的 Fun,解压后即可使用。
执行 fun --version 检查 Fun 是否安装成功。 $ fun --version 3.4 . 0

Fun 判定大依赖的标准是什么?
fun install 是 fun 工具的一个子命令,用于安装 pip,apt 依赖等,提供了命令行接口和 Funfile 描述文件两种形式。对于 fun install 安装的依赖,当 fun deploy 部署时会自动处理大依赖。
当 Fun 检测打包的代码压缩后超过限制(50M),会根据对应的 runtime 分离大依赖和代码。Fun 会将大依赖目录分为:系统依赖和语言依赖。系统依赖的本地路径为 .fun/root ,语言依赖根据函数 runtime 得到,各个 runtime 对应的大依赖目录映射如下:
语言(runtime) 大依赖目录(directory) nodejs6 node_modules
nodejs8 node_modules
nodejs10 node_modules
python2.7 .fun/python
python3 .fun/python
java8
custom
.fun/build/artifacts
/
自定义执行环境 custom 大依赖目录为 / ,可以理解为其他 runtime 大依赖目录的合集。例如:函数 runtime 为 custom,若目录下存在 node_modules 或 .fun/python 等,Fun 在部署向导过程中会把它们都认定为大依赖,会分别对其处理。

Fun deploy 对大依赖的支持
函数计算的命令行工具 Fun 现在原生支持了这种大依赖部署,不需要任何额外操作。仅仅执行 fun deploy : $ fun deploy
整体流程如下图所示:
fun deploy 会自动完成依赖的部署。而当检测到打包的函数目录超过了平台的限制时,会进入到配置向导,帮助用户自动化地配置。即上图可以理解为:Fun 通过内置 NAS(阿里云文件存储)解决方案,可以一键帮用户创建、配置 NAS,并上传依赖到 NAS 上。而函数计算在运行时,可以自动从 NAS 读取到函数依赖。

大依赖向导
大依赖向导部分截图如下:
Fun 部署当前函数时,检测到压缩后(.zip)依赖超过了 50M,提示配置向导(后续日志省略...)。只需要输入回车或 yes 即可。最后 Fun 会自动完成配置,成功部署资源到函数计算。

体验升级
fun deploy 大依赖向导完成后,函数会部署到函数计算并对外提供服务。此时大依赖和代码通过 NAS 进行了分离,再次部署时打包本地代码目录时由于没有了大依赖,所以部署速度会非常的快。
这里推荐一篇使用 fun deploy 进行大依赖部署的实战案例,展示了 Fun 工具对大依赖场景的顺滑体验 Serverless 实战 —— 快速开发一个分布式 Puppeteer 网页截图服务 。

Fun package 对大依赖的支持
Fun Package 是用来将代码、编译产物、静态资源等本地资源上传到 OSS 的功能。使用 fun Package 的场景,通常是,想仅仅通过一个模板文件进行部署的场景。比如,本地开发完成后,可以通过 fun package ,将模板依赖的本地资源上传到 OSS,这样,无论是在其他服务器上部署,还是使用 ROS 部署时,仅仅通过一个文本格式的模板文件,就可以完成了。推荐阅读 Fun Package 功能介绍 。

非大依赖场景
流程如下图所示:
通过 Fun Package 可以将模板文件包含的 本地资源 一键上传到 OSS 上,完成资源的打包操作。

模版文件差异
将打包后的模板文件(template.packaged.yml)与原文件相比较,可以发现,差异仅仅在使用了本地资源的场景,比如: - CodeUri: './' + CodeUri: 'oss://bucket/PackageDemo/function/39ce6e9109a23d313bc267b1a5211273'

大依赖场景
流程如下图所示:
当遇到打包的函数体积过大时,同样会进入大依赖向导,Fun 内置 Ros 的解决方案,帮你完成自动配置。同时 Fun 会分开大依赖和代码并分别上传到 OSS。这样做的目的下文中会有提到。

模版文件差异
大依赖场景下打包完成后生成的 template.packaged.yml 模版文件会与非大依赖场景下有所不同,除上述 CodeUri 的差异外,还会新增许多资源描述作为使用 Ros 部署时的前置条件,例如 NasCpFunc , 这里只介绍一种,其它不做赘述。 NasCpFunc: Type: 'Aliyun::Serverless::Function' Properties: Handler : index.cpFromOssToNasHandler Runtime: nodejs8 CodeUri: 'oss://ellison-hongkong/9e610f5540e21ace83d5b742241da6aa' MemorySize: 3072 Timeout : 300
NasCpFunc 为大依赖场景下 Fun 为用户内置的资源函数,可以将它理解为一个辅助函数。当用 Ros 方式部署时,将自动执行辅助函数。它用于实现从 OSS 上下载大依赖(.zip)以及解压到 Nas 的功能。这也就是为什么上述 fun pakckage 打包时,要将大依赖和代码分离开并分别上传到 OSS 的原因。

Fun Packge + Ros 部署实战
Serverless 实战——使用 Rendertron 搭建 Headless Chrome 渲染解决方案 使用 Rendertron + 函数计算 快速搭建一个可以直接用于生产的 Headless Chrome 渲染解决方案,以便于帮助网站更好的进行 SEO。基于文章,我们将升级文章中 一键部署 的体验。您可以参照上述文章中步骤,其中 依赖安装 , 项目编译 等均无需额外操作。
Rendertron 项目代码依赖过大,基于 Fun 工具对大依赖项目的支持,现将其原 Fun deploy 部署改造为 Fun Packge + Ros 部署方式。Fun package 自动处理大依赖上传到 OSS,Ros 部署将大依赖从 OSS 解压到 Nas,同时模版中描述的资源自动创建成功。基于函数计算,项目的服务架构如下:
1. Fun pakcage
按照 RenderTron 文章中步骤操作,在 一键部署 前,执行 fun package 命令: fun package --oss-bucket aliyun-ellison
这里的 --oss-bucket 名称为自己所拥有读写权限的 OSS 的 Bucket 名称。完整日志如下:

2. Ros 部署
ROS 通过 Transform 宏实现了将函数计算的模板语法转换为 ROS 支持的语法。这意味着对于 Fun 规范文档 里描述的语法规则,ROS 是同样支持的。同时, ROS 支持的资源 也能在 Fun 模板文件中进行声明了,比如 RAM、 函数工作流 等等。 在体验上,由于 ROS 部署,要求资源必须“云化”。也就是没办法直接使用本地的代码资源。必须先通过 fun package 命令将资源上传到 oss。
可见这一步我们已经完成,不管是大依赖场景还是非大依赖场景,fun package 打包完成后,后续的部署操作,只需要完全基于这个打包后的模板文件(template.packaged.yml)即可。不再依赖本地的代码等资源,可以简化部署的难度。
最后将资源通过 ROS 的方式进行部署,推荐阅读 开发函数计算的正确姿势 —— 使用 ROS 进行资源编排 。 fun deploy --use-ros -- stack -name bucket-name
--stack-name 表示要部署的环境,可以基于该名称的不同,建立多套开发环境,比如 test、staging、prod。

3. 验证
可通过上述 RenderTron 文章中提到的方式验证,这里不做赘述。

常见问题梳理 FAQ

1. 大依赖被一个服务下的两个函数同时引用,Fun 是怎么处理的呢?
Fun 支持大依赖的场景是函数级别的,即当打包某一函数时发现超过限制才会进入向导。当两个函数处于相同 runtime 和 codeUri,Fun 会在结束第一次向导时,同时自动配置第二个函数,确保部署后,两个函数都部署成功且可用。
2. 大依赖自动配置后,如果我本地添加了新的依赖,部署时会自动将依赖更新到 Nas 吗?
不会。如果添加了新的依赖,比如 node_modules 目录添加了新的依赖库,需要在 template.yml 模版文件所在目录执行 fun nas sync,将本地 nas 资源同步到 nas 服务。如果修改了代码,只需要使用 fun deploy 重新部署即可。由于大依赖和代码通过 NAS 进行了分离,依赖通常不需要频繁变化,所以调用的频率比较低,而 fun deploy 的由于没有了大依赖,部署速度也会非常的快。
3. 为什么 java8 环境 Fun 支持大依赖的目录是 .fun/build/artifacts?
在很多场景,编译型语言从源码距离交付物其实是有一定的距离,比如 java,写完 java 代码后,还要考虑如何编译、打包的问题。Fun build 的职责就是完成从源码到交付产物的构建过程,推荐阅读 《开发函数计算的正确姿势 —— 使用 Fun Build 构建函数》 。
Fun build 会将编译打包后的交付产物 copy 到 .fun/build/artifacts 目录,在部署时检测到代码大小超过限制,自然会去 .fun/build/artifacts 下查找对应 serviceName/functionName 目录,并将所有的 jar 包上传到 Nas。所以 Fun 大依赖部署支持 java8 是以 Fun build 的场景为基础。未来 Fun 会集成更多的解决方案,敬请期待!
总结
Fun 通过内置 NAS(阿里云文件存储)解决方案,可以一键帮用户创建、配置 NAS,并上传依赖到 NAS 上。而函数计算在运行时,可以自动从 NAS 读取到函数依赖。同时也展现 Fun 工具对大依赖场景的顺滑体验。
如果大家在使用 Fun 的过程中遇到了一些问题,可以在 github 上提 issue ,或者加入我们的钉钉群 11721331 进行反馈。

查看更多:https://yq.aliyun.com/articles/744357?utm_content=g_1000104314
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-18 13:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
18年有个数学家宣布成功证明了RH,引起了业界轰动,虽然最后发现其证明错了,但也由此引发众人对RH的关注,上百年来众多数学家针对RH开展的各种传奇一般的工作过程也颇为有趣。
《素数之恋》的作者称“如果你看了本书还是不能理解RH,那你永远都不会理解了”。
第一章 从纸牌游戏开始说起,介绍了级数、收敛等术语。
第三章 介绍素数是无穷的,并找到N每大100倍,N/π(N)增加7的关系,从而提出了对数的公式(素数定理):π(N) ~N/lnN,简写为PNT
第五章 从欧拉解决了巴塞尔问题开始(无穷和为什么会有π出现),介绍了幂运算,并引出了Riemann的Zeta函数ζ(S),还初步指出S>1时才收敛
第七章 把ζ(S)从无穷和换成了无穷素数积(欧拉积公式),并介绍了微积分,从而给出更好的PNT:Li(N)
第九章 先通过一个无穷和的例子说明了级别可能只表达了函数的一部分。然后经过某种方式让ζ(S)在<1时也收敛,这时能得到负偶数的零点(平凡零点)
,欧拉还给出了一个公式关联了ζ(S)和ζ(1-S),公式中有π,有Sin,而且1/2很特别,因为这时正好1-S=S
第十一章 从人类对数字的认识,介绍了根号-1的计算,得出了虚数,并引起复数和复平面:数字不再是实数轴的一条线,而是加上了虚数变成了二维平面。
第十二章 介绍了Riemann论文的内容:认为Li(N)是OK,而且给出了 消除误差的公式 ,这个公式就涉及到了“非平凡零点”。 https://zhuanlan.zhihu.com/p/45121719 有拟合曲线,堪称完美。而且曲线还表现了某种与傅利叶变换类似的特点。
第十三章 表现了自变量与函数值在复平面的对照关系,自变量蚂蚁的例子很生动,图13.6是ζ(S)的图形,其中有一些线条从正虚数变成负虚数,变化的“急转弯”可以猜就是非平凡零点所在。图13.8则是ζ(1/2+ti),看得出蚂蚁在临界线上不停的往上走时,函数值在转圈并不时回到零点。
第十四章 Li(N)-π(N)就是误差,数学家发现这个误差并不永远是正的,而是在一个范围内不断的有正负值,第一个被认为的反例(拟合曲线交叉点)在“斯克维斯数”:e-e-e-79(庞大的怪物)之前。
第十五章 数据结构与算法的书已经介绍过大O了,而本书这里正好用了大O来表示误差项的范围,这其实就是基于RH的应用之一。默比乌斯函数累加值对应的M函数则是另一个与RH有关联的推论--是个更强的推论:如果他成立,RH也成立;如果他不成立,不能说明RH不成立。
第十六章 展示了非平凡零点的计算历程,零点的精度情况。这些内容也存在于在很多关于RH的科普文章中,数学家们的探索过程还是很有意思的。
第十七章 介绍了矩阵,并把埃尔米特矩阵与ζ(S)关联,得出一个猜想:非平凡零点对应于某种埃尔米特算子的本征值。
第十八章 随机矩阵对量子计算有很大的帮助,正好有一个研究零点分布情况的数学家与一个研究量子能级分布的物理学家聊天,发现了零点分布与GUE算子在统计意义上一致。素数的分布与亚原子粒子的行为有什么关系?
第十九章 相当复杂,大体是有一个J(X)函数,由π(N)构成。然后Riemann用微积分,写出了由J(X)构成的ζ(S)函数,换言之,π(N)与ζ(S)存在着某种联系!(Riemann就是用ζ(S)的零点来消除Li函数的误差)
第二十章 也很复杂,看起来象是RH的一些应用,在一些与物理相关的计算能很好拟合(虽然大家不知道这是为什么)。现在还没找到反例,从逻辑学的角度来看,最后证明或证伪都有可能。
第二十一章 深度介绍了误差项消除公式(J(X)公式的第二项),太复杂已经看不懂了。只知道这个误差项的计算非常有效果,于是得出了Riemann论文的目标:如果你知道所有零点的值,我就能准确告诉你π(N)的值,而且我猜这些零点的实部都是1/2。
第二十二章 数学家不关心这些理论的实用性,所以别问如果RH成立,能不能去火星,能不能提高人类健康的问题。而且,问RH到达会不会成立的问题也没用,数学家现在真的不知道。
大数据
2020-02-18 13:29:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
场景描述
本实践适用于使用弹性高性能计算EHPC+
对象存储OSS运行仿真软件进行模型仿真
的场景中,这里运行的是LAMMPS这款开
源的仿真软件,数据通过OSS上传。
部署架构图

解决问题
1. 使用EHPC运行工业仿真软件
2. 使用OSS存储数据和代码
3. 可视化计算结果

产品列表

l 弹性高性能计算E-HPC

l 对象存储OSS

查看更多:https://www.aliyun.com/acts/best-practice/preview?id=52161
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-18 10:37:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
按照本文档操作,您可以快速上手阿里云CDN,加速您的域名。快速入门主要包含开通CDN服务、添加加速域名和配置CNAME三个步骤。
前提条件 开通CDN服务前,请确保您已经完成阿里云账号注册和实名认证。如果没有,请分别参见 账号注册 和 实名认证 。
步骤一:开通CDN服务 登录 CDN控制台 ,开通CDN服务。 在 云产品开通页 ,选择适合您的 计费类型 。在这里,您可以选择 按使用流量计费 或 按带宽峰值计费 。 选中 我已阅读并同意《内容分发网络CDN(按量付费)服务协议》 复选框,单击 立即开通 。 在 确认订单 页面,单击 管理控制台 ,进入 CDN管理控制台 。
步骤二:添加加速域名 在左侧导航栏,单击 域名管理 。 在 域名管理 页面,单击 添加域名 。 配置 加速域名 、 业务类型 、 源站信息 、 端口 和 加速区域 。
说明 每个配置项的具体配置说明,请参见 添加加速域名 。 单击 下一步 。
出现 加速域名添加成功 弹窗。
步骤三:配置CNAME
加速域名添加成功后,会进入审核流程。当您的加速域名审核通过后,阿里云CDN会为您分配对应的CNAME地址。您需要将加速域名指向CNAME地址,访问加速域名的请求才能转发到CDN节点上,达到加速效果。 这里以您的域名在阿里云解析(原万网)为例。 获取加速域名的CNAME地址。 在左侧导航栏,单击 域名管理 。 进入 域名管理 页面,复制加速域名对应的CNAME值。 添加CNAME记录。 登录 云解析DNS控制台 。 进入 域名解析 页面,在您需要设置的域名后面,单击 解析设置 。 单击 添加记录 ,添加CNAME记录。 单击 确定 。
配置CNAME完毕。CNAME配置生效后,加速服务也会立即生效。 新增CNAME记录会实时生效,修改CNAME记录72小时之内生效。 验证CNAME配置是否生效。 打开Windows的CMD命令行程序。 输入 ping 或 dig 和加速域名。
如果下行出现 *.*kunlun*.com ,即表示CNAME配置已经生效,域名加速也已生效。
查看更多:https://help.aliyun.com/document_detail/141972.html
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-18 10:34:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
包年包月模式
计费项 :按所选购的套餐版本计费。
付费方式 :预付费。
计费周期 :按月/年计费,购买时生成账单付费。
扣费周期 :自购买当日起,根据所选择的购买时长(月/年)计算;购买时长支持选择一个月、三个月、半年、一年。
到期说明 :当您购买的防护服务到期时,服务自动停止。 距离服务到期前7天,您会收到短信或邮件,提醒您服务即将到期并提示您及时续费。如在服务到期前没有续费,则在服务到期时您将失去防护效果。 服务到期后Web应用防火墙服务中的配置将为您继续保留7天。如果7天内您完成续费则可以使用原有配置继续进行防护;否则在7天后,Web应用防火墙中的相关配置将被释放,您只能重新购买才能继续使用。
按量付费模式
计费项 :当日已添加防护的网站的QPS峰值 * 所选用的功能规格对应系数
付费方式 :后付费
扣费周期 :根据网站QPS峰值和当日已启用的功能规格系数按日计费。按照前一日实际发生的网站的QPS峰值及已启用的功能规格所对应的系数计算,生成后付费账单。
注意 开通按量付费实例后,如果WAF实例连续五天不存在业务流量且未产生后付费账单,实例将会自动释放。
说明 网站QPS峰值指的是当日从00:00:00-23:59:59之间访问网站的每秒请求数峰值。如果网站当日有请求访问,则QPS峰值最小计费单位为1。系统将每10秒统计一次QPS峰值,即一天共有 (24 小时 * 60 分钟/小时 * 60 秒/分钟) / 10 秒/次 = 8640个峰值点,自动去除QPS峰值 > 0 的前2‰个峰值点,即以第18个最高峰值点作为当日最终的网站QPS峰值。 网站QPS峰值不区分请求是否为攻击,即以Web应用防火墙接收到的您已配置的所有网站域名的每秒请求总数为准。 为了避免因遭受攻击导致QPS暴涨而产生巨额防护费用,当日单个网站最多以500次作为QPS峰值进行计费。即某网站因遭受攻击导致当日QPS峰值超过500次时,以500次作为该网站当日的QPS峰值。 只要您在Web应用防火墙管理控制台添加了域名配置,系统检测到对该网站域名的请求就会产生费用,与域名的DNS解析是否指向WAF实例无关。由于可能有各种因素导致网站域名流量经过WAF实例,因此如果您不想产生任何相关费用,请确保删除所有域名配置记录。
更多关于按量付费模式Web应用防火墙的计费说明,请查看 按量付费模式常见问题 。
欠费说明 欠费 :当您的账号出现欠费后,您的WAF实例将在第二日停止提供服务。在您的账号欠费后,WAF实例当天仍将正常提供服务并且生成当天的费用账单。 结清 :在WAF实例停止提供服务前,只要您将所欠费用结清,WAF实例将继续正常提供服务。
Web应用防火墙资源包
开通按量付费模式Web应用防火墙后,您可以 购买Web应用防火墙资源包 (预付费)来抵扣Web应用防火墙根据网站QPS峰值和已启用的功能规格折算后的QPS消费值。您购买Web应用防火墙资源包后,在实际使用过程中将优先抵扣您Web应用防火墙资源包中的剩余QPS消费值。相比按量付费模式,购买Web应用防火墙资源包可以以更低廉的价格使用按量付费模式的Web应用防火墙服务。
查看更多:https://help.aliyun.com/document_detail/28525.html
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-18 10:32:00
「深度学习福利」大神带你进阶工程师,立即查看>>> zookeeper集群基础安装可参考 https://my.oschina.net/dingyibo/blog/3160232 zk测试集群 3个节点组成的测试集群 server.1=192.168.189.133 server.2=192.168.189.135 server.3=192.168.189.130 一、zk集群间server节点认证配置( Server-Server mutual authentication ) 1、 ZooKeeper SASL配置 zoo.cfg添加如下内容 #Server-Server mutual authentication #开启sasl认证 quorum.auth.enableSasl=true #zk集群角色分为 leader和learner(follower、observer),其中 observer 节点没有投票权,即它不参与选举和写请求的投票。 quorum.auth.learnerRequireSasl=true quorum.auth.serverRequireSasl=true quorum.auth.learner.saslLoginContext=QuorumLearner quorum.auth.server.saslLoginContext=QuorumServer #quorum.auth.kerberos.servicePrincipal=servicename/_HOST #建议集群节点数n*2 quorum.cnxn.threads.size=6 2、 创建${ZK_HOME}/conf/java.env 文件 [root@localhost conf]# cat java.env SERVER_JVMFLAGS="-Djava.security.auth.login.config=/usr/local/apache-zookeeper-3.5.5-bin/conf/jaas/zk_server.conf" #java.env文件在zkEnv.sh中会被加载 [root@localhost bin]# grep "java.env" zkEnv.sh if [ -f "$ZOOCFGDIR/java.env" ] . "$ZOOCFGDIR/java.env" 3、 DIGEST-MD5 based authentication配置 zookeeper sasl实现提供2种认证机制,分别为Kerberos和DIGEST-MD5认证机制,本文基于DIGEST-MD5 JAAS configuration file #SASL是一个Internet标准协议,Java JAAS只是这个协议的一个种Java实现 [root@localhost conf]# cat jaas/zk_server.conf QuorumServer { org.apache.zookeeper.server.auth.DigestLoginModule required user_test="test123"; }; QuorumLearner { org.apache.zookeeper.server.auth.DigestLoginModule required username="test" password="test123"; }; 二、zk集群间server节点认证测试 1、启动zk集群(成功配置集群认证的情况下) leader节点的启动日志: [root@localhost bin]# ifconfig eth0 Link encap:Ethernet HWaddr 00:0C:29:E4:57:C6 inet addr:192.168.189.135 Bcast:192.168.189.255 Mask:255.255.255.0 inet6 addr: fe80::20c:29ff:fee4:57c6/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:7359 errors:0 dropped:0 overruns:0 frame:0 TX packets:9000 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:633128 (618.2 KiB) TX bytes:985418 (962.3 KiB) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:1500 errors:0 dropped:0 overruns:0 frame:0 TX packets:1500 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:106198 (103.7 KiB) TX bytes:106198 (103.7 KiB) [root@localhost bin]# ./zkServer.sh status ZooKeeper JMX enabled by default Using config: /usr/local/apache-zookeeper-3.5.5-bin/bin/../conf/zoo.cfg Client port found: 2181. Client address: localhost. Mode: leader [root@localhost bin]# grep "Sasl" ../logs/zookeeper-root-server-localhost.localdomain.out 2020-02-17 15:10:08,937 [myid:2] - INFO [main:QuorumPeer@2156] - quorum.auth.enableSasl set to true 2020-02-17 15:10:08,937 [myid:2] - INFO [main:QuorumPeer@2141] - quorum.auth.serverRequireSasl set to true 2020-02-17 15:10:08,937 [myid:2] - INFO [main:QuorumPeer@2147] - quorum.auth.learnerRequireSasl set to true 2020-02-17 15:10:10,081 [myid:2] - INFO [QuorumConnectionThread-[myid=2]-1:SaslQuorumAuthLearner@152] - Successfully completed the authentication using SASL. server addr: /192.168.189.133:3888, status: SUCCESS 2020-02-17 15:10:11,396 [myid:2] - INFO [LearnerCnxAcceptor-/192.168.189.135:2888:SaslQuorumServerCallbackHandler@143] - Successfully authenticated learner : authenticationID=test; authorizationID=test. 2020-02-17 15:10:11,397 [myid:2] - INFO [LearnerCnxAcceptor-/192.168.189.135:2888:SaslQuorumAuthServer@115] - Successfully completed the authentication using SASL . learner addr: /192.168.189.133:40802 2020-02-17 15:10:12,579 [myid:2] - INFO [QuorumConnectionThread-[myid=2]-2:SaslQuorumServerCallbackHandler@143] - Successfully authenticated learner: authenticationID=test; authorizationID=test. 2020-02-17 15:10:12,579 [myid:2] - INFO [QuorumConnectionThread-[myid=2]-2:SaslQuorumAuthServer@115] - Successfully completed the authentication using SASL. learner addr: /192.168.189.130:44406 2020-02-17 15:10:12,625 [myid:2] - INFO [LearnerCnxAcceptor-/192.168.189.135:2888:SaslQuorumServerCallbackHandler@143] - Successfully authenticated learner: authenticationID=test; authorizationID=test. 2020-02-17 15:10:12,626 [myid:2] - INFO [LearnerCnxAcceptor-/192.168.189.135:2888:SaslQuorumAuthServer@115] - Successfully completed the authentication using SASL. learner addr: /192.168.189.130:45244 2、启动zk集群(模拟192.168.189.130节点配置错误的情况下) --修改zk_server.conf将QuorumLearner下的password更改为错误的test12 leader启动日志: 2020-02-17 15:20:29,243 [myid:2] - INFO [/192.168.189.135:3888:QuorumCnxManager$Listener@888] - Received connection request /192.168.189.130:44462 2020-02-17 15:20:29,267 [myid:2] - ERROR [QuorumConnectionThread-[myid=2]-7:SaslQuorumAuthServer@132] - Failed to authenticate using SASL javax.security.sasl.SaslException: DIGEST-MD5: digest response format violation. Mismatched response. at com.sun.security.sasl.digest.DigestMD5Server.validateClientResponse(DigestMD5Server.java:627) at com.sun.security.sasl.digest.DigestMD5Server.evaluateResponse(DigestMD5Server.java:244) at org.apache.zookeeper.server.quorum.auth.SaslQuorumAuthServer.authenticate(SaslQuorumAuthServer.java:99) at org.apache.zookeeper.server.quorum.QuorumCnxManager.handleConnection(QuorumCnxManager.java:546) at org.apache.zookeeper.server.quorum.QuorumCnxManager.receiveConnection(QuorumCnxManager.java:470) at org.apache.zookeeper.server.quorum.QuorumCnxManager$QuorumConnectionReceiverThread.run(QuorumCnxManager.java:506) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) learner问题节点启动日志: 2020-02-17 15:21:20,479 [myid:3] - INFO [QuorumPeer[myid=3](plain=/0:0:0:0:0:0:0:0:2181)(secure=disabled):FastLeaderElection@919] - Notification time out: 60000 2020-02-17 15:21:20,484 [myid:3] - INFO [QuorumConnectionThread-[myid=3]-2:SecurityUtils@68] - QuorumLearner will use DIGEST-MD5 as SASL mechanism. 2020-02-17 15:21:20,502 [myid:3] - INFO [QuorumConnectionThread-[myid=3]-4:SecurityUtils@68] - QuorumLearner will use DIGEST-MD5 as SASL mechanism. 2020-02-17 15:21:20,509 [myid:3] - ERROR [QuorumConnectionThread-[myid=3]-2:QuorumCnxManager@336] - Exception while connecting, id: [2, /192.168.189.135:3888], addr: {}, closing learner connection javax.security.sasl.SaslException: Authentication failed against server addr: /192.168.189.135:3888 at org.apache.zookeeper.server.quorum.auth.SaslQuorumAuthLearner.authenticate(SaslQuorumAuthLearner.java:126) at org.apache.zookeeper.server.quorum.QuorumCnxManager.startConnection(QuorumCnxManager.java:425) at org.apache.zookeeper.server.quorum.QuorumCnxManager.initiateConnection(QuorumCnxManager.java:334) at org.apache.zookeeper.server.quorum.QuorumCnxManager$QuorumConnectionReqThread.run(QuorumCnxManager.java:385) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) 2020-02-17 15:21:20,527 [myid:3] - ERROR [QuorumConnectionThread-[myid=3]-4:QuorumCnxManager@336] - Exception while connecting, id: [1, /192.168.189.133:3888], addr: {}, closing learner connection javax.security.sasl.SaslException: Authentication failed against server addr: /192.168.189.133:3888 at org.apache.zookeeper.server.quorum.auth.SaslQuorumAuthLearner.authenticate(SaslQuorumAuthLearner.java:126) at org.apache.zookeeper.server.quorum.QuorumCnxManager.startConnection(QuorumCnxManager.java:425) at org.apache.zookeeper.server.quorum.QuorumCnxManager.initiateConnection(QuorumCnxManager.java:334) at org.apache.zookeeper.server.quorum.QuorumCnxManager$QuorumConnectionReqThread.run(QuorumCnxManager.java:385) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) 至此zk集群server间认证配置成功,将jaas配置文件密码修改正确后,问题节点就会正常加入集群。
参考链接: https://cwiki.apache.org/confluence/display/ZOOKEEPER/Server-Server+mutual+authentication
大数据
2020-02-17 16:30:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
近日,阿里云弹性计算发布全新一代云服务ECS共享标准型S6,性能相对上一代实例提升15%以上,价格相对上一代最高降低42%,是目前国内云计算厂商更能够提供的最具性价比的云服务器产品。一些中小型网站、轻量数据库以及轻量企业应用的客户在阿里云有更多的产品选择,也是个人开发者和小企业的首选。
什么是共享型云主机?
共享性云主机指的是没有做主机和vCPU绑定的主机类型,该类型云主机使用的vCPU是在物理服务器中根据一定算法自动选择的。共享型云主机的优势是性价比高,同时可能会受其他用户的资源争抢的影响。
共享标准型主机S6的产品优势
基于阿里自研神龙芯片和全新的轻量化Hypervisor
基于阿里云智能神龙芯片和全新的轻量化VMM,将大量传统虚拟化功能卸载到专用硬件上,大大降低了虚拟化的性能开销,同时用户几乎可以获得所有的宿主机CPU和内存资源,提高整机和大规格实例的各项能力。
硬件升级,使用最新第二代英特尔至强可扩展处理器
使用最新一代Intel Cascade Lake CPU, 睿频提升至3.2GHz, 各场景E2E性能大幅提升,并在深度学习的多种场景有数倍的提升。同时搭配DDR4内存,底层环境开启NUMA(不可见),降低内存时延。
更高的产品能性能
共享标准型主机S6的处理器和内存配比1:1(仅限1核1GB),1:2,1:4。最高规格为8核32GB。网络带宽最高可突发至6 Gbit/s。
实例规格存储共享整机资源,支持ESSD云盘、SSD云盘和高效云盘。
性能相对上一代实例提升15%以上。
CPU性能测试
应用测试
网络PPS值测试
内存性能测试
阿里云弹性计算一直致力于推出更丰富的产品,满足更多客户的不同需求。共享型实例S6的推出,让阿里云弹性计算云服务器产品的性价比又上一个新台阶,在行业内无其他产品可出其右,是个人开发者及中小型企业上云首选。

查看更多:https://www.aliyun.com/daily-act/ecs/s6_sales
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-17 16:11:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
近日,阿里云弹性计算发布全新一代云服务ECS共享标准型S6,性能相对上一代实例提升15%以上,价格相对上一代最高降低42%,是目前国内云计算厂商更能够提供的最具性价比的云服务器产品。一些中小型网站、轻量数据库以及轻量企业应用的客户在阿里云有更多的产品选择,也是个人开发者和小企业的首选。
什么是共享型云主机?
共享性云主机指的是没有做主机和vCPU绑定的主机类型,该类型云主机使用的vCPU是在物理服务器中根据一定算法自动选择的。共享型云主机的优势是性价比高,同时可能会受其他用户的资源争抢的影响。
共享标准型主机S6的产品优势
基于阿里自研神龙芯片和全新的轻量化Hypervisor
基于阿里云智能神龙芯片和全新的轻量化VMM,将大量传统虚拟化功能卸载到专用硬件上,大大降低了虚拟化的性能开销,同时用户几乎可以获得所有的宿主机CPU和内存资源,提高整机和大规格实例的各项能力。
硬件升级,使用最新第二代英特尔至强可扩展处理器
使用最新一代Intel Cascade Lake CPU, 睿频提升至3.2GHz, 各场景E2E性能大幅提升,并在深度学习的多种场景有数倍的提升。同时搭配DDR4内存,底层环境开启NUMA(不可见),降低内存时延。
更高的产品能性能
共享标准型主机S6的处理器和内存配比1:1(仅限1核1GB),1:2,1:4。最高规格为8核32GB。网络带宽最高可突发至6 Gbit/s。
实例规格存储共享整机资源,支持ESSD云盘、SSD云盘和高效云盘。
性能相对上一代实例提升15%以上。
CPU性能测试
应用测试
网络PPS值测试
内存性能测试
阿里云弹性计算一直致力于推出更丰富的产品,满足更多客户的不同需求。共享型实例S6的推出,让阿里云弹性计算云服务器产品的性价比又上一个新台阶,在行业内无其他产品可出其右,是个人开发者及中小型企业上云首选。

查看更多:https://www.aliyun.com/daily-act/ecs/s6_sales
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-17 16:06:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
新冠肺炎疫情牵动人心,危机面前,阿里巴巴打响全面战“疫”:集团设立10亿元医疗物资供给专项基金、菜鸟开通医疗防护用品全球绿色通道、阿里健康推出居民“线上义诊“、阿里云为全国28个省市搭建“数字防疫系统”......全集团共同为抵抗疫情贡献力量。
当下无论是政府还是企业,对数字化服务的需求都已到达高潮。 作为阿里云生态落地的最后一公里,面对疫情,阿里云云市场积极响应,联合数家生态伙伴“千万补贴,共抗疫情”,助力政企打赢这场没有硝烟的“云上战役”。 阿里云云市场是中国最大的云计算/企业服务平台之一,目前已经覆盖了企业应用、基础软件、上云服务、解决方案、IoT等多种场景。
助力政府,全场景疫情防控
肺炎疫情防控战目前正处于关键时期,阿里云云市场面向政府和公益组织,免费开放了多项云端服务,第一时间提供了疫情信息管理、收集、上报、展示等覆盖全场景的疫情防控综合解决方案,保障防疫工作高效开展,打赢这场没有硝烟的“云上战役”。
疫情管理/企业复工系统,无门槛搭建-宜搭
疫情期间免费开放。 宜搭是阿里巴巴自主研发的,无门槛应用搭建平台。面对疫情,宜搭快速响应,搭建了“疫情上报系统”和“企业复工申报系统”,10万+基层组织正基于平台进行疫情信息管控。宜搭通过可视化拖拽的方式,让不会代码的人员也能迅速搭建应用,对抗疫情。
防疫物资信息高效管理与分发-云枢
疫情期间免费开放。 云枢是一款面向开发者的云端低代码应用服务平台。云枢疫情响应机制快速搭建服务平台,2天内部署上线云枢物资管理系统,实现了医院及相关部门紧缺物资实时上报等在线管理。目前,已上线某市610+家医疗机构,节省50+人力,为防疫争分夺秒。
疫情讯息、预防指南及时公示-党建云疫情监控云屏
疫情期间, 面向各地政府组织和公益组织开放免费服务。 云屏打造了数据可视化的宣传模式,全国疫情实时更新、本地疫情自定义播报、疫情资讯和政策宣传、症状科普、预防指南......全部一屏搞定。全国已有800多个县市开通使用该服务。
一站式解决综合防疫需求-防疫小程序
疫情期间, 面向各地政府组织和公益组织开放免费服务。 疫情防控多端小程序围绕辟谣、官方发布、预防技巧、疫情上报,智能问诊、口罩预约、出入登记等刚需场景,帮助政府更好的做好疫情防控助,让政府工作人员有更多的时间精力投入到防控工作中去。
在线签署企业复工、监控承诺书-E签宝
企事业单位可通过E签宝在线高效签署企业复工、健康承诺书,疫情期间, 免费提供100份(政府全免) 。在提升签署效率的同时,实现无接触签署,杜绝病毒传播风险,还有多种电子合同在线签约,业务不掉线。
助力教育,智慧校园停课不停学
湖北省产品免费一年 其他地区免费至疫情结束。 通过智慧校园,全国学校可进行线上教务、教学管理、统计、分析,减少学校人员聚集造成疫情扩散的风险,停课不停学,守护师生健康。疫情发生后,太原市教育局快速行动,启动空中课堂直播方案,解决了50w+中小学生在家学习问题。
助力企业,线上办公有“神器”
受疫情影响,很多线下门店和企业无法正常复工,线下没有客源,又不具备线上获客能力,生存压力陡增。在全面互联网化的趋势下,如何利用数字化手段对销售模式升级,如何协同员工开展远程办公,特殊时期怎么保障财务做账和报税......无论是什么类型的企业,都必须思考这些问题。
基于此,阿里云云市场从前端的销售获客,中端的协同办公到后端的财务管理,为企业提供了全链路的线上商业模式,助力企业线上持续增长,化“危”为“机”,部分远程办公产品免费开放。
线下生意搬到线上-智慧门店小程序
疫情期间免费开放。 针对闭店和寻求线上生意的企业商家,夺冠互动免费提供智慧门店小程序,用蜂鸟的运力提供同城配送到家的功能,帮助传统门店顺利转型。
会打字就会做网站-云·速成美站
云梦网络自助型网站模板+阿里云共享服务器,预置1000套精美设计模板,覆盖125个行业,模板随意切换,即开即用,会打字就会做网站。
离店经营,营销活动工具包-伯俊智能营销
基于伯俊社交电商管理工具,实现社群营销,支持直营、经销等各种业务模式,并能集成常用的营销工具实现多种营销活动。帮助企业找到业绩突破口。
解决远程办公难题-蓝凌智能OA
免费提供1个月(100个账号)的使用权限。 蓝凌在智慧办公与知识管理领域的经验沉淀,融合钉钉的数字化能力,提供在家考勤、在线审批、在线会议、Ding通知、工作总结、员工关怀等功能。
传递信息安全稳定高效-阿里云邮箱
疫情期间低至五折。 阿里云开发并运营的企业邮箱,即买即用,安全无忧,打造随时随地收发使用的全生态开放电子邮箱系统。
让项目管理更容易-Teambition
免费开放至 2020年5月1日。 Teambition帮助企业更好的在线管好工作事:项目管理、任务协同、日程同步、文档协作、知识共享、数据统计......是超700万用户的协同办公首选。
小微企业财务管理神器-畅捷通
疫情期间部分功能免费。 畅捷通财务软件可以实现多端协同,随时随地移动记账,数据还能实时完成自动备份。财务在家办公轻松做账,老板在线实时查询企业经营数据。
在疫情难关面前,阿里云云市场利用大平台的响召能力,不断提供阿里云底层的云计算能力,召集和孵化了一个又一个的硬核产品和解决方案,合力为政府和企业疫情期间的各种数字化需求助力,相信全国人民万众一心,一定可以共克时艰。

查看更多:https://yq.aliyun.com/articles/744484?utm_content=g_1000104170
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-17 15:43:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
一场突如其来的新冠病毒肺炎疫情,让在无数在备考阶段的高三学子陷入苦恼。为了阻断疫情蔓延,减少人员聚集,教育部下发“关于2020年春季延期开学的通知”。距离高考仅剩120多天,虽停课,不能停学。
1天开通直播 助力黄冈高三学子安心冲刺
对于高三的学子来说,居家“隔离”时期更是复习冲刺的关键时期。为了实现正常授课,湖北黄冈中学高三班采用线上直播的方式进行授课,确保复习计划有序开展。
黄冈中学高三班直播中
而备受高三班师生青睐的线上直播课正是由阿里云视频云与合作伙伴保利威共同提供,仅有1天时间,保利威就完成了线上直播应急方案的搭建,帮助湖北黄冈中学高三班实现了正常授课,而阿里云视频云作为技术底座,提供资源弹性伸缩、海量数据的处理和数据智能化能力以及直播、点播等垂直视频场景的云产品能力部署,全力保障高并发量下系统的平稳运行。
经过多方共同的努力,目前,学生们在家中可以使用电脑、平板、电视等各种设备终端,扫描二维码或打开链接即可进入课堂。还可以使用答题、签到、语音连麦等功能和老师实现高效互动。学校也表示有计划把线上直播方案推广到其他年级。
迅速集结 联手打造“空中课堂”解决方案
“空中课堂”不仅能保证师生健康安全,也是现阶段满足高三学子迫切学习需求的最佳途径。自从大年二十九开始,保利威与阿里云接到项目任务,双双迅速进入“战疫”状态。
保利威深入“前线”快速响应,集中调度资源,提供场景化、一站式的在线直播解决方案,开放全终端直播教学工具,并有技术专家7*24小时在线进行保障,力求最快速度完成与现有教育系统的对接,帮助学校搭建多端远程授课应用。
阿里云作为大后方,为保利威提供视频直播、窄带高清转码、CDN、ECS、OSS、云安全等产品,输出阿里云强大的基础设施与先进的云技术,确保在整个线上直播系统在增长数倍的情况下,依旧能保持高清的画质和流畅的交互体验,为疫情期间在线教育平台、视频会议等业务高并发情况下,提供强有力的技术后盾。
截至目前,阿里云与保利威联手打造的“空中课堂”,已经协助了包括黄冈中学、衡水中学、新东方优能中学等在内的上百所公立学校接入了应急直播,帮助学校快速的实现线下到线上教学的平移,在保证学生安全健康同时,也让学生轻松得跟上复习进度。
停课不停学 阿里云视频云一直在行动
面对疫情,阿里云技术人义不容辞。春节假期期间,各地教育部门先后官宣延期开学并着手搭建“空中课堂”,阿里云视频云团队的小伙伴们也一直在行动。
我们第一时间集结精兵强将,面向在线教育业务的爆发式增长,提前进行了系统优化和运维保障,同时积极输出基于场景的视频PaaS方案,面对IT基础设施较差和存在技术短板的机构,我们也提供特殊点对点技术支持和7*24小时在线响应。不光是黄冈,杭州、宁波、武汉、河南等数十个地市空中课堂的顺利开课,背后都离不开团队的保障。
为了帮助更多中小学生“停课不停学”,我们推出在线教育专项扶持计划,组建“停课不停学”联盟,释放底层资源与技术,与数十家视频技术平台合力推出在线教育解决方案,力保全国中小学校更顺畅、稳定地开展在线教学与管理。
面对疫情,全民阻击。学校虽然远离前线,却也积极备战,通过互联网与云计算技术确保广大学生的学习进度。在这背后,阿里云与合作伙伴们也将持续提供技术保障,携手学校、师生共同打好“停课不停学”这场硬仗。

查看更多:https://yq.aliyun.com/articles/744701?utm_content=g_1000104169
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-17 15:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在BI产品的所有图表组件中,地图图表组件,由于其展示效果直观且生动,一直是报表中必不可少的部分。地图图表的插入,也可以立马让报表变得高大上,分分钟吸引老板的注意!
在这个风和日丽的好日子里,Quick BI上线了筹备已久的新版地图组件(撒花)。一起跟着小编来看看Quick BI里面有哪些地图组件以及怎么用好这些好看的图表吧~
1. 新版色彩地图
作图要求:
1个地理区域(维度),色彩饱和度(度量)至少1个,至多5个(色彩地图用色彩的深浅来展示数据的大小和分布范围)
注意️️:
此处的维度类型必须为地理类型,如果非地理类型需要在数据集中将维度字段修改为地理类型哦。
Quick BI的地图还支持不同的样式模版,同样的数据设计,只要切换了不同的样式模板,整个色彩地图立马大变样! 轻轻松松达到匹配不同仪表板主题的效果。
更厉害的是,Quick BI新版的色彩地图,还支持世界地图哦~只要在统计设置中将显示范围切换为世界地图,就可以轻松地从中国地图切换到世界地图,目前支持中国、新加坡、德国、墨西哥、日本、美国这6个国家。
2. 新版气泡地图
作图要求:
1个地理区域(维度),气泡大小(度量)至少1个,至多5个(气泡地图主要由地理区域和气泡大小构成,可以直观地显示国家或地区的相关数据指标大小和分布范围)
注意️️:
气泡地图的维度类型必须为地理类型,如果非地理类型需要在数据集中将维度字段修改为地理类型哦~
气泡地图也可以在样式配置中选择不同的模板类型来满足不同的展示需求,下面的这些样式只需要动动小手改改图表的样式就能实现啦~

除此之外,我们还可以改变气泡的配色(包括配色范围、透明度等等)、气泡的大小,甚至是气泡的形状。老板如果不喜欢普通的圆形气泡,没关系!Quick BI还提供三角形、六边形等的气泡样式,总有一款老板心仪的。
3. LBS气泡地图
作图要求:
1个地理区域(维度),LBDS气泡大小(度量)至少1个,至多5个
LBS气泡地图与气泡地图类似,都是以一个地图轮廓为背景,用附着在地图上面的气泡来反映数据的大小。Quick BI的LBS气泡地图还可以切换地图的底图样式,例如高德地图、谷歌地图和GeoQ来显示国家或地区的相关数据指标大小和分布范围。
4. LBS热力地图
作图要求:
1个地理区域(维度),LBS热力深度(度量)至少1个,至多5个(LBS热力地图用热力的深浅来展示数据的大小和分布范围)
5. LBS飞线地图
作图要求:
2个地理区域(维度),需要分别选择飞线的起始点,1个LBS飞线(度量)。LBS飞线地图以一个地图轮廓为背景,用动态的飞线反映两地或者多地之间的数据关系。
这么多丰富的图表配置,大家都了解了吗?
如此酷炫的图表,赶快来Quick BI体验一下吧~
阿里巴巴数据中台团队,致力于输出阿里云数据智能的最佳实践,助力每个企业建设自己的数据中台,进而共同实现新时代下的智能商业!
阿里巴巴数据中台解决方案,核心产品:
• Dataphin,以阿里巴巴大数据核心方法论OneData为内核驱动,提供一站式数据构建与管理能力;
• Quick BI,集阿里巴巴数据分析经验沉淀,提供一站式数据分析与展现能力;
• Quick Audience,集阿里巴巴消费者洞察及营销经验,提供一站式人群圈选、洞察及营销投放能力,连接阿里巴巴商业,实现用户增长。

查看更多:https://yq.aliyun.com/articles/744447?utm_content=g_1000104167
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-17 14:52:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 本文主要记录如何使用 hutool 工具类解析 json 对象
1、在 pom 文件内添加 hutool 依赖: cn.hutool hutool-all 5.0.6
2、字符串 转 JSONObject String jsonStr = "{\"DispositionNotificationListObject\":{\"DispositionNotificationObject\":[{\"PersonObject\":{\"GenderCode\":0,\"EthicCode\":0,\"DeviceID\":\"0\",\"SourceID\":\"022019121117052900016\",\"SubImageList\":{\"SubImageInfoObject\":[{\"Type\":\"11\",\"StoragePath\":\"xxx\",\"DeviceID\":\"0\",\"ImageID\":\"022019121117052900016\",\"EventSort\":0,\"ShotTime\":\"19700119134716\",\"Height\":-1,\"FileFormat\":\"jpg\",\"Width\":-1},{\"Type\":\"11\",\"StoragePath\":\"xxx\",\"DeviceID\":\"0\",\"ImageID\":\"022019121117052900016\",\"EventSort\":0,\"ShotTime\":\"19700119134600\",\"Height\":-1,\"FileFormat\":\"jpg\",\"Width\":-1},{\"Type\":\"14\",\"StoragePath\":\"xxx\",\"DeviceID\":\"0\",\"ImageID\":\"022019121117052900016\",\"EventSort\":0,\"ShotTime\":\"19700119134600\",\"Height\":-1,\"FileFormat\":\"jpg\",\"Width\":-1}]},\"LeftTopY\":141,\"LeftTopX\":104,\"Name\":\"图片姓名测试\",\"InfoKind\":1,\"PersonID\":\"0220191211170529000180100019\",\"RightBtmY\":806,\"RightBtmX\":932,\"IDNumber\":\"\"},\"NotificationID\":\"202002031736\",\"DispositionID\":\"71\",\"TriggerTime\":\"2020-02-03 15:34:15\",\"Title\":\"第三方1400告警信息接收测试\"}]}}"; JSONObject json = new JSONObject(jsonStr);
3、解析 JSONObject:
1)获取对象 JSONObject dispositionNotificationListObject = json.getJSONObject("DispositionNotificationListObject");
2)获取单值 String alertPersonName = personObject.getStr("Name"); Integer eventSort = subImageInfoObject.getInt("EventSort");
4、字符串 转 JSONArray String jsonArrayStr = "[{\"Type\":\"11\",\"StoragePath\":\"http://192.168.166.203:11180/storage/v1/image/global?cluster_id=ShenSi&uri_base64=bm9ybWFsOi8vcmVwb3NpdG9yeS1idWlsZGVyLzIwMjAwMTIwL1ZRQnV1LVlsWFpGd29lN1dJSnlXUmc9PUAx\",\"DeviceID\":\"0\",\"ImageID\":\"022019121117052900016\",\"EventSort\":0,\"ShotTime\":\"19700119134716\",\"Height\":-1,\"FileFormat\":\"jpg\",\"Width\":-1},{\"Type\":\"11\",\"StoragePath\":\"http://192.168.166.203:11180/storage/v1/image/global?cluster_id=ShenSi&uri_base64=bm9ybWFsOi8vcmVwb3NpdG9yeS1idWlsZGVyLzIwMjAwMTIwL0gzc1dYN3lXRmh1Zmd0Sjd6Tlo1cnc9PUAx\",\"DeviceID\":\"0\",\"ImageID\":\"022019121117052900016\",\"EventSort\":0,\"ShotTime\":\"19700119134600\",\"Height\":-1,\"FileFormat\":\"jpg\",\"Width\":-1},{\"Type\":\"14\",\"StoragePath\":\"http://192.168.166.203:11180/storage/v1/image/global?cluster_id=ShenSi&uri_base64=bm9ybWFsOi8vcmVwb3NpdG9yeS1idWlsZGVyLzIwMjAwMTIwL0JXS3RIQm1aVXpUT3prNzJOYW50S1E9PUAx\",\"DeviceID\":\"0\",\"ImageID\":\"022019121117052900016\",\"EventSort\":0,\"ShotTime\":\"19700119134600\",\"Height\":-1,\"FileFormat\":\"jpg\",\"Width\":-1}]"; JSONArray jsonArray = new JSONArray(jsonArrayStr);
5、解析 JSONArray:
1)获取数组对象 JSONArray subImageInfoObjectList = subImageList.getJSONArray("SubImageInfoObject");
2)获取单值 for(int j=0; j< jsonArray.size(); j++){ JSONObject subImageInfoObject = jsonArray.getJSONObject(j); String Type = subImageInfoObject.getStr("Type"); Integer width = subImageInfoObject.getInt("Width"); System.out.println(Type); }
注:以上代码可能有些值对应不上,本文只是提供一个切实有效的思路,测试代码需要根据示例自我调整。
点关注,不迷路
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是 人才 。
白嫖不好,创作不易。 各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !
点关注,不迷路
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是 人才 。
白嫖不好,创作不易。 各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !
本文来自: 微信公众号【大数据实战演练】。阅读更多精彩好文,欢迎关注微信公众号【大数据实战演练】。
大数据
2020-02-07 10:20:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
一、前言
很多小伙伴也都知道,最近一直在做 Ambari 集成自定义服务的教学笔记和视频。
之前在准备 Ambari 环境的时候,考虑到有朋友会在 Ambari 安装部署时遇到问题,所以贴心的我呢,就在搭建 Ambari 环境的时候,把这个视频录制好了,总共时长共 87 分钟,将近1个半小时,附带移除 SmartSense 服务及 FAQ 。
也提前介绍一下搭建好的 Ambari 相关版本信息: Ambari:2.7.3.0 hdp:3.1.0.0
二、课程特色
三、课程大纲
四、观看须知
五、再唠叨一下
以上为安装 Ambari 以及部署 Hadoop 集群的视频介绍及宣传,完整视频已上传到 哔哩哔哩-bilibili 平台,视频链接: Ambari 2.7.3.0 安装部署 hadoop 3.1.0.0 集群完整版,附带移除 SmartSense 服务及 FAQ 。
很多小伙伴也都知道,最近一直在做 Ambari 集成自定义服务的教学笔记和视频。目前笔记已经完成 90 %,笔记一整理完,就会参考笔记录制 Ambari 集成自定义服务的教学视频。
Ambari 集成自定义服务脑图 ,如下图所示:
**目前已整理好的笔记,**如下图所示:
一直在行动!希望大家也多多关注,多多宣传,谢谢!
点关注,不迷路
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是 人才 。
白嫖不好,创作不易。 各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !
点关注,不迷路
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是 人才 。
白嫖不好,创作不易。 各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !
本文来自: 微信公众号【大数据实战演练】。阅读更多精彩好文,欢迎关注微信公众号【大数据实战演练】。
大数据
2020-02-06 23:52:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
前段时间写了一个数据导入工具,数据导入的路径为Hadoop-->公司自研的时序数据库。运行一段时间后发现出现Kerberos认证过期的问题。日志如下:
一开始很奇怪,因为额外开了一个线程,每隔8小时更新Kerberos认证,照理说不该出现这个问题。看直接报错,显示是找不到Kerberos票据,更是一头雾水。
直到看到红框框住的这句话,知道问题所在了。代码中有个逻辑为忽略大小为0的文件,是这样写的: val hadoopConf:Configuration = new Configuration() hadoopConf.addResource( new Path("core-site.xml")) hadoopConf.addResource( new Path("hdfs-site.xml")) hadoopConf.addResource( new Path("hive-site.xml")) hadoopConf.addResource( new Path("yarn-site.xml")) val path = new Path("hdfs://hacluster/user/oper/private/123") val hdfs = FileSystem.get(hadoopConf) val status = hdfs.listStatus(path) status.foreach( file=>{ val filePath = file.getPath().toString; val fileSize = hdfs.getContentSummary(file.getPath()).getLength() if (fileSize > 0 ) { //逻辑代码 } else { logger.info("path:{}, fileSize = {}.",filePath,fileSize) } } )
这个情况应该是hdfs对象的Kerberos认证过期了,Kerberos认证更新的代码对这个对象不起作用。认清问题后,就知道如何解决这个问题了,不需要边迭代,边判断大小,可以提前过滤掉这些文件。代码如下: val hadoopConf:Configuration = new Configuration() hadoopConf.addResource( new Path("core-site.xml")) hadoopConf.addResource( new Path("hdfs-site.xml")) hadoopConf.addResource( new Path("hive-site.xml")) hadoopConf.addResource( new Path("yarn-site.xml")) val path = new Path("hdfs://hacluster/user/oper/private/123") val hdfs = FileSystem.get(hadoopConf) val status = hdfs.listStatus(path).filter(file=>(hdfs.getContentSummary(file.getPath()).getLength())>0).map(p=>p.getPath.toString) hdfs.close() FileSystem.closeAll() status.foreach( file=>{ //逻辑代码 } )
重新打包程序,上传并运行。36小时后,程序仍无异常,问题解决。
大数据
2020-02-06 18:38:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
nbdev是一个使用Jupyter Notebook进行多模块软件开发的辅助工具,可以将多个Notebook组装为一个大型的软件系统,自动生成代码和文档,并能够在Notebook中进行交互运行和探索性测试。 nbdev 源码: https://github.com/fastai/nbdev nbdev 文档: https://nbdev.fast.ai/
nbdev主要完成下面的功能: 建立python开发框架和git repo版本。 在notebook中的cell中使用#export标记为输出代码区块。 使用库函数notebook2script()将#export标记的代码转化为python库*.py。 所生成的代码放在project name的目录下,可以在notebook中引用或者直接运行。 例程项目,用于nCoV分析(初级阶段): https://github.com/openthings/anti2020ncov
1、安装
nbdev已经加入 PyPI,使用下面的命令安装: pip install nbdev
对于 editable install ,使用: git clone https://github.com/fastai/nbdev pip install -e nbdev
2、快速开始
2.1 创建项目
开始自己的 project, 点击: nbdev template (会要求登录到 GitHub )。填写要求的信息,然后点击 Create repository from template , 新的 GitHub repo将会在你的账户下创建出来。
注意: 项目的名称将成为 Python package的名称,由 nbdev 自动创建。建议选择短的全部小写而且没有连接线的名称(下划线可以)。
现在,打开终端,将该 repo 拉去到本地(git clone ...),可以使用Jupyter里面创建的终端窗口。
可选方式,可以使用nbdev的客户端命令 nbdev_new 来创建 nbdev project,并且初始化git repository到本地,不会创建 github repo。
2.2 设置参数
下一步,编辑 settings.ini 文件。
该文件包含所有的打包你的库的信息因此不需要改变 setup.py 文件,这是由模版工程创建的 。基本结构 (that can be personalized provided you change the relevant information in settings.ini ) 是 repo的根目录将包含你的 notebooks, 目录 docs 包含文档是自动创建的,可用于 jekyll -powered 站点。因为 GitHub Pages supports Jekyll , 可以将该文档放到 GitHub ,不需要做任何额外的设置。
你的 settings.ini 放在 nbdev,用于任何需要的配置信息。如果编辑了该文件,运行命令 nbdev_build_lib (安装 nbdev时自动安装)。 你将发现有了一个新的新的目录,按照 lib_name 在 settings.ini 中的设置。
现在运行 jupyter notebook , 点击 00_core.ipynb。 这是你创建的第一个模块。可以在notebook中创建多个Jupyter cells。 在需要包含在python模块的cell最前面加上 #export ,必须是第一行。 注意: 对于新创建的 .ipynb,需要加入 #default_exp target_module_name , 用于定义创建的模块名称(lib_name/target_module_name.py)。
在notebook的最后一个cell,可以运行下面的函数调用,用于创建*.py文件。 from nbdev.export import * notebook2script() Converted 00_export.ipynb. Converted 01_sync.ipynb. Converted 02_showdoc.ipynb. Converted 03_export2html.ipynb. Converted 04_test.ipynb. Converted 05_merge.ipynb. Converted 06_cli.ipynb. Converted 07_clean.ipynb. Converted 99_search.ipynb. Converted index.ipynb. Converted tutorial.ipynb.
或者在命令行下运行命令:
nbdev_build_lib
将会更新模块,包含所有的exported cells。
为了在 GitHub repo启用文档功能,到github.com的项目中点击 'Settings' ,到 'GitHub Pages', 在 'Source' 选择'master branch /docs folder'。GitHub 将现实文档站点的连接。
最后,编辑 edit index.ipynb 。该页面将转化为项目文档的 README 文件,也是你的文档的索引。你可以使用输出库中的模块,意味着可以显示正式的工作代码和实际输出 。一旦准备好了,到命令行运行 nbdev_build_docs,将输出 将notebook输出到 HTML 版本并保存到 docs 目录。同时将创建链接 ,创建所有notebook的菜单,以及内容的目录表等。
注意,如何使用子目录保存 .ipynb 文件
如果你设置过参数 nbs_path 在 project root之外,将不能 import 创建的模块,需要一些额外的操作: 手动安装模块, relative import 将与top-level package关联,通过运行 pip install -e . 在项目根目录,安装模块到 环境中。 创建软连接到notebook 目录到library folder,通过运行 ln -s lib_path lib_name (修改 lib_path 和 lib_name为你的项目情况 ).
其它功能
nbdev 还有很多其他功能,查看相应的文档。下面简单提一下:
添加project 到 pypi
如果你希望其他人可以通过pypi来进行安装,需要将其提交到 pypi 。好消息是,我们已经创建了一个 pypi compliant installer for your project!创建一个文件为 ~/.pypirc 填入登录信息。内容看起来如下所示: [pypi] username = your_pypi_username password = your_pypi_password
需要安装 twine ,运行: pip install twine
上载 project 到pypi,只需要运行 make pypi , 在 project root 目录下。完成后,指向 pypi 的连接会被打印出来。
NB : 确保每一次增加 version number,位于 settings.py ,用于向pypi推送一个新的版本。
避免和处理 git conflicts
Jupyter Notebooks 会引起 git conflicts, 但使用 nbdev 就要轻松很多。 第一步,运行 nbdev_install_git_hooks将设置 git hooks,每一次commit时会移除元数据,会极大减少 conflict。
但是,如果遇上 conflict, 简单滴运行 nbdev_fix_merge filename.ipynb . 将用你的版本替换任何 conflicts in cell outputs , 如果 conflicts in input cells,两个 cells 都将被包含在 merged file, 使用标准的冲突标示 (e.g. ===== )。然后你可以打开 notebook到 Jupyter,然后选择保留哪一个。
使用nbdev作为 CI的一部分
可以使用 GitHub actions 来提升 nbdev的功能,容易滴构建 CI : 检查 notebooks are readable (with nbdev_read_nbs ) 检查the notebooks have been cleaned of needless metadata to avoid merge conflicts (with nbdev_clean_nbs ) 检查there is no diff between the notebooks and the exported library (with nbdev_diff_nbs ) 运行notebooks 中的测试(with nbdev_test_nbs )
该template 包含基本的 CI,使用了上面四点,编辑文件 .github/workflows/main.yml 修改相关的部分。
Math 公式支持
nbdev 支持公式 (采用 KaTeX library )。使能 use_math: true 到 your _config.yml (缺省已开启)。应用方法如下:
使用 $$ , e.g.: $$\sum_{i=1}^{k+1}i$$
显示为: $$\sum_{i=1}^{k+1}i$$
使用 $ , e.g.: This version is diplayed inline: $\sum_{i=1}^{k+1}i$ . You can include text before and after.
显示为: This version is diplayed inline: $\sum_{i=1}^{k+1}i$ . You can include text before and after.

更多参考: 关于nbdev的介绍, https://tech.sina.com.cn/roll/2020-02-03/doc-iimxxste8470190.shtml 运用Jupyter Notebook进行系统管理 将Jupyter/JupyterHub/JupyterLab运行为系统服务 快速设置JupyterHub for K8s 抗击新冠病毒(2)-基于Jupyter+nbdev的数据分析
大数据
2020-02-06 17:00:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
2020年的1月份过去了,漫长的像过了一个年。新型冠状病毒疫情牵动着无数人的心。
面对疫情导致的封停隔离,众多企业在复工后,都选择了远程协同办公。远程办公不是个新鲜事儿,国外已经玩了多年,但中国人还不太习惯。在毫无准备的情况下,突然开展对很多企业和团队管理者还是蛮有杀伤力的。
越是危难时刻,越要凝聚人心。这时候需要leader思考怎么带队伍,怎么发挥领导力,怎么让我们的团队更加精进,怎么把远程协同开发做到更高效,同时也给团队成员把握住这次千载难逢的学习机会。
msup作为拥有11年经验的软件研发团队的培训咨询机构,在这场疫情防控阻击战中,我们也希望能够践行我们的企业责任。
为此,我们 决定把msup价值数百万元的线下技术大会视频实录放送出来 ,供大家免费学习,这些内容汇聚了全球300+国内外顶尖大厂技术实践(视频已陆续上线中), 包含产品、架构、AI、大数据、数字化转型、研发管理、测试运维等几十个领域。 希望这些实录课程能够给疫情期间影响下的研发团队提供关于人才成长的一些帮助,让大家在疫情期间不间断学习。下面是关于限免视频的一些介绍,欢迎感兴趣的同学接着向下看。
同时,也欢迎大家将此活动分享给同事、朋友,一起学习,共同成长。疫情当前,msup衷心祝愿大家平安健康,若大家有任何研发问题或苦恼,随时召唤我们,我们会一直在您身边。
具体参与方式:微信公号搜索msup或msupclub,及会弹出视频地址,已关注的小伙伴可移步到msup菜单栏进入案例学院观看学习。
大数据
2020-02-06 15:06:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
甩开技术包袱,做出业务特色,越发成为垂直电商的生存之道
  今天,人们通过天猫、淘宝、苏宁进行网络购物,不仅方便,而且快乐,通过盒马、饿了么享受更加快捷的本地生活服务,与此同时,家电、食品等垂直领域的电商平台,也越来越有特色。
  A公司是中国较早的食品领域相关产品和服务供应商,旗下的A1网致力于打造安全的大型食品购物网站。
对食品进行优选,在保质期上严格把关,对食品库存温湿度以及人员健康严格把关的同时,网站不断丰富食品种类,满足不同人群的需求,和其他垂直领域电商平台一样,A1网也决定把更多的精力用在不断提升服务质量和特色,网站本身往云上迁移,借助云计算的优势,不断提升系统响应速度,让购物更加快捷,这已经成为该领域正在发生着的一个显著变化。
  把IT基础设施重资产(包括基于IDC建设的服务器、存储设备、网络与安全设备等)转移到云上,实现IT基础设施的虚拟化,根据业务量弹性灵活的使用资源,做到按量使用、按需付费,还有另一个附加好处,增强了经济贸易风险抵御能力。当经济形势触底的时候,对于最坏的情况进行兜底,对于经济形势随着拐点上行的时候,能够轻装前进,支持业务的快速扩张。
  本文将以案例说明上云为支撑业务和降低成本带来的转变,以及客户的顾虑如何消除,最后通过什么样的步骤上云。
系统越复杂越需要上云,才能更敏捷的支撑业务
  业务的敏捷性要求,对使用传统的IDC部署开源软件运维的方式提出了挑战。
  上云之前,网站已经具有了相当的规模,在北京世纪互联主机房有数十个机柜,在同城的备机房也有几个机柜,主要是存放备份、数据库和代码。
图1 云下信息图
  使用了万网的DNS服务和第三方的CDN服务,采用7层负载均衡,主机为物理机+kvm+vmware+docker的组合,存储设备在主机房,有着数十TB的数据,使用了开源的漏洞扫描软件,应用服务器采用了Nginx+Apache+Resin+Tomcat的组合,数据库使用了SQLServer+MySql+Oracle等多种产品。
  另外还使用了Redis缓存、ZooKeeper、MongoDB、ELK、Docker、K8S以及监控软件和SVN,应用服务器+web前端+缓存+数据库一共有上千台服务器,操作系统则包括了centos和windows的多个版本。
  与复杂的系统对应的是一支数十人的运维团队,随着时间的推移,不同厂商的产品越来越错综复杂,而业务的增长进一步为系统的平稳运行带来了更大的挑战,如何减少服务之间的依赖关系,如何打通数据,提高对新业务新需求的响应速度,如何提高运维人机比,这一系列的问题成为需要长期应对的挑战。
  而云上通过微服务架构,通过使用企业分布式服务EDAS、消息队列、容器等服务和产品,极大的保障了系统的服务化和异步化,通过云上数据库、中间件、移动和物联网套件以及小程序,可以快速搭建应用试错,快速响应业务需求。
上云后可以大幅度缩减成本
图2 云上架构图
  由于电商特色的各种打折促销活动,带来了系统流量的波峰波谷,导致平时服务器的利用率远远不到50%,对于这部分业务,云上可以使用包年包月的服务,性价比足够高,而对于其他波峰的业务在云上则可以使用按量付费的服务,在SLB设置规则当流量达到一定量的时候弹起一台新机器,还可以考虑纵向的弹性扩展,比如提高配置规格,使得总体成本缩减到原来的一半。
上云后,客户需要通过等保三级测评,可以通过我们和合作伙伴的服务帮助客户准备材料,指导客户顺利通过测评,和自建机房相比,成本更低。
  存储都通过OSS上云,云下不存放数据,后续根据需要可以增加异地OSS备份,既节省了原来的存储成本,云上三副本的AZ内高可用、同城双AZ容灾和异地三AZ备份,很好的确保了数据可靠性与可用性。
以客户第一的理念消除顾虑
  在行业大背景下,以实实在在的业务和技术的痛点为驱动,上云带来的好处为指引,使得今天更多的客户不是谈论什么是云计算,为什么要上云,而是在谈论如何快速高效的上云。与此同时,作为一家专门提供云服务的技术输出公司,我们正以专业的态度服务客户,从而打消一切顾虑,下面列举了一些客户因为顾虑而提出的问题,以及我们的解答。 双11阿里会不会先保障天猫、淘宝,由此可能伤害客户?
面对这样尖锐的问题,我们其实想到了前面,早在每年十月份或更早就开始询问客户的增量,提前为客户做好扩容准备,以确保客户的业务为首先要保障的目标。 如果要下云的时候会不会像某云计算厂商一样制造阻碍,关掉Redis的相应功能,使得客户不能使用DTS进行不停服迁移?
我们的回答是,绝对不会的,可以通过实际使用来验证我们的承诺。 对于A1网而言,由于客服线路等原因,有些数据库迁移不上云,能否云上应用访问云下数据库?
回答是可以的,通过ECC建立云上和IDC之间的高速通道就可以很好的满足业务需要。 我们为客户提供的安全合规服务收费吗?
我们提供资料都是免费的,如果需要购买安全产品比如WAF则要按照WAF产品付费。 上云后的成本怎么样?
通常比原成本节省30%,可以使用TCO计算器进行成本分析和现有IDC成本对比。 部分运维人员担心上云后会不会没有工作?
事实上,上云后运维同学可以解放出来从事云服务管理等更加高级的工作,大数据开发工程师可以做数据分析师和技术支持工作,产生的价值更大,因为业务价值更大。同时,阿里云还提供了从Apsara Clouder、ACA助理工程师、ACP专业工程师到ACE高级工程师的云计算专业技能和资格认证服务,基于云计算、大数据、云安全、人工智能、中间件几大领域,证明技术人员可以基于阿里云产品在对应的技术方向上解决企业的基本业务问题。
上云七步走
图3 上云方案图
   网络迁移,是首先要解决的问题 。迁移前,了解机房和办公区之间、机房之间的专线情况,包括带宽、类型等;迁移时,建立ECC高速通道。
   应用迁移,一般先于数据库进行 。迁移前,确定双机房之间是否实现了主从,比如通过Bind服务域名解析配置主从服务器等;迁移时,在云上重新部署,或者使用SMC做应用迁移;技术细节可以参考“ 服务器迁移最佳实践 ”。
   缓存迁移,包括Redis、MemCache迁移 。迁移前,确定存放的是什么数据,比如session,用户登录信息等,确定存放的哪些数据是可以丢弃的,应用重新从数据库更新,确定需要迁移的数据量;迁移时,使用DTS工具进行迁移,且支持增量;应用和Redis之间如果有代理程序,需要先进行失效处理。
   数据库迁移,主要是MySQL迁移 。迁移前,确定每个业务线对应的实例使用情况,实例共享物理机情况,确定端口是否都使用3306,不同的实例是否都使用不同的域名,MySQL以哪些版本为主,确认云上是否覆盖,统计实例总数,一台物理机多少个实例,每个实例数据量级别,通过POC来熟悉DTS用法;迁移时,使用DTS工具迁移;如果出现异常则可采用回退策略,DTS回流到线下,客户可能做业务改动,比如将IP改成域名,需要提前检查。技术细节可以参考 最佳实践
   半结构化数据迁移,包括MongoDB迁移 。迁移前,需要了解有几个集群,总共多少TB数据,需要做云服务POC验证;迁移时,使用云自建方式,加分片方式迁移,也可以使用云服务方式,通过使用DTS工具从自建迁移过来。
存储迁移,主要指非结构化数据迁移。迁移前,了解现有存储的数据是哪些,含副本数据量和实际数据量,负载情况,读写比例等,需要POC来熟悉产品用法,包括测试使用OSSImport来将IDC文件迁移到OSS;迁移时,历史数据使用闪电立方迁移到OSS,热数据使用OSSImport迁移到OSS云上存储;迁移到OSS需要改应用,需要注意检查避免出错,另外,如果小文件较多,则迁移耗时可能会较长,需要提前做好安排。
   如上所述,通过分析需求和关键的技术难点,制定了上云方案,该方案按照标准化的流程对应考虑的技术问题做了规范约定,包括准备工作阶段、迁移方法和风险预案,通过方案的实施,能够有效的帮助客户提高上云的效率,降低风险。

查看更多:https://yq.aliyun.com/articles/740083?utm_content=g_1000102637上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-06 15:04:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
“通过宜搭开放的接口能力,我们将审批流程与财务系统、支付系统打通。现在日常报销、对外付款,资金上交下拨等业务只需手机点一点审批,后续记账、付款数据自动生成,大幅提升了效率,节约了人工。”
——居然之家家居新零售财务总监:朱蝉飞
案例背景
居然之家是中国家居建材流通行业的龙头企业,2018年市场销售额超过750亿元。企业经过了20年的持续发展,目前全国设有20余家分公司、300余家分店,设立各类部门组织1800多个,管理直营及加盟店员工超14000人。为支撑业务的快速发展,居然之家从未停止建设IT系统的步伐,一方面斥巨资打造内部大ERP,建立前、中、后台架构的“智汇家”系统,另一方面引入阿里的理念与技术建设了新零售平台,成为了家居新零售的排头兵。
然而,与业务系统的不断创新与完善相比,企业内部协同系统的建设水平却相对滞后。庞大的组织带来了企业内部沟通不便、信息上传下达链路过长等问题。为使企业管理更加扁平化,组织协同更加高效,2017年底居然之家使用了钉钉,从组织在线入手开启了企业数字化转型之路。
钉钉的产品能力很好地解决了组织在线、沟通在线的问题,但面对企业流程协同、应用搭建及业务集成方面需要更专业的产品来支持,以满足集团型企业下的复杂表单及流程场景需求。审批不仅仅是文件的上传下达,而是作为整个业务流程中的重要一环,要与各个业务系统集成并实现在线化。大型企业亟需一套能满足复杂场景并且能低成本使用的表单流程搭建工具。
基于以上痛点,居然之家信息化部门开始寻找合适的OA产品,但后来发现传统OA虽然能满足场景需要,但产品一般较为复杂,另外,集团正在打造以钉钉为统一入口的移动办公门户,这就要求所有的应用和流程搭建能快速地发布在钉钉上。传统OA需要额外投入资源打通产品与钉钉的组织人员权限,并不是最优的选择。
直到“宜搭”的出现为上述问题提供了全新解决方案:高度可配置的表单和流程;天然可“长”在钉钉上的组织人员数据;PC和移动双端适配的开发平台;强大的接口能力及可配置化的服务调用等,大幅减少了系统集成难度和开发量,满足了大型企业面对复杂业务场景的应用开发需求。
方案架构
案例效果

行政办公流程应用
行政办公流程应用中包含办公和物资的相关审批流程。根据使用用户视角分为集团 / 家居连锁集团流程类、外埠分公司及分店流程类、设计家网络公司流程类。行政办公应用内主要搭建了刻印公章申请流程、单位间盖章流程、公文发布申请流程、借用公章申请流程、借用营业执照申请流程。

办公门户建设
通过宜搭搭建的流程展示页,作为 PC 端用户进入相关流程的入口。根据使用视角对所有展示页进行分组展现,共分集团展示页、北京分店展示页、外埠分店展示页、外埠分公司展示页、设计家网络公司展示页和矩阵维护展示页,共计 6 个展示页面。

宜搭集成
通过宜搭处理人事、综合、行政、财务的审批流程,为了保持宜搭与 HR 数据的一致性,将基于 active MQ、RFC 接口通过以下方式将 HR 和财务系统与宜搭进行同步。
https 数据服务,提供宜搭单据中 HR 数据的及时查询及返回,宜搭单据中配置数据池,通过 js 将数据池中数据写入单据中显示。
https 回调服务,提供宜搭审批节点提交时,通过自定义回调服务进行数据处理。
主数据同步,将 HR 中人员信息、岗位信息、组织架构信息及时同步至宜搭中搭建的主数据单据中。
定时任务同步,通过配置同步时间及频率,自动检查并同步 HR 系统中至宜搭数据单据中,同步宜搭中审批实例至集成方。
手动同步,通过运维管理应用可手动触发完成同步。将同步包括不限于以下数据:HR 中组织架构信息、员工信息、岗位信息、人事范围、员工组等信息。
同步逻辑:HR 为主数据,系统同步均为单向同步。员工信息、组织架构、岗位等信息以 HR 系统中编码为唯一标识,存在时通过标识更新数据,否则新增该条数据。
财务集成:员工报销、对外付款、资金下拨等财务流程审批后同步至财务系统,财务自动记账并调用银企直连支付接口实现付款。

查看更多: https:// yqh.aliyun.com/

上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问: 云栖号 - 上云就看云栖号
大数据
2020-02-06 11:59:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
这篇文章来自一个读者在面试过程中的一个问题,Hadoop在shuffle过程中使用了一个数据结构-环形缓冲区。
环形队列是在实际编程极为有用的数据结构,它是一个首尾相连的FIFO的数据结构,采用数组的线性空间,数据组织简单。能很快知道队列是否满为空。能以很快速度的来存取数据。 因为有简单高效的原因,甚至在硬件都实现了环形队列。
环形队列广泛用于网络数据收发,和不同程序间数据交换(比如内核与应用程序大量交换数据,从硬件接收大量数据)均使用了环形队列。 环形缓冲区数据结构
Map过程中环形缓冲区是指数据被map处理之后会先放入内存,内存中的这片区域就是环形缓冲区。
环形缓冲区是在MapTask.MapOutputBuffer中定义的,相关的属性如下:
// k/v accounting // 存放meta数据的IntBuffer,都是int entry,占4byte private IntBuffer kvmeta; // metadata overlay on backing store int kvstart; // marks origin of spill metadata int kvend; // marks end of spill metadata int kvindex; // marks end of fully serialized records // 分割meta和key value内容的标识 // meta数据和key value内容都存放在同一个环形缓冲区,所以需要分隔开 int equator; // marks origin of meta/serialization int bufstart; // marks beginning of spill int bufend; // marks beginning of collectable int bufmark; // marks end of record int bufindex; // marks end of collected int bufvoid; // marks the point where we should stop // reading at the end of the buffer // 存放key value的byte数组,单位是byte,注意与kvmeta区分 byte[] kvbuffer; // main output buffer private final byte[] b0 = new byte[0];
// key value在kvbuffer中的地址存放在偏移kvindex的距离 private static final int VALSTART = 0; // val offset in acct private static final int KEYSTART = 1; // key offset in acct // partition信息存在kvmeta中偏移kvindex的距离 private static final int PARTITION = 2; // partition offset in acct private static final int VALLEN = 3; // length of value // 一对key value的meta数据在kvmeta中占用的个数 private static final int NMETA = 4; // num meta ints // 一对key value的meta数据在kvmeta中占用的byte数 private static final int METASIZE = NMETA * 4; // size in bytes
环形缓冲区其实是一个数组,数组中存放着key、value的序列化数据和key、value的元数据信息,key/value的元数据存储的格式是int类型,每个key/value对应一个元数据,元数据由4个int组成,第一个int存放value的起始位置,第二个存放key的起始位置,第三个存放partition,最后一个存放value的长度。
key/value序列化的数据和元数据在环形缓冲区中的存储是由equator分隔的,key/value按照索引递增的方向存储,meta则按照索引递减的方向存储,将其数组抽象为一个环形结构之后,以equator为界,key/value顺时针存储,meta逆时针存储。
初始化
环形缓冲区的结构在MapOutputBuffer.init中创建。
public void init(MapOutputCollector.Context context ) throws IOException, ClassNotFoundException { ... //MAP_SORT_SPILL_PERCENT = mapreduce.map.sort.spill.percent // map 端buffer所占的百分比 //sanity checks final float spillper = job.getFloat(JobContext.MAP_SORT_SPILL_PERCENT, (float)0.8); //IO_SORT_MB = "mapreduce.task.io.sort.mb" // map 端buffer大小 // mapreduce.task.io.sort.mb * mapreduce.map.sort.spill.percent 最好是16的整数倍 final int sortmb = job.getInt(JobContext.IO_SORT_MB, 100); // 所有的spill index 在内存所占的大小的阈值 indexCacheMemoryLimit = job.getInt(JobContext.INDEX_CACHE_MEMORY_LIMIT, INDEX_CACHE_MEMORY_LIMIT_DEFAULT); ... // 排序的实现类,可以自己实现。这里用的是改写的快排 sorter = ReflectionUtils.newInstance(job.getClass("map.sort.class", QuickSort.class, IndexedSorter.class), job); // buffers and accounting // 上面IO_SORT_MB的单位是MB,左移20位将单位转化为byte int maxMemUsage = sortmb << 20; // METASIZE是元数据的长度,元数据有4个int单元,分别为 // VALSTART、KEYSTART、PARTITION、VALLEN,而int为4个byte, // 所以METASIZE长度为16。下面是计算buffer中最多有多少byte来存元数据 maxMemUsage -= maxMemUsage % METASIZE; // 元数据数组 以byte为单位 kvbuffer = new byte[maxMemUsage]; bufvoid = kvbuffer.length; // 将kvbuffer转化为int型的kvmeta 以int为单位,也就是4byte kvmeta = ByteBuffer.wrap(kvbuffer) .order(ByteOrder.nativeOrder()) .asIntBuffer(); // 设置buf和kvmeta的分界线 setEquator(0); bufstart = bufend = bufindex = equator; kvstart = kvend = kvindex; // kvmeta中存放元数据实体的最大个数 maxRec = kvmeta.capacity() / NMETA; // buffer spill时的阈值(不单单是sortmb spillper) // 更加精确的是kvbuffer.length spiller softLimit = (int)(kvbuffer.length * spillper); // 此变量较为重要,作为spill的动态衡量标准 bufferRemaining = softLimit; ... // k/v serialization comparator = job.getOutputKeyComparator(); keyClass = (Class)job.getMapOutputKeyClass(); valClass = (Class)job.getMapOutputValueClass(); serializationFactory = new SerializationFactory(job); keySerializer = serializationFactory.getSerializer(keyClass); // 将bb作为key序列化写入的output keySerializer.open(bb); valSerializer = serializationFactory.getSerializer(valClass); // 将bb作为value序列化写入的output valSerializer.open(bb); ... // combiner ... spillInProgress = false; // 最后一次merge时,在有combiner的情况下,超过此阈值才执行combiner minSpillsForCombine = job.getInt(JobContext.MAP_COMBINE_MIN_SPILLS, 3); spillThread.setDaemon(true); spillThread.setName("SpillThread"); spillLock.lock(); try { spillThread.start(); while (!spillThreadRunning) { spillDone.await(); } } catch (InterruptedException e) { throw new IOException("Spill thread failed to initialize", e); } finally { spillLock.unlock(); } if (sortSpillException != null) { throw new IOException("Spill thread failed to initialize", sortSpillException); } }
init是对环形缓冲区进行初始化构造,由mapreduce.task.io.sort.mb决定map中环形缓冲区的大小sortmb,默认是100M。
此缓冲区也用于存放meta,一个meta占用METASIZE(16byte),则其中用于存放数据的大小是maxMemUsage -= sortmb << 20 % METASIZE(由此可知最好设置sortmb转换为byte之后是16的整数倍),然后用maxMemUsage初始化kvbuffer字节数组和kvmeta整形数组,最后设置数组的一些标识信息。利用setEquator(0)设置kvbuffer和kvmeta的分界线,初始化的时候以0为分界线,kvindex为aligned - METASIZE + kvbuffer.length,其位置在环形数组中相当于按照逆时针方向减去METASIZE,由kvindex设置kvstart = kvend = kvindex,由equator设置bufstart = bufend = bufindex = equator,还得设置bufvoid = kvbuffer.length,bufvoid用于标识用于存放数据的最大位置。
为了提高效率,当buffer占用达到阈值之后,会进行spill,这个阈值是由bufferRemaining进行检查的,bufferRemaining由softLimit = (int)(kvbuffer.length * spillper); bufferRemaining = softLimit;进行初始化赋值,这里需要注意的是softLimit并不是sortmb spillper,而是kvbuffer.length * spillper,当sortmb << 20是16的整数倍时,才可以认为softLimit是sortmb spillper。
下面是setEquator的代码
// setEquator(0)的代码如下 private void setEquator(int pos) { equator = pos; // set index prior to first entry, aligned at meta boundary // 第一个 entry的末尾位置,即元数据和kv数据的分界线 单位是byte final int aligned = pos - (pos % METASIZE); // Cast one of the operands to long to avoid integer overflow // 元数据中存放数据的起始位置 kvindex = (int) (((long)aligned - METASIZE + kvbuffer.length) % kvbuffer.length) / 4; LOG.info("(EQUATOR) " + pos + " kvi " + kvindex + "(" + (kvindex * 4) + ")"); }
buffer初始化之后的抽象数据结构如下图所示: 环形缓冲区数据结构图
环形缓冲区数据结构图
写入buffer
Map通过NewOutputCollector.write方法调用collector.collect向buffer中写入数据,数据写入之前已在NewOutputCollector.write中对要写入的数据进行逐条分区,下面看下collect
// MapOutputBuffer.collect public synchronized void collect(K key, V value, final int partition ) throws IOException { ... // 新数据collect时,先将剩余的空间减去元数据的长度,之后进行判断 bufferRemaining -= METASIZE; if (bufferRemaining <= 0) { // start spill if the thread is not running and the soft limit has been // reached spillLock.lock(); try { do { // 首次spill时,spillInProgress是false if (!spillInProgress) { // 得到kvindex的byte位置 final int kvbidx = 4 * kvindex; // 得到kvend的byte位置 final int kvbend = 4 * kvend; // serialized, unspilled bytes always lie between kvindex and // bufindex, crossing the equator. Note that any void space // created by a reset must be included in "used" bytes final int bUsed = distanceTo(kvbidx, bufindex); final boolean bufsoftlimit = bUsed >= softLimit; if ((kvbend + METASIZE) % kvbuffer.length != equator - (equator % METASIZE)) { // spill finished, reclaim space resetSpill(); bufferRemaining = Math.min( distanceTo(bufindex, kvbidx) - 2 * METASIZE, softLimit - bUsed) - METASIZE; continue; } else if (bufsoftlimit && kvindex != kvend) { // spill records, if any collected; check latter, as it may // be possible for metadata alignment to hit spill pcnt startSpill(); final int avgRec = (int) (mapOutputByteCounter.getCounter() / mapOutputRecordCounter.getCounter()); // leave at least half the split buffer for serialization data // ensure that kvindex >= bufindex final int distkvi = distanceTo(bufindex, kvbidx); final int newPos = (bufindex + Math.max(2 * METASIZE - 1, Math.min(distkvi / 2, distkvi / (METASIZE + avgRec) * METASIZE))) % kvbuffer.length; setEquator(newPos); bufmark = bufindex = newPos; final int serBound = 4 * kvend; // bytes remaining before the lock must be held and limits // checked is the minimum of three arcs: the metadata space, the // serialization space, and the soft limit bufferRemaining = Math.min( // metadata max distanceTo(bufend, newPos), Math.min( // serialization max distanceTo(newPos, serBound), // soft limit softLimit)) - 2 * METASIZE; } } } while (false); } finally { spillLock.unlock(); } } // 将key value 及元数据信息写入缓冲区 try { // serialize key bytes into buffer int keystart = bufindex; // 将key序列化写入kvbuffer中,并移动bufindex keySerializer.serialize(key); // key所占空间被bufvoid分隔,则移动key, // 将其值放在连续的空间中便于sort时key的对比 if (bufindex < keystart) { // wrapped the key; must make contiguous bb.shiftBufferedKey(); keystart = 0; } // serialize value bytes into buffer final int valstart = bufindex; valSerializer.serialize(value); // It's possible for records to have zero length, i.e. the serializer // will perform no writes. To ensure that the boundary conditions are // checked and that the kvindex invariant is maintained, perform a // zero-length write into the buffer. The logic monitoring this could be // moved into collect, but this is cleaner and inexpensive. For now, it // is acceptable. bb.write(b0, 0, 0); // the record must be marked after the preceding write, as the metadata // for this record are not yet written int valend = bb.markRecord(); mapOutputRecordCounter.increment(1); mapOutputByteCounter.increment( distanceTo(keystart, valend, bufvoid)); // write accounting info kvmeta.put(kvindex + PARTITION, partition); kvmeta.put(kvindex + KEYSTART, keystart); kvmeta.put(kvindex + VALSTART, valstart); kvmeta.put(kvindex + VALLEN, distanceTo(valstart, valend)); // advance kvindex kvindex = (kvindex - NMETA + kvmeta.capacity()) % kvmeta.capacity();
} catch (MapBufferTooSmallException e) { LOG.info("Record too large for in-memory buffer: " + e.getMessage()); spillSingleRecord(key, value, partition); mapOutputRecordCounter.increment(1); return; } } 每次写入数据时,执行bufferRemaining -= METASIZE之后,检查bufferRemaining,
如果大于0,直接将key/value序列化对和对应的meta写入buffer中,key/value是序列化之后写入的,key/value经过一些列的方法调用Serializer.serialize(key/value) -> WritableSerializer.serialize(key/value) -> BytesWritable.write(dataOut) -> DataOutputStream.write(bytes, 0, size) -> MapOutputBuffer.Buffer.write(b, off, len),最后由MapOutputBuffer.Buffer.write(b, off, len)将数据写入kvbuffer中,write方法如下:
public void write(byte b[], int off, int len) throws IOException { // must always verify the invariant that at least METASIZE bytes are // available beyond kvindex, even when len == 0 bufferRemaining -= len; if (bufferRemaining <= 0) { // writing these bytes could exhaust available buffer space or fill // the buffer to soft limit. check if spill or blocking are necessary boolean blockwrite = false; spillLock.lock(); try { do { checkSpillException(); final int kvbidx = 4 * kvindex; final int kvbend = 4 * kvend; // ser distance to key index final int distkvi = distanceTo(bufindex, kvbidx); // ser distance to spill end index final int distkve = distanceTo(bufindex, kvbend); // if kvindex is closer than kvend, then a spill is neither in // progress nor complete and reset since the lock was held. The // write should block only if there is insufficient space to // complete the current write, write the metadata for this record, // and write the metadata for the next record. If kvend is closer, // then the write should block if there is too little space for // either the metadata or the current write. Note that collect // ensures its metadata requirement with a zero-length write blockwrite = distkvi <= distkve ? distkvi <= len + 2 * METASIZE : distkve <= len || distanceTo(bufend, kvbidx) < 2 * METASIZE; if (!spillInProgress) { if (blockwrite) { if ((kvbend + METASIZE) % kvbuffer.length != equator - (equator % METASIZE)) { // spill finished, reclaim space // need to use meta exclusively; zero-len rec & 100% spill // pcnt would fail resetSpill(); // resetSpill doesn't move bufindex, kvindex bufferRemaining = Math.min( distkvi - 2 * METASIZE, softLimit - distanceTo(kvbidx, bufindex)) - len; continue; } // we have records we can spill; only spill if blocked if (kvindex != kvend) { startSpill(); // Blocked on this write, waiting for the spill just // initiated to finish. Instead of repositioning the marker // and copying the partial record, we set the record start // to be the new equator setEquator(bufmark); } else { // We have no buffered records, and this record is too large // to write into kvbuffer. We must spill it directly from // collect final int size = distanceTo(bufstart, bufindex) + len; setEquator(0); bufstart = bufend = bufindex = equator; kvstart = kvend = kvindex; bufvoid = kvbuffer.length; throw new MapBufferTooSmallException(size + " bytes"); } } } if (blockwrite) { // wait for spill try { while (spillInProgress) { reporter.progress(); spillDone.await(); } } catch (InterruptedException e) { throw new IOException( "Buffer interrupted while waiting for the writer", e); } } } while (blockwrite); } finally { spillLock.unlock(); }
} // here, we know that we have sufficient space to write if (bufindex + len > bufvoid) { final int gaplen = bufvoid - bufindex; System.arraycopy(b, off, kvbuffer, bufindex, gaplen); len -= gaplen; off += gaplen; bufindex = 0; } System.arraycopy(b, off, kvbuffer, bufindex, len); bufindex += len; } write方法将key/value写入kvbuffer中,如果bufindex+len超过了bufvoid,则将写入的内容分开存储,将一部分写入bufindex和bufvoid之间,然后重置bufindex,将剩余的部分写入,这里不区分key和value,写入key之后会在collect中判断bufindex < keystart,当bufindex小时,则key被分开存储,执行bb.shiftBufferedKey(),value则直接写入,不用判断是否被分开存储,key不能分开存储是因为要对key进行排序。
这里需要注意的是要写入的数据太长,并且kvinde==kvend,则抛出MapBufferTooSmallException异常,在collect中捕获,将此数据直接spill到磁盘spillSingleRecord,也就是当单条记录过长时,不写buffer,直接写入磁盘。
下面看下bb.shiftBufferedKey()代码
// BlockingBuffer.shiftBufferedKey protected void shiftBufferedKey() throws IOException { // spillLock unnecessary; both kvend and kvindex are current int headbytelen = bufvoid - bufmark; bufvoid = bufmark; final int kvbidx = 4 * kvindex; final int kvbend = 4 * kvend; final int avail = Math.min(distanceTo(0, kvbidx), distanceTo(0, kvbend)); if (bufindex + headbytelen < avail) { System.arraycopy(kvbuffer, 0, kvbuffer, headbytelen, bufindex); System.arraycopy(kvbuffer, bufvoid, kvbuffer, 0, headbytelen); bufindex += headbytelen; bufferRemaining -= kvbuffer.length - bufvoid; } else { byte[] keytmp = new byte[bufindex]; System.arraycopy(kvbuffer, 0, keytmp, 0, bufindex); bufindex = 0; out.write(kvbuffer, bufmark, headbytelen); out.write(keytmp); } } shiftBufferedKey时,判断首部是否有足够的空间存放key,有没有足够的空间,则先将首部的部分key写入keytmp中,然后分两次写入,再次调用Buffer.write,如果有足够的空间,分两次copy,先将首部的部分key复制到headbytelen的位置,然后将末尾的部分key复制到首部,移动bufindex,重置bufferRemaining的值。
key/value写入之后,继续写入元数据信息并重置kvindex的值。
spill
一次写入buffer结束,当写入数据比较多,bufferRemaining小于等于0时,准备进行spill,首次spill,spillInProgress为false,此时查看bUsed = distanceTo(kvbidx, bufindex),此时bUsed >= softLimit 并且 (kvbend + METASIZE) % kvbuffer.length == equator - (equator % METASIZE),则进行spill,调用startSpill
private void startSpill() { // 元数据的边界赋值 kvend = (kvindex + NMETA) % kvmeta.capacity(); // key/value的边界赋值 bufend = bufmark; // 设置spill运行标识 spillInProgress = true; ... // 利用重入锁,对spill线程进行唤醒 spillReady.signal(); } startSpill唤醒spill线程之后,进程spill操作,但此时map向buffer的写入操作并没有阻塞,需要重新边界equator和bufferRemaining的值,先来看下equator和bufferRemaining值的设定:
// 根据已经写入的kv得出每个record的平均长度 final int avgRec = (int) (mapOutputByteCounter.getCounter() / mapOutputRecordCounter.getCounter()); // leave at least half the split buffer for serialization data // ensure that kvindex >= bufindex // 得到空余空间的大小 final int distkvi = distanceTo(bufindex, kvbidx); // 得出新equator的位置 final int newPos = (bufindex + Math.max(2 * METASIZE - 1, Math.min(distkvi / 2, distkvi / (METASIZE + avgRec) * METASIZE))) % kvbuffer.length; setEquator(newPos); bufmark = bufindex = newPos; final int serBound = 4 * kvend; // bytes remaining before the lock must be held and limits // checked is the minimum of three arcs: the metadata space, the // serialization space, and the soft limit bufferRemaining = Math.min( // metadata max distanceTo(bufend, newPos), Math.min( // serialization max distanceTo(newPos, serBound), // soft limit softLimit)) - 2 * METASIZE;
因为equator是kvbuffer和kvmeta的分界线,为了更多的空间存储kv,则最多拿出distkvi的一半来存储meta,并且利用avgRec估算distkvi能存放多少个record和meta对,根据record和meta对的个数估算meta所占空间的大小,从distkvi/2和meta所占空间的大小中取最小值,又因为distkvi中最少得存放一个meta,所占空间为METASIZE,在选取kvindex时需要求aligned,aligned最多为METASIZE-1,总和上述因素,最终选取equator为(bufindex + Math.max(2 * METASIZE - 1, Math.min(distkvi / 2, distkvi / (METASIZE + avgRec) * METASIZE)))。equator选取之后,设置bufmark = bufindex = newPos和kvindex,但此时并不设置bufstart、bufend和kvstart、kvend,因为这几个值要用来表示spill数据的边界。
spill之后,可用的空间减少了,则控制spill的bufferRemaining也应该重新设置,bufferRemaining取三个值的最小值减去2 METASIZE,三个值分别是meta可用占用的空间distanceTo(bufend, newPos),kv可用空间distanceTo(newPos, serBound)和softLimit。这里为什么要减去2 METASIZE,一个是spill之前kvend到kvindex的距离,另一个是当时的kvindex空间????此时,已有一个record要写入buffer,需要从bufferRemaining中减去当前record的元数据占用的空间,即减去METASIZE,另一个METASIZE是在计算equator时,没有包括kvindex到kvend(spill之前)的这段METASIZE,所以要减去这个METASIZE。
接下来解析下SpillThread线程,查看其run方法:
public void run() { spillLock.lock(); spillThreadRunning = true; try { while (true) { spillDone.signal(); // 判断是否在spill,false则挂起SpillThread线程,等待唤醒 while (!spillInProgress) { spillReady.await(); } try { spillLock.unlock(); // 唤醒之后,进行排序和溢写到磁盘 sortAndSpill(); } catch (Throwable t) { sortSpillException = t; } finally { spillLock.lock(); if (bufend < bufstart) { bufvoid = kvbuffer.length; } kvstart = kvend; bufstart = bufend; spillInProgress = false; } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { spillLock.unlock(); spillThreadRunning = false; } }
run中主要是sortAndSpill,
private void sortAndSpill() throws IOException, ClassNotFoundException, InterruptedException { //approximate the length of the output file to be the length of the //buffer + header lengths for the partitions final long size = distanceTo(bufstart, bufend, bufvoid) + partitions * APPROX_HEADER_LENGTH; FSDataOutputStream out = null; try { // create spill file // 用来存储index文件 final SpillRecord spillRec = new SpillRecord(partitions); // 创建写入磁盘的spill文件 final Path filename = mapOutputFile.getSpillFileForWrite(numSpills, size); // 打开文件流 out = rfs.create(filename); // kvend/4 是截止到当前位置能存放多少个元数据实体 final int mstart = kvend / NMETA; // kvstart 处能存放多少个元数据实体 // 元数据则在mstart和mend之间,(mstart - mend)则是元数据的个数 final int mend = 1 + // kvend is a valid record (kvstart >= kvend ? kvstart : kvmeta.capacity() + kvstart) / NMETA; // 排序 只对元数据进行排序,只调整元数据在kvmeta中的顺序 // 排序规则是MapOutputBuffer.compare, // 先对partition进行排序其次对key值排序 sorter.sort(MapOutputBuffer.this, mstart, mend, reporter); int spindex = mstart; // 创建rec,用于存放该分区在数据文件中的信息 final IndexRecord rec = new IndexRecord(); final InMemValBytes value = new InMemValBytes(); for (int i = 0; i < partitions; ++i) { // 临时文件是IFile格式的 IFile.Writer writer = null; try { long segmentStart = out.getPos(); FSDataOutputStream partitionOut = CryptoUtils.wrapIfNecessary(job, out); writer = new Writer(job, partitionOut, keyClass, valClass, codec, spilledRecordsCounter); // 往磁盘写数据时先判断是否有combiner if (combinerRunner == null) { // spill directly DataInputBuffer key = new DataInputBuffer(); // 写入相同partition的数据 while (spindex < mend && kvmeta.get(offsetFor(spindex % maxRec) + PARTITION) == i) { final int kvoff = offsetFor(spindex % maxRec); int keystart = kvmeta.get(kvoff + KEYSTART); int valstart = kvmeta.get(kvoff + VALSTART); key.reset(kvbuffer, keystart, valstart - keystart); getVBytesForOffset(kvoff, value); writer.append(key, value); ++spindex; } } else { int spstart = spindex; while (spindex < mend && kvmeta.get(offsetFor(spindex % maxRec) + PARTITION) == i) { ++spindex; } // Note: we would like to avoid the combiner if we've fewer // than some threshold of records for a partition if (spstart != spindex) { combineCollector.setWriter(writer); RawKeyValueIterator kvIter = new MRResultIterator(spstart, spindex); combinerRunner.combine(kvIter, combineCollector); } } // close the writer writer.close(); // record offsets // 记录当前partition i的信息写入索文件rec中 rec.startOffset = segmentStart; rec.rawLength = writer.getRawLength() + CryptoUtils.cryptoPadding(job); rec.partLength = writer.getCompressedLength() + CryptoUtils.cryptoPadding(job); // spillRec中存放了spill中partition的信息,便于后续堆排序时,取出partition相关的数据进行排序 spillRec.putIndex(rec, i); writer = null; } finally { if (null != writer) writer.close(); } } // 判断内存中的index文件是否超出阈值,超出则将index文件写入磁盘 // 当超出阈值时只是把当前index和之后的index写入磁盘 if (totalIndexCacheMemory >= indexCacheMemoryLimit) { // create spill index file // 创建index文件 Path indexFilename = mapOutputFile.getSpillIndexFileForWrite(numSpills, partitions * MAP_OUTPUT_INDEX_RECORD_LENGTH); spillRec.writeToFile(indexFilename, job); } else { indexCacheList.add(spillRec); totalIndexCacheMemory += spillRec.size() * MAP_OUTPUT_INDEX_RECORD_LENGTH; } LOG.info("Finished spill " + numSpills); ++numSpills;
} finally { if (out != null) out.close(); } } sortAndSpill中,有mstart和mend得到一共有多少条record需要spill到磁盘,调用sorter.sort对meta进行排序,先对partition进行排序,然后按key排序,排序的结果只调整meta的顺序。
排序之后,判断是否有combiner,没有则直接将record写入磁盘,写入时是一个partition一个IndexRecord,如果有combiner,则将该partition的record写入kvIter,然后调用combinerRunner.combine执行combiner。
写入磁盘之后,将spillx.out对应的spillRec放入内存indexCacheList.add(spillRec),如果所占内存totalIndexCacheMemory超过了indexCacheMemoryLimit,则创建index文件,将此次及以后的spillRec写入index文件存入磁盘。
最后spill次数递增。sortAndSpill结束之后,回到run方法中,执行finally中的代码,对kvstart和bufstart赋值,kvstart = kvend,bufstart = bufend,设置spillInProgress的状态为false。
在spill的同时,map往buffer的写操作并没有停止,依然在调用collect,再次回到collect方法中,
// MapOutputBuffer.collect public synchronized void collect(K key, V value, final int partition ) throws IOException { ... // 新数据collect时,先将剩余的空间减去元数据的长度,之后进行判断 bufferRemaining -= METASIZE; if (bufferRemaining <= 0) { // start spill if the thread is not running and the soft limit has been // reached spillLock.lock(); try { do { // 首次spill时,spillInProgress是false if (!spillInProgress) { // 得到kvindex的byte位置 final int kvbidx = 4 * kvindex; // 得到kvend的byte位置 final int kvbend = 4 * kvend; // serialized, unspilled bytes always lie between kvindex and // bufindex, crossing the equator. Note that any void space // created by a reset must be included in "used" bytes final int bUsed = distanceTo(kvbidx, bufindex); final boolean bufsoftlimit = bUsed >= softLimit; if ((kvbend + METASIZE) % kvbuffer.length != equator - (equator % METASIZE)) { // spill finished, reclaim space resetSpill(); bufferRemaining = Math.min( distanceTo(bufindex, kvbidx) - 2 * METASIZE, softLimit - bUsed) - METASIZE; continue; } else if (bufsoftlimit && kvindex != kvend) { ... } } } while (false); } finally { spillLock.unlock(); } } ... } 有新的record需要写入buffer时,判断bufferRemaining -= METASIZE,此时的bufferRemaining是在开始spill时被重置过的(此时的bufferRemaining应该比初始的softLimit要小),当bufferRemaining小于等最后一个METASIZE是当前record进入collect之后bufferRemaining减去的那个METASIZE。
于0时,进入if,此时spillInProgress的状态为false,进入if (!spillInProgress),startSpill时对kvend和bufend进行了重置,则此时(kvbend + METASIZE) % kvbuffer.length != equator - (equator % METASIZE),调用resetSpill(),将kvstart、kvend和bufstart、bufend设置为上次startSpill时的位置。此时buffer已将一部分内容写入磁盘,有大量空余的空间,则对bufferRemaining进行重置,此次不spill。
bufferRemaining取值为Math.min(distanceTo(bufindex, kvbidx) - 2 * METASIZE, softLimit - bUsed) - METASIZE
private void resetSpill() { final int e = equator; bufstart = bufend = e; final int aligned = e - (e % METASIZE); // set start/end to point to first meta record // Cast one of the operands to long to avoid integer overflow kvstart = kvend = (int) (((long)aligned - METASIZE + kvbuffer.length) % kvbuffer.length) / 4; LOG.info("(RESET) equator " + e + " kv " + kvstart + "(" + (kvstart * 4) + ")" + " kvi " + kvindex + "(" + (kvindex * 4) + ")"); }
当bufferRemaining再次小于等于0时,进行spill,这以后就都是套路了。环形缓冲区分析到此结束。
声明:本号所有文章除特殊注明,都为原创,公众号读者拥有优先阅读权,未经作者本人允许不得转载,否则追究侵权责任。
关注我的公众号,后台回复【JAVAPDF】获取200页面试题! 5万人关注的大数据成神之路,不来了解一下吗? 5万人关注的大数据成神之路,真的不来了解一下吗? 5万人关注的大数据成神之路,确定真的不来了解一下吗?
欢迎您关注 《大数据成神之路》
大数据
2020-02-09 22:31:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Spark发展至今,应该说已经非常成熟了。是大数据计算领域不得不学习的框架。尤其是Spark在稳定性和社区发展的成熟度方面,吊打其他的大数据处理框架。
Spark至今只经历过1.x、2.x和3.x三个大版本的变化,在核心实现上,我们在Github能看到的最早的实现是0.5版本,这个版本只有1万多行代码,就把Spark的核心功能实现了。
当然我们不可能从这么古老的版本看,假如你接触过Spark,现在准备看源码,那么我建议从2.x版本中选取一个,最好是2.3或者2.4。但是经过如此多的迭代,Spark的代码量已经暴增了几倍。所以你需要抓住重点,本文就是想跟大家说说,我们该怎么看,看哪些东西。
基础概念篇
首先假如你是第一次接触Spark,那么你需要对Spark的设计思想有所了解,知道Spark用了哪些抽象,Spark在提出RDD的时候是基于什么样的考虑。
在这里给大家推荐几篇论文如下:
第一篇: 这个不多说,直接给出一个链接,这篇文章不长,很快能看完。 弹性分布式数据集:一种为内存化集群计算设计的容错抽象 https://fasionchan.com/blog/2017/10/19/yi-wen-tan-xing-fen-bu-shi-shu-ju-ji-yi-zhong-wei-nei-cun-hua-ji-qun-ji-suan-she-ji-de-rong-cuo-mo-xing/
第二篇: 大型集群上的快速和通用数据处理架构
这篇论文长达170多页,当然我们没有必要看完,我在网上找到一个总结,我们在《弹性分布式数据集:一种为内存化集群计算设计的容错抽象》这篇论文基础上看起来也会轻松不少。 链接如下: https://blog.csdn.net/weixin_44024821/article/details/89948115
环境准备
Spark的源码环境搭建网上有很多资源,主要是环境依赖比如JDK、Scala、Maven等,整个过程大概耗时要1-4个小时,主要是编译源码费时。大家可以在网上任意找一篇文章搭建起来环境就可以。 直到你在编译时出现下图就大功告成了。(我个人强烈不建议使用sbt的方式编译,推荐Maven)
Spark核心设计篇
上图是一个最简单的Spark任务的执行图。
在核心设计篇,我们最重要的模块列表如下:
Spark的初始化
SparkContext SparkEnv SparkConf RpcEnv SparkStatusTracker SecurityManager SparkUI MetricsSystem TaskScheduler
Spark的存储体系
SerializerManager BroadcastManager ShuffleManager MemoryManager NettyBlockTransferService BlockManagerMaster BlockManager CacheManager
Spark的内存管理
MemoryManager MemoryPool ExecutionMemoryPool StorageMemoryPool MemoryStore UnifiedMemoryManager
Spark的运算体系
LiveListenerBus MapOutputTracker DAGScheduler TaskScheduler ExecutorAllocationManager OutputCommitCoordinator ContextClearner
Spark的部署模式
LocalSparkCluster Standalone Mater/Executor/Worker的容错
Spark Streaming
StreamingContext Receiver Dstream 窗口操作
Spark SQL
Catalog TreeNode 词法解析器Parser RuleExecutor Analyzer与Optimizer Hive SQL相关
其他
假如你对图计算Spark GraphX和机器学习Spark MLlib感兴趣,可以单独看看。
整个跟实时计算相关的包和类大部分都已经包含在上述目录中了。假如你在使用的过程中出现了问题,就需要针对其中的具体部门去看。
源码阅读是我们每一个开发者都需要经历的阶段,阅读源码的好处就不说了。你我都懂。
声明:本号所有文章除特殊注明,都为原创,公众号读者拥有优先阅读权,未经作者本人允许不得转载,否则追究侵权责任。
关注我的公众号,后台回复【JAVAPDF】获取200页面试题! 5万人关注的大数据成神之路,不来了解一下吗? 5万人关注的大数据成神之路,真的不来了解一下吗? 5万人关注的大数据成神之路,确定真的不来了解一下吗?
欢迎您关注 《大数据成神之路》
大数据
2020-02-09 21:32:06
「深度学习福利」大神带你进阶工程师,立即查看>>>
小文件的定义
一般来说小于等于30M的文件,都叫小文件。 在HDFS中会将一个文件分成若干个block块(128M)存储,如:129M=128M+1M,就会生成2个block块,而在hadoop中的目录、文件、block都会以元数据的方式存储下来(200字节)。 在HDFS中,通常NN维护一个文件的名称,目录结构等大约是250字节。现实中,HDFS的小文件如果不做任何操作增长会很快,现在假设NN节点的内存为4G,差不多42亿字节,现在在HDFS上有一亿个小文件,那么需要250乘一亿大约是250亿字节,这样会将NN撑爆。小文件到达一定数目,就会将NN节点撑爆。就算NN能够存储,对于hive,spark计算时,小文件意味着需要更多的task和资源,同样也会将节点弄挂掉。
为什么会有小文件 批处理时造成过多的小文件 数据源有大量小文件,未做处理就直接拷贝到hadoop集群中 MR运算时reduce输出没有做很好的设置
小文件给Hadoop集群带来的瓶颈问题 在处理数据时,读取数据会有进程开启和销毁的过程,小文件过多会导致大量的网络磁盘IO的消耗。 在MapReduce过程中,会开启大量的MR任务,也就是说会造成大量的shuffle过程。也会造成大量任务处于等待资源的状态。
小文件问题的解决(合并)
源头减少小文件的产生
如果是由数据源产生大量小文件并直接拷贝到Hadoop,可以调研了解数据源是否能生成一些大文件,或者从数据源到HDFS的数据抽取过程中进行数据处理合并小文件。如果每小时只抽取10MB的数据,考虑是否改为每天一次,这样创建1个240MB的文件而不是24个10MB的文件。但是,可能无法控制数据源的改动配合或业务对数据抽取间隔的需求,这样小文件问题无法避免,这时可能需要考虑其他的解决方案。
批量文件合并
当产生小文件是不可避免时,你可以 定期运行一个MapReduce任务,读取某一个文件夹中的所有小文件,并将它们重写为较少数量的大文件。 比如一个文件夹中有1000个文件,你可以在一个MapReduce任务中指定reduce的数量为5,这样1000个输入文件会被合并为5个文件。随后进行一些简单的HDFS文件/文件夹操作(将新文件覆盖回原目录),则可以将NameNode的内存使用减少到200分之1,并且可以提高以后MapReduce或其他计算引擎对同一数据处理的性能。 批量合并文件的方法无法保留原始文件名,如果原始文件名对于你了解数据来源非常重要,则批量合并文件的方法也不适用。但一般来说,我们一般只会设计HDFS的各级目录的文件名,而不会细化到每个文件的名字,所以理论来说这种方法问题也不大。
使用Sequence文件
当需要维护原始文件名时,常见的方法是使用Sequence文件。 在此解决方案中, 文件名作为key保存在sequence文件中,然后文件内容会作为value保存。 如果一个sequence文件包含10000个小文件,则同时会包含10000个key在一个文件中。sequence文件支持块压缩,并且是可被拆分的。这样MapReduce作业在处理这个sequence文件时,只需要为每个128MB的block启动一个map任务,而不是每个小文件启动一个map任务。当你在同时抽取数百个或者数千个小文件,并且需要保留原始文件名时,这是非常不错的方案。
但是,如果你一次仅抽取少量的小文件到HDFS,则sequence文件的方法也不太可行,因为 sequence文件是不可变的,无法追加 。比如3个10MB文件将产生1个30MB的sequence文件,根据本文前面的定义,这仍然是一个小文件。另外一个问题是如果需要检索sequence文件中的文件名列表则需要遍历整个文件。
另外一个问题是Hive并不能较好的处理由该方法合并出来的sequence文件。Hive将value中的所有数据视为单行。这样会导致Hive查看这些数据不方便,因为以前小文件中的一行的所有数据也是Hive中的单行,即相当于只有一个字段。同时,Hive没办法访问这种sequence的key,即文件名。当然你可以自定义Hive serde来实现,不过这个超过了本文需要讨论的范围。








大数据
2020-02-09 18:47:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本文来源于云栖社区: https://yq.aliyun.com/articles/743514 作者:xy_xin
共同点
定性上讲,三者均为 Data Lake 的数据存储中间层,其数据管理的功能均是基于一系列的 meta 文件。meta 文件的角色类似于数据库的 catalog/wal,起到 schema 管理、事务管理和数据管理的功能。与数据库不同的是,这些 meta 文件是与数据文件一起存放在存储引擎中的,用户可以直接看到。这种做法直接继承了大数据分析中数据对用户可见的传统,但是无形中也增加了数据被不小心破坏的风险。一旦某个用户不小心删了 meta 目录,表就被破坏了,想要恢复难度非常大。
Meta 文件包含有表的 schema 信息。因此系统可以自己掌握 Schema 的变动,提供 Schema 演化的支持。Meta 文件也有 transaction log 的功能(需要文件系统有原子性和一致性的支持)。所有对表的变更都会生成一份新的 meta 文件,于是系统就有了 ACID 和多版本的支持,同时可以提供访问历史的功能。在这些方面,三者是相同的。
下面来谈一下三者的不同。
Hudi
先说 Hudi。Hudi 的设计目标正如其名,Hadoop Upserts Deletes and Incrementals(原为 Hadoop Upserts anD Incrementals),强调了其主要支持 Upserts、Deletes 和 Incremental 数据处理,其主要提供的写入工具是 Spark HudiDataSource API 和自身提供的 DeltaStreamer,均支持三种数据写入方式:UPSERT,INSERT 和 BULK_INSERT。其对 Delete 的支持也是通过写入时指定一定的选项支持的,并不支持纯粹的 delete 接口。
其典型用法是将上游数据通过 Kafka 或者 Sqoop,经由 DeltaStreamer 写入 Hudi。DeltaStreamer 是一个常驻服务,不断地从上游拉取数据,并写入 hudi。写入是分批次的,并且可以设置批次之间的调度间隔。默认间隔为 0,类似于 Spark Streaming 的 As-soon-as-possible 策略。随着数据不断写入,会有小文件产生。对于这些小文件,DeltaStreamer 可以自动地触发小文件合并的任务。
在查询方面,Hudi 支持 Hive、Spark、Presto。
在性能方面,Hudi 设计了 HoodieKey,一个类似于主键的东西。HoodieKey有 Min/Max 统计,BloomFilter,用于快速定位 Record 所在的文件。在具体做 Upserts 时,如果 HoodieKey 不存在于 BloomFilter,则执行插入,否则,确认 HoodieKey 是否真正存在,如果真正存在,则执行 update。这种基于 HoodieKey + BloomFilter 的 upserts 方法是比较高效的,否则,需要做全表的 Join 才能实现 upserts。对于查询性能,一般需求是根据查询谓词生成过滤条件下推至 datasource。Hudi 这方面没怎么做工作,其性能完全基于引擎自带的谓词下推和 partition prune 功能。
Hudi 的另一大特色是支持 Copy On Write 和 Merge On Read。前者在写入时做数据的 merge,写入性能略差,但是读性能更高一些。后者读的时候做 merge,读性能查,但是写入数据会比较及时,因而后者可以提供近实时的数据分析能力。
最后,Hudi 提供了一个名为 run_sync_tool 的脚本同步数据的 schema 到 Hive 表。Hudi 还提供了一个命令行工具用于管理 Hudi 表。
hudiimage
Iceberg
Iceberg 没有类似的 HoodieKey 设计,其不强调主键。上文已经说到,没有主键,做 update/delete/merge 等操作就要通过 Join 来实现,而 Join 需要有一个 类似 SQL 的执行引擎。Iceberg 并不绑定某个引擎,也没有自己的引擎,所以 Iceberg 并不支持 update/delete/merge。如果用户需要 update 数据,最好的方法就是找出哪些 partition 需要更新,然后通过 overwrite 的方式重写数据。Iceberg 官网提供的 quickstart 以及 Spark 的接口均只是提到了使用 Spark dataframe API 向 Iceberg 写数据的方式,没有提及别的数据摄入方法。至于使用 Spark Streaming 写入,代码中是实现了相应的 StreamWriteSupport,应该是支持流式写入,但是貌似官网并未明确提及这一点。支持流式写入意味着有小文件问题,对于怎么合并小文件,官网也未提及。我怀疑对于流式写入和小文件合并,可能 Iceberg 还没有很好的生产 ready,因而没有提及(纯属个人猜测)。
在查询方面,Iceberg 支持 Spark、Presto。
Iceberg 在查询性能方面做了大量的工作。值得一提的是它的 hidden partition 功能。Hidden partition 意思是说,对于用户输入的数据,用户可以选取其中某些列做适当的变换(Transform)形成一个新的列作为 partition 列。这个 partition 列仅仅为了将数据进行分区,并不直接体现在表的 schema 中。例如,用户有 timestamp 列,那么可以通过 hour(timestamp) 生成一个 timestamp_hour 的新分区列。timestamp_hour 对用户不可见,仅仅用于组织数据。Partition 列有 partition 列的统计,如该 partition 包含的数据范围。当用户查询时,可以根据 partition 的统计信息做 partition prune。
除了 hidden partition,Iceberg 也对普通的 column 列做了信息收集。这些统计信息非常全,包括列的 size,列的 value count,null value count,以及列的最大最小值等等。这些信息都可以用来在查询时过滤数据。
Iceberg 提供了建表的 API,用户可以使用该 API 指定表明、schema、partition 信息等,然后在 Hive catalog 中完成建表。
Delta
我们最后来说 Delta。Delta 的定位是流批一体的 Data Lake 存储层,支持 update/delete/merge。由于出自 Databricks,spark 的所有数据写入方式,包括基于 dataframe 的批式、流式,以及 SQL 的 Insert、Insert Overwrite 等都是支持的(开源的 SQL 写暂不支持,EMR 做了支持)。与 Iceberg 类似,Delta 不强调主键,因此其 update/delete/merge 的实现均是基于 spark 的 join 功能。在数据写入方面,Delta 与 Spark 是强绑定的,这一点 Hudi 是不同的:Hudi 的数据写入不绑定 Spark(可以用 Spark,也可以使用 Hudi 自己的写入工具写入)。
在查询方面,开源 Delta 目前支持 Spark 与 Presto,但是,Spark 是不可或缺的,因为 delta log 的处理需要用到 Spark。这意味着如果要用 Presto 查询 Delta,查询时还要跑一个 Spark 作业。更为蛋疼的是,Presto 查询是基于 SymlinkTextInputFormat。在查询之前,要运行 Spark 作业生成这么个 Symlink 文件。如果表数据是实时更新的,意味着每次在查询之前先要跑一个 SparkSQL,再跑 Presto。这样的话为何不都在 SparkSQL 里搞定呢?这是一个非常蛋疼的设计。为此,EMR 在这方面做了改进,支持了 DeltaInputFormat,用户可以直接使用 Presto 查询 Delta 数据,而不必事先启动一个 Spark 任务。
在查询性能方面,开源的 Delta 几乎没有任何优化。Iceberg 的 hidden partition 且不说,普通的 column 的统计信息也没有。Databricks 对他们引以为傲的 Data Skipping 技术做了保留。不得不说这对于推广 Delta 来说不是件好事。EMR 团队在这方面正在做一些工作,希望能弥补这方面能力的缺失。
Delta 在数据 merge 方面性能不如 Hudi,在查询方面性能不如 Iceberg,是不是意味着 Delta 一无是处了呢?其实不然。Delta 的一大优点就是与 Spark 的整合能力(虽然目前仍不是很完善,但 Spark-3.0 之后会好很多),尤其是其流批一体的设计,配合 multi-hop 的 data pipeline,可以支持分析、Machine learning、CDC 等多种场景。使用灵活、场景支持完善是它相比 Hudi 和 Iceberg 的最大优点。另外,Delta 号称是 Lambda 架构、Kappa 架构的改进版,无需关心流批,无需关心架构。这一点上 Hudi 和 Iceberg 是力所不及的。
deltaimage
总结
通过上面的分析能够看到,三个引擎的初衷场景并不完全相同,Hudi 为了 incremental 的 upserts,Iceberg 定位于高性能的分析与可靠的数据管理,Delta 定位于流批一体的数据处理。这种场景的不同也造成了三者在设计上的差别。尤其是 Hudi,其设计与另外两个相比差别更为明显。随着时间的发展,三者都在不断补齐自己缺失的能力,可能在将来会彼此趋同,互相侵入对方的领地。当然也有可能各自关注自己专长的场景,筑起自己的优势壁垒,因此最终谁赢谁输还是未知之数。 声明:本号所有文章除特殊注明,都为原创,公众号读者拥有优先阅读权,未经作者本人允许不得转载,否则追究侵权责任。
关注我的公众号,后台回复【JAVAPDF】获取200页面试题! 5万人关注的大数据成神之路,不来了解一下吗? 5万人关注的大数据成神之路,真的不来了解一下吗? 5万人关注的大数据成神之路,确定真的不来了解一下吗?
欢迎您关注 《大数据成神之路》
大数据
2020-02-08 15:23:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
新型冠状病毒(ncov)肺炎疫情分析项目。
采用Jupyter Notebook,基于nbdev创建,目前还处于 初级阶段。 项目数据, https://github.com/openthings/anti2020ncov/tree/master/data 项目源码, https://github.com/openthings/anti2020ncov 其它项目, 2020-nCoV open-source projects
“这不是数据,是生命。”
1、说明
本项目仅作为nbdev应用示范,nCoV数据及分析结论请参考专业机构发布的报告,如: 丁香园-新型冠状病毒肺炎疫情实时动态, https://ncov.dxy.cn/ncovh5/view/pneumonia?from=singlemessage&isappinstalled=0 全国卫健委-新型冠状病毒肺炎疫情防控, http://www.nhc.gov.cn/xcs/xxgzbd/gzbd_index.shtml 全国卫健委-官方网站, http://www.nhc.gov.cn
2、运行环境 运行环境为Jupyter Notebook。 Jupyter Notebook参考: https://my.oschina.net/u/2306127?q=jupyter 本项目采用nbdev( https://github.com/fastai/nbdev )创建。 nbdev使用参考《 文学式编程-nbdev入门教程 》。
数据将被保存为文件,然后使用 pandas 进行分析,使用 CuteCharts 、 pyechart 进行可视化。
3、项目结构
该文件是本项目的索引文件。
包含子目录: data,本项目用到的数据。 events,发生的事件记录。 analyst,基于上述数据和事件的分析结果。 docs,本项目的文档。 anti2020ncov,自动生成的python代码。
下图为使用matplotlib绘制的2020-02-08的病例数量直方图。
遍历绘制各省的地市情况图:
大数据
2020-02-07 18:49:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
作者 | 董鹏 阿里巴巴技术专家
微服务
好处:实现跨团队的解耦,实现更高的并发(目前单机只能实现 c10k)不用再拷贝代码,基础服务可以公用,更好的支持服务治理,能够更好的兼容云计算平台。
RPC rpc:像调用本地方法一样调用远程函数; 客户端:一般利用动态代理生成一个接口的实现类,在这个实现类里通过网络把接口名称、参数、方法序列化后传出去,然后控制同步调用还是异步调用,异步调用需要设置一个回调函数;
客户端还需要维护负载均衡、超时处理、连接池管理等,连接池维护了和多个 server 的连接,靠此做负载均衡,当某个服务器宕机后去除该连接。请求上下文维护了请求 ID 和回调函数,超时的请求当回复报文到达后由于找不到请求上下文就会丢弃。 服务端:维护连接,网络收到请求后反序列化获得方法名称,接口名称,参数名称后通过反射进行调用,然后将结果在传回客户端; 序列化的方式:一种是只序列化字段的值,反序列化的时候重新构建对象再把值设置进去,另外一种方式直接将整个对象的结构序列化成二进制。
前者节省空间,后者反序列化速度快,目前的序列化框架也是在反序列化时间和占用空间之间权衡。有点类似哈夫曼编码,或者数据库怎么存储一行一行的数据。
注册中心
一般有 3 种模式: f5 做集中式代理; 客户端嵌入式代理例如 dubbo; 还有一种是综合上面两种,多个客户端共用一个代理,代理作为一个独立进程部署在和客户端服务器同一台物理机上,ServiceMesh 就是这种模式。
zookeeper 不适合做注册中心的原因:zookeeper 为了一致性牺牲了可用性,但是注册中心实际上对一致性要求并不高,不一致产生的后果也就是某个服务下线了而客户端并不知道,但是客户端通过重试其他节点就可以了。
另外当发生网络分区的时候,如果超过半数节点挂了,zookeeper 就不可用,但是实际上它应该仍然可以对它所在机房的节点提供注册服务,例如三个机房分别放了 2 台、2 台、1 台,如果各个机房之间网络断了,但是机房内部上是通的,这样注册中心不可用即使内部节点也不能服务了。
zookeeper 并不是严格的一致性,它支持读写分离,其它节点收到写请求会转发给 master 节点,而其它节点可以支持读请求,当数据还没有从主节点复制过来的时候读到的可能是过期的数据。
配置中心
配置中心的需求:保证高可用、实时通知、灰度发布、权限控制、一键回滚、环境隔离(开发/测试/生产)等,目前的开源实现:nacos disconf apollo。 disconf:scan 模块扫描注解和监听器; store 模块将远程获取到的配置存储到本地,本地一个 job 检测配置是否有变化,有变化就通知监听器; fetch 模块从远程通过 http 获取配置; watch 模块监听 zookeeper 上节点的变化,有变化就会调用 fetch 进行获取。
apollo 有以下 4 个模块: portal 作为一个管理后台,提供管理员操作的入口。 有独立的数据库; adminservice 提供配置的修改和发布服务的底层服务,和 configservice 公用一个数据库 configdb,每次修改配置就会往数据库里插入一条记录 releasemessage; configservice 用一个定时任务去扫描数据库是否有新的 releasemessage,有的话就通知客户端,而客户端采用定时轮询的方式去查询 configservice 是否有新消息,这里采用 deferredresult 异步执行; eruka 为 adminservice 和 configservice 提供了注册发现的服务。客户端获取到配置文件后也会写入磁盘。
任务调度 执行器也就是应用本身,任务单元也就是具体执行任务的线程,能够主动注册调度器中,并在启动的时候进行更新,例如删除已经清空的任务; 调度中心支持集群部署避免单点,可以选举一个主节点,其它为 slave; 支持负载均衡算法为每个任务随机选择执行器,能够支持失败重试,将执行很慢或者失去连接的执行器移除; 支持控制任务并发,例如是否允许一个任务没执行完又被调度; 支持任务依赖,例如一个任务没执行完另一个任务不能执行,或者自动执行另外一个任务; 支持任务分片,将一个任务根据参数分片到不同的执行器上一起执行; 可以取消一个任务; 已经支持 glue 模式,可以不用发布就执行一个任务单元。
分布式锁 redis setnx 里面已经有参数可以支持分布式锁,但是最好能把锁的拥有方存到 value 里,释放的时候做比较,不然可能释放错锁,也就是会出现 A 释放了 B 的锁; zk 采用创建临时节点,其他创建失败的线程监听锁的状态。 SET resource_name my_random_value NX PX 30000
统一监控 收集日志并分析,日志也可以和 rpc 链路进行关联,也可以对日志进行降噪或者压缩存储; 提供 api 的方式以及拦截器模式,可以基于 javaagent 做到无嵌入; 实现 opentracing 链路追踪; 可以基于 disruptor ringbuffer 的生产消费者模式; 海量数据的存储 elasticsearch; 报表生成,监控指标设置; 各个节点进行收集,消息上传到服务端统一处理; 监控指标:rpc 链路、数据库、cpu 指标等、http 状态、各种中间件; 日志收集可以通过直接在日志框架上加拦截器,或者用 flink+kafka 收集。

缓存
先清空缓存还是先更新数据库? 如果是更新缓存而不是删除缓存:则不管哪种方式都会造成缓存和数据库不一致; 如果是删除缓存:则先删除缓存在更新数据库,如果更新数据库失败了也没有太大影响,缓存被清了重新加载即可。但是也要考虑到缓存穿透的问题,如果这个时候大流量进来是否会压垮数据库?
以上是考虑到分布式事务中一个成功一个失败的情况,但是这种概率毕竟是小的,可以用在并发量不是很高但是对数据一致性要求很高的情况,如果并发很高建议先更新数据库后清空缓存。
如果先清空缓存,后更新数据库,在还没有更新到数据库的情况下另外一个事务去查询,发现缓存没命中就去数据库取,然后又写入缓存,之后上一个事务的数据库更新,这样就导致了缓存和数据库不一致,如果先更新数据库再清空缓存,更新完数据库后缓存还没更新,这个时候来读取缓存是旧的值,也出现不一致,但是最终清空缓存后会一致。
不过这种方式也会产生永久不一致,但是概率很小,例如一个读请求,没有命中缓存,这个时候可能另一个线程刚好清空缓存,然后它就去数据里面取,但是又有一个线程在它读完数据库后将数据库改为另外一个值,这样那个读请求写入到缓存的数据就是脏数据了。

redis 采用单线程模型,对只有 io 操作来说性能很好,但是 redis 也提供了计算功能,如排序聚合,cpu 在计算的时候所有的 io 操作都是阻塞的。
memecached 先申请一块内存,将其分割成大小不等的若干内存块以存储不同大小的键值对。这种方式效率高但是可能产生空间浪费。而 redis 只是单纯的包装了下 malloc 和 free。
redis 提供了两种方式持久化数据,一种方式是把某一时刻所有的数据都写入磁盘,另外一种方式通过增量日志的形式
memecache 提供了 cas 来保证数据一致性;redis 提供了事务,将一连串指令一起执行或者回滚。
memechache 只能通过一致性哈希来进行集群,而 redis 提供了集群功能,客户端做路由选择那个 master 节点,master 节点可以有多个 slave 节点做为备用和读。 redis 中的字符串没有采用 c 语言里的结构,额外加上了空闲内存和已占用内存,这样读取的时候由于已经知道 char 数组大小,所以可以直接取出,避免遍历操作,当字符串变大或缩小的时候可以避免重新分配内存,可以用到空闲空间,也就是 redis 会预分配一个空间。
另外 redis 里的哈希,用了两个 table 存储,主要为了扩容,也就是 rehash,这样当扩容的时候双方就可以互换,redis 采用渐近式扩容,也就是每一次操作都执行两个哈希表,当新增的时候只在新表。set 数据结构可以用来存储总的点赞次数,而 zset 是一个有序链表,为了加快查询用跳表进行存储。 如何防止缓存雪崩:缓存要高可用,可以设置多级缓存; 如何预防缓存穿透:设置不同的失效时间。
消息队列
如何保证消息的顺序
严格的一致,只能一个生产者,发送到一个 broker 上,然后只有一个队列一个消费者,但是这种模式有很多弊端,一个地方异常将阻塞整个流程,RocketMQ 将这个问题交给应用层处理,也就是发送端自己选择发送到哪个队列,例如同一个订单的消息发送到同一个队列。但是算法在其中一个队列异常的时候也会有问题。
如何保证消息不重复
只要网络上传输肯定会有这种问题,所以应用层最好能够支持幂等,或者用一张去重表存储每一个处理过的消息 ID。
发送消息流程 先获取 topic 对应的路由信息(路由信息会从 namesrv 返回,在客户端缓存,返回这个 topic 对应哪几个 broker 以及每个 broker 上有多少个队列); 如果没有获取到,可能没有 topic,需要自动创建,自动创建是客户端发信息个 namesrv,namesrv在去请求 broker,broker 创建好后返回 根据路由策略获取一个 queue(从所有的 queue 中根据对应的路由策略获取 queue,然后再判断这个 queue 对应的 broker 是否健康,健康就返回),这个地方就可以做到 broker 的高可用; 所以我们发现消息是发给哪个 broker 的哪个 queue 是在客户端发送的时候决定的,不是在生成 commitlog 之后再派发的,这样我们就可以指定都到某一个固定 queue 了; 消息发送的时候会构建发送请求,里面包含了消息体、队列信息和 topic 信息等,消息体里面会增加一个消息ID; 如果消息重试多次后还是失败就会进入死信队列,一个固定的 topic。
消息存储
每个 commitlog 大小为 1G,第二个文件的起始偏移量就是 1G 的 byte 大小,当根据一个偏移量获取对应某个文件的时候,根据偏移量对 1G 取余就可以,这些 commitlog 文件通过一个文件队列维护,每次写文件返回队列的最后一个文件,然后需要加锁。
创建完文件后会进行预热,预热的时候会在每一个内存页 4kb 里面写一个 byte0,让系统对缓存页缓存,防止真正写入的时候发生缺页,mmap 的机制是只会记录一个虚拟地址,当缺页时才会去获取物理内存的地址。
创建文件有两种方式: 一种是 FileChannel.map 获取 MappedByteBuffer; 另外一种是使用堆外内存池,然后 flush。
消息的消费
一个队列只能被一个客户端消费。
当存在多个队列,但只有一个客户端的时候,这个客户端需要去 4 个队列上消费,当只有一个队列的时候只会有一个客户端可以收到消息,所以一般情况下需要客户端数量和队列数量一致,客户端一般会保存每个队列消费的位置,因为这个队列只会有一个客户端消费,所以这个客户端每次消费都会记录下队列的 offset,broker 端,也会记录同一个 grouo 消费的 offset。 MappedByteBuffer 的原理是老的 read 是先将数据从文件系统读取到操作系统内核缓存,然后再将数据拷贝到用户态的内存供应用使用,而使用 mmap 可以将文件的数据或者某一段数据映射到虚拟内存,这个时候并没有进行数据读取,当用户访问虚拟内存的地址的时候会触发缺页异常,这个时候会从底层文件系统直接将数据读取到用户态内存。
而 MappedByteBuffer 通过 FileChannel 的 map 方法进行映射的时候会返回一个虚拟地址,MappedByteBuffer就是通过这个虚拟地址配合 UnSafe 获取字节数据。
操作系统在触发缺页异常的时候会去文件系统读取数据加载到内存,这个时候一般会进行预读取,一般为 4KB,当系统下次访问数据的时候就不会发生缺页异常,因为数据已经在内存里了,为了让 MappedByteBuffer 读取文件的速度更高,我们可以对 MappedByteBuffer 所映射的文件进行预热,例如将每个 pagecache 写一个数据,这样在真正写数据的时候就不会发生缺页了。
分库分表
一般三种方式:在 dao 层和 orm 层利用 mybatis 拦截器,基于 jdbc 层进行拦截重写 JDBC 接口做增强,基于数据库代理。
jdbc 代理,实现 datasource,connection,preparestatement,druid 解析 sql,生成执行计划,利用 resultset 对结果集进行合并(group by order max sum)。

分表策略,一般是哈希,要保证分库和分表的算法完全没有关联,不然会数据分布不均匀。
数据扩容的时候可以通过配置中心动态的修改写入策略,如何一开始可以先读老表,数据同时写入新表和老表,等数据迁移完成后,在读新表并双写,之后在读新表写新表。
唯一 id
数据库自增 id,一次取多个,单机限制,另外数据库自增 id 内部也用了个锁,只是在 sql 执行结束即使事务没提交也会释放锁。
雪花算法变种 : 15 位时间戳,4 位自增序列,2 位区分订单类型,7 位机器ID,2 位分库后缀,2 位分表后缀,共 32 位。
利用 zookeeper 的顺序节点获取自增 ID。
分布式事务 两阶段提交:事务管理器,资源管理器,一阶段准备,二阶段提交 (XA 方案对业务无侵入,由数据库厂商提供支持,但是性能很差)。
事物补偿
TCC :也是两阶段,第一阶段尝试锁定资源,第二阶段确认或者回滚。
设计规范: 业务操作分成两部,例如转账:尝试阶段为冻结余额,第二阶段提交为从冻结余额扣款,回滚为解冻; 事务协调器记录主事务日志和分支事务日志,支持在任意一步发生异常后进行补偿或者逆向补偿保证最终一致性; 并发控制,降低锁的粒度提高并发,保证两个事务间不需要加排他锁,例如热点账户的转账操作,由于第一阶段进行了冻结,所以后面的扣减余额不同事务之间没有影响; 允许空回滚:可能一阶段的尝试操作发生超时,然后二阶段发起回滚,回滚的时候要判断一阶段是否进行过操作,如果一阶段没有收到请求,回滚操作直接返回成功; 避免一阶段操作悬挂:可能一阶段超时,二阶段回滚后,一阶段的请求到达,这时候要拒绝一阶段的尝试操作; 幂等控制,由于第一阶段和第二阶段的操作可能都会执行多次,另外操作接口最好能提供状态查询接口供后台的补偿任务正常执行。 框架事务(seata)
一阶段:框架会拦截业务 sql,根据语句执行前结果生成 undolog , 根据语句执行后对结果生成 redolog , 根据数据库表名加主键生成行锁; 二阶段:如果事务正常结束,将删除 undolog redolog 行锁,如果事务将回滚,则执行 undolog sql , 删除中间数据,在执行 undolog 的时候会校验脏写,也就是有没有其他事务已经修改了这行记录,就用 redolog 做对比,如果出现脏写只能人工修数据 (二阶段的清理工作可以异步执行)。
开启事务的时候会向 tc 申请一个全局的事务 id,这个事务 id 会通过 rpc 框架的拦截器传入到被调用端,然后放入 threadlocal,被调用方在执行 sql 的时候会去检查一下是否在一个全局事务里。
默认的隔离级别为读未提交,因为事务一阶段已经本地事务提交而全局事务并没有完成,后续可能会回滚,其他事务可以看到这个状态,提供的读已提交的方式是通过 for update,当解析到该语句的时候会检查是否存在行锁冲突,如果存在冲突就等待直到释放。
tm 向 tc 发起开启一个全局事务,生成一个全局唯一的 xid; xid 在微服务调用链上进行传递; rm 向 tc 注册分支事务; tm 向 tc 发起全局提交或者回滚决议; tc 向 rm 发起回滚或提交请求。
一致性消息队列:先发送半消息,如果成功了在执行本地事务,本地事务成功就提交半消息,本地事务失败就回滚半消息,如果消息队列长期没有收到确认或者回滚可以反查本地事务的状态,消费端收到消息后,执行消费端业务,如果执行失败可以重新获取,执行成功发送消费成功的确认。
MYCAT
CAP C:一致性 A:可用性 P:分区容忍性
可以简单地这样理解:MySQL 单机是C;主从同步复制 CP;主从异步复制 AP。
Zookeeper 选择了 P,但是既没有实现 C,也没有实现 A,而是选择最终一致性。可以在多个节点上读取,但是只允许一个节点接受写请求,其他节点接收的写请求会转发给主节点,只要过半节点返回成功就会提交。
如果一个客户端连接的正好是没有被提交的 follower 节点,那么这个节点上读取到的数据就是旧的,这样就出现了数据的不一致,所以没有完全实现 C。由于需要过半节点返回成功才提交,如果超过半数返回失败或者不返回,那么 zookeeper 将出现不可用,所以也没有完全实现 A。
当然衡量一个系统是 CP 还是 AP,可以根据它牺牲 A 更多还是牺牲 C 更多,而 ZK 其实就是牺牲了 A 来满足 C,当超过集群半数的节点宕机后,系统将不可用,这也是不建议使用 zk 做注册中心的原因。
CAP 理论只是描述了在分布式环境中一致性、可用性、分区容忍不能同时满足,并没有让我们一定要三选二,由于网络分区在分布式环境下是不可避免的,所以为了追求高可用,往往我们会牺牲强一执行,采用弱一致性和最终一致性的方案,也就是著名的 BASE 理论,而 base 理论其实是针对传统关系型数据的 ACID 而言的。
但 ACID 的提出是基于单节点下的,在分布式环境下,如何协调数据一致性,也就是在数据的隔离级别上做出取舍,即使是单机的关系型数据库为了提高性能,也就是可用性,定义了隔离级别,去打破 ACID 里面的强一致性 C,当然数据库也是为业务服务的,某些业务或者说大部分业务都没有强一致性的需求。
秒杀的处理 动静分离:ajax 不刷新页面,缓存,cdn; 发现热点数据:业务流程上变通让热点业务隔离出来,也通过链路监控获取一段时间的热点数据; 隔离:业务隔离,数据库隔离; 兜底方案:服务降级,限流; 流量削峰:排队,过滤无效请求,答题或者验证码,消息队列; 减库存:(下单减库存用户不付款需要回滚,付款减库存最终可能库存不足需要退款,下单后占库存一段时间后在回滚)。
正常电商采用第三种,秒杀采用第一种,不超卖的控制不用放在应用层,直接在 sql 层加 where 语句进行判断,但是 mysql 针对同一行记录也就是同一个商品的减库存,肯定会高并发下争取行锁,这将导致数据库的 tps 下降(死锁检测会遍历所有需要等待锁的连接,这个操作非常耗 cpu),从而影响其他商品的销售,所以我们可以将请求在应用层进行排队,如果份额较少可以直接舍弃,另一种方案是在数据库层排队,这种方案需要采用 mysql 的补丁。
docker
namespace
docker 在创建容器进程的时候可以指定一组 namespace 参数,这样容器就只能看到当前 namespace 所限定的资源、文件、设备、网络、用户、配置信息,而对于宿主机和其他不相关的程序就看不到了,PID namespace 让进程只看到当前 namespace 内的进程,Mount namespace 让进程只看到当前 namespace 内的挂载点信息,Network namespace 让进程只看到当前 namespace 内的网卡和配置信息,
cgroup
全名 linux control group,用来限制一个进程组能够使用的资源上限,如 CPU、内存、网络等,另外 Cgroup 还能够对进程设置优先级和将进程挂起和恢复,cgroup 对用户暴露的接口是一个文件系统,/sys/fs/cgroup 下这个目录下面有 cpuset,memery 等文件,每一个可以被管理的资源都会有一个文件,如何对一个进程设置资源访问上限呢?
在 /sys/fs/cgroup 目录下新建一个文件夹,系统会默认创建上面一系列文件,然后 docker 容器启动后,将进程 ID 写入 taskid 文件中,在根据 docker 启动时候传人的参数修改对应的资源文件。
chroot
通过 chroot 来更改 change root file system 更改进程的根目录到挂载的位置,一般会通过 chroot 挂载一个完整的 linux 的文件系统,但是不包括 linux 内核,这样当我们交付一个 docker 镜像的时候,不仅包含需要运行的程序还包括这个程序依赖运行的这个环境,因为我们打包了整个依赖的 linux 文件系统,对一个应用来说,操作系统才是它所依赖的最完整的依赖库。
增量层
docker 在镜像的设计中引入层的概念,也就是用户在制作 docker 镜像中的每一次修改,都是在原来的 rootfs 上新增一层 roofs,之后通过一种联合文件系统 union fs 的技术进行合并,合并的过程中如果两个 rootfs 中有相同的文件,则会用最外层的文件覆盖原来的文件来进行去重操作。
举个例子,我们从镜像中心 pull 一个 mysql 的镜像到本地,当我们通过这个镜像创建一个容器的时候,就在这个镜像原有的层上新加了一个增 roofs,这个文件系统只保留增量修改,包括文件的新增删除、修改,这个增量层会借助 union fs 和原有层一起挂载到同一个目录,这个增加的层可以读写,原有的其他层只能读,于是就保证了所有对 docker 镜像的操作都是增量。
之后用户可以 commit 这个镜像将对该镜像的修改生成一个新的镜像,新的镜像就包含了原有的层和新增的层,只有最原始的层才是一个完整的 linux fs, 那么既然只读层不允许修改,我怎么删除只读层的文件呢?这时只需要在读写层(也就是最外层),生成一个 whiteout 文件来遮挡原来的文件就可以了。
发布与部署
目前的大部分公司采用下面的部署方式。
创建 pileline 指定项目名称和对应的 tag,以及依赖工程。一个 pipeline 指一个完整的项目生命周期(开发提交代码到代码仓库、打包、部署到开发环境、自动化测试、部署到测试环境、部署到生产环境); 根据项目名称和 tag 去 gitlab 上 拉取最新的代码(利用 java 里的 Runtime 执行 shell 脚本); 利用 maven 进行打包,这个时候可以为 maven 创建一个单独的 workspace (shell 脚本); 根据预先写好的 docfile,拷贝 maven 打的包生成镜像,并上传镜像 (shell 脚本); 通过 K8s 的 api 在测试环境发布升级; 通过灰度等方案发布到生产环境。

查看更多:https://yq.aliyun.com/articles/743558?utm_content=g_1000102890
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-07 15:40:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
项目背景
阿里巴巴内部从最早引入混沌工程解决微服务的依赖问题,到业务服务、云服务稳态验证,进一步升级到公共云、专有云的业务连续性保障,以及在验证云原生系统的稳定性等方面积累了比较丰富的场景和实践经验。并且当时混沌工程相关的开源工具存在场景能力分散、上手难度大、缺少实验模型标准,场景难以扩展和沉淀等问题。这些问题就会导致很难实现平台化,你很难通过一个平台去囊括这些工具。所以我们开源了 ChaosBlade 这个混沌工程实验执行工具,目的是服务于混沌工程社区,共同推进混沌工程领域的发展。
项目介绍
ChaosBlade 项目托管在 Github 平台,放在 chaosblade-io 组织下,方便项目管理和社区发展。设计 ChaosBlade 初期就考虑了易用性和场景扩展的便捷性,方便大家上手使用以及根据各自需要扩展更多的实验场景,遵循混沌实验模型提供了统一的操作简洁的执行工具,并且根据领域划分将场景实现封装成一个一个单独的项目,方便实现领域内场景扩展。目前包含的场景领域如下: 基础资源:比如 CPU、内存、网络、磁盘、进程等实验场景 Java 应用:比如数据库、缓存、消息、JVM 本身、微服务等,还可以指定任意类方法注入各种复杂的实验场景 C++ 应用:比如指定任意方法或某行代码注入延迟、变量和返回值篡改等实验场景 Docker 容器:比如杀容器、容器内 CPU、内存、网络、磁盘、进程等实验场景 Kubernetes 平台:比如节点上 CPU、内存、网络、磁盘、进程实验场景,Pod 网络和 Pod 本身实验场景如杀 Pod,容器的实验场景如上述的 Docker 容器实验场景 云资源:比如阿里云 ECS 宕机等实验场景
以上场景领域都单独封装成一个项目来实现,目前包含的项目如下: chaosblade :混沌实验管理工具,包含创建实验、销毁实验、查询实验、实验环境准备、实验环境撤销等命令,是混沌实验的执行工具,执行方式包含 CLI 和 HTTP 两种。提供完善的命令、实验场景、场景参数说明,操作简洁清晰。 chaosblade-spec-go : 混沌实验模型 Golang 语言定义,便于使用 Golang 语言实现的场景都基于此规范便捷实现。 chaosblade-exec-os : 基础资源实验场景实现。 chaosblade-exec-docker : Docker 容器实验场景实现,通过调用 Docker API 标准化实现。 chaosblade-operator : Kubernetes 平台实验场景实现,将混沌实验通过 Kubernetes 标准的 CRD 方式定义,很方便的使用 Kubernetes 资源操作的方式来创建、更新、删除实验场景,包括使用 kubectl、client-go 等方式执行,而且还可以使用上述的 chaosblade cli 工具执行。 chaosblade-exec-jvm : Java 应用实验场景实现,使用 Java Agent 技术动态挂载,无需任何接入,零成本使用,而且支持卸载,完全回收 Agent 创建的各种资源。 chaosblade-exec-cplus : C++ 应用实验场景实现,使用 GDB 技术实现方法、代码行级别的实验场景注入。
以上项目都遵循混沌实验模型定义实验场景,这样不仅实现实验场景水平领域扩展,而且每个场景领域单独一个项目,使用该领域下标准方式去设计实现场景,所以很方便的实现领域内场景垂直扩展。
除了实验场景相关项目,还有相关的文档项目: chaosblade-help-doc : ChaosBlade 工具和场景使用文档 chaosblade-dev-doc : ChaosBlade 项目开发文档 awesome-chaosblade : ChaosBlade 相关的外部文档
实验模型
前面提到 ChaosBlade 项目是遵循混沌实验模型设计,不仅简化了实验场景定义,而且可以很方便的扩展场景,并且通过 chaosblade cli 工具可以统一调用,便于构建上层的混沌实验平台。下面通过实验模型的推导、介绍、意义和具体的应用来详细介绍此模型。
实验模型的推导
目前的混沌实验主要包含故障模拟,我们一般对故障的描述如下: 10.0.0.1 机器上挂载的 A 磁盘满造成了服务不可用 所有节点上的 B dubbo 服务因为执行缓慢造成上游 A dubbo 服务调用延迟,从而造成用户访问缓慢 Kubernetes A 集群中 B 节点上 CPU 所有核使用率满载,造成 A 集群中的 Pod 调度异常 Kubernetes C 集群中 D Pod 网络异常,造成 D 相关的 Service 访问异常
通过上述,我们可以使用以下句式来描述故障:因为某某机器(或集群中的资源,如 Node,Pod)上的哪个组件发生了什么故障,从而造成了相关影响。我们也可以通过下图来看故障描述拆分:
可以通过这四部分来描述现有的故障场景,所有我们抽象出了一个故障场景模型,也称为混沌实验模型
实验模型的介绍
此实验模型详细描述如下: Scope: 实验实施范围,指具体实施实验的机器、集群及其资源等 Target: 实验靶点,指实验发生的组件。如基础资源场景中的 CPU、网络、磁盘等,Java 场景中的应用组件如 Dubbo、Redis、RocketMQ、JVM 等,容器场景中的 Node、Pod、Container自身等 Matcher: 实验规则匹配器,根据所配置的 Target,定义相关的实验匹配规则,可以配置多个。由于每个 Target 可能有各自特殊的匹配条件,比如 RPC 领域的 Dubbo、gRPC 可以根据服务提供者提供的服务和服务消费者调用的服务进行匹配,缓存领域的 Redis,可以根据 set、get 操作进行匹配。还可以对 matcher 进行扩展,比如扩展实验场景执行策略,控制实验触发时间。 Action: 指实验模拟的具体场景,Target 不同,实施的场景也不一样,比如磁盘,可以演练磁盘满,磁盘 IO 读写高,磁盘硬件故障等。如果是应用,可以抽象出延迟、异常、返回指定值(错误码、大对象等)、参数篡改、重复调用等实验场景。如果是容器服务,可以模拟 Node、Pod、Container 资源异常或者其上的基础资源异常等。
使用此模型可以很清晰表达出以下实施混沌实验需要明确的问题: 混沌实验的实施范围是什么 实施混沌实验的对象是什么 实验对象触发实验的条件有哪些 具体实施什么实验场景
实验模型的意义
此模型具有以下特点: 简洁:层次清晰,通俗易懂 通用:覆盖目前所有的故障场景,包含基础资源、应用服务、容器服务、云资源等 易实现:很方便的定义清晰的接口规范,实验场景扩展实现简单 语言、领域无关:可以扩展多语言、多领域的模型实现
此模型具有以下的意义: 更精准的描述混沌实验场景 更好的理解混沌实验注入 方便沉淀现有的实验场景 依据模型发掘更多的场景 混沌实验工具更加规范、简洁
实验模型的应用
ChaosBlade 下的项目遵循此混沌实验模型设计,需要注意的是此模型定义了混沌实验场景如何设计,但是实验场景的具体实现每个领域各不相同,所以将 ChaosBlade 依据领域实现封装成各自独立的项目,每个项目根据各领域的最佳实践来实现,不仅能满足各领域使用习惯,而且还可以通过混沌实验模型来建立与 chaosblade cli 项目的关系,方便使用 chaosblade 来统一调用,各领域下的实验场景依据混沌实验模型生成 yaml 文件描述,暴露给上层混沌实验平台,混沌实验平台根据实验场景描述文件的变更,自动感知实验场景的变化,无需新增场景时再做平台开发,使混沌平台更加专注于混沌工程其他部分。以下分为基于混沌实验模型的 chaosblade cli 设计、基于混沌实验模型的 chaosblade operator 设计和基于混沌实验模型构建混沌实验平台三部分详细介绍混沌实验模型的应用。
基于混沌实验模型的 chaosblade cli 设计
chaosblade 项目本身使用 Golang 构建,解压即用,工具采用 CLI 方式执行,使用简单,具备完善的命令提示。根据 chaosblade-spec-go 项目对混沌实验模型的定义,解析遵循混沌实验模型实现的实验场景 yaml 描述,将实验场景转换为 cobra 框架所支持的命令参数,实现变量参数化、参数规范化,而且将整个实验对象化,每个实验对象都会有个 UID,方便管理。
通过一个具体的实验场景来说明 chaosblade cli 的使用。
我们执行的实验是对其中一个 provider 服务实例注入调用 mk-demo 数据库延迟的故障,可以看到上图左下角,这个就是对 demo 数据库注入延迟的命令,可以看出命令非常简洁清晰,比如很明确的表达出我们的实验目标是 mysql,我们的实验场景是做延迟,后面这些都是这些数据库的匹配器,比如表,查询类型,还有控制实验的影响条数等等,使用 ChaosBlade 可以很有效的控制实验的爆炸半径。执行这条命令就可以对这台机器的 provider 服务注入故障,大家可以看到我注入故障之后,这里这个图就是我立刻收到了钉钉的报警,那么这个 case 是符合预期的 case,但是即使符合预期的case,也是有价值的,需要相关的开发和运维人员是要去排查延迟的问题根因并恢复,有助于提高故障应急效率。
chaosblade 的中文使用文档: https://chaosblade-io.gitbook.io/chaosblade-help-zh-cn
基于混沌实验模型的 chaosblade operator 设计
chaosblade-operator 项目是针对 Kubernetes 平台所实现的混沌实验注入工具,遵循上述混沌实验模型规范化实验场景,把实验定义为 Kubernetes CRD 资源,将实验模型中的四部分映射为 Kubernetes 资源属性,很友好的将混沌实验模型与 Kubernetes 声明式设计结合在一起,依靠混沌实验模型便捷开发场景的同时,又可以很好的结合 Kubernetes 设计理念,通过 kubectl 或者编写代码直接调用 Kubernetes API 来创建、更新、删除混沌实验,而且资源状态可以非常清晰的表示实验的执行状态,标准化实现 Kubernetes 故障注入。除了使用上述方式执行实验外,还可以使用 chaosblade cli 方式非常方便的执行 kubernetes 实验场景,查询实验状态等。
遵循混沌实验模型实现的 chaosblade operator 除上述优势之外,还可以实现基础资源、应用服务、Docker 容器等场景复用,大大方便了 Kubernetes 场景的扩展,所以在符合 Kubernetes 标准化实现场景方式之上,结合混沌实验模型可以更有效、更清晰、更方便的实现、使用混沌实验场景。
下面通过一个具体的案例来说明 chaosblade-operator 的使用:对 cn-hangzhou.192.168.0.205 节点本地端口 40690 访问模拟 60% 的网络丢包。
使用 yaml 配置方式,使用 kubectl 来执行实验 apiVersion: chaosblade.io/v1alpha1 kind: ChaosBlade metadata: name: loss-node-network-by-names spec: experiments: - scope: node target: network action: loss desc: "node network loss" matchers: - name: names value: [ "cn-hangzhou.192.168.0.205" ] - name: percent value: [ "60" ] - name: interface value: [ "eth0" ] - name: local -port value: [ "40690" ]
执行实验: kubectl apply -f loss-node-network-by-names.yaml
查询实验状态,返回信息如下(省略了 spec 等内容): ~ » kubectl get blade loss-node-network- by -names -o json { "apiVersion" : "chaosblade.io/v1alpha1" , "kind" : "ChaosBlade" , "metadata" : { "creationTimestamp" : "2019-11-04T09:56:36Z" , "finalizers" : [ "finalizer.chaosblade.io" ], "generation" : 1 , "name" : "loss-node-network-by-names" , "resourceVersion" : "9262302" , "selfLink" : "/apis/chaosblade.io/v1alpha1/chaosblades/loss-node-network-by-names" , "uid" : "63a926dd-fee9-11e9-b3be-00163e136d88" }, "status" : { "expStatuses" : [ { "action" : "loss" , "resStatuses" : [ { "id" : "057acaa47ae69363" , "kind" : "node" , "name" : "cn-hangzhou.192.168.0.205" , "nodeName" : "cn-hangzhou.192.168.0.205" , "state" : "Success" , "success" : true , "uid" : "e179b30d-df77-11e9-b3be-00163e136d88" } ], "scope" : "node" , "state" : "Success" , "success" : true , "target" : "network" } ], "phase" : "Running" } }
通过以上内容可以很清晰的看出混沌实验的运行状态,执行以下命令停止实验: kubectl delete -f loss-node-network- by -names.yaml
或者直接删除此 blade 资源 kubectl delete blade loss-node-network- by - names
还可以编辑 yaml 文件,更新实验内容执行,chaosblade operator 会完成实验的更新操作。
使用 chaosblade cli 的 blade 命令执行 blade create k8s node-network loss --percent 60 --interface eth0 --local-port 40690 --kubeconfig config --names cn-hangzhou .192 .168 .0 .205
如果执行失败,会返回详细的错误信息;如果执行成功,会返回实验的 UID: { "code" : 200 , "success" : true , "result" : "e647064f5f20953c" }
可通过以下命令查询实验状态: blade query k8s create e647064f5f20953c --kubeconfig config { "code" : 200 , "success" : true , "result" : { "uid" : "e647064f5f20953c" , "success" : true , "error" : "" , "statuses" : [ { "id" : "fa471a6285ec45f5" , "uid" : "e179b30d-df77-11e9-b3be-00163e136d88" , "name" : "cn-hangzhou.192.168.0.205" , "state" : "Success" , "kind" : "node" , "success" : true , "nodeName" : "cn-hangzhou.192.168.0.205" } ] } }
销毁实验: blade destroy e647064f5f20953c
除了上述两种方式调用外,还可以使用 kubernetes client-go 方式执行,具体可参考: https://github.com/chaosblade-io/chaosblade/blob/master/exec/kubernetes/executor.go 代码实现。
通过上述介绍,可以看出在设计 ChaosBlade 项目初期就考虑了云原生实验场景,将混沌实验模型与 Kubernetes 设计理念友好的结合在一起,不仅可以遵循 Kubernetes 标准化实现,还可以复用其他领域场景和 chaosblade cli 调用方式,所谓的历史包袱根本不存在 :-)。
基于混沌实验模型构建混沌实验平台
前面也提到了遵循混沌实验模型实现的实验场景,可通过 yaml 文件来描述,上层实验平台可以自动感知实验场景的变更,无需平台再做开发,达到实验平台与实验场景解耦的目的,使大家可以更加专注于混沌实验平台本身的开发上。下面拿 AHAS Chaos 平台举例来说明如何基于混沌实验模型和 ChaosBlade 构建混沌实验平台。
可以看到: chaosblade 会合并所有领域场景的 yaml 文件,提供给 ChaosBlade SDK ChaosBlade SDK 感知 yaml 文件变化,重新解析场景描述文件,透传给上层平台,包含场景和场景参数的变更 ChaosBlade SDK 透传用户在平台上所配置的参数,调用 chaosblade 工具执行 chaosblade 工具会根据调用参数,和解析各领域 yaml 场景描述文件来调用不同的执行器
总结
混沌实验模型的应用可归纳为以下几点: 混沌实验模型使实验场景变量参数化,参数规范化 可遵循模型实现实验场景领域化的水平扩展 可将混沌实验模型和领域内标准化实现相结合,便捷实现领域内场景垂直扩展 上层的领域场景可以复用遵循混沌实验模型定义的场景 通过混沌实验模型声明的场景描述可以很好的接入到 chaosblade cli 中 遵循实验模型可以很方便的构建上层混沌实验平台
项目意义
混沌工程领域已提出多年,混沌工程社区的每一个人都贡献着自己的力量来完善整个混沌工程领域体系,尤其是混沌工程理论的提出推动了整个混沌工程领域快速发展。我们在阿里巴巴内部实践混沌工程很多年,深知落地混沌工程之路充满各种挑战,也知道注入混沌实验只是混沌工程中的一环,混沌工程背后的思考、落地方案和实践经验也是很重要的一部分。我们只是想把我们认为好用的内部工具奉献给社区,随后将刚才提到的实践经验也通过各种渠道分享给大家,大家可以将此工具与实践经验相结合,作为企业落地混沌工程的一个入手点,共同推进混沌工程领域的进步,仅此而已。
上述详细介绍了 ChaosBlade 工具的设计和背后的思考,以及将混沌实验模型与各领域标准实现相结合的优势,欢迎对高可用架构感兴趣的各位加入到 ChaosBlade 社区中来,加入到混沌工程社区中来。总而言之,ChaosBlade 相信:开源世界中,任何帮助都是贡献。
未来规划
ChaosBlade 社区在增强原有领域的同时,比如增强云原生领域场景,还会增加更多领域的场景,例如: Golang 应用混沌实验场景 NodeJS 应用混沌实验场景
除实验场景外,还会以下规划: 提供一个混沌实验平台供大家使用 完善 ChaosBlade 各项目的开发文档 完善 chaosblade 工具的英文文档
欢迎大家加入,一起共建,不限于: bug report feature request performance issue help wanted doc incomplete test missing feature design any question on project
ChaosBlade 项目才刚刚开始,欢迎开源爱好者在使用 ChaosBlade 过程中产生的任何想法和问题,都可以通过 issue 或者 pull request 的方式反馈到 Github 上。

查看更多:https://yq.aliyun.com/articles/743102?utm_content=g_1000102888
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-07 14:52:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
去年,阿里云发布了本地 IDE 插件 Cloud Toolkit,仅 IntelliJ IDEA 一个平台,就有 15 万以上的开发者进行了下载,体验了一键部署带来的开发便利。时隔一年的今天,阿里云正式发布了 Visual Studio Code 版本,全面覆盖前端开发者,帮助前端实现一键打包部署,让开发提速 8 倍。
VSCode 版本的插件,目前能做到什么?
安装插件之后,开发者可以立即体验以下任何一个功能: 将本地 Visual Studio Code 中的代码,一键打包、部署到任意的阿里云 ECS 服务器; 将本地 Visual Studio Code 中的代码,一键打包、部署到任意的远程服务器; 向任意的阿里云 ECS 或者 任意远程服务器 发送并执行指定的命令(下周上线);
VSCode 版本的插件正不断更新迭代中,不久将会有更多强大的功能上线,请期待!或向我们提出需求!
如何下载插件?
开发者可以通过在线安装或离线安装方式来下载插件: 在线安装:从 Visual Studio Code 的 Marketplace 进行安装,访问插件页面,点击 这里 。 离线安装:在插件的群里(文末扫码进群)获得离线安装包,进行离线安装;
阿里云 Cloud Toolkit 和其他任何插件安装方式一致,因此不做赘述,下面为大家详细介绍插件功能。
一键打包部署
一、添加服务器
如下图所示,在 Visual Studio Code 左侧,点击阿里云图标,在出现的菜单中,点击Alibaba Cloud Toolkit - Host View打开机器视图界面。
然后点击右上角Add Host按钮,出现添加机器界面,如下图。按照表单提示,填入对应的机器 IP,端口、用户名和密码即可。
二、部署
点击 Run Configurations - Deployments - Deploy to Host 创建一个新的部署配置,配置界面如下:
在 Deploy to Host 对话框设置部署参数,然后单击 Apply,即可保存。
部署参数说明 : Name:未配置定义一个名字,以便更容易区分多个配置; File:选择打包方式; Project:选择待部署工程的根目录; Build Output Directory:打包之后的 Output 目录; Webpack Configuration:Webpack 配置; Target Host:部署的远程目标服务器; Target Directory:远程目标服务器上的指定部署目录; After deploy:输入应用启动命令,如 sh /root/restart.sh,表示在完成应用包的部署后,需要执行的命令 —— 对于 Java 程序而言,通常是一句 Tomcat 的启动命令。
查看更多:https://yq.aliyun.com/articles/743040?utm_content=g_1000102887
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-07 14:48:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
引言
在可观察性领域,Metrics,Tracing 和 Logging 的介绍由来已久。三种之间的边界越来越模糊。OpenTracing 中已经支持 LogEvent,OpenTelemetry 已经把 OpenMetric 和 OpenTracing 整合到一块。今天我们要介绍的链路追踪的业务分析功能,通过对链路数据进行聚合统计,可以查看各种业务报表。
为什么需要业务分析关联链路数据?
精细化分析,全链路数据,快速发现和定位问题。
在传统的 APM 分析中,主要是关注各种接口的响应时间,对业务不够贴切,这样会带来一些问题:
发现问题
同一个接口被很多业务方调用,某一个重要业务调用出了问题,由于这个业务占比不高,无法通过平均数或者 p90 分析出来。例如创建订单中重要客户,这些客户订单占比不高,但是重要性比较高,需要重点分析和监控。
排查定位问题
某些用户反馈下单或者付款出现问题,但是这种情况只存在少量用户,重现比较困难。如果把用户的异常和链路关联起来,分析异常用户的链路数据,比如出入参数,用户属性等,可以发现一些线索。例如查看用户 ID 对应的失败订单,分析订单上链路,发现失败订单的来源都是某个旧版的客户端调用。
业务分析概述
在介绍业务分析功能之前,先简单提两个问题,大家发散思考下怎么解决这两个问题。
1、某个应用的对外接口流量突增,是不是由于某个用户或者地域流量暴涨?
2、应用出现比较多空指针异常,这些异常对业务有多大的影响?
通用的监控系统主要是对操作系统,分布式调用,数据库组件进行监控。如果对业务监控,那就需要开发同学对调用链埋点添加业务属性(用 tag 来记录各种业务属性)。通过对 tag 标签进行过滤和统计聚合,达到业务分析和监控的效果。
业务分析的实现方式
业务分析是在链路上标注一些业务数据,以下我们用 Jaeger 和 Skywalking 为例子讲解业务埋点。
Jaeger 中添加 Tag 信息
以 Java 语言为例, // 获取业务中的用户Id String userId = ****; Tracer tracer = GlobalTracer. get (); if (tracer != null && tracer.activeSpan() != null ) { // 将 UserId以 Tag 的形式存放到链路中 tracer.activeSpan().setTag( "userId" , userId); }
Skywalking 中添加 Tag 信息,
不插入代码的方式:
https://github.com/apache/skywalking/blob/master/docs/en/setup/service-agent/java-agent/Customize-enhance-trace.md < class class_name = "test.apache.skywalking.testcase.customize.service.TestService2" > < method method = "staticMethod(java.lang.String,int.class)" operation_name = "/is_2_static_method" static = "true" > < tag key = "tag_2_1" > arg[0] < log key = "log_1_1" > arg[1] < /class>
用 OpenTracing 插入代码方式 // global tracer object private static Tracer tracer = new SkywalkingTracer(); ... if (tracer != null && tracer.activeSpan() != null ) { tracer.activeSpan().setTag( "userId" , yourUserId); }
Java 代码方式: ActiveSpan .tag( "userId" , yourUserId);
Java 注解方式 @Tag (key = "userId" , value = "arg[0]" ) @Tag (key = "requestId" , value = "arg[1]" ) public someMethod ( final String userId, final String requestId) { // ... }
对链路数据进行业务分析
Tag 信息就好比给图书打上编号分类信息,让我们可以在图书馆海量的图书中找到我们需要图书。通过 Tag 来进行分类、查找、统计,可以快速找到我们需要的数据。
业务标签的过滤
可以通过筛选 Tag 进行过滤,寻找所需要的链路。如下图,查看手机端下单的请求的变化趋势,详情等。
业务标签的聚合
指定 Tag 进行聚合,分析 Tag 聚合的统计。如下图查看每个入口(PC 和 Mobile)的请求数,响应时间,异常查询。可以识别是不同业务的对比情况。
业务分析的一些使用场景案例
Tag 功能主要是业务开发同学用来进行业务监控的, 通过在链路上写入 Tag,可以把业务和链路打通,从而很好的发现,定位,跟踪问题。
我们接下来做一些业务场景举例。
业务异常分析
生成订单时,会调用优惠,库存,物流,商品等系统接口。这些接口都有可能会返回异常,有些异常有些比较重要,有些只是用于展示。我们怎么快速识别当前应用的异常严重程度呢?可以通过加 Tag 的方式来实现。我们对异常进行分 1~5 级,1 级表示非常严重。
那我们每次调用远程接口完成后,对返回的异常添加 Tag,例如调用优惠时返回系统异常加 Tag: tracer.activeSpan().setTag("bizErr", “1-PromotionSysExcep”); ,调用库存返回库存不足,可以添加tracer.activeSpan().setTag("bizErr", “5-InventoryNotEmpty”);
那么通过 Tag bizErr 进行聚合,对数量进行排序下,如果 1-** 开头的比较多,那表示当前系统比较严重,需要紧急处理。查看这些异常链路的请求参数或者返回值,我们可以快速定位问题。
用户分析
将 UserId 用 Tag 方式写入到 Span 中,可以统计到没有用户的数据,例如访问量,响应时间。我们之前遇到一个问题,Web 的流量突然变大,响应比较慢。从监控来看流量比较大,但为什么流量会比较大呢,通过 UserId 分组统计后,发现某个用户的流量暴涨,导致整个后台响应比较慢,对这个用户进行限流后,web流量和相应时间恢复正常。
业务大盘
中台系统中各个业务的创建订单大盘。
在中台应用中,各个业务使用同一个交易系统,通过订单中的一个业务类型字段来区分不同的业务。通过在链路上的标签,可以查看到不同业务线的下单成功和失败曲线。比如大盘上可以展示天猫,聚划算,盒马生鲜等业务的订单情况。一旦有异常就可以分析链路的明细信息来查找原因。
和传统的统计不同点
传统的 ELK 方式,用户通过插入代码,将业务的属性都打到日志里面。通过数据聚合,可以生成各种 Metrics,添加告警等。这样做监控是合适的,但是需要更深层次的挖掘问题的话,还需要和链路打通。将监控的内容上下游的信息都串起来,可以更方便的定位问题。
比如:更新库存失败,发现异常都是上游某个业务的某台机器上,而这条机器是灰度机器,使用了非稳定版本的。
总结
业务排查的问题方式有很多种,调用链和业务信息打通已经是一种行之有效的方法。通过在调用链写入业务信息,将会给调用链配上显微镜,快速发现隐藏在链路数据中的各种问题。而业务数据关联调用链,也给业务排查插上翅膀,从链路的上下文信息中快速找到线索。

查看更多:https://yq.aliyun.com/articles/743030?utm_content=g_1000102886
上云就看云栖号:更多云资讯,上云案例,最佳实践,产品入门,访问:https://yqh.aliyun.com/
大数据
2020-02-07 14:44:00