Shared posts

29 Jul 01:47

500万在北京能买什么房?

by 未来可栖

每个计划长期留在北京的人,都想在北京买套房。比如我一好朋友石头。

石头昨天让推荐总价 500 万以内的项目给他参考,但是当我们搜刮了一圈项目信息之后,这些信息严重打击了石头的信心。

500 万,这已经是刚需能接受的最高价格。但 500 万,是开发商最不愿意做,也最不愿意卖的区间。要不是因为限价,你可能已经在北京六环以内见不到总价 500 万以内的新盘(住宅性质)。

但是,一大批限价商品房即将上市,楼市江湖上将会出现大量雷同的 89 平米小三居,这代表着刚需的美好时代又回光返照了吗?未必!

攒够多少钱才能留在北京?这可能是未来五年每个「新北京人」都要面对的问题,因为刚需已经被刚到北京边缘了。2007 年那会儿,刚需购房客攒够一年首付只需要 8.8 年。十年之后,这个数字变成了 23 年。调控越来越严,可留在北京越来越难。

2018 年,一大波限竞房项目开盘入市,在政府规定的「限价」政策之下,首付年限这个数字可能会变小一些。限价房的江湖水深至此,你要如何在茫茫江湖中找到契合自己的那套房子?

在热辣的七月末,我们顶着烈日骄阳,用脚丫子去丈量了限价房的温度。

桃谷六仙大兴论道

说到 500 万以下的限竞房,大兴是「重粮仓」,目前光是已确定案名的项目就有 6 个,分布在黄村、瀛海、亦庄和旧宫四大区域。

新机场、京津冀、壮南城给了大兴限竞房相当大的「底气」,6 个项目里 5 个都是「能毛坯就不精装」,公立幼儿园和小区底商也很难看到,「除了区位就剩灰白墙」。

「大兴最贵」要数黄村的金地悦风华和颐景万和,靠着相对成熟的配套每平方米均价 5.5 万元。住在金地悦风华 89㎡ 的三室两厅两卫很局促,书房就只放得下一张单人床。而颐景万和可以选择大两居或者小三居,可腿儿着半小时就能到黄村火车站,看你是觉得「跑路」方便还是听着火车叮咣响。

剩下的 5.2 万元/㎡ 价位的项目里,瀛海的万和斐丽不仅被 diss 户型设计师「脑子进了水」,东西朝向的设计,屋子还不方正,而且没走几步路就到了回迁房。旧宫的熙悦林语倒是配套齐全,有商业有学校有精装,可未来 7 成的邻居都是租房的流动人口,求稳定的家庭们还是别想。

五里坨三兄弟西山开荒

看完五里坨扎堆的三个限竞房项目,小编实在忍不住高歌一曲——“在山的那边,水的那边,有三个「好兄弟」,他们价格很美丽,他们精装不毛坯,他们建了商场足足有 25 万平米,但是他们还在暗地里置气……”

都是 5.2万/㎡,都是 89 ㎡的三居,「五里坨三兄弟」万科翡翠山晓、远洋五里春秋、绿城西府海棠都想拼个高低,都在服务、物业上狠使劲。

老大万科偷跑的最快,抢人抢的最狠。翡翠云图在规划证还没拿到的情况下,抢先在阜石路上开放了样板间揽客,89 ㎡的三室一厅带飘窗,客厅面宽就有 3 米 7 ,还给“懒癌”业主准备了豪华装修包。万科卡在石景山的进山口上,收割了一大把新鲜出炉的购房客。一期开放 600 多套 89 ㎡三居和叠拼,现在就已经有 600 多人排卡。

老二远洋最是「心机」,论物业拼不过万科和绿城,就在户型上下了功夫。 89 ㎡做成了三室两厅两卫,还邀请了日本收纳大师提供整体收纳方案,出了小区就能爬上西山。新中式风格和园林设计都跟 2000 万起的天著春秋保持一致,每个来看房的人都能听到他对老大哥的 diss ,「翡翠山晓都不是正南正北的户型,还是我们上风上水」。

老三绿城最「低调奢华有内涵」,虽然没有哥哥们「敢说」,但三兄弟共享的配套就数他离的最近。东边有三甲医院,西边有附中和购物中心,北边有部队大院,南边还有国际学校。「别人有的我都有,别人没有的我还有」,西府海棠小区里还有绿城物业自带的四点半午托班和养老服务中心,周边配置堪称齐全,但也难免人流冗杂、难得安静。

尽管弟弟们明里暗里地呛着,万科这个老大哥明面上可表现的很「佛系」,「没办法,我们就是最好的。」

翠湖姐妹花海淀互撕

大西山南边的五里坨三兄弟掐来掐去,东边的翠湖三姐妹也在经历着「修罗场」,除了暂时不出声的葛洲坝,战火主要集中在万科翡翠云图和中铁建+碧桂园+国瑞鼎起的山屿湖中间。

这两个「好姐妹」地段一样、价格一样,就连临时售楼中心都放在永丰路的同一侧上,相距不过两公里。可规模不同,走的路数也不同。

山屿湖从规模上就十分「碧桂园」,1000 多套房子走的是接地气的「傻白甜」人设,全装修没有附加豪装包。由于户型图还没出,山屿湖的城市展厅里就只有三块介绍展板。除了世界 500 强、宇宙第一房企以及中铁建的物业,你不能从销售那里知道更多了。

「高冷」如翡翠云图,十分看不上大体量的小姐妹,每次提到山屿湖都要翻上好久的白眼。翡翠云图只有 12 栋楼,最高不过 9 层,有洋房有叠拼,还自带万科物业、万科 V 盟光环,而且提供附加豪华装修包,新风、智能一应俱全(算上装修大概6万/㎡)。不过未来 40% 的小户型有可能都租出去,真正的「档次」在哪里还不好说。

不过,具体规划都没出,翡翠云图和山屿湖倒还维持着表面的「塑料姐妹情」,等葛洲坝再加入战斗,翠湖大概率会变成姐妹互撕的「修罗场」。

除了大兴桃谷六仙、五里坨三兄弟和翠湖姐妹花,北京还有 30 多个暂时还没有名字、不清楚规划的地块。按照大部分地块遵循的「7090」政策来看,未来,总价 500 万以内的限竞房江湖还会更加庞大。他们基本上师出同门,身法一致且招式雷同,要想在刚需市场中拔得头筹,拼的是物业、是管理、是品牌。

限价江湖史告一段落,预知后事如何,我们还有下回分解。

18 Apr 06:44

七位CTO眼中的技术领导者核心能力

by TGO智囊团

国内很多程序员,即便不做管理,但终究是要带团队打硬仗的,技术领导力很多时候可能变得比技术能力还要重要。

本文摘自极客时间App 技术管理专栏《技术领导力300讲》一部分,欲阅读更多领导力文章,请点击此处订阅全年专栏,一次订阅,永久阅读。

关于技术领导者的具体职责和能力要求,每个企业会给出自己的定义。同样的职位,由于企业文化的区别,以及具体的业务和发展阶段不同,相应的要求也会有所不同。但总体来说,有一些核心的能力,是衡量一位技术领导者是否优秀的通用标准。

通常来说,CTO这个岗位对技术领导者的能力要求是最全面的,基本上可以覆盖技术管理核心能力的各个方面。在之前的专栏中,我们分享过《技术人的能力模型决定你的职位》,所以特地邀请了七位现任或曾任CTO,请他们根据自己工作中的体会,总结CTO需要具备的核心能力。对比来看,非常有意思。

AdMaster 联合创始人兼 CTO 洪倍:

“CTO”“首席技术官”这三个英文单词或五个汉字,其实已经揭示了CTO所需要的核心能力。我自己总结起来是 5个"shi"字。

第一个是"将士"的士,也就是要会带兵。“首席”和“官”这两个词天然赋予了你带兵用兵的责任。所以了解每一个团队成员的特征,关心每一个小伙伴的成长,打造一支能战斗的团队,是对CTO最重要的要求;

第二个是"做事"的事,除了带人,CTO也需要身先士卒、迎难而上,CTO没有攻坚平事的能力,愧对"技术"这个词;

第三个是"视角"的视,CTO也要有行业洞察、市场思维、成本视角,只有多视角的观察自己所研发的产品、服务的用户、所处的市场环境,才能找到更匹配的技术解决方案,为员工、企业乃至社会创造价值;

第四个是"试错"的试,CTO要勇于试错,当然有错了也要勇于承担,勤于总结,CTO不敢试错,那团队成员就更不敢试错了。创新和成功是来自于无数次的试错;

第五个是"顺势"的势,CTO如果不能预判技术发展趋势,团队一定会面临无头苍蝇或者 得过且过的窘境。同时 CTO也需要学会顺势而为,借助各方的资源和力量才能更好地带团队、攻难关、聪明地试错;

CTO 就是这样一个需要会带人、能攻坚、有视野、愿试错、懂得顺势而为的岗位。

杭州福强科技有限公司创始人王福强:

CTO的能力要求很多,但我觉得有几个能力属于核心能力:

第一个是运营能力, 当然,因为CTO主要是focus在技术前缀下,所以,他负责的是技术组织的运营, 这包括日常,组织结构以及技术文化和营销等一系列软硬结合的运营方式;

第二个是转承的能力, CTO和产品技术部门的核心职责是支撑CEO和公司业务,所以,要能够很好地理解CTO和Peer部门的意图和规划,进而推动落地;

第三个就是吸星的能力, 爱才如命,什么时候CTO对人的重视略高于对技术的重视的时候,CTO的格局就更上一层楼了。

这是我的几点粗浅认识,仅供参考。

有赞 CTO 崔玉松:

CTO首先是一个O,O本身代表着一个管理职位,其次还有Owner这层意思,他要很了解这家公司的业务,包括人才战略、市场战略、用户战略、增长战略等,甚至必须参与这些战略的制订。

技术说到底是为业务服务的,除非技术就是业务本身。在很多公司里技术真的就成了实现其他部门需求的工具,我觉得这样做CTO肯定是不合格的。首先它不能影响战略,需求提出方会经过很多转化,如果不是基于战略去推导,整个过程会失真。

第二个,我认为最最重要的是业务架构的能力,可能管理能力还次之。对于管理能力我认为最重要的是对团队的感知能力,因为一旦到了CTO这个级别,他已经脱离一线,很难再把一些细节还原。如果没有很细腻的感知能力,很多的决策会有偏差。

如果他不是一个业务架构师,不是一个能给团队指明更好方向的人,他最终会沦为一个需求翻译器,产品经理说怎么做就怎么做。他更多的只是负责保证产品的质量、开发的速度,最终被肢解成一个很琐碎的人。一旦团队上了一定的规模,团队就会从单纯的需求实现走向团队运营,而运营是需要方向的,业务架构就是一个基于运营和数据的一种综合的能力。

总结一下,我觉得最重要的就是参与战略制订、战略实施和战略分解的能力。其次,就是业务架构的能力和对团队感知的能力,微观事物感知的能力。

丁香园 CTO 范凯:

我觉得CTO需要具备如下5方面的素质:

一、技术视野良好,架构设计能力出色

CTO要有良好的技术视野,不需要各种技术都样样精通,但是必须要所有涉猎,有所了解,对各种技术领域的发展趋势,主流非主流技术的应用场景要非常了解。知道在什么场景应用什么技术,公司业务发展到什么规模应该预先做哪些技术储备。产品架构的设计要有足够的弹性,既能够保证当前开发的高效率,又能够对未来产品架构的演进留出扩展的余地。

二、动手能力要强,学习能力出色

CTO并不需要自己亲自动手写代码,但是如有必要,自己可以随时动手参与第一线的编码工作,CTO不能长期远离一线工作,自废武功,纸上谈兵。否则长此以往,会对技术的判断产生严重的失误。另外,CTO也应该是一个学习能力非常出色的人,毕竟IT行业的技术更新换代速度非常快,如果没有快速学习能力,是没有资格做好CTO的。

三、管理研发团队过硬,能建立团队研发文化

CTO的责任是负责整个公司的产品实现,所以CTO要善于管理研发团队,掌控好研发工作进度,能够在规划好的时间内,步步为营,好整以暇地完成公司产品的研发工作。

此外CTO还要擅长培养研发梯队力量,建立研发团队内部具有向心力的,开放性的,交流学习型组织文化。让研发团队具备自我学习能力,自我培养能力,自我建设能力。这样的研发团队工作极度默契,战斗力极强,而且员工归属感很强,流失率很低。

四、具备良好的产品意识,以及跨部门跨背景的沟通能力

CTO不仅要懂技术,还要对互联网产品有良好的感觉,从产品的逻辑性,可实现角度提出产品改进和完善的总体性设想。因为产品经理或者业务人员设想的产品,很可能是逻辑上不严密,存在实现矛盾的。

此外CTO还需要极强的沟通能力,要能够和不同背景的人有良好的沟通能力,能够用对方的思维方式和话语体系来描述他不理解的专业问题。

五、敢于对CEO说“不”

只要不是技术出身的CEO,必然对研发是门外汉,很可能对产品也是门外汉。因此,CEO不是每个想法都靠谱的,CTO有责任站在更加专业的角度去帮助CEO纠正,推演,完善想法。一个不敢对CEO说不的CTO,这个公司肯定要走很长很长的弯路的。

易宝支付 CTO 陈斌:

在我看来,CTO可以不写代码,但是这意味着你有更重要的事情要做,比如需要具备技术战略、体系建设、人才培养、企业文化、商业眼光、领导艺术等方面的素质。

首先,CTO是技术战略的主导者。在CTO的这些素质中,最基本是要有技术战略。比如:编程语言选Java还是其他的语言,有新技术出现的时候,及时地安排团队的人去学,以备不时之需。当然CTO自己也要不断学习新的技术,不断更新自己的知识储备。

其次,企业文化是非常有力的管理工具。企业文化很重要,但在实践当中经常会被忽略。CTO也要管企业文化,不但要引导企业文化,而且要身体力行。其实企业文化是一个能够帮你把整个企业的研发人员、氛围、管理过程组织起来的有效手段。

最后,要打造良好的技术团队氛围。研发人员需要一位能指导他们、了解他们的大哥,我在易宝经常会跟兄弟们抽抽烟,聊一聊,有的时候也会写写代码,原因在于可以通过写代码去了解大家在想些什么。你作为技术领导者,必须进到那个氛围,了解一线员工在做什么。然后作为他们的代言人,代表技术团队跟公司管理层争取一些利益和福利,大家才会觉得这是我们的带路人。

携程首席科学家叶亚明:

在我看来,合格的CTO有六大要素:

要素1:技术问题的解决能力

技术领导者会面临很多技术问题,尤其业务发展了以后,过去的一些技术瓶颈,都会变成问题。在这种情况下,要具备快速解决问题的能力;另外,你要指明方向,带领团队共同前进。

要素2:具备强烈“还债”意识

几乎所有的互联网公司都会遇到技术债,当业务发展到某个阶段时,一定会爆发。有技术债怎么办?除了能够“发现”债的存在,还要适时“还债”,作为技术领导要有这种前瞻性。

要素3:构建与CEO的良好伙伴关系

首先,要从业务负责人或CEO的角度去思考他提的需求是什么。其次,要会平衡产品需求,判断产品需求的优先级。第三,要提高交付满意度。第四,要有业务洞察力,要对业务有强烈的兴趣。如果你对业务非常有感觉,那么你跟CEO的交流就会上升一个层次。

要素4:清晰的自我认识

你要意识到自己处于什么位置?周围的人跟你的平衡点在哪里?自己的优势和短板在哪里?对自我有一个清晰的认识非常重要。认识自我还不够,还要认识团队。团队整体技术水平是什么样?团队的短板在哪里?有这样的意识,你就知道在哪个方面应该补强。

要素5:团队人才建设

实践起来主要有四点:第一,招募培养接班人;第二,CTO自身的影响力;第三,自己的人格经要得起别人的挑剔,做到客观公正;第四,能创造优秀的团队文化。

要素6:给工作注入新的东西

作为一个技术领导者,如果你经常为整个业务或者团队带来新的东西或思路,与公司授予你的权力相结合,那么推进起来会非常快。虽然自下而上的创新也可以成功,但是根据经验和中国互联网过去的发展,从上到下去推动改变会更快一些。

磁云科技创始人及CEO、 京东终身荣誉技术顾问李大学:

一个好的CTO是能够把商业和技术结合起来,或者说具有商业敏感性的CTO。我认为有三点:

第一,CTO应该具备商业洞察能力。我比较喜欢看一些商业书籍,来锻炼自己的商业分析能力、判断力、决策力,因为只有把技术和商业去结合的时候,它的价值才能呈现出来。

第二是战略思维。很多CTO都是从技术人员做起来的,技术人员往往会想一个问题怎么做,他解决的是how的问题,但是在研究战略问题的时候,我们更多研究的是What和who的问题,或者说,我们更多的是看趋势、看未来,如果看得远,就可以反过来思考,现在做得是否对。我们有时候太关注技术本身,没有站在战略角度考虑问题。

第三是个人的影响力。这种影响力包括影响同事,影响下属,甚至影响CEO,当你有一定影响力后你会发现,你能团结凝聚更多的人,那你做事情就比较容易。对于CTO来说,领导力的核心就是影响力。

结语

以上就是七位现任或曾任CTO总结的“CTO的核心能力”,我们会发现,其中有很多相通之处,也有一些独特的观点。或许可以这样理解:是否具备CTO们都非常看重的能力,决定了你是否能够成为一个技术领导者;而是否具备部分CTO非常看重的能力,决定了你将成为一个什么风格的技术领导者。

戳此订阅《技术领导力300讲》专栏,与100 CTO 与一线技术管理者聊聊技术与管理的实战经验,让每一位带技术团队打硬仗的技术人,踏平每一个常见的“坑”。

福利一:原价 ¥299/年,极客时间新用户注册立减 ¥30

福利二:每邀请一位好友购买,你可获得41元现金返现,多邀多得,上不封顶,立即提现

内容经过极客时间App授权,未经许可,不可转载

评价本文

专业度
风格

此内容所在的主题为文化 & 方法

相关主题:

您好,朋友!

您需要 注册一个InfoQ账号 或者 登录 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论
关闭 主题 您的回复 引用原消息

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
关闭 主题 您的回复

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论
10 Oct 00:39

机器学习实践指南

by 十七树

你可能在各种应用中听说过机器学习machine learning(ML),比如垃圾邮件过滤、光学字符识别(OCR)和计算机视觉。

开启机器学习之旅是一个涉及多方面的漫长旅途。对于新手,有很多的书籍,有学术论文,有指导练习,有独立项目。在这些众多的选择里面,很容易迷失你最初想学习的目标。

所以在今天的文章中,我会列出 7 个步骤(和 50 多个资源)帮助你开启这个令人兴奋的计算机科学领域的大门,并逐渐成为一个机器学习高手。

请注意,这个资源列表并不详尽,只是为了让你入门。 除此之外,还有更多的资源。

1、 学习必要的背景知识

你可能还记得 DataCamp 网站上的学习数据科学这篇文章里面的信息图:数学和统计学是开始机器学习(ML)的关键。 基础可能看起来很容易,因为它只有三个主题。 但不要忘记这些实际上是三个广泛的话题。

在这里需要记住两件非常重要的事情:

  • 首先,你一定会需要一些进一步的指导,以了解开始机器学习需要覆盖哪些知识点。
  • 其次,这些是你进一步学习的基础。 不要害怕花时间,有了这些知识你才能构建一切。

第一点很简单:学习线性代数和统计学是个好主意。这两门知识是必须要理解的。但是在你学习的同时,也应该尝试学习诸如最优化和高等微积分等主题。当你越来越深入 ML 的时候,它们就能派上用场。

如果是从零开始的,这里有一些入门指南可供参考:

统计学是学习 ML 的关键之一

如果你更多喜欢阅读书籍,请参考以下内容:

然而,在大多数情况下,你已经对统计学和数学有了一个初步的了解。很有可能你已经浏览过上面列举的的那些资源。

在这种情况下,诚实地回顾和评价你的知识是一个好主意,是否有一些领域是需要复习的,或者现在掌握的比较好的?

如果你一切都准备好了,那么现在是时候使用 R 或者 Python 应用这些知识了。作为一个通用的指导方针,选择一门语言开始是个好主意。另外,你仍然可以将另一门语言加入到你的技能池里。

为什么这些编程知识是必需的?

嗯,你会看到上面列出的课程(或你在学校或大学学习的课程)将为你提供关于数学和统计学主题的更理论性的介绍(而不是应用性的)。 然而,ML 非常便于应用,你需要能够应用你所学到的所有主题。 所以最好再次复习一遍之前的材料,但是这次需要付诸应用。

如果你想掌握 R 和 Python 的基础,可以看以下课程:

当你打牢基础知识后,请查看 DataCamp 上的博客 Python 统计学:40+ 数据科学资源。 这篇文章提供了统计学方面的 40 多个资源,这些资源都是你开始数据科学(以及 ML)需要学习的。

还要确保你查看了关于向量和数组的 这篇 SciPy 教程文章,以及使用 Python 进行科学计算的研讨会

要使用 Python 和微积分进行实践,你可以了解下 SymPy 软件包

2、 不要害怕在 ML 的“理论”上浪费时间

很多人并不会花很多精力去浏览理论材料,因为理论是枯燥的、无聊的。但从长远来看,在理论知识上投入时间是至关重要的、非常值得的。 你将会更好地了解机器学习的新进展,也能和背景知识结合起来。 这将有助于你保持学习积极性。

此外,理论并不会多无聊。 正如你在介绍中所看到的,你可以借助非常多的资料深入学习。

书籍是吸收理论知识的最佳途径之一。 它们可以让你停下来想一会儿。 当然,看书是一件非常平静的事情,可能不符合你的学习风格。 不过,请尝试阅读下列书籍,看看它是否适合你:

  • 机器学习教程Machine Learning textbook, Tom Mitchell 著,书可能比较旧,但是却很经典。这本书很好的解释介绍了机器学习中最重要的课题,步骤详尽,逐层深入。
  • 机器学习: 使数据有意义的算法艺术和科学Machine Learning: The Art and Science of Algorithms that Make Sense of Data(你可以在这里看到这本书的幻灯片版本):这本书对初学者来说非常棒。 里面讨论了许多实践中的应用程序,其中有一些是在 Tom Mitchell 的书中缺少的。
  • 机器学习之向往Machine Learning Yearning :这本书由吴恩达Andrew Ng编写的,仍未完本,但对于那些正在学习 ML 的学生来说,这一定是很好的参考资料。
  • 算法与数据结构Algorithms and Data Structures  由 Jurg Nievergelt 和 Klaus Hinrichs 著。
  • 也可以参阅 Matthew North 的面向大众的数据挖掘Data Mining for the Masses。 你会发现这本书引导你完成一些最困难的主题。
  • 机器学习介绍Introduction to Machine Learning  由 Alex Smola 和 S.V.N. Vishwanathan 著。

花些时间看书并研究其中涵盖的资料

视频和慕课对于喜欢边听边看来学习的人来说非常棒。 慕课和视频非常的多,多到可能你都很难找到适合你的。 下面列出了最知名的几个:

在这一点上,重要的是要将各种独立的技术融会贯通,形成整体的结构图。 首先了解关键的概念:监督学习supervised learning和无监督学习unsupervised learning的区别、分类和回归等。 手动(书面)练习可以派上用场,能帮你了解算法是如何工作的以及如何应用这些算法。 在大学课程里你经常会找到一些书面练习,可以看看波特兰州立大学的 ML 课程

3、 开始动手

通过看书和看视频了解理论和算法都非常好,但是需要超越这一阶段,就要开始做一些练习。你要学着去实现这些算法,应用学到的理论。

首先,有很多介绍 Python 和 R 方面的机器学习的基础知识。当然最好的方法就是使用交互式教程:

还请查看以下静态的(非互动的)教程,这些需要你在 IDE 中操作:

除了教程之外,还有一些课程。参加课程可以帮助你系统性地应用学到的概念。 经验丰富的导师很有帮助。 以下是 Python 和机器学习的一些互动课程:

  • 用 scikit-learn 做监督学习: 学习如何构建预测模型,调整参数,并预测在未知数据上执行的效果。你将使用 Scikit-Learn 操作真实世界的数据集。
  • 用 Python 做无监督学习: 展示给你如何从未标记的数据集进行聚类、转换、可视化和提取关键信息。 在课程结束时,还会构建一个推荐系统。
  • Python 深度学习: 你将获得如何使用 Keras 2.0 进行深度学习的实践知识,Keras 2.0 是前沿的 Python 深度学习库 Keras 的最新版本。
  • 在 Python 中应用机器学习: 将学习者引入到机器学习实践中,更多地关注技术和方法,而不是这些方法背后的统计学知识。

理论学习之后,花点时间来应用你所学到的知识。

对于那些正在学习 R 语言机器学习的人,还有这些互动课程:

  • 机器学习介绍 可以让你宏观了解机器学习学科最常见的技术和应用,还可以更多地了解不同机器学习模型的评估和训练。这门课程剩下的部分重点介绍三个最基本的机器学习任务: 分类、回归和聚类。
  • R 语言无监督学习 ,用 R 语言从 ML 角度提供聚类和降维的基本介绍。 可以让你尽快获得数据的关键信息。
  • 实操机器学习涵盖了构建和应用预测功能的基本组成部分,其重点是实际应用。

最后,还有很多书籍以偏向实践的方式介绍了 ML 主题。 如果你想借助书籍内容和 IDE 来学习,请查看这些书籍:

4、 练习

实践比使用 Python 进行练习和修改材料更重要。 这一步对我来说可能是最难的。 在做了一些练习后看看其他人是如何实现 ML 算法的。 然后,开始你自己的项目,阐述你对 ML 算法和理论的理解。

最直接的方法之一就是将练习的规模做得更大些。 要做一个更大的练习,就需要你做更多的数据清理和功能工程。

熟能生巧。

5、 项目

虽然做一些小的练习也不错,但是在最后,您需要做一个项目,可以在其中展示您对使用到的 ML 算法的理解。

最好的练习是实现你自己的 ML 算法。 您可以在以下页面中阅读更多关于为什么您应该做这样的练习,以及您可以从中学到什么内容:

接下来,您可以查看以下文章和仓库。 可以从中获得一些灵感,并且了解他们是如何实现 ML 算法的。

开始时项目可能会很难,但是可以极大增加你的理解。

6、 不要停止

对 ML 的学习永远不能停止,即使你在这个领域工作了十年,总是有新的东西要学习,许多人都将会证实这一点。

例如,ML 趋势,比如深度学习deep learning现在就很受欢迎。你也可以专注于那些现在不怎么火,但是将来会火的话题上。如果你想了解更多,可以看看这个有趣的问题和答案

当你苦恼于掌握基础知识时,你最先想到的可能不是论文。 但是它们是你紧跟最新研究的一个途径。 论文并不适合刚刚开始学习的人,但是绝对适合高级人员。

其他技术也是需要考虑的。 但是当你刚开始学习时,不要担心这些。 例如,您可以专注于 Python 或 R 语言 (取决于你已经知道哪一个),并把它到你的技能池里。 你可以通过这篇文章来查找一些感兴趣的资源。

如果您还想转向大数据,您可以考虑研究 Spark。 这里有一些有趣的资源:

其他编程语言,比如 Java、JavaScript、C 和 C++ 在 ML 中越来越重要。 从长远来看,您可以考虑将其中一种语言添加到学习列表中。 你可以使用这些博客文章来指导你选择:

学无止境。

7、 利用一切可以利用的资源

机器学习是一个充满难度的话题,有时候可能会让你失去动力。 或者也许你觉得你需要点改变。 在这种情况下,请记住,有很多资源可以让你打消掉这种想法。 查看以下资源:

播客是可以让你继续你的 ML 旅程,紧跟这个领域最新的发展的伟大资源:

当然,还有更多的播客。

文档和软件包源代码是深入了解 ML 算法的实现的两种方法。 查看这些仓库:

  • Scikit-Learn:知名的 Python ML 软件包
  • Keras: Python 深度学习软件包
  • caret: 非常受欢迎的用于分类和回归训练 R 软件包

可视化是深入 ML 理论的最新也是最流行的方式之一。 它们对初学者来说非常棒,但对于更高级的学习者来说也是非常有趣的。 你肯定会被下面这些可视化资源所吸引,它们能让你更加了解 ML 的工作原理:

学习中的一些变化更加能激励你。

现在你可以开始了

现在一切都取决于你自己了。学习机器学习是一个持续的过程,所以开始的越早就会越好。 运用你手边的一切工具开始吧。 祝你好运,并确保让我们知道你的进步。

这篇文章是我基于 Quora 问题(小白该如何开始机器学习)给出的答案。


作者简介:

Karlijn Willems,数据科学记者

 

机器学习实践指南,首发于文章 - 伯乐在线

01 May 00:04

Java基础中一些值得聊的话题(加载篇)

by 唐尤华

在开始Java的类加载旅程之前,可以先参考这里了解一些类加载器在Tomcat中的应用。

在最初执行java这个命令时,便会调用 ClassLoader 的 getSystemClassLoader 方法显式或者隐式加载 main 方法所在的类及其所引用的类。getSystemClassLoader 会返回 AppClassLoader,后者是 URLClassLoader 的一个子类。

先有鸡还是先有蛋?

所以,最初的一个问题是:先有鸡还是先有蛋?因为 ClassLoader 的整套体系是打包在 jre/lib/rt.jar 中的。只有 rt.jar 先被加载进来,才能够加载别的类;但是 rt.jar 又是被谁加载的呢?自然就是大名鼎鼎的 BootstrapClassLoader。它就是“鸡”。所以严格来讲,BootStrapClassLoader 并不是整个体系中的一部分(可以用 -Xbootclasspath 指定bootstrap 加载的位置)。

rt.jar 被加载进来后,ClassLoader 会调用 getSystemClassLoader,其中最重要的一步就是初始化 Launcher、ExtClassLoader 以及AppClassLoader,另外就是将 ContextClassLoader 设为 AppClassLoader。ExtClassLoader 与 AppClassLoader 都是 URLClassLoader 的子类,分别会加载 java.ext.dirs java.class.path 路径下的 jar资源,前者一般指向 jre/lib/ext 下的所有jar,后者就是我们经常念叨的classpath。区分这两个 ClassLoader 的主要目的是,让他们形成层级关系,ExtClassLoader 为 AppClassLoader 的父 ClassLoader,有了层级关系,便可随意使用双亲委托模型了。

ClassLoader extcl;
        try {
            extcl = ExtClassLoader.getExtClassLoader();
        } catch (IOException e) {
            throw new InternalError(
                "Could not create extension class loader");
        }

        // Now create the class loader to use to launch the application
        try {
            loader = AppClassLoader.getAppClassLoader(extcl);
        } catch (IOException e) {
            throw new InternalError(
                "Could not create application class loader");
        }

        // Also set the context class loader for the primordial thread.
        Thread.currentThread().setContextClassLoader(loader);

ClassLoader究竟干了什么?

接下来一个比较重要的问题是ClassLoader究竟干了什么?通常我们只知道它加载了一个类进了jvm,但是具体做了什么呢?

Java设计者把classloader加载一个类的过程分为4步:

  • 第一步,从某个地方得到我们想要的字节码二进制流;
  • 第二步,读入字节码流并转化为Class;
  • 第三步,链接;
  • 第四步,初始化。

其中,第二步一般比较固定,因此ClassLoader提供了defineClass来完成这步;

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);

        Class c = null;
        String source = defineClassSourceLocation(protectionDomain);

        try {
            c = defineClass1(name, b, off, len, protectionDomain, source);
        } catch (ClassFormatError cfe) {
            c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,
                                       source);
        }

        postDefineClass(c, protectionDomain);
        return c;
    }

而 ClassLoader 提供了另一个方法 findClass 来完成第一步及第二步,即从某个地方读入类的二进制流,然后调用 defineClass 返回 Class

protected Class<?> findClass(final String name)
         throws ClassNotFoundException

ClassLoader提供了resolveClass方法完成第三步链接的工作。

protected final void resolveClass(Class<?> c)

除非特殊需要,否则尽量重载 findClass 而不是 loadClass

loadClass 是 Java 1.0 就存在的类,为了增强可扩展性,将findClass和resolveClass封装到了loadClass中,一般我们只需要定义类的加载路径,因此仅需覆盖findClass。

通常我们显示加载类一般会用到ClassLoader.loadClass、Class.forName,他们的区别见这里

resolveClass做了什么?

resolveClass最终调用了一个本地方法做link,这里的link主要做了这么几步事情:

  1. 验证Class以确保类装载器格式和行为正确;
  2. 准备后续步骤所需的数据结构
  3. 解析所引用的其他类。

关于这些内容的具体细节,请参考这里

ClassNotFoundException、NoClassDefFoundError、ClassCastException常见问题

ClassNotFoundException一般发生在显式类加载;NoClassDefFoundError一般发生在隐式加载;ClassCastException一般发生在jar包冲突,比如某个jar包已经被更上层的加载器加载了,但你却要求他强制转为下层加载器加载的同名类;

链接的相关知识

接下来讲讲链接的相关知识。

为什么会发明链接器?

最初程序员写程序都在一个文件里,随着程序规模的增加,逐渐发现越来越难以维护,扩展。于是,分多个文件和模块就成为必然。

但是文件间必然相互调用,这就带来了另一个问题:文件A引用了B的某个变量或方法,但是运行时A并不知道他们在内存中的位置。于是人们发明了链接,这种方式会在编译阶段将需要引用的变量或者方法作个记号,这时形成A.o和B.o两个目标文件;通过ld链接器的链接,会将B中变量或方法的地址重新修改A的记号最终形成一个可执行文件,实际上就是把A和B合在一起工作了。

这种方式就是静态链接

但是静态链接有个问题,比如A需要B模块的方法,C需要B模块的变量,D需要B模块的方法……如此一来,当我们编译为A, C, D几个可执行文件时,他们都会在内存中引用B,即B在内存中有多份拷贝。这带来很多问题:首先浪费了内存;其次修改了B模块需要重新修改并发布A, C, D几个可执行文件,这是非常不方便的;于是动态链接的一个思想是在A, C, D调用时再确定记号的地址,而B则通过延迟加载的方式按需加载到内存(如果已经加载则不再重复)。这样一来,内存中总是只有一份B的拷贝,解决了上面的问题。Java的类加载机制即是这种灵活的方式。 

相关文章

07 Jul 14:14

《南方周末》银行离职潮:物极必反 “金饭碗”正在熔化

by 墙外仙

银行业曾经是个“只进不出”的好去处:收入高、福利好、旱涝保收。但当银行不再能“躺着挣钱”,职业发展空间逼仄,而外面的世界更精彩,越来越多的银行人感叹着“时代变了”,开始告别这个行业。

2016年6月16日,百度披露,光大银行原资产管理部总经理张旭阳正式加盟百度,出任分管百度金融理财和资产管理业务的副总裁。

这并非孤例。年初,中国建设银行原网络金融部总经理黄浩也出任了蚂蚁金服集团总裁助理。

据各银行公告,自2015年起至2016年6月,已有超过60位银行的“董(事)监(事)高(管)”离职,涉及岗位从董事、行长、副行长到风控总监、首席信息官等。

离职潮并不仅仅在中高层蔓延。吴丽是一家国有银行的支行副行长,她明显感受到这一年来所在支行走的人比往年更多了,一个三十多人的支行,今年以来“已经走了近一半”。

银行业曾经是个“只进不出”的好去处:收入高、福利好、旱涝保收。因此,过去银行的人员流动也多发生在行业内不同银行间。但当银行不再能“躺着挣钱”,职业发展空间逼仄,而外面的世界更精彩,越来越多的银行人感叹着“时代变了”,开始告别这个行业。

不同的人离开的理由不尽相同。备受媒体关注的银行中高层或是因为薪酬待遇和更好的发展空间,而普通的基层
职员则或许是由于银行业风光不再后,不堪承受压力的选择。中国社科院金融所银行研究室主任曾刚认为,所谓的银行离职潮是中国金融业市场化程度不断提高,竞争不断加剧的必然结果。

骤增的不良率

不良率高企的数字落地到现实中,最先被砸中的是银行内部负责拉存贷款的客户经理,高企的不良率让其收入几乎在几年间腰斩。

6月中旬,一段山西长治漳泽农商银行公开暴打员工屁股进行“业绩考核”的视频在网上流传。事后据媒体跟踪报道,事件发生的背景,是最近几年农商行的业绩考核压力越来越大。

压力大,是南方周末近期采访的所有银行从业人员的通感。而最直接的压力源,即是与所有银行员工工资收入挂钩的不良率。

银行业不良贷款数量的升高仿佛就发生在一夜之间。王浩博是一家股份制银行浙江某地分行的法务,2013年他所在分行全年发生的不良贷款不过才7-8笔,而这个数字在2014年暴增到了60-70笔,2015年则到了近百笔。银行甚至为此不得不扩充了法务的编制,以应对数量大幅上涨的诉讼。

据银监会数据,截至2016年一季度末,中国银行业不良贷款余额已达到2.1万亿,不良贷款率达到2.04%,突破了2%的“生死线”。其中商业银行不良贷款余额近1.4万亿元,不良贷款率1.75%,环比上升0.07个百分点,这已是商业银行不良贷款率连续第11个季度上升。

四大国有银行中,农业银行2015年的不良率高达2.39%,相比上一年增幅达70.33%。其他三大国有银行的不良率增幅也在30%以上。股份制银行与国有银行情况类似,不良率都在1.4%-1.7%之间,除了中信银行增幅为10%之外,招商银行、光大银行等增幅都在30%以上。

过剩产能是不良贷款的高发区,在政府推进供给侧改革的背景下,银行的不良贷款还将继续增加。

不良率高企的数字落到现实中,最先被砸中的是银行内部负责拉存贷款的客户经理。

刘妮是一家股份制银行浙江省某支行的客户经理,身处不良贷款重灾区的她,手上负责的14个客户中已经有5个出了问题。其中3家已经被起诉到法院,要求查封清算,其余两家则勉强维持着3个月一结利息的状态。

“因为银行规定贷款利息一月一结,如果3个月不结这笔贷款就会被降为不良贷款,就要启动相关法律程序,有的企业还在想尽办法活下去。”刘妮估算,就她所在的支行,目前实际不良率在10%左右,未来还将继续攀升。

高企的不良率让客户经理的收入几乎在几年间腰斩。刘妮表示,2010-2012年形势好的时候,她所在支行客户经理平均能拿到40万左右的年薪,这一数字在去年只有税前20万,同时还要承担银行内部对不良贷款审计后的处罚。

刘妮表示,在不良资产完成诉讼拍卖后,银行合规部门会对整笔贷款中客户经理发挥的职责,进行尽职或渎职的认定,然后予以定级。最后,对客户经理处以该笔不良贷款最终损失的千分之一到百分之几不等的罚款。去年一年,她因为不良贷款被扣罚了4万元,这还是在她仅作为贷款协办人员,并不承担主要责任的情况下。

不少银行客户经理目前处于“想走走不了”的状态,在清收完手上的不良贷款之前,他们并不能轻易离职,只能每天去跑清收。

过去,银行从业者在公积金缴存、住房补贴、节假日补贴等方面隐性福利令人艳羡,但近两年随着业绩下滑,银行各种奖金福利相应减少。“原来5天年休假补贴三四千元,现在已经降到了一千元不到。”王浩博说。

暴跌的净利润增速

经济下行周期的影响,和利率市场化改革等,也让银行的好日子似乎到头了。

与不良率攀升相呼应的,是银行净利润增速的迅猛下跌,各大银行的财报显示,大家的日子都不怎么好过。

2015年,在18家A股和H股上市的银行中,四大国有行净利润增速全都在1%以下,增速同比跌幅均在90%以上。相比四大国有行,交通银行增速为1.03%,但跌幅也高达80%。6家股份制银行平均增速为4.21%,不到2014年的9.74%的一半。7家城商行则仍然保持了较快的增速,达到了26.60%。

净利润增速下跌,与不良率升高关系密切。人民银行财政金融学院副院长赵锡军表示,一般的银行出现了不良贷款,内部解决途径就是核销,“核销不良贷款意味着利润的减少,不良贷款冲销的越多,减少的利润就越大。”
经济下行周期的影响和利率市场化改革等,也让银行的好日子难以为继。

贾童杰2014年离开了银行业,之前他是杭州银行北京中关村支行行长。那时候他就切身感受到了压力,并不看好银行业的未来发展,“基层银行的业务就是存贷款、吃息差,形势好时企业一年可以沉淀下来几千万元的存款,
形势不好的时候,企业放在银行的存款自然就少了,银行业绩也就下降了。”

贷款也是如此,经济形势好的时候信贷条件会宽松一些,经济形势下行时,企业的收入和业务量在逐渐减少,银行在就会收紧信贷,过去能够操作的贷款,此时就不能做。

“银行的任务还在不断上调,没有任务是不可能的。”贾童杰离职前觉得业务不那么好做了,“存贷款任务完不成,就会影响你的打分和绩效,最后自然会影响你的收入。”

吴丽也表示,现在她所在区域的银行更多把目光转向个人理财,“企业可能没钱了,但是不少个人手里还是有钱的,他们没有好的投资渠道,我们就拉过来做理财。”

利率市场化和存款保险等改革的推进,也不断压缩着银行的盈利空间。银行的大部分利润来自存贷款的息差,2015年10月存款利率上限取消后,中国的利率管制已经基本放开。理论上来说,取消利率管制之后,银行可以采用更具竞争性的利率吸收存款,即资金成本会有所上升,同时银行也会相应提高贷款利率的敏感性,加之银行间的竞争加剧,银行存贷利差会伴随利率市场化的进程逐渐缩小。

赵锡军认为,在利率市场化前后,商业银行传统存贷款业务的要求和管理是不一样的,中小银行将面临更大的挑战,甚至是市场的“挤出效应”。大银行有足够的资本优势,可以采取较低的利率放贷,也许其存款利率同样很低,但是由于存款保险制度,资金会流向更稳健的大银行,其资金池会更加充沛。

互联网金融的诱惑

对传统金融行业来说,互联网金融所带来最大的竞争压力,是对人才的竞争。

近一年来,不少银行中高层打破在银行业内流动的惯例,投奔互联网金融或民营银行等新型机构。

兴业银行原行长李仁杰出任陆金所董事长;中国银行原副行长王永利出任乐视高级副总裁;华夏银行原副行长黄金老出任苏宁云商副总裁;渣打中国总行原副行长罗龙翔、平安普惠原副首席风险官冯瑞彬,以及汇丰银行原中国区首席法律顾问周莉莉,则在今年初入职点融网。

南方周末采访的多位银行业人士并不觉得互联网金融对银行业务产生了较大冲击,但互联网金融所带来最大的竞争压力,是对人才的竞争。

一家国有行总行的一名处长对南方周末表示,国有行员工离职的原因,一般是其新入职机构的薪酬高出很多,且与业绩挂钩比较明显;另一个原因是在国有行晋升空间比较小,“有些人能力特别强,其领导还比较年轻”。

2015年央企高管限薪之后,国有银行高管的薪酬几近腰斩。据公开数据,工商银行行长易会满的2014年税前薪酬为108.9万元,2015年则为54.68万元,降幅达49.79%;中国银行行长陈四清2014年的税前薪酬为108.32万元,2015年则为61.33万元,降幅达43.48%;交通银行行长彭纯2014年税前薪酬为100.76万元,2015年则为52.57万元,降幅达47.83%。

但限薪令并不被认为是不少国有行中高层出走的主要原因,中国社科院金融所银行研究室主任曾刚认为,银行的中高层更看重平台的发展空间。

赵锡军将发展空间归结为新兴金融机构相对于传统银行的比较优势,他认为传统的银行业是以存贷为主要业务的,利率市场化以后,竞争压力就越来越大,收入增长不那么容易了。加之传统银行业各种各样的监管和管理规范比较多,互联网金融新业态还没有形成细致的规范的管理,自由度会大一些。同时,新兴的互联网公司提供的股权、期权等各种薪酬以外的条件,也更具吸引力。

(应受访者要求,吴丽、王浩博、刘妮为化名)

镜像链接:谷歌镜像 | 亚马逊镜像

相关日志

21 May 03:25

Docker + DigitalOcean + Shadowsocks 5分钟科学上网

by Qiang Fan

介绍在 Docker 环境安装 Shadowsocks 的步骤。

来源:http://liujin.me/blog/2015/05/27/Docker-DigitalOcean-Shadowsocks-5-%E5%88%86%E9%92%9F%E7%A7%91%E5%AD%A6%E4%B8%8A%E7%BD%91/

Docker —— 從入門到實踐:https://philipzheng.gitbooks.io/docker_practice/content/index.html

春色满园关不住,一枝红杏出墙来。
– 科学上网办
5分钟?就能科学上网?!!!!
有人肯定要说我标题党了,
如果你已经有一个 DigitalOcean(以下简称 DO) 账号或者 一个 VPS,
5 分钟已经算多了。
不信你自己掐表算,不废话,上教程。
PS:
  • 因为这是给对服务器不熟悉的新手写的教程,像应该创建独立用户而不是使用 root 操作等涉及服务器安全的问题不在讨论范围内。有兴趣的可以自行Google, 因为看完此文你已经能科学上网了。^^
  • Q: Shadowsocks 的安装已经足够简单了,为什么要用 Docker?
    A: 再强调一次,写这个教程的时候,我的假想读者是对 OPS 所知甚少、对科学上网有强烈需求、又想自己折腾一番的初级电脑使用者。在一台全新的主机安装 SS?谁能保证安装过程不会出错?而 Docker 开箱即用,出错率更低。当然还有一个原因,我是强迫症,喜欢 Docker 没道理,也想让更多人认识 Docker.

你需要准备

  1. DO 账号
    利益相关:DO 有一个 Referral Program,
    用我的小尾巴注册 DO 会马上送你 10 刀,> 我的小尾巴,
    10 刀相当于可以免费使用 2 个月,当然你可以无视我。
  2. Shadowsocks for OSX 客户端
    本文以 Mac 平台客户端为例,SS 有各个平台的客户端 (传送门),具体使用不再赘述,请自行搜索。
更新:有朋友反映 Shadowsocks for OSX 给的下载链接不能访问,看来 Sourceforge 也被认证了,提供一个网盘链接给大家:ShadowsocksX-2.6.3.dmg 百度网盘

服务端配置

创建一个 Droplet

  1. 填上 Droplet 名字
  2. 选择一个 Size
  3. 选择 Region
    本人亲测,电信用户选择旧金山速度最快最稳定。

    Ref: 知乎:DigitalOcean 选择 Region 的问题?
  4. 选择 Image
    这里选择 Docker
  5. 添加 SSH keys (可选)
    不想每次连 SSH 都输密码的建议还是添加,我这里已经添加过就不再详细说明了。
  6. 点击创建,等待一分钟左右,一台已经装有 Docker 的服务器就已经创建成功,顺便记下 ip 地址。

安装 Shadowsocks

  1. 首先先连上刚刚创建好的主机
    打开『终端』,输入:
    1
    ssh root@ip地址 #没记下 ip 地址的可以去 DO Droplets 页面找到
    如果你添加了 SSH keys 的话,直接就可以连上不需要输密码,
    如果你没有添加的话,DO 会通过邮件把密码发送给你,你只需要输入密码就可以连接上主机。
    看到这个界面的证明你已经连上主机。
  2. 接下来安装 Shadowsocks (以下简称 SS)
    现在就是见证 docker 的强大之处的时候了,安装 SS 你只需要:
    1
    docker pull oddrationale/docker-shadowsocks
    完成后再输入:
    1
    docker run -d -p 1984:1984 oddrationale/docker-shadowsocks -s 0.0.0.0 -p 1984 -k paaassswwword -m aes-256-cfb
    上述命令中得 paaassswwword 就是等下配置客户端需要的密码,你可以换成你自己的密码。
    现在来检查一下 SS 有没有安装成功了:
    1
    docker ps
    如果你看到 STATUS 是 up 的话,服务器端已经配置成功,你可以断开 SSH 回到你的本机。
    1
    exit
    至此服务端就已经配置好了。

客户端配置

以 Shadowsocks for MAC 客户端 为例,安装好后添加服务器配置:
填上 ip 地址,端口,密码,密码就是刚刚的 paaassswwword,保存后现在回到浏览器,打开Youtube, 你已经在科学上网了。

大功告成。是不是 5 分钟都多余了?
翻墙技术博客订阅地址及社交帐号
19 May 11:25

2016年排名Top 100的Java类库——在分析了47,251个依赖之后得出的结论

by liuchi1993

我们分析了GitHub中47,251个依赖,从中找出了排名前一百的Java类库,让我们看看谁在前面,谁在后面。

我们在漫长的周末的消遣方式就是浏览GitHub并且搜索流行的Java类库。我们决定把其中的乐趣与结果分享给你。

我们分析了GitHub中排名前3,862个项目中的47,251个导入语句,其中有12,059个Java类库被依赖。我们从这个列表中提取出前一百并把结果分享给你。

最受欢迎的前20个Java类库

Top20

上次分析结果一致,junit依旧是GitHub中最受欢迎的类库。Java中的日志API slf4j排名第二,log4j排名第四。

Google的开源类库Guava呈上升趋势,排名第三(去年排名第四)。Guava中包含一系列诞生在谷歌内部的核心Java类库。如果你不了解Guava或者你不知道如何使用它,你可以阅读Google Guava: 5 Things You Never Knew It Could Do

Spring类库的崛起

Spring框架作为Java EE的主要竞争对手在Java社区很流行,这一点也在GitHub中很好的提现了出来。在排名一百名以外,有44个类库是与Spring相关的。最有趣的部分是Spring Boot的迅速崛起,关于这部分内容可以阅读Java Bootstrap: Dropwizard vs. Spring Boot.

排名靠前的Spring类库:

#13 – springframework.spring-context

#17 – springframework.spring-test

#22 – springframework.spring-webmvc

#24 – springframework.spring-core

#27 – springframework.spring-web

#36 – springframework.spring-jdbc

#37 – springframework.spring-orm

#38 – springframework.spring-tx

#40 – springframework.spring-aop

#47 – springframework.spring-context-support

#72 – springframework.boot.spring-boot-starter-web

#81 – springframework.security.spring-security-web

#82 – springframework.security.spring-security-config

#88 – springframework.boot.spring-boot-starter-test

#99 – springframework.security.spring-security-core

最受欢迎的JSON类库

因为Java本身还不支持JSON(尽管Java9宣称支持),所以我们想通过GitHub中的项目来看看这些JSON类库的受欢迎程度。

你不能通过他的使用量多少来选择一个使用哪个类库,因为这些JSON框架的功能不尽相同。而是应该根据实际使用环境选择最适合的。如果你不知道如何选择,可以参考JSON benchmark.

排名靠前的JSON类库:

#14 – fasterxml.jackson.core.jackson-databind

#19 – google.code.gson.gson

#43 – json.json

#80 – googlecode.json-simple.json-simple

#89 – thoughtworks.xstream.xstream

神奇四侠

有很多有趣的新库,甚至引起了我们的注意,但我们决定关注以下他们:

#68 – projectlombok.lombok – Lombok提供了简单的注解的形式来帮助我们消除一些必须有但显得很臃肿的Java样板代码。

#90 – jsoup.jsoup – jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 jQuery 的操作方法来取出和操作数据。

#92 – io.Netty.netty-all – 网络应用程序框架,用于快速和方便的开发维护高性能协议服务器和客户端

#98 –dom4j.dom4j – 处理XML的开源框架。它集成了XPath并提供全力支持DOM,JAXP和Java平台。

前100个类库的类型

topType

魔术背后的科学(我们是如何得出这份列表的)

你可能想知道我们是如何得出这些信息的。我们首先按照star数量把GitHub中的项目代码pull到本地。我们提取并分析了使用了Maven和Ivy的项目中用于依赖管理的 pom.xml / ivy.xml ,这给我们提供了47,251分数据来源。

我们做了一些疯狂的挖掘和分析,最终我们得到GitHub中排名前3,862项目中的12,059个Java类库。这样就可以很方便的对他们进行排名了,只要按照他们出现的次数排序就可以了。

如果你想看看我们的原始数据,这个文件可以。虽然这篇文章中我们已经介绍的比较清楚了,我们仍然欢迎你来看看,确保我们没有错过任何有趣的见解。

最后的一点想法

当我们拿着这份列表与去年的结果做对比的时候,我们发现一些小的类库的排名有一些小的波动,Spring有明显上升,而MongoDB消失在名单中。

如果你已经对类库有了选择,但你仍然寻找最终的工具,我们有一个完美的建议给你。 Top 15 Tools Java Developers Use After Major Releases

可能感兴趣的文章

14 May 05:27

Java的日期API真烂

by liuchi1993

记得在我刚学Java的时候,真是搞不清楚Date和Calendar这两个类,后来我渐渐知道,原来不能全怪我啊,Java日期API之烂是公认的(不妨参见这篇文章,Tiago Fernandez做过一个投票,就是要选举最烂的Java API,结果Java日期API排行第二,仅次于臭名远扬的EJB2,嘿嘿)。

蛋疼的java.sql.Date

只有Date和Calendar搞定一切吗?那还好啊。当然不是!光Date就有java.util.Date和java.sql.Date,而且关系是java.sql.Date extends java.util.Date。为了把前者转成后者,我写了这样的代码:

Date date = new Date();
java.sql.Date d = new java.sql.Date(date.getTime());

居然不支持Date参数的构造器,我只好传入long类型的时间。接下去,我尝试把当前小时数取出来:

System.out.println(d.getHours());

悲剧出现了:

Exception in thread "main" java.lang.IllegalArgumentException
    at java.sql.Date.getHours(Date.java:177)

一看源码,坑爹啊:

public int getHours() {
    throw new java.lang.IllegalArgumentException();
}

在java.util.Date里面好好的方法怎么变成这个鸟样了?

方法注释给出了说明:

This method is deprecated and should not be used because SQL Date values do not have a time component.

也就是说,java.sql.Date是SQL中的单纯的日期类型,哪会有时分秒啊?我觉得它根本不应该设计成java.util.Date的子类。如果你把java.sql.Date通过JDBC插入数据库,你会发现时分秒都丢失了,因此如果你同时需要日期和时间,你应该使用Timestamp,它也是java.util.Date的子类。

另外还有一个java.util.Date的子类叫Time,java.sql包下面的Date、Time和Timestamp可以放在一起记忆。Date只包含年月日信息、Time只包含时分秒信息,而Timestamp则包含时间戳的完整信息。

现在知道人家抛出IllegalArgumentException的用心良苦了吧……

坑爹的year和month

看看Date类的构造器:

public Date(int year, int month, int day)

长得并不奇葩嘛。

好,现在我要输出2012年的1月1号了:

Date date = new Date(2012,1,1);
System.out.println(date);

结果,我傻眼了:

Thu Feb 01 00:00:00 CST 3912

等等,这是啥?3192年?

原来实际年份是要在你的年份参数上加上个起始年份1900。

更坑爹的是,月份参数我不是给了1吗?怎么输出二月(Feb)了?

Date里面的月份居然是用0~11表示的,换句话说,一月用0来表示,二月用1来表示。如果不用常量或者枚举,很容易踩到坑里去,对不对?

后来发现Go语言的time.Date方法,对于月份做了个恶心但是不容易坑人的处理(看奇葩的月份参数啊):

func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location)

我甚至怀疑Google这样处理是在用极端的方法鄙视Java(另,据我所知,JavaScript好像也是这样的,月份从0开始)……

坑爹的事情还没完,前面已经说了,构造函数的时间起始基准是1900年,可是getTime()方法却特立独行,返回的时间是相对于“1970-01-01 00:00:00”的毫秒数差值……

尝试Joda吧

最开始的时候,Date既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂,从JDK 1.1 开始,这三项职责分开了:

  1. 使用Calendar类实现日期和时间字段之间转换;
  2. 使用DateFormat类来格式化和分析日期字符串;
  3. 而Date只用来承载日期和时间信息。

原有Date中的相应方法已废弃。不过,无论是Date,还是Calendar,都用着太不方便了,这是API没有设计好的地方。

比如Calendar的getInstance方法,并未提供一个指定年月日和时分秒的重载方法,每次要指定特定的日期时间,必须先获取一个表示当前时间的Calendar实例,再去设值,比如:

Calendar c = Calendar.getInstance();
c.set(2012, 0, 1, 11, 11, 11);
System.out.println(c.getTime());

注意上面代码中对于年份的传值——是的,和Date不一样的是,Calendar年份的传值不需要减去1900(当然月份的定义和Date还是一样),这种不一致真是让人抓狂!

打印:

Sun Jan 01 11:11:11 CST 2012

有很多开源库都在努力弥补Java的这一问题,比如Joda-Time,获取Calendar对象和设置时间完全可以合成一步完成:

DateTime dateTime = new DateTime(2012, 1, 1, 11, 11, 11, 0);

而且,一月份总是可以传1来表示了。

再如,如果要给上述时间增加3天再按格式输出的话,使用Joda更加便捷:

System.out.println(dateTime.plusDays(3).toString("E MM/dd/yyyy HH:mm:ss");

有兴趣的话请阅读此文,并下载Joda-Time使用。

JSR-310

众所周知Java的规范就是多、而且啰嗦,这帮老大们(Export Group中除了有Oracle的人,还有IBM、Google和RedHat的人)终于再也无法忍受Java那么烂的日期API了,于是就有了JSR-310(感兴趣的请移步),官方的描述叫做“This JSR will provide a new and improved date and time API for Java.”,目前的阶段还在“Early Draft Review 2”,有得等。

JSR-310将解决许多现有Java日期API的设计问题。比如Date和Calendar目前是可变对象,你可以随意改变对象的日期或者时间,而Joda就将DateTime对象设计成String对象一样地不可变,能够带来线程安全等等的好处,因此这一点也将被JSR-310采纳。

很多JSR规范都是在程序员的诋毁和谩骂声中萌芽的,然后会有开源项目来尝试解决Java的这些弊端,最后就轮到JSR就去抄他们的实现。除了新的日期API,再比如JCache(JSR-107),你知道它抄了多少EhCache的东西么……

可能感兴趣的文章

07 May 07:03

Java 9发布在即,Oracle OpenJDK着手优化Unsafe类

by Monica Beckwith

java 9正式版预计在2017年2季度发布,目前大部分JEP已经基本成型。其中,最关键特性或许是JEP 261, 该JEP实现了java平台的模块系统, 具体说明可以参见JSR376。 模块系统依赖于JEP260(封装了大部分内部API),导致的结果是JEP193定义的多个句柄会暴露sun.misc.Unsafe类的功能。此前Info报道过致力于解决sun.misc.Unsafe句柄问题的团队,可能的解决方案细节亦可参见另一篇报道

Bug 8149159最近被提交到JDK Bug管理系统, 建议优化和清理Unsafe类, 包括将参数检查从本地代码移入Java(简化JIT)、 sun.misc.Unsafe类和jdk.internal.misc.Unsafe类的统一、 以及本地代码的整体清理。

2月18日,Oracle工程师Mikael Vidstedt向OpenJDK开发者社区提交了两个补丁(分别针对OpenJDK和OpenJDK HotSpot VM)

关于这两个补丁,Vidstedt总结道:

  • 避免代码重复,sun.misc.Unsafe将全部实现委托给jdk.internal.misc.Unsafe,这意味着java虚拟机(特别是unsafe.cpp)不再需要关心s.m.Unsafe的实现。
  • s.m.Unsafe的委托方法通常会被内联,但是为了避免性能下降的风险,仍然添加了@ForceInline注解
  • 更新文档,指明用户应该确保Unsafe类的参数正确
  • 参数检查从Unsage.cpp移入java,简化本地代码以及允许JIT进一步优化
  • 放松了特定参数的检查,比方说最近引入的U.copySwapMemory没有检查空指针。具体原因可以参考j.i.m.U.checkPointer的文档。除了U.copySwapMemory,现在Unsafe类方法也都没有对参数执行NULL检查
  • 在U.copySwapMemory类的基础上,对j.i.m.U.copyMemory增加了一个测试案例。请随时提醒我合并过来(本该如此)

在Vidstedt看来,Usage类的清理算是“相当激进”了,值得注意的地方有:

  • Unsafe_方法以及unsafe.cpp中的其他本地方法被申明为静态方法

  • 新增unsafe.hpp代码文件,文件中移入VM其他组件的一些方法。移除部分“extern”函数声明(不要过度使用extern)

  • 对于不怎么用到的UNSAFE_LEAF,移除警告性质的注释(没有必要,只是个VM_LEAF)
  • 一些简单的leaf方法使用UNSAFE_LEAF
  • UNSAFE_ENTRY/UNSAFE_END代码块新增大括号,帮助自动缩进
  • 移除未使用的Unsafe_<...>##140形式的函数和宏
  • 更新宏参数,与unsafe.cpp的宏定义保持一致
  • 更换带断言的参数检查,正如前面提及,这些检查移入了j.i.m.Unsafe,移除所有s.m.Unsafe相关的代码

查看英文原文:Oracle's OpenJDK Cleanup of "Unsafe" Implementation


感谢张龙对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

评价本文

专业度
风格
30 Apr 12:31

Spark性能优化指南——基础篇

by 美团点评技术团队

前言

在大数据计算领域,Spark已经成为了越来越流行、越来越受欢迎的计算平台之一。Spark的功能涵盖了大数据领域的离线批处理、SQL类处理、流式/实时计算、机器学习、图计算等各种不同类型的计算操作,应用范围与前景非常广泛。在美团•大众点评,已经有很多同学在各种项目中尝试使用Spark。大多数同学(包括笔者在内),最初开始尝试使用Spark的原因很简单,主要就是为了让大数据计算作业的执行速度更快、性能更高。

然而,通过Spark开发出高性能的大数据计算作业,并不是那么简单的。如果没有对Spark作业进行合理的调优,Spark作业的执行速度可能会很慢,这样就完全体现不出Spark作为一种快速大数据计算引擎的优势来。因此,想要用好Spark,就必须对其进行合理的性能优化。

Spark的性能调优实际上是由很多部分组成的,不是调节几个参数就可以立竿见影提升作业性能的。我们需要根据不同的业务场景以及数据情况,对Spark作业进行综合性的分析,然后进行多个方面的调节和优化,才能获得最佳性能。

笔者根据之前的Spark作业开发经验以及实践积累,总结出了一套Spark作业的性能优化方案。整套方案主要分为开发调优、资源调优、数据倾斜调优、shuffle调优几个部分。开发调优和资源调优是所有Spark作业都需要注意和遵循的一些基本原则,是高性能Spark作业的基础;数据倾斜调优,主要讲解了一套完整的用来解决Spark作业数据倾斜的解决方案;shuffle调优,面向的是对Spark的原理有较深层次掌握和研究的同学,主要讲解了如何对Spark作业的shuffle运行过程以及细节进行调优。

本文作为Spark性能优化指南的基础篇,主要讲解开发调优以及资源调优。

开发调优

调优概述

Spark性能优化的第一步,就是要在开发Spark作业的过程中注意和应用一些性能优化的基本原则。开发调优,就是要让大家了解以下一些Spark基本开发原则,包括:RDD lineage设计、算子的合理使用、特殊操作的优化等。在开发过程中,时时刻刻都应该注意以上原则,并将这些原则根据具体的业务以及实际的应用场景,灵活地运用到自己的Spark作业中。

原则一:避免创建重复的RDD

通常来说,我们在开发一个Spark作业时,首先是基于某个数据源(比如Hive表或HDFS文件)创建一个初始的RDD;接着对这个RDD执行某个算子操作,然后得到下一个RDD;以此类推,循环往复,直到计算出最终我们需要的结果。在这个过程中,多个RDD会通过不同的算子操作(比如map、reduce等)串起来,这个“RDD串”,就是RDD lineage,也就是“RDD的血缘关系链”。

我们在开发过程中要注意:对于同一份数据,只应该创建一个RDD,不能创建多个RDD来代表同一份数据。

一些Spark初学者在刚开始开发Spark作业时,或者是有经验的工程师在开发RDD lineage极其冗长的Spark作业时,可能会忘了自己之前对于某一份数据已经创建过一个RDD了,从而导致对于同一份数据,创建了多个RDD。这就意味着,我们的Spark作业会进行多次重复计算来创建多个代表相同数据的RDD,进而增加了作业的性能开销。

一个简单的例子

// 需要对名为“hello.txt”的HDFS文件进行一次map操作,再进行一次reduce操作。也就是说,需要对一份数据执行两次算子操作。

// 错误的做法:对于同一份数据执行多次算子操作时,创建多个RDD。
// 这里执行了两次textFile方法,针对同一个HDFS文件,创建了两个RDD出来,然后分别对每个RDD都执行了一个算子操作。
// 这种情况下,Spark需要从HDFS上两次加载hello.txt文件的内容,并创建两个单独的RDD;第二次加载HDFS文件以及创建RDD的性能开销,很明显是白白浪费掉的。
val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
rdd1.map(...)
val rdd2 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
rdd2.reduce(...)

// 正确的用法:对于一份数据执行多次算子操作时,只使用一个RDD。
// 这种写法很明显比上一种写法要好多了,因为我们对于同一份数据只创建了一个RDD,然后对这一个RDD执行了多次算子操作。
// 但是要注意到这里为止优化还没有结束,由于rdd1被执行了两次算子操作,第二次执行reduce操作的时候,还会再次从源头处重新计算一次rdd1的数据,因此还是会有重复计算的性能开销。
// 要彻底解决这个问题,必须结合“原则三:对多次使用的RDD进行持久化”,才能保证一个RDD被多次使用时只被计算一次。
val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt")
rdd1.map(...)
rdd1.reduce(...)

原则二:尽可能复用同一个RDD

除了要避免在开发过程中对一份完全相同的数据创建多个RDD之外,在对不同的数据执行算子操作时还要尽可能地复用一个RDD。比如说,有一个RDD的数据格式是key-value类型的,另一个是单value类型的,这两个RDD的value数据是完全一样的。那么此时我们可以只使用key-value类型的那个RDD,因为其中已经包含了另一个的数据。对于类似这种多个RDD的数据有重叠或者包含的情况,我们应该尽量复用一个RDD,这样可以尽可能地减少RDD的数量,从而尽可能减少算子执行的次数。

一个简单的例子

// 错误的做法。

// 有一个<Long, String>格式的RDD,即rdd1。
// 接着由于业务需要,对rdd1执行了一个map操作,创建了一个rdd2,而rdd2中的数据仅仅是rdd1中的value值而已,也就是说,rdd2是rdd1的子集。
JavaPairRDD<Long, String> rdd1 = ...
JavaRDD<String> rdd2 = rdd1.map(...)

// 分别对rdd1和rdd2执行了不同的算子操作。
rdd1.reduceByKey(...)
rdd2.map(...)

// 正确的做法。

// 上面这个case中,其实rdd1和rdd2的区别无非就是数据格式不同而已,rdd2的数据完全就是rdd1的子集而已,却创建了两个rdd,并对两个rdd都执行了一次算子操作。
// 此时会因为对rdd1执行map算子来创建rdd2,而多执行一次算子操作,进而增加性能开销。

// 其实在这种情况下完全可以复用同一个RDD。
// 我们可以使用rdd1,既做reduceByKey操作,也做map操作。
// 在进行第二个map操作时,只使用每个数据的tuple._2,也就是rdd1中的value值,即可。
JavaPairRDD<Long, String> rdd1 = ...
rdd1.reduceByKey(...)
rdd1.map(tuple._2...)

// 第二种方式相较于第一种方式而言,很明显减少了一次rdd2的计算开销。
// 但是到这里为止,优化还没有结束,对rdd1我们还是执行了两次算子操作,rdd1实际上还是会被计算两次。
// 因此还需要配合“原则三:对多次使用的RDD进行持久化”进行使用,才能保证一个RDD被多次使用时只被计算一次。

原则三:对多次使用的RDD进行持久化

当你在Spark代码中多次对一个RDD做了算子操作后,恭喜,你已经实现Spark作业第一步的优化了,也就是尽可能复用RDD。此时就该在这个基础之上,进行第二步优化了,也就是要保证对一个RDD执行多次算子操作时,这个RDD本身仅仅被计算一次。

Spark中对于一个RDD执行多次算子的默认原理是这样的:每次你对一个RDD执行一个算子操作时,都会重新从源头处计算一遍,计算出那个RDD来,然后再对这个RDD执行你的算子操作。这种方式的性能是很差的。

因此对于这种情况,我们的建议是:对多次使用的RDD进行持久化。此时Spark就会根据你的持久化策略,将RDD中的数据保存到内存或者磁盘中。以后每次对这个RDD进行算子操作时,都会直接从内存或磁盘中提取持久化的RDD数据,然后执行算子,而不会从源头处重新计算一遍这个RDD,再执行算子操作。

对多次使用的RDD进行持久化的代码示例

// 如果要对一个RDD进行持久化,只要对这个RDD调用cache()和persist()即可。

// 正确的做法。
// cache()方法表示:使用非序列化的方式将RDD中的数据全部尝试持久化到内存中。
// 此时再对rdd1执行两次算子操作时,只有在第一次执行map算子时,才会将这个rdd1从源头处计算一次。
// 第二次执行reduce算子时,就会直接从内存中提取数据进行计算,不会重复计算一个rdd。
val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt").cache()
rdd1.map(...)
rdd1.reduce(...)

// persist()方法表示:手动选择持久化级别,并使用指定的方式进行持久化。
// 比如说,StorageLevel.MEMORY_AND_DISK_SER表示,内存充足时优先持久化到内存中,内存不充足时持久化到磁盘文件中。
// 而且其中的_SER后缀表示,使用序列化的方式来保存RDD数据,此时RDD中的每个partition都会序列化成一个大的字节数组,然后再持久化到内存或磁盘中。
// 序列化的方式可以减少持久化的数据对内存/磁盘的占用量,进而避免内存被持久化数据占用过多,从而发生频繁GC。
val rdd1 = sc.textFile("hdfs://192.168.0.1:9000/hello.txt").persist(StorageLevel.MEMORY_AND_DISK_SER)
rdd1.map(...)
rdd1.reduce(...)

对于persist()方法而言,我们可以根据不同的业务场景选择不同的持久化级别。

Spark的持久化级别

持久化级别 含义解释
MEMORY_ONLY 使用未序列化的Java对象格式,将数据保存在内存中。如果内存不够存放所有的数据,则数据可能就不会进行持久化。那么下次对这个RDD执行算子操作时,那些没有被持久化的数据,需要从源头处重新计算一遍。这是默认的持久化策略,使用cache()方法时,实际就是使用的这种持久化策略。
MEMORY_AND_DISK 使用未序列化的Java对象格式,优先尝试将数据保存在内存中。如果内存不够存放所有的数据,会将数据写入磁盘文件中,下次对这个RDD执行算子时,持久化在磁盘文件中的数据会被读取出来使用。
MEMORY_ONLY_SER 基本含义同MEMORY_ONLY。唯一的区别是,会将RDD中的数据进行序列化,RDD的每个partition会被序列化成一个字节数组。这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC。
MEMORY_AND_DISK_SER 基本含义同MEMORY_AND_DISK。唯一的区别是,会将RDD中的数据进行序列化,RDD的每个partition会被序列化成一个字节数组。这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC。
DISK_ONLY 使用未序列化的Java对象格式,将数据全部写入磁盘文件中。
MEMORY_ONLY_2, MEMORY_AND_DISK_2, 等等. 对于上述任意一种持久化策略,如果加上后缀_2,代表的是将每个持久化的数据,都复制一份副本,并将副本保存到其他节点上。这种基于副本的持久化机制主要用于进行容错。假如某个节点挂掉,节点的内存或磁盘中的持久化数据丢失了,那么后续对RDD计算时还可以使用该数据在其他节点上的副本。如果没有副本的话,就只能将这些数据从源头处重新计算一遍了。

如何选择一种最合适的持久化策略

  • 默认情况下,性能最高的当然是MEMORY_ONLY,但前提是你的内存必须足够足够大,可以绰绰有余地存放下整个RDD的所有数据。因为不进行序列化与反序列化操作,就避免了这部分的性能开销;对这个RDD的后续算子操作,都是基于纯内存中的数据的操作,不需要从磁盘文件中读取数据,性能也很高;而且不需要复制一份数据副本,并远程传送到其他节点上。但是这里必须要注意的是,在实际的生产环境中,恐怕能够直接用这种策略的场景还是有限的,如果RDD中数据比较多时(比如几十亿),直接用这种持久化级别,会导致JVM的OOM内存溢出异常。

  • 如果使用MEMORY_ONLY级别时发生了内存溢出,那么建议尝试使用MEMORY_ONLY_SER级别。该级别会将RDD数据序列化后再保存在内存中,此时每个partition仅仅是一个字节数组而已,大大减少了对象数量,并降低了内存占用。这种级别比MEMORY_ONLY多出来的性能开销,主要就是序列化与反序列化的开销。但是后续算子可以基于纯内存进行操作,因此性能总体还是比较高的。此外,可能发生的问题同上,如果RDD中的数据量过多的话,还是可能会导致OOM内存溢出的异常。

  • 如果纯内存的级别都无法使用,那么建议使用MEMORY_AND_DISK_SER策略,而不是MEMORY_AND_DISK策略。因为既然到了这一步,就说明RDD的数据量很大,内存无法完全放下。序列化后的数据比较少,可以节省内存和磁盘的空间开销。同时该策略会优先尽量尝试将数据缓存在内存中,内存缓存不下才会写入磁盘。

  • 通常不建议使用DISK_ONLY和后缀为_2的级别:因为完全基于磁盘文件进行数据的读写,会导致性能急剧降低,有时还不如重新计算一次所有RDD。后缀为_2的级别,必须将所有数据都复制一份副本,并发送到其他节点上,数据复制以及网络传输会导致较大的性能开销,除非是要求作业的高可用性,否则不建议使用。

原则四:尽量避免使用shuffle类算子

如果有可能的话,要尽量避免使用shuffle类算子。因为Spark作业运行过程中,最消耗性能的地方就是shuffle过程。shuffle过程,简单来说,就是将分布在集群中多个节点上的同一个key,拉取到同一个节点上,进行聚合或join等操作。比如reduceByKey、join等算子,都会触发shuffle操作。

shuffle过程中,各个节点上的相同key都会先写入本地磁盘文件中,然后其他节点需要通过网络传输拉取各个节点上的磁盘文件中的相同key。而且相同key都拉取到同一个节点进行聚合操作时,还有可能会因为一个节点上处理的key过多,导致内存不够存放,进而溢写到磁盘文件中。因此在shuffle过程中,可能会发生大量的磁盘文件读写的IO操作,以及数据的网络传输操作。磁盘IO和网络数据传输也是shuffle性能较差的主要原因。

因此在我们的开发过程中,能避免则尽可能避免使用reduceByKey、join、distinct、repartition等会进行shuffle的算子,尽量使用map类的非shuffle算子。这样的话,没有shuffle操作或者仅有较少shuffle操作的Spark作业,可以大大减少性能开销。

Broadcast与map进行join代码示例

// 传统的join操作会导致shuffle操作。
// 因为两个RDD中,相同的key都需要通过网络拉取到一个节点上,由一个task进行join操作。
val rdd3 = rdd1.join(rdd2)

// Broadcast+map的join操作,不会导致shuffle操作。
// 使用Broadcast将一个数据量较小的RDD作为广播变量。
val rdd2Data = rdd2.collect()
val rdd2DataBroadcast = sc.broadcast(rdd2Data)

// 在rdd1.map算子中,可以从rdd2DataBroadcast中,获取rdd2的所有数据。
// 然后进行遍历,如果发现rdd2中某条数据的key与rdd1的当前数据的key是相同的,那么就判定可以进行join。
// 此时就可以根据自己需要的方式,将rdd1当前数据与rdd2中可以连接的数据,拼接在一起(String或Tuple)。
val rdd3 = rdd1.map(rdd2DataBroadcast...)

// 注意,以上操作,建议仅仅在rdd2的数据量比较少(比如几百M,或者一两G)的情况下使用。
// 因为每个Executor的内存中,都会驻留一份rdd2的全量数据。

原则五:使用map-side预聚合的shuffle操作

如果因为业务需要,一定要使用shuffle操作,无法用map类的算子来替代,那么尽量使用可以map-side预聚合的算子。

所谓的map-side预聚合,说的是在每个节点本地对相同的key进行一次聚合操作,类似于MapReduce中的本地combiner。map-side预聚合之后,每个节点本地就只会有一条相同的key,因为多条相同的key都被聚合起来了。其他节点在拉取所有节点上的相同key时,就会大大减少需要拉取的数据数量,从而也就减少了磁盘IO以及网络传输开销。通常来说,在可能的情况下,建议使用reduceByKey或者aggregateByKey算子来替代掉groupByKey算子。因为reduceByKey和aggregateByKey算子都会使用用户自定义的函数对每个节点本地的相同key进行预聚合。而groupByKey算子是不会进行预聚合的,全量的数据会在集群的各个节点之间分发和传输,性能相对来说比较差。

比如如下两幅图,就是典型的例子,分别基于reduceByKey和groupByKey进行单词计数。其中第一张图是groupByKey的原理图,可以看到,没有进行任何本地聚合时,所有数据都会在集群节点之间传输;第二张图是reduceByKey的原理图,可以看到,每个节点本地的相同key数据,都进行了预聚合,然后才传输到其他节点上进行全局聚合。

groupByKey实现wordcount原理

reduceByKey实现wordcount原理

原则六:使用高性能的算子

除了shuffle相关的算子有优化原则之外,其他的算子也都有着相应的优化原则。

使用reduceByKey/aggregateByKey替代groupByKey

详情见“原则五:使用map-side预聚合的shuffle操作”。

使用mapPartitions替代普通map

mapPartitions类的算子,一次函数调用会处理一个partition所有的数据,而不是一次函数调用处理一条,性能相对来说会高一些。但是有的时候,使用mapPartitions会出现OOM(内存溢出)的问题。因为单次函数调用就要处理掉一个partition所有的数据,如果内存不够,垃圾回收时是无法回收掉太多对象的,很可能出现OOM异常。所以使用这类操作时要慎重!

使用foreachPartitions替代foreach

原理类似于“使用mapPartitions替代map”,也是一次函数调用处理一个partition的所有数据,而不是一次函数调用处理一条数据。在实践中发现,foreachPartitions类的算子,对性能的提升还是很有帮助的。比如在foreach函数中,将RDD中所有数据写MySQL,那么如果是普通的foreach算子,就会一条数据一条数据地写,每次函数调用可能就会创建一个数据库连接,此时就势必会频繁地创建和销毁数据库连接,性能是非常低下;但是如果用foreachPartitions算子一次性处理一个partition的数据,那么对于每个partition,只要创建一个数据库连接即可,然后执行批量插入操作,此时性能是比较高的。实践中发现,对于1万条左右的数据量写MySQL,性能可以提升30%以上。

使用filter之后进行coalesce操作

通常对一个RDD执行filter算子过滤掉RDD中较多数据后(比如30%以上的数据),建议使用coalesce算子,手动减少RDD的partition数量,将RDD中的数据压缩到更少的partition中去。因为filter之后,RDD的每个partition中都会有很多数据被过滤掉,此时如果照常进行后续的计算,其实每个task处理的partition中的数据量并不是很多,有一点资源浪费,而且此时处理的task越多,可能速度反而越慢。因此用coalesce减少partition数量,将RDD中的数据压缩到更少的partition之后,只要使用更少的task即可处理完所有的partition。在某些场景下,对于性能的提升会有一定的帮助。

使用repartitionAndSortWithinPartitions替代repartition与sort类操作

repartitionAndSortWithinPartitions是Spark官网推荐的一个算子,官方建议,如果需要在repartition重分区之后,还要进行排序,建议直接使用repartitionAndSortWithinPartitions算子。因为该算子可以一边进行重分区的shuffle操作,一边进行排序。shuffle与sort两个操作同时进行,比先shuffle再sort来说,性能可能是要高的。

原则七:广播大变量

有时在开发过程中,会遇到需要在算子函数中使用外部变量的场景(尤其是大变量,比如100M以上的大集合),那么此时就应该使用Spark的广播(Broadcast)功能来提升性能。

在算子函数中使用到外部变量时,默认情况下,Spark会将该变量复制多个副本,通过网络传输到task中,此时每个task都有一个变量副本。如果变量本身比较大的话(比如100M,甚至1G),那么大量的变量副本在网络中传输的性能开销,以及在各个节点的Executor中占用过多内存导致的频繁GC,都会极大地影响性能。

因此对于上述情况,如果使用的外部变量比较大,建议使用Spark的广播功能,对该变量进行广播。广播后的变量,会保证每个Executor的内存中,只驻留一份变量副本,而Executor中的task执行时共享该Executor中的那份变量副本。这样的话,可以大大减少变量副本的数量,从而减少网络传输的性能开销,并减少对Executor内存的占用开销,降低GC的频率。

广播大变量的代码示例

// 以下代码在算子函数中,使用了外部的变量。
// 此时没有做任何特殊操作,每个task都会有一份list1的副本。
val list1 = ...
rdd1.map(list1...)

// 以下代码将list1封装成了Broadcast类型的广播变量。
// 在算子函数中,使用广播变量时,首先会判断当前task所在Executor内存中,是否有变量副本。
// 如果有则直接使用;如果没有则从Driver或者其他Executor节点上远程拉取一份放到本地Executor内存中。
// 每个Executor内存中,就只会驻留一份广播变量副本。
val list1 = ...
val list1Broadcast = sc.broadcast(list1)
rdd1.map(list1Broadcast...)

原则八:使用Kryo优化序列化性能

在Spark中,主要有三个地方涉及到了序列化:

  • 在算子函数中使用到外部变量时,该变量会被序列化后进行网络传输(见“原则七:广播大变量”中的讲解)。
  • 将自定义的类型作为RDD的泛型类型时(比如JavaRDD,Student是自定义类型),所有自定义类型对象,都会进行序列化。因此这种情况下,也要求自定义的类必须实现Serializable接口。
  • 使用可序列化的持久化策略时(比如MEMORY_ONLY_SER),Spark会将RDD中的每个partition都序列化成一个大的字节数组。

对于这三种出现序列化的地方,我们都可以通过使用Kryo序列化类库,来优化序列化和反序列化的性能。Spark默认使用的是Java的序列化机制,也就是ObjectOutputStream/ObjectInputStream API来进行序列化和反序列化。但是Spark同时支持使用Kryo序列化库,Kryo序列化类库的性能比Java序列化类库的性能要高很多。官方介绍,Kryo序列化机制比Java序列化机制,性能高10倍左右。Spark之所以默认没有使用Kryo作为序列化类库,是因为Kryo要求最好要注册所有需要进行序列化的自定义类型,因此对于开发者来说,这种方式比较麻烦。

以下是使用Kryo的代码示例,我们只要设置序列化类,再注册要序列化的自定义类型即可(比如算子函数中使用到的外部变量类型、作为RDD泛型类型的自定义类型等):

// 创建SparkConf对象。
val conf = new SparkConf().setMaster(...).setAppName(...)
// 设置序列化器为KryoSerializer。
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
// 注册要序列化的自定义类型。
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))

原则九:优化数据结构

Java中,有三种类型比较耗费内存:

  • 对象,每个Java对象都有对象头、引用等额外的信息,因此比较占用内存空间。
  • 字符串,每个字符串内部都有一个字符数组以及长度等额外信息。
  • 集合类型,比如HashMap、LinkedList等,因为集合类型内部通常会使用一些内部类来封装集合元素,比如Map.Entry。

因此Spark官方建议,在Spark编码实现中,特别是对于算子函数中的代码,尽量不要使用上述三种数据结构,尽量使用字符串替代对象,使用原始类型(比如Int、Long)替代字符串,使用数组替代集合类型,这样尽可能地减少内存占用,从而降低GC频率,提升性能。

但是在笔者的编码实践中发现,要做到该原则其实并不容易。因为我们同时要考虑到代码的可维护性,如果一个代码中,完全没有任何对象抽象,全部是字符串拼接的方式,那么对于后续的代码维护和修改,无疑是一场巨大的灾难。同理,如果所有操作都基于数组实现,而不使用HashMap、LinkedList等集合类型,那么对于我们的编码难度以及代码可维护性,也是一个极大的挑战。因此笔者建议,在可能以及合适的情况下,使用占用内存较少的数据结构,但是前提是要保证代码的可维护性。

资源调优

调优概述

在开发完Spark作业之后,就该为作业配置合适的资源了。Spark的资源参数,基本都可以在spark-submit命令中作为参数设置。很多Spark初学者,通常不知道该设置哪些必要的参数,以及如何设置这些参数,最后就只能胡乱设置,甚至压根儿不设置。资源参数设置的不合理,可能会导致没有充分利用集群资源,作业运行会极其缓慢;或者设置的资源过大,队列没有足够的资源来提供,进而导致各种异常。总之,无论是哪种情况,都会导致Spark作业的运行效率低下,甚至根本无法运行。因此我们必须对Spark作业的资源使用原理有一个清晰的认识,并知道在Spark作业运行过程中,有哪些资源参数是可以设置的,以及如何设置合适的参数值。

Spark作业基本运行原理

Spark基本运行原理

详细原理见上图。我们使用spark-submit提交一个Spark作业之后,这个作业就会启动一个对应的Driver进程。根据你使用的部署模式(deploy-mode)不同,Driver进程可能在本地启动,也可能在集群中某个工作节点上启动。Driver进程本身会根据我们设置的参数,占有一定数量的内存和CPU core。而Driver进程要做的第一件事情,就是向集群管理器(可以是Spark Standalone集群,也可以是其他的资源管理集群,美团•大众点评使用的是YARN作为资源管理集群)申请运行Spark作业需要使用的资源,这里的资源指的就是Executor进程。YARN集群管理器会根据我们为Spark作业设置的资源参数,在各个工作节点上,启动一定数量的Executor进程,每个Executor进程都占有一定数量的内存和CPU core。

在申请到了作业执行所需的资源之后,Driver进程就会开始调度和执行我们编写的作业代码了。Driver进程会将我们编写的Spark作业代码分拆为多个stage,每个stage执行一部分代码片段,并为每个stage创建一批task,然后将这些task分配到各个Executor进程中执行。task是最小的计算单元,负责执行一模一样的计算逻辑(也就是我们自己编写的某个代码片段),只是每个task处理的数据不同而已。一个stage的所有task都执行完毕之后,会在各个节点本地的磁盘文件中写入计算中间结果,然后Driver就会调度运行下一个stage。下一个stage的task的输入数据就是上一个stage输出的中间结果。如此循环往复,直到将我们自己编写的代码逻辑全部执行完,并且计算完所有的数据,得到我们想要的结果为止。

Spark是根据shuffle类算子来进行stage的划分。如果我们的代码中执行了某个shuffle类算子(比如reduceByKey、join等),那么就会在该算子处,划分出一个stage界限来。可以大致理解为,shuffle算子执行之前的代码会被划分为一个stage,shuffle算子执行以及之后的代码会被划分为下一个stage。因此一个stage刚开始执行的时候,它的每个task可能都会从上一个stage的task所在的节点,去通过网络传输拉取需要自己处理的所有key,然后对拉取到的所有相同的key使用我们自己编写的算子函数执行聚合操作(比如reduceByKey()算子接收的函数)。这个过程就是shuffle。

当我们在代码中执行了cache/persist等持久化操作时,根据我们选择的持久化级别的不同,每个task计算出来的数据也会保存到Executor进程的内存或者所在节点的磁盘文件中。

因此Executor的内存主要分为三块:第一块是让task执行我们自己编写的代码时使用,默认是占Executor总内存的20%;第二块是让task通过shuffle过程拉取了上一个stage的task的输出后,进行聚合等操作时使用,默认也是占Executor总内存的20%;第三块是让RDD持久化时使用,默认占Executor总内存的60%。

task的执行速度是跟每个Executor进程的CPU core数量有直接关系的。一个CPU core同一时间只能执行一个线程。而每个Executor进程上分配到的多个task,都是以每个task一条线程的方式,多线程并发运行的。如果CPU core数量比较充足,而且分配到的task数量比较合理,那么通常来说,可以比较快速和高效地执行完这些task线程。

以上就是Spark作业的基本运行原理的说明,大家可以结合上图来理解。理解作业基本原理,是我们进行资源参数调优的基本前提。

资源参数调优

了解完了Spark作业运行的基本原理之后,对资源相关的参数就容易理解了。所谓的Spark资源参数调优,其实主要就是对Spark运行过程中各个使用资源的地方,通过调节各种参数,来优化资源使用的效率,从而提升Spark作业的执行性能。以下参数就是Spark中主要的资源参数,每个参数都对应着作业运行原理中的某个部分,我们同时也给出了一个调优的参考值。

num-executors

  • 参数说明:该参数用于设置Spark作业总共要用多少个Executor进程来执行。Driver在向YARN集群管理器申请资源时,YARN集群管理器会尽可能按照你的设置来在集群的各个工作节点上,启动相应数量的Executor进程。这个参数非常之重要,如果不设置的话,默认只会给你启动少量的Executor进程,此时你的Spark作业的运行速度是非常慢的。
  • 参数调优建议:每个Spark作业的运行一般设置50~100个左右的Executor进程比较合适,设置太少或太多的Executor进程都不好。设置的太少,无法充分利用集群资源;设置的太多的话,大部分队列可能无法给予充分的资源。

executor-memory

  • 参数说明:该参数用于设置每个Executor进程的内存。Executor内存的大小,很多时候直接决定了Spark作业的性能,而且跟常见的JVM OOM异常,也有直接的关联。
  • 参数调优建议:每个Executor进程的内存设置4G~8G较为合适。但是这只是一个参考值,具体的设置还是得根据不同部门的资源队列来定。可以看看自己团队的资源队列的最大内存限制是多少,num-executors乘以executor-memory,就代表了你的Spark作业申请到的总内存量(也就是所有Executor进程的内存总和),这个量是不能超过队列的最大内存量的。此外,如果你是跟团队里其他人共享这个资源队列,那么申请的总内存量最好不要超过资源队列最大总内存的1/3~1/2,避免你自己的Spark作业占用了队列所有的资源,导致别的同学的作业无法运行。

executor-cores

  • 参数说明:该参数用于设置每个Executor进程的CPU core数量。这个参数决定了每个Executor进程并行执行task线程的能力。因为每个CPU core同一时间只能执行一个task线程,因此每个Executor进程的CPU core数量越多,越能够快速地执行完分配给自己的所有task线程。
  • 参数调优建议:Executor的CPU core数量设置为2~4个较为合适。同样得根据不同部门的资源队列来定,可以看看自己的资源队列的最大CPU core限制是多少,再依据设置的Executor数量,来决定每个Executor进程可以分配到几个CPU core。同样建议,如果是跟他人共享这个队列,那么num-executors * executor-cores不要超过队列总CPU core的1/3~1/2左右比较合适,也是避免影响其他同学的作业运行。

driver-memory

  • 参数说明:该参数用于设置Driver进程的内存。
  • 参数调优建议:Driver的内存通常来说不设置,或者设置1G左右应该就够了。唯一需要注意的一点是,如果需要使用collect算子将RDD的数据全部拉取到Driver上进行处理,那么必须确保Driver的内存足够大,否则会出现OOM内存溢出的问题。

spark.default.parallelism

  • 参数说明:该参数用于设置每个stage的默认task数量。这个参数极为重要,如果不设置可能会直接影响你的Spark作业性能。
  • 参数调优建议:Spark作业的默认task数量为500~1000个较为合适。很多同学常犯的一个错误就是不去设置这个参数,那么此时就会导致Spark自己根据底层HDFS的block数量来设置task的数量,默认是一个HDFS block对应一个task。通常来说,Spark默认设置的数量是偏少的(比如就几十个task),如果task数量偏少的话,就会导致你前面设置好的Executor的参数都前功尽弃。试想一下,无论你的Executor进程有多少个,内存和CPU有多大,但是task只有1个或者10个,那么90%的Executor进程可能根本就没有task执行,也就是白白浪费了资源!因此Spark官网建议的设置原则是,设置该参数为num-executors * executor-cores的2~3倍较为合适,比如Executor的总CPU core数量为300个,那么设置1000个task是可以的,此时可以充分地利用Spark集群的资源。

spark.storage.memoryFraction

  • 参数说明:该参数用于设置RDD持久化数据在Executor内存中能占的比例,默认是0.6。也就是说,默认Executor 60%的内存,可以用来保存持久化的RDD数据。根据你选择的不同的持久化策略,如果内存不够时,可能数据就不会持久化,或者数据会写入磁盘。
  • 参数调优建议:如果Spark作业中,有较多的RDD持久化操作,该参数的值可以适当提高一些,保证持久化的数据能够容纳在内存中。避免内存不够缓存所有的数据,导致数据只能写入磁盘中,降低了性能。但是如果Spark作业中的shuffle类操作比较多,而持久化操作比较少,那么这个参数的值适当降低一些比较合适。此外,如果发现作业由于频繁的gc导致运行缓慢(通过spark web ui可以观察到作业的gc耗时),意味着task执行用户代码的内存不够用,那么同样建议调低这个参数的值。

spark.shuffle.memoryFraction

  • 参数说明:该参数用于设置shuffle过程中一个task拉取到上个stage的task的输出后,进行聚合操作时能够使用的Executor内存的比例,默认是0.2。也就是说,Executor默认只有20%的内存用来进行该操作。shuffle操作在进行聚合时,如果发现使用的内存超出了这个20%的限制,那么多余的数据就会溢写到磁盘文件中去,此时就会极大地降低性能。
  • 参数调优建议:如果Spark作业中的RDD持久化操作较少,shuffle操作较多时,建议降低持久化操作的内存占比,提高shuffle操作的内存占比比例,避免shuffle过程中数据过多时内存不够用,必须溢写到磁盘上,降低了性能。此外,如果发现作业由于频繁的gc导致运行缓慢,意味着task执行用户代码的内存不够用,那么同样建议调低这个参数的值。

资源参数的调优,没有一个固定的值,需要同学们根据自己的实际情况(包括Spark作业中的shuffle操作数量、RDD持久化操作数量以及spark web ui中显示的作业gc情况),同时参考本篇文章中给出的原理以及调优建议,合理地设置上述参数。

资源参数参考示例

以下是一份spark-submit命令的示例,大家可以参考一下,并根据自己的实际情况进行调节:

./bin/spark-submit \
  --master yarn-cluster \
  --num-executors 100 \
  --executor-memory 6G \
  --executor-cores 4 \
  --driver-memory 1G \
  --conf spark.default.parallelism=1000 \
  --conf spark.storage.memoryFraction=0.5 \
  --conf spark.shuffle.memoryFraction=0.3 \

写在最后的话

根据实践经验来看,大部分Spark作业经过本次基础篇所讲解的开发调优与资源调优之后,一般都能以较高的性能运行了,足以满足我们的需求。但是在不同的生产环境和项目背景下,可能会遇到其他更加棘手的问题(比如各种数据倾斜),也可能会遇到更高的性能要求。为了应对这些挑战,需要使用更高级的技巧来处理这类问题。在后续的《Spark性能优化指南——高级篇》中,我们会详细讲解数据倾斜调优以及Shuffle调优。

25 Apr 10:53

巅峰对决 - 框架的性能比较

by 鸟窝

天下武功,无坚不摧,无快不破

这句话也可以应用在软件开发上,"无快不破"强调的是软件的性能。我陆陆续续写了多篇各种框架的文章,也在github上开了多个性能比较的开源项目,本文做一个汇总,以备将来的查找。

  1. 最快的web服务器
  2. 最快的并发框架
  3. 最快的RPC服务器
  4. 最快的websocket框架
  5. 最快的RESTful框架
  6. 最快的Go序列化框架
  7. 最快的Go web框架
  8. 最快的Java序列化框架
22 Apr 21:59

JVM调优总结(4):分代垃圾回收

by liuchi1993

为什么要分代

分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。

在Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如Http请求中的Session对象、线程、Socket连接,这类对象跟业务直接挂钩,因此生命周期比较长。但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。

试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。

如何分代

如图所示:

虚拟机中的共划分为三个代:年轻代(Young Generation)、年老点(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。

年轻代:

所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

年老代:

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

持久代:

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。

什么情况下触发垃圾回收

由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GCFull GC

Scavenge GC

一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

Full GC

对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

· 年老代(Tenured)被写满

· 持久代(Perm)被写满

· System.gc()被显示调用

·上一次GC之后Heap的各域分配策略动态变化

分代垃圾回收流程示意

选择合适的垃圾收集算法

串行收集器

用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以此收集器适合单处理器机器。当然,此收集器也可以用在小数据量(100M左右)情况下的多处理器机器上。可以使用-XX:+UseSerialGC打开。

并行收集器

对年轻代进行并行垃圾回收,因此可以减少垃圾回收时间。一般在多线程多处理器机器上使用。使用-XX:+UseParallelGC.打开。并行收集器在J2SE5.0第六6更新上引入,在Java SE6.0中进行了增强–可以对年老代进行并行收集。如果年老代不使用并发收集的话,默认是使用单线程进行垃圾回收,因此会制约扩展能力。使用-XX:+UseParallelOldGC打开。

使用-XX:ParallelGCThreads=<N>设置并行垃圾回收的线程数。此值可以设置与机器处理器数量相等。

此收集器可以进行如下配置:

最大垃圾回收暂停:指定垃圾回收时的最长暂停时间,通过-XX:MaxGCPauseMillis=<N>指定。<N>为毫秒.如果指定了此值的话,堆大小和垃圾回收相关参数会进行调整以达到指定值。设定此值可能会减少应用的吞吐量。

吞吐量:吞吐量为垃圾回收时间与非垃圾回收时间的比值,通过-XX:GCTimeRatio=<N>来设定,公式为1/(1+N)。例如,-XX:GCTimeRatio=19时,表示5%的时间用于垃圾回收。默认情况为99,即1%的时间用于垃圾回收。 

并发收集器

可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。使用-XX:+UseConcMarkSweepGC打开。

并发收集器主要减少年老代的暂停时间,他在应用不停止的情况下使用独立的垃圾回收线程,跟踪可达对象。在每个年老代垃圾回收周期中,在收集初期并发收集器 会对整个应用进行简短的暂停,在收集中还会再暂停一次。第二次暂停会比第一次稍长,在此过程中多个线程同时进行垃圾回收工作。

并发收集器使用处理器换来短暂的停顿时间。在一个N个处理器的系统上,并发收集部分使用K/N个可用处理器进行回收,一般情况下1<=K<=N/4。

在只有一个处理器的主机上使用并发收集器,设置为incremental mode模式也可获得较短的停顿时间。

浮动垃圾:由于在应用运行的同时进行垃圾回收,所以有些垃圾可能在垃圾回收进行完成时产生,这样就造成了“Floating Garbage”,这些垃圾需要在下次垃圾回收周期时才能回收掉。所以,并发收集器一般需要20%的预留空间用于这些浮动垃圾。

Concurrent Mode Failure:并发收集器在应用运行时进行收集,所以需要保证堆在垃圾回收的这段时间有足够的空间供程序使用,否则,垃圾回收还未完成,堆空间先满了。这种情况下将会发生“并发模式失败”,此时整个应用将会暂停,进行垃圾回收。

启动并发收集器:因为并发收集在应用运行时进行收集,所以必须保证收集完成之前有足够的内存空间供程序使用,否则会出现“Concurrent Mode Failure”。通过设置-XX:CMSInitiatingOccupancyFraction=<N>指定还有多少剩余堆时开始执行并发收集

小结

串行处理器:

–适用情况:数据量比较小(100M左右);单处理器下并且对响应时间无要求的应用。
–缺点:只能用于小型应用

并行处理器:

–适用情况:“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。
–缺点:垃圾收集过程中应用响应时间可能加长

并发处理器:

–适用情况:“对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用。举例:Web服务器/应用服务器、电信交换、集成开发环境。

本系列:

相关文章

22 Apr 21:57

JVM调优总结(8):反思

by liuchi1993

垃圾回收的悖论

所谓“成也萧何败萧何”。Java的垃圾回收确实带来了很多好处,为开发带来了便利。但是在一些高性能、高并发的情况下,垃圾回收确成为了制约Java应用的瓶颈。目前JDK的垃圾回收算法,始终无法解决垃圾回收时的暂停问题,因为这个暂停严重影响了程序的相应时间,造成拥塞或堆积。这也是后续JDK增加G1算法的一个重要原因。

当然,上面是从技术角度出发解决垃圾回收带来的问题,但是从系统设计方面我们就需要问一下了:

    我们需要分配如此大的内存空间给应用吗?

    我们是否能够通过有效使用内存而不是通过扩大内存的方式来设计我们的系统呢?     

我们的内存中都放了什么

内存中需要放什么呢?个人认为,内存中需要放的是你的应用需要在不久的将来再次用到到的东西。想想看,如果你在将来不用这些东西,何必放内存呢?放文件、数据库不是更好?这些东西一般包括:

1. 系统运行时业务相关的数据。比如web应用中的session、即时消息的session等。这些数据一般在一个用户访问周期或者一个使用过程中都需要存在。

2. 缓存。缓存就比较多了,你所要快速访问的都可以放这里面。其实上面的业务数据也可以理解为一种缓存。

3.  线程。

因此,我们是不是可以这么认为,如果我们不把业务数据和缓存放在JVM中,或者把他们独立出来,那么Java应用使用时所需的内存将会大大减少,同时垃圾回收时间也会相应减少。

我认为这是可能的。

解决之道

数据库、文件系统

把所有数据都放入数据库或者文件系统,这是一种最为简单的方式。在这种方式下,Java应用的内存基本上等于处理一次峰值并发请求所需的内存。数据的获取都在每次请求时从数据库和文件系统中获取。也可以理解为,一次业务访问以后,所有对象都可以进行回收了。

这是一种内存使用最有效的方式,但是从应用角度来说,这种方式很低效。

内存-硬盘映射

上面的问题是因为我们使用了文件系统带来了低效。但是如果我们不是读写硬盘,而是写内存的话效率将会提高很多。

数据库和文件系统都是实实在在进行了持久化,但是当我们并不需要这样持久化的时候,我们可以做一些变通——把内存当硬盘使。

内存-硬盘映射很好很强大,既用了缓存又对Java应用的内存使用又没有影响。Java应用还是Java应用,他只知道读写的还是文件,但是实际上是内存。

这种方式兼得的Java应用与缓存两方面的好处。memcached的广泛使用也正是这一类的代表。

同一机器部署多个JVM

这也是一种很好的方式,可以分为纵拆和横拆。纵拆可以理解为把Java应用划分为不同模块,各个模块使用一个独立的Java进程。而横拆则是同样功能的应用部署多个JVM。

通过部署多个JVM,可以把每个JVM的内存控制一个垃圾回收可以忍受的范围内即可。但是这相当于进行了分布式的处理,其额外带来的复杂性也是需要评估的。另外,也有支持分布式的这种JVM可以考虑,不要要钱哦:)

程序控制的对象生命周期

这种方式是理想当中的方式,目前的虚拟机还没有,纯属假设。即:考虑由编程方式配置哪些对象在垃圾收集过程中可以直接跳过,减少垃圾回收线程遍历标记的时间。

这种方式相当于在编程的时候告诉虚拟机某些对象你可以在*时间后在进行收集或者由代码标识可以收集了(类似C、C++),在这之前你即便去遍历他也是没有效果的,他肯定是还在被引用的。

这种方式如果JVM可以实现,个人认为将是一个飞跃,Java即有了垃圾回收的优势,又有了C、C++对内存的可控性。

线程分配

Java的阻塞式的线程模型基本上可以抛弃了,目前成熟的NIO框架也比较多了。阻塞式IO带来的问题是线程数量的线性增长,而NIO则可以转换成为常数线程。因此,对于服务端的应用而言,NIO还是唯一选择。不过,JDK7中为我们带来的AIO是否能让人眼前一亮呢?我们拭目以待。

其他的JDK

本文说的都是Sun的JDK,目前常见的JDK还有JRocket和IBM的JDK。其中JRocket在IO方面比Sun的高很多,不过Sun JDK6.0以后提高也很大。而且JRocket在垃圾回收方面,也具有优势,其可设置垃圾回收的最大暂停时间也是很吸引人的。不过,系统Sun的G1实现以后,在这方面会有一个质的飞跃。

参考资料

能整理出上面一些东西,也是因为站在巨人的肩上。下面是一些参考资料,供大家学习,大家有更好的,可以继续完善:)

· Java 理论与实践: 垃圾收集简史

· Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning

· Improving Java Application Performance and Scalability by Reducing Garbage Collection Times and Sizing Memory Using JDK 1.4.1

· Hotspot memory management whitepaper

· Java Tuning White Paper

· Diagnosing a Garbage Collection problem

· Java HotSpot VM Options

· A Collection of JVM Options

· Garbage-First Garbage Collection

· Frequently Asked Questions about Garbage Collection in the HotspotTM JavaTM Virtual Machine

· JProfiler试用手记

· Java6 JVM参数选项大全

· 《深入Java虚拟机》。虽然过去了很多年,但这本书依旧是经典。

这里是本系列的最后一篇了,很高兴大家能够喜欢这系列的文章。期间也提了很多问题,其中有些是我之前没有想到的或者考虑欠妥的,感谢提出这些问题的朋友,我也学到的不少东西。

本系列:

相关文章

22 Apr 00:38

《Spark 官方文档》Spark调优

by 邓 林
spark-1.6.0 原文地址

Spark调优

由于大部分Spark计算都是在内存中完成的,所以Spark程序的瓶颈可能由集群中任意一种资源导致,如:CPU、网络带宽、或者内存等。最常见的情况是,数据能装进内存,而瓶颈是网络带宽;当然,有时候我们也需要做一些优化调整来减少内存占用,例如将RDD以序列化格式保存(storing RDDs in serialized form)。本文将主要涵盖两个主题:1.数据序列化(这对于优化网络性能极为重要);2.减少内存占用以及内存调优。同时,我们也会提及其他几个比较小的主题。

 

数据序列化

序列化在任何一种分布式应用性能优化时都扮演几位重要的角色。如果序列化格式序列化过程缓慢,或者需要占用字节很多,都会大大拖慢整体的计算效率。通常,序列化都是Spark应用优化时首先需要关注的地方。Spark着眼于要达到便利性(允许你在计算过程中使用任何Java类型)和性能的一个平衡。Spark主要提供了两个序列化库:

  • Java serialization: 默认情况,Spark使用Java自带的ObjectOutputStream 框架来序列化对象,这样任何实现了 java.io.Serializable 接口的对象,都能被序列化。同时,你还可以通过扩展 java.io.Externalizable 来控制序列化性能。Java序列化很灵活但性能较差,同时序列化后占用的字节数也较多。
  • Kryo serialization: Spark还可以使用Kryo 库(版本2)提供更高效的序列化格式。Kryo的序列化速度和字节占用都比Java序列化好很多(通常是10倍左右),但Kryo不支持所有实现了Serializable 接口的类型,它需要你在程序中 register 需要序列化的类型,以得到最佳性能。

要切换到使用 Kryo,你可以在 SparkConf 初始化的时候调用 conf.set(“spark.serializer”, “org.apache.spark.serializer.KryoSerializer”)。这个设置不仅控制各个worker节点之间的混洗数据序列化格式,同时还控制RDD存到磁盘上的序列化格式。目前,Kryo不是默认的序列化格式,因为它需要你在使用前注册需要序列化的类型,不过我们还是建议在对网络敏感的应用场景下使用Kryo。

Spark对一些常用的Scala核心类型(包括在Twitter chill 库的AllScalaRegistrar中)自动使用Kryo序列化格式。

如果你的自定义类型需要使用Kryo序列化,可以用 registerKryoClasses 方法先注册:

val conf = new SparkConf().setMaster(...).setAppName(...)
conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2]))
val sc = new SparkContext(conf)

Kryo的文档(Kryo documentation )中有详细描述了更多的高级选项,如:自定义序列化代码等。

如果你的对象很大,你可能需要增大 spark.kryoserializer.buffer 配置项(config)。其值至少需要大于最大对象的序列化长度。

最后,如果你不注册需要序列化的自定义类型,Kryo也能工作,不过每一个对象实例的序列化结果都会包含一份完整的类名,这有点浪费空间。

内存调优

内存占用调优主要需要考虑3点:1.数据占用的总内存(你多半会希望整个数据集都能装进内存吧);2.访问数据集中每个对象的开销;3.垃圾回收的开销(如果你的数据集中对象周转速度很快的话)。

一般,Java对象的访问时很快的,但同时Java对象会比原始数据(仅包含各个字段值)占用的空间多2~5倍。主要原因有:

  • 每个Java对象都有一个对象头(object header),对象头大约占用16字节,其中包含像其对应class的指针这样的信息。对于一些包含较少数据的对象(比如只包含一个Int字段),这个对象头可能比对象数据本身还大。
  • Java字符串(String)有大约40子节点额外开销(Java String以Char数据的形式保存原始数据,所以需要一些额外的字段,如数组长度等),并且每个字符都以两字节的UTF-16编码在内部保存。因此,10个字符的String很容易就占了60字节。
  • 一些常见的集合类,如 HashMap、LinkedList,使用的是链表类数据结构,因此它们对每项数据都有一个包装器。这些包装器对象不仅其自身就有“对象头”,同时还有指向下一个包装器对象的链表指针(通常为8字节)。
  • 原始类型的集合通常也是以“装箱”的形式包装成对象(如:java.lang.Integer)。

本节只是Spark内存管理的一个概要,下面我们会更详细地讨论各种Spark内存调优的具体策略。特别地,我们会讨论如何评估数据的内存使用量,以及如何改进 – 要么改变你的数据结构,要么以某种序列化格式存储数据。最后,我们还会讨论如何调整Spark的缓存大小,以及如何调优Java的垃圾回收器。

内存管理概览

Spark中内存主要用于两类目的:执行计算和数据存储。执行计算的内存主要用于混洗(Shuffle)、关联(join)、排序(sort)以及聚合(aggregation),而数据存储的内存主要用于缓存和集群内部数据传播。Spark中执行计算和数据存储都是共享同一个内存区域(M)。如果执行计算没有占用内存,那么数据存储可以申请占用所有可用的内存,反之亦然。执行计算可能会抢占数据存储使用的内存,并将存储于内存的数据逐出内存,直到数据存储占用的内存比例降低到一个指定的比例(R)。换句话说,R是M基础上的一个子区域,这个区域的内存数据永远不会被逐出内存。然而,数据存储不会抢占执行计算的内存(否则实现太复杂了)。

这样设计主要有这么几个需要考虑的点。首先,不需要缓存数据的应用可以把整个空间用来执行计算,从而避免频繁地把数据吐到磁盘上。其次,需要缓存数据的应用能够有一个数据存储比例(R)的最低保证,也避免这部分缓存数据被全部逐出内存。最后,这个实现方式能够在默认情况下,为大多数使用场景提供合理的性能,而不需要专家级用户来设置内存使用如何划分。

虽然有两个内存划分相关的配置参数,但一般来说,用户不需要设置,因为默认值已经能够适用于绝大部分的使用场景:

  • spark.memory.fraction 表示上面M的大小,其值为相对于JVM堆内存的比例(默认0.75)。剩余的25%是为其他用户数据结构、Spark内部元数据以及避免OOM错误的安全预留空间(大量稀疏数据和异常大的数据记录)。
  • spark.memory.storageFraction 表示上面R的大小,其值为相对于M的一个比例(默认0.5)。R是M中专门用于缓存数据块,且这部分数据块永远不会因执行计算任务而逐出内存。

评估内存消耗

确定一个数据集占用内存总量最好的办法就是,创建一个RDD,并缓存到内存中,然后再到web UI上”Storage”页面查看。页面上会展示这个RDD总共占用了多少内存。

要评估一个特定对象的内存占用量,可以用 SizeEstimator.estimate 方法。这个方法对试验哪种数据结构能够裁剪内存占用量比较有用,同时,也可以帮助用户了解广播变量在每个执行器堆上占用的内存量。

数据结构调优

减少内存消耗的首要方法就是避免过多的Java封装(减少对象头和额外辅助字段),比如基于指针的数据结构和包装对象等。以下有几条建议:

  1. 设计数据结构的时候,优先使用对象数组和原生类型,减少对复杂集合类型(如:HashMap)的使用。fastutil 提供了一些很方便的原声类型集合,同时兼容Java标准库。
  2. 尽可能避免嵌套大量的小对象和指针。
  3. 对应键值应尽量使用数值型或枚举型,而不是字符串型。
  4. 如果内存小于32GB,可以设置JVM标志参数 -XX:+UseCompressdOops 将指针设为4字节而不是8字节。你可以在  spark-env.sh 中设置这个参数。

序列化RDD存储

如果经过上面的调整后,存储的数据对象还是太大,那么你可以试试将这些对象以序列化格式存储,所需要做的只是通过 RDD persistence API 设置好存储级别,如:MEMORY_ONLY_SER。Spark会将RDD的每个分区以一个巨大的字节数组形式存储起来。以序列化格式存储的唯一缺点就是访问数据会变慢一点,因为Spark需要反序列化每个被访问的对象。如果你需要序列化缓存数据,我们强烈建议你使用Kryo(using Kryo),和Java序列化相比,Kryo能大大减少序列化对象占用的空间(当然也比原始Java对象小很多)。

垃圾回收调优

JVM的垃圾回收在某些情况下可能会造成瓶颈,比如,你的RDD存储经常需要“换入换出”(新RDD抢占了老RDD内存,不过如果你的程序没有这种情况的话那JVM垃圾回收一般不是问题,比如,你的RDD只是载入一次,后续只是在这一个RDD上做操作)。当Java需要把老对象逐出内存的时候,JVM需要跟踪所有的Java对象,并找出那些对象已经没有用了。概括起来就是,垃圾回收的开销和对象个数成正比,所以减少对象的个数(比如用 Int数组取代 LinkedList),就能大大减少垃圾回收的开销。当然,一个更好的方法就如前面所说的,以序列化形式存储数据,这时每个RDD分区都只包含有一个对象了(一个巨大的字节数组)。在尝试其他技术方案前,首先可以试试用序列化RDD的方式(serialized caching)评估一下GC是不是一个瓶颈。

如果你的作业中各个任务需要的工作内存和节点上存储的RDD缓存占用的内存产生冲突,那么GC很可能会出现问题。下面我们将讨论一下如何控制好RDD缓存使用的内存空间,以减少这种冲突。

衡量GC的影响

GC调优的第一步是统计一下,垃圾回收启动的频率以及GC所使用的总时间。给JVM设置一下这几个参数(参考Spark配置指南 –  configuration guide,查看Spark作业中的Java选项参数):-verbose:gc -XX:+PrintGCDetails,就可以在后续Spark作业的worker日志中看到每次GC花费的时间。注意,这些日志是在集群worker节点上(在各节点的工作目录下stdout文件中),而不是你的驱动器所在节点。

高级GC调优

为了进一步调优GC,我们就需要对JVM内存管理有一个基本的了解:

  • Java堆内存可分配的空间有两个区域:新生代(Young generation)和老生代(Old generation)。新生代用以保存生存周期短的对象,而老生代则是保存生存周期长的对象。
  • 新生代区域被进一步划分为三个子区域:Eden,Survivor1,Survivor2。
  • 简要描述一下垃圾回收的过程:如果Eden区满了,则启动一轮minor GC回收Eden中的对象,生存下来(没有被回收掉)的Eden中的对象和Survivor1区中的对象一并复制到Survivor2中。两个Survivor区域是互相切换使用的(就是说,下次从Eden和Survivor2中复制到Survivor1中)。如果某个对象的年龄(每次GC所有生存下来的对象长一岁)超过某个阈值,或者Survivor2(下次是Survivor1)区域满了,则将对象移到老生代(Old区)。最终如果老生代也满了,就会启动full GC。

Spark GC调优的目标就是确保老生代(Old generation )只保存长生命周期RDD,而同时新生代(Young generation )的空间又能足够保存短生命周期的对象。这样就能在任务执行期间,避免启动full GC。以下是GC调优的主要步骤:

  • 从GC的统计日志中观察GC是否启动太多。如果某个任务结束前,多次启动了full GC,则意味着用以执行该任务的内存不够。
  • 如果GC统计信息中显示,老生代内存空间已经接近存满,可以通过降低 spark.memory.storageFraction 来减少RDD缓存占用的内存;减少缓存对象总比任务执行缓慢要强!
  • 如果major GC比较少,但minor GC很多的话,可以多分配一些Eden内存。你可以把Eden的大小设为高于各个任务执行所需的工作内存。如果要把Eden大小设为E,则可以这样设置新生代区域大小:-Xmn=4/3*E。(放大4/3倍,主要是为了给Survivor区域保留空间)
  • 举例来说,如果你的任务会从HDFS上读取数据,那么单个任务的内存需求可以用其所读取的HDFS数据块的大小来评估。需要特别注意的是,解压后的HDFS块是解压前的2~3倍大。所以如果我们希望保留3~4个任务并行的工作内存,并且HDFS块大小为64MB,那么可以评估Eden的大小应该设为 4*3*64MB。
  • 最后,再观察一下垃圾回收的启动频率和总耗时有没有什么变化。

我们的很多经验表明,GC调优的效果和你的程序代码以及可用的总内存相关。网上还有不少调优的选项说明(many more tuning options),但总体来说,就是控制好full GC的启动频率,就能有效减少垃圾回收开销。

其他注意事项

并行度

一般来说集群并不会满负荷运转,除非你吧每个操作的并行度都设得足够大。Spark会自动根据对应的输入文件大小来设置“map”类算子的并行度(当然你可以通过一个SparkContext.textFile等函数的可选参数来控制并行度),而对于想 groupByKey 或reduceByKey这类 “reduce” 算子,会使用其各父RDD分区数的最大值。你可以将并行度作为构建RDD第二个参数(参考spark.PairRDDFunctions ),或者设置 spark.default.parallelism 这个默认值。一般来说,评估并行度的时候,我们建议2~3个任务共享一个CPU。

Reduce任务的内存占用

如果RDD比内存要大,有时候你可能收到一个OutOfMemoryError,但其实这是因为你的任务集中的某个任务太大了,如reduce任务groupByKey。Spark的混洗(Shuffle)算子(sortByKey,groupByKey,reduceByKey,join等)会在每个任务中构建一个哈希表,以便在任务中对数据分组,这个哈希表有时会很大。最简单的修复办法就是增大并行度,以减小单个任务的输入集。Spark对于200ms以内的短任务支持非常好,因为Spark可以跨任务复用执行器JVM,任务的启动开销很小,因此把并行度增加到比集群中总CPU核数还多是没有任何问题的。

广播大变量

使用SparkContext中的广播变量相关功能(broadcast functionality)能大大减少每个任务本身序列化的大小,以及集群中启动作业的开销。如果你的Spark任务正在使用驱动器(driver)程序中定义的巨大对象(比如:静态查询表),请考虑使用广播变量替代之。Spark会在master上将各个任务的序列化后大小打印出来,所以你可以检查一下各个任务是否过大;通常来说,大于20KB的任务就值得优化一下。

数据本地性

数据本地性对Spark作业往往会有较大的影响。如果代码和其所操作的数据在统一节点上,那么计算速度肯定会更快一些。但如果二者不在一起,那必然需要挪动其中之一。一般来说,挪动序列化好的代码肯定比挪动一大堆数据要快。Spark就是基于这个一般性原则来构建数据本地性的调度。

数据本地性是指代码和其所处理的数据的距离。基于数据当前的位置,数据本地性可以划分成以下几个层次(按从近到远排序):

  • PROCESS_LOCAL 数据和运行的代码处于同一个JVM进程内。
  • NODE_LOCAL 数据和代码处于同一节点。例如,数据处于HDFS上某个节点,而对应的执行器(executor)也在同一个机器节点上。这会比PROCESS_LOCAL稍微慢一些,因为数据需要跨进程传递。
  • NO_PREF 数据在任何地方处理都一样,没有本地性偏好。
  • RACK_LOCAL 数据和代码处于同一个机架上的不同机器。这时,数据和代码处于不同机器上,需要通过网络传递,但还是在同一个机架上,一般也就通过一个交换机传输即可。
  • ANY 数据在网络中其他未知,即数据和代码不在同一个机架上。

Spark倾向于让所有任务都具有最佳的数据本地性,但这并非总是可行的。某些情况下,可能会出现一些空闲的执行器(executor)没有待处理的数据,那么Spark可能就会牺牲一些数据本地性。有两种可能的选项:a)等待已经有任务的CPU,待其释放后立即在同一台机器上启动一个任务;b)立即在其他节点上启动新任务,并把所需要的数据复制过去。

而通常,Spark会等待一小会,看看是否有CPU会被释放出来。一旦等待超时,则立即在其他节点上启动并将所需的数据复制过去。数据本地性各个级别之间的回落超时可以单独配置,也可以在统一参数内一起设定;详细请参考 configuration page 中的 spark.locality 相关参数。如果你的任务执行时间比较长并且数据本地性很差,你就应该试试调大这几个参数,不过默认值一般都能适用于大多数场景了。

总结

本文是一个简短的Spark调优指南,列举了Spark应用调优一些比较重要的考虑点 – 最重要的就是,数据序列化和内存调优。对于绝大多数应用来说,用Kryo格式序列化数据能够解决大多数的性能问题。如果您有其他关于性能调优最佳实践的问题,欢迎邮件咨询(Spark mailing list )。

 

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 《Spark 官方文档》Spark调优


20 Apr 00:31

老司机使用 Redis 缓存复杂查询

by ABOER

最近上线了一个复杂的报表, 这个报表后面是一个几百行的 sql 查询,很不幸但又是预料之中, 这个 sql 查询性能非常低下,并且需要在网站的一个访问量非常大的页面显示这个 sql 的查询结果。幸运的是这个查询结果不需要
实时更新,只要每天更新一次即可, 于是为这个 sql 查询加上缓存就成为了一个很好的优化方法。开始我们使用 Rails.cache 来缓存这个查询结果,Rails.cache 的 backend 配置如下:

  # config/environments/production.rb

  couch_host = YAML.load_file(Rails.root.join("config/couch.yml")).symbolize_keys[:host]
  config.cache_store = :mem_cache_store, couch_host , { :namespace => 'rails_cache' }
  # config/couch.yml

  host: xxx.xxx.xxx.xxx:xxx

从上面的代码可以看出我们使用了 couchdb 作为 Rails.cache 的 backend, 我开始不太清楚为什么会使用 couchdb, 因为我们的系统中已经使用了 Redis, 并且 Redis 无论是使用舒适度还是性能都不输 couchdb, 后来我打开 Gemfile 发现:

 # Gemfile

gem 'rails', '3.0.9'
#gem 'redis-rails', '3.1.3'
#gem "redis-store", "~> 1.0.0"

我们看到 redis-rails 和 redis-store 都被加在了 Gemfile 里,然后又被注释掉了,由此我估计前面的同事也想使用 Redis, 但是由于我们的 Rails 版本过老(现在 Rails 5 都发布了,我们还在使用 Rails 3), 导致 redis-rails 和 redis-store 无法使用,而我们既不想冒升级 Rails 的风险(这个升级的跨度有点大了), 也不愿意花时间去改造 redis-store 使其兼容 Rails 3(每天改 ticket 已经让人心力交瘁了,这个借口让自己无法反驳)。 报表上线之初,没有什么问题,后来随着数据量变大,发现报表展示的速度变慢但由于还可以容忍,也就没有去花时间去研究速度变慢的原因,直到不久运行 couchdb 的机器莫名宕机,造成整个网站 502(前端请求拿不到缓存就会去读数据库,这个查询很耗时就一直挂着,请求量一大数据库就受不住了,导致整个网站不可访问), 这时候我们决定重新设计下这个报表的缓存。我们的设计如下:

  1. 使用 Redis 替换 couchdb, 主要原因是对 Redis 熟悉,并且系统中的很多异步队列服务用的是 Redis,非常稳定。
  2. 前端请求只能从 Redis 中读取已经被缓存的查询结果,而不能直接读数据库,如果缓存为空则返回空数组,这样做是为了防止数据库被大量的耗时请求拖垮,保证整个网站的可访问性。
  3. 缓存的过期时间设置为 999 天,其实就是缓存不过期的意思,并且写一个 rake task 每天运行一次用于更新 Redis 缓存。

这个设计的 2 和 3 步其实就是一个典型的生产&消费模式, rake task 作为生产者每天定时生成一次查询结果存入 Redis, 前端请求作为消费者通过读取 Redis 获得查询结果供页面展示。

有了设计我们并不急于编写代码,而是画一个设计图,一方面是为了梳理下思路看看设计是否会有缺陷,另一方面是为了更好地编写代码。

设计图如下:Snip20160326_36

通过设计图我们可以看到数据是单向流动的, 这样生产者和消费者是互不干扰的隔离状态, 前端请求不生产数据,只从 Redis 中拿数据,这样情况下前端请求对数据库的访问压力几乎为0。从设计图中我们也可以看出我们的代码大概会
分成三部分:

  1. 生产者的代码
  2. Redis的代码(主要是读写 Redis)
  3. 消费者的代码

其中生产者和消费者都依赖 Redis 的代码, 因为两者都需要和 Redis 产生交互。

Redis 相关代码

前面说过由于我们使用的 Rails 版本过低, 将 Redis 整合到 Rails.cache 会是一件比较费力费时的事情,所以我们将直接使用 Reids。

首先配置 Redis,

  # config/redis_store.yml

  cache:
    host: xxx.xxx.xxx.xxx
    port: xxx
    db: 2
    driver: hiredis
    thread_safe: true
    timeout: 200
  # config/initializers/redis.rb

  $redis = Redis.new(YAML.load_file("#{Rails.root}/config/redis_store.yml").symbolize_keys[:cache])

从上面的代码中可以看到我们定义了一个全局变量 $redis 用来访问 Redis。

接下来是将 $redis 封装到一个 service 中,这样做的目的是方便进行测试,也便于使用及以后的扩展。

class ReportCacheService

    def initialize(data = {})

      # 设置过期时间
      @expire_in = data[:expire_in]

      # 强制更新缓存,用于生产者生产数据
      @refresh_cache = data[:refresh_cache]
    end

    # block 里面是数据计算的过程
    def call(&block)
      if @refresh_cache == true
        res = nil
      else
        res = read()
      end

      if res.nil?
        res = block.call
        write(res)
      end

      res
    end

    # 读缓存
    def read
      value = $redis.get(@key)
      if value.present?
      JSON.parse(value)
      end
    end

    # 写缓存
    def write(res)
      value = res.to_json
      value = $redis.set(@key, value)
      $redis.expire(@key, @expire_in)
      true
    end

  end

这样我们就完成了 Redis 这部分的代码,接下来是生产者的代码。

生产者代码

我们将和报表相关的业务和逻辑封装到了一个叫 StmReport 的模型中, 我们为 StmReport 定义了一个 class 方法: warm_cache 用于生产报表数据。

  # app/models/stm_report.rb

  class StmReport

    NON_EXPIRED = 999.days.seconds

    def self.warm_cache(parasm = {})

      # refresh_cache: true 表示强制更新缓存,即生产数据
	  # 生产的数据的过期时间是 999 天
      cs  = ReportCacheService.new(key: build_cache_key(params),
                                 refresh_cache: true,
                                 expire_in: NON_EXPIRED)

      cs.call do
	    # do some heavy works
	    ...
	  end

    end

  end

接着我们编写一个 rake task, 并且使用 crontab 每天定时运行此 rake task 用于生产数据。

  # lib/tasks/cron.rake

  task :warn_stm_report_cache => :environment do

    puts "#{DateTime.now}: start cron warm cache"
    StmReport.warm_cache(qualified: true)
    StmReport.warm_cache(qualified: true, per_page: 50)
    puts "#{DateTime.now}: end cron warm cache"

  end

生产者的代码也完成,接下来是消费者的代码。

消费者的代码

在本文中,消费的过程即创建报表的过程, 创建报表的过程很自然地也封装到了 StmReport 模型中,

  class StmReport

    NON_EXPIRED = 999.days.seconds

    def self.create(params = {})
      cs  = ReportCacheService.new(key: build_cache_key(params))

      # 从缓存中读取数据
      items = cs.read

      # 如果缓存中没有数据即返回空数组,避免从数据库中查询数据
	  items = [] if items.nil?

      # do some other works
	  ...
    end

  end

这样整个实现就完成了,重新上线后的报表运行地非常稳定迅速,证明这个实现是成功的。

老司机使用 Redis 缓存复杂查询,首发于文章 - 伯乐在线

18 Apr 00:33

Apache Storm 1.0发布,带来性能提升和许多新特性

by Sergio De Simone

1.0版本是Apache Storm发展过程中一座重要的里程牌,负责Apache Storm项目的Apache软件基金会副总裁P. Taylor Goetz这样写道。该版本包含许多新特性和改进。尤其是,Goetz声称它带来了3到16倍的性能提升。

Storm是一个事件处理程序,可以对流数据进行分布式处理。一个Storm应用程序由“spouts”和“bolts”构成,它们被配置成一个有向无环图,用来表示信息源和数据处理程序。Storm的主要特点是能处理实时数据,不像Hadoop那样允许批处理。

据Goetz介绍,与先前的版本相比,Storm 1.0的性能最高提升了16倍,在大多数情况下预计都会有3倍的性能提升。特别地,性能的重大改善似乎来自下面的更改:

  • SpoutOutputCollector.emit()调用中使用Java重新实现了Clojure reduce函数;
  • DisruptorQueue引入批处理,代替spout层的批处理,这以增加延迟为代价大幅提升了吞吐量。

特别地,雅虎工程师所做的大量的基准测试表明,与其他两个流行的分布式处理框架Apache FlinkApache Spark相比,性能历来是Storm的主要竞争优势之一。

此外,Storm 1.0包含许多值得注意的新特性,例如:

  • Pacemaker:一个处理工作进程心跳的心跳守护进程,它常驻内存,提供了比ZooKeeper更好的性能;
  • 分布式缓存及相关API:它允许在拓扑之间共享文件。文件可以随时更新,而不需要重新部署受影响的拓扑。这对于当前将资源文件包含在拓扑jar包中的做法是一种改进,这种做法更新文件时需要重新部署;
  • 高可用Nimbus:使用一个Nimbus节点的动态集群代替单个Nimbus实例,如果当前的群首节点出现故障,就会选出新的“群首”;
  • 流窗口API:新增窗口定义支持,这些窗口可以应用于数据处理,比如在最后一个小时里计算最热门的话题。以前,开发人员必须构建自己的窗口逻辑;
  • 自动反压:当任务缓冲区的大小达到了指定的限制(以百分比表示),Storm就会自动降低拓扑spouts的速度;
  • 资源感知调度器:一种新的调度器实现,在将任务分配给最能满足特定需求的工作进程时考虑了集群中可用的内存和CPU资源;
  • 动态工作进程性能分析:旨在让用户可以从Storm UI获取工作进程性能数据,比如堆转储文件、JStack输出。

读者可以从GitHub上下载Apache Storm 1.0,或者从Storm下载页面上获取各种打包格式。

查看英文原文:Apache Storm Reaches 1.0, Brings Improved Performance, Many New Features

评价本文

专业度
风格
16 Apr 08:38

Intel欲业务转型,或将裁员上千人

by Mihawk

Oregonlive 消息, Intel 公司内部消息人士表示,今年公司将开始新一轮的裁员计划,涉及多个业务部门,部分部门裁员比例将超过十分之一,预计总人数在今年年 底达到数千。消息称 Intel 将在发布第一季度财报后,公布这个裁员计划。

截至去年年 底,Intel 全球雇员人数约为 107000,在去年销售没能达到预期的情况下,美国当地就削减了 1000 多个岗位,而今年的裁员力度将会更大。

目前 PC 芯片业务占 Intel 总营收的 60%,但近年来 PC 市场一直处于下滑趋势,今年第一季度 PC 出货量甚至为 9年 来最低,这让 Intel 正在寻求移动芯片和 PC 旧芯片更新方面的业务转型。裁员计划伴随的也是相关业务高管调整,来适应新的业务线。

公司总裁 Renée James 由高通晶片部门前总裁 Venkata Murthy Renduchintala 接任,上周也有 Intel 两位 VP 离职的消息。根据这位消息人士的信息,现在 Intel 高管位置并不稳定。

除了 PC 市场的下滑,Intel 还要面临摩尔定律逐渐失效,芯片设计越来越复杂化的问题,这会让 Intel 在芯片制造的成本越来越高。

上周,现任 Intel 总裁 Renduchintala 发布了开发关键产品计划的备忘录,希望能解决 Intel 在产品上竞争力差距的问题,显然整个公司也意识到了业务转型的重要性。

14 Apr 11:14

Hadoop Summit 2016欧洲峰会开幕Keynote回顾

by 李扬

2016年4月13日,都柏林的Liffey河畔,Hadoop Summit 2016在Convention会展中心盛大开幕。以下是Kyligence CTO李扬在现场为我们带来的开幕Keynote回顾。

 

在可容纳2000人的Auditorium大厅,爱尔兰风十足的打击乐和踢踏舞过后,Hortonworks总裁Herb Cunitz首先亮相。开场白很简单,除了强调与会人数和讲演场次的再攀新高,着重说明了为什么要在都柏林举办大会--因为大家最爱的Guinness黑啤酒的产地就在都柏林!

第一个Keynote来自Hortonworks CEO RobBearden,他的主题是“数据正在改变商业世界”。Hadoop技术走过了十年,大数据不再是象牙塔和实验室里的玩具,它已经能切实地创造商业价值,深切地改变商业世界。零售商通过大数据技术做精准市场预测,洞察物流效率,每年可以节省7000万美元系统开支,营收增长8%,利润增长3%。保险公司通过实时分析司机的驾驶模式,动态计算行驶风险并奖励安全驾驶,带来每年26亿美金的保险金增长,减少4%的理赔损失。

之后Hortonworks联合创始人Arun Murthy登台,从技术角度展望Hadoop平台。HDFS和YARN作为大数据的操作系统已经非常成熟,将来是中间件和上层应用百花齐放的年代。Apache Nifi是集群系统之间的胶水,能实时和可视化地操控数据的流转和对接。Apache Range和Apache Atlas一并可以实现基于标签的全局访问策略。最后YARN.NEXT技术可以根据应用的组装描述文件自动适配资源,部署集群应用,并自动伸缩,重新定义了Hadoop应用这一概念。原本的组件如Zookeeper,Solr,Search UI都弱化为应用的一个部分,不再是YARN.NEXT的一等公民。

此外EMC和微软也都做Keynote,展示各自公司的大数据解决方案。其中微软提出的Tiered HDFS可以根据数据的特性(比如活跃度)透明地在多种性价比不同存储介质之间移动数据,同时保证就近计算的原则。

最后Emer Coleman女士关于Technoethics(技术伦理学)的演讲将Keynote环节提升到了一个新的高度。大数据和人工智能技术一旦被滥用,将对整个人类社会造成及其可怕的后果,这不是科幻小说里的妄想,而很可能正在我们身边发生。我们应当立即行动,为技术伦理制定规范。

 

作者介绍:李扬,Kyligence联合创始人兼CTO,Apache Kylin联合创建者及项目管理委员会成员(PMC), 主创团队架构师和技术负责人,专注于大数据分析,并行计算,数据索引,关系数学,近似算法,压缩算法等前沿技术。曾任eBay全球分析基础架构部大数据资深架构师、IBM InfoSphere BigInsights的技术负责人,负责Hadoop开源产品架构,“杰出技术贡献奖”的获奖者、摩根士丹利副总裁,负责全球监管报表基础架构。

评价本文

专业度
风格
13 Apr 00:38

文章: 大数据的明天将驶向何方?

by 36Kr

编者注:原文是 FirstMark Capital 的 Matt Turck 的文章。本文全面总结了大数据领域的发展态势,分析认为尽管大数据作为一个术语似乎已经过气,但是大数据分析与应用才刚刚开始兴起,在与 AI、人工智能等新兴技术的结合下,大数据的机会也许要比大家想象的还要大。

大数据是否“过气”?

在喜新厌旧的技术初创企业界,已有 3年 历史 “大数据” 听起来似乎已经过气了。虽然 Hadoop 在 2006年 已经出来,但 “大数据” 这个概念大概是在 2011 到 2014年 左右才真正火起来的。也就是在这段时间里,至少是在媒体或者专家眼里,“大数据” 成为了新的 “金子” 或者 “石油”。然而,至少在我跟业界人士交谈中,大家越来越感觉到这项技术已经在某种程度上陷入了停滞。2015年 可能是数据领域的那些酷小子转移兴趣,开始沉迷于 AI 以及机器智能、深度学习等许多相关概念的年份。

抛开不可避免的炒作周期曲线态势不管,我们的 “大数据版图” 已经进入第 4 个年头了,趁这个时候退一步来反思一下去年发生了什么,思考一下这个行业的未来会怎样是很有意义的。

那么 2016年 大数据到底还算不算回事儿呢?我们不妨探讨一下。

大数据应用的两个难点

大数据有趣的一点在于,它不再像当初经历过那样有可能成为炒作的题材了。

经过炒作周期后仍能引起广泛兴趣的产品和服务往往那些大家能够接触、可以感知,或者与大众相关联的:比如移动应用、社交网络、可穿戴、虚拟现实等。

大数据基本上就是管道设施的一种。当然,大数据为许多消费者或商业用户体验提供了动力,但它的核心是企业技术:数据库、分析等,这些东西都是在后端运行的,没几个人能看得见。就像在那个世界工作的任何人都知道那样,用一个晚上的时间就想适应企业端的新技术是不可能的。

大数据现象在早期主要是受到了与一批骨干互联网公司(尤其是 Google、Facebook、Twitter 等)的共生关系的推动,这些公司既是核心大数据技术的重度用户,同时也是这些技术的创造者。这些公司突然间面对着规模前所未有的庞大数据时,由于本身缺乏传统的(昂贵的)基础设施,也没有办法招募到一些最好的工程师,所以只好自己动手来开发所需的技术。

后来随着开源运动的迅速发展,一大批此类新技术开始共享到更广的范围。然后,一些互联网大公司的工程师离职去创办自己的大数据初创企业。其他的一些 “数字原生” 公司,包括崭露头角的独角兽公司,也开始面临着互联网大公司的类似需求,由于它们自身也没有传统的基础设施,所以自然就成为了那些大数据技术的早期采用者。而早期的成功又导致了更多的创业活动发生,并获得了更多的 VC 资助,从而带动了大数据的起势。

快速发展了几年之后,现在我们面临的是更加广阔、但也更加棘手的机遇:让中等规模到跨国公司级别的更大一批企业采用大数据技术。这些公司跟 “数字原生” 公司不一样的是,他们没有从零开始的有利条件。而且他们失去的会更多:这些公司绝大部分的现有技术基础设施都是成功的。那些基础设施当然未必是功能完备的,组织内部许多人也意识到对自己的遗留基础设施进行现代化应该是早点好过晚点,但他们不会一夜间就把自己的关键业务取代掉。

任何革命都需要过程、预算、项目管理、试点、局部部署以及完备的安全审计等。大企业对由年轻的初创企业来处理自己基础设施的关键部分的谨慎是可以理解的。还有,令创业者感到绝望的是,许多(还是大多数?)企业仍顽固地拒绝把数据迁移到云端(至少不愿迁移到公有云)。

还需要理解的另一个关键是:大数据的成功不在于实现技术的某一方面(像 Hadoop 什么的),而是需要把一连串的技术、人和流程糅合到一起。你得捕捉数据、存储数据、清洗数据、查询数据、分析数据并对数据进行可视化。这些工作一部分可以由产品来完成,而有的则需要人来做。一切都需要无缝集成起来。最后,要想让所有这一切发挥作用,整个公司从上到下都需要树立以数据驱动的文化,这样大数据才不仅仅是个 “东西”,而且就是那个(关键的)“东西”。

换句话说:有一堆艰苦的工作要做。

尝试还是观望?

所以,这就是在经过几年引人瞩目的初创企业如雨后春笋冒头,VC 投资频登头条后,我们开始步入大数据的部署期和早期成熟期的原因。

更有前瞻性的大公司(姑且称之为传统技术采用周期的 “早期采用者”)在 2011 到 2013年 间开始实验大数据技术,推出了若干的 Hadoop 试点计划(往往是因为赶时髦)或者尝试一些点方案。他们招募了各种各样此前并不存在的岗位(如 “数据科学家” 或 “首席数据官”)。

他们进行了各种努力,包括吧全部数据都堆到一个数据容器(“data lake”),然后希望紧跟着就会发生奇迹(往往不会)。他们逐步建设自己的内部能力,试验了各种供应商,从试点计划到生产中的局部部署,然后到现在争论要不要全企业铺开(全范围铺开实施的情况还很罕见)。

许多情况下,他们正处在这样一个重要的拐点上,即经过大数据基础设施的数年建设后,能够展示的成果还不多,至少在公司内部的商业用户看来是这样的。但是大量吃力不讨好的工作已经做完了,现在开始进入到有影响力的应用部署阶段了。只是从目前来看,这种建构在核心架构之上的应用数量还不成比例。

接下来的一波大公司(称之为传统技术采用周期的 “早期多数使用者”)大多数时候对大数据技术是持观望态度的,对于整个大数据方面的东西,他们还在心存一定程度困惑中观望。直到最近,他们还在指望某个大型供应商(比如 IBM)会提供一个一站式的解决方案,不过现在看来这种情况近期内并不会出现。他们看待这个大数据版图的态度是心怀恐惧,在想自己是不是真的需要跟这一堆看起来并没有什么不同的初创企业合作,然后修补出各种解决方案。

生态体系正在成熟

与此同时,在初创企业 / 供应商这一块,整个第一波的大数据公司(2009 至 2013年 间成立的那批)现在已经融了数轮的资金,企业规模已经得到了扩大,并且从早期部署的成功或失败中学到了东西,现在他们已经能够提供更成熟的、经受过考验的产品了。少数一些已经成为了上市公司(包括 2015年 上市的 HortonWorks 和 New Relic),而有的(比如 Cloudera、MongoDB 等)融资已经达上亿美元了。

这个领域的 VC 融资活动仍然很有生气,2016年 的前几周我们见证好几轮相当可观的后期阶段大数据融资事件:DataDog(9400 万美元),BloomReach(5600 万美元),Qubole(3000 万美元),PlaceIQ(2500 万美元)等。2015年 大数据初创企业拿到的融资额达到了 66.4 亿美元,占整个技术 VC 总融资额额 11%。

并购活动则开展得中规中矩(自从上一版大数据版图发布以来完成了 34 项并购,具体可参见附注)

随着该领域的创业活动持续进行以及资金的不断流入,加上适度的少量退出,以及越来越活跃的技术巨头(尤其是 Amazon、Google、IBM),使得这个领域的公司日益增多,最后汇成了这幅 2016 版的大数据版图。

(点击放大图像)

显然这张图已经很挤了,而且还有很多都没办法列进去(关于我们的方法论可以参见附注)

在基本趋势方面,行动开始慢慢从左转到右(即创新、推出新产品和新公司),从基础设施层(开发者 / 工程师的世界)转移到分析层(数据科学家和分析师的世界)乃至应用层(商业用户和消费者的世界),“大数据原生应用” 已经在迅速冒头—这多少符合了我们原先的一些预期。

大数据基础设施:仍有大量创新

Google 关于 MapReduce 和 BigTable 的论文(Cutting 和 MikeCafarella 因为这个而做出了 Hadoop)的诞生问世已有 10年 了,在这段时间里,大数据的基础设施层已经逐渐成熟,一些关键问题也得到了解决。

但是,基础设施领域的创新仍然富有活力,这很大程度上是得益于可观的开源活动规模。

2015年 无疑是 Apache Spark 之年。自我们发布上一版大数据版图以来,这个利用了内存处理的开源框架就开始引发众多讨论。自那以后,Spark 受到了从 IBM 到 Cloudera 的各式玩家的拥护,让它获得了可观的信任度。Spark 的出现是很有意义的,因为它解决了一些导致 Hadoop 采用放缓的关键问题:Spark 速度变快了很多(基准测试表明 Spark 比 Hadoop 的 MapReduce 快 10 到 100 倍),更容易编程,并且跟机器学习能够很好地搭配。

除了 Spark 以外,还出现了其他的一些令人兴奋的框架,比如 Flink、Ignite、Samza、Kudu 等,这些框架的发展势头也很好。一些思想领袖认为,Mesos(数据中心资源管理系统,把数据中心当作一台大计算资源池进行编程)的出现也刺激了对 Hadoop 的需求。

即便在数据库的世界里,新兴的玩家似乎也越来越多。多到市场已经难以承受的地步,这里发生了很多令人兴奋的事情,从图形数据库(如 Neo4j )的成熟,到专门数据库的推出(如统计时序数据库 InfluxDB),乃至于 CockroachDB 的出现(受 Google Spanner 灵感启发诞生的融合了 SQL 与 NoSQL 长处的新型数据库)。数据仓库也在演变(如云数据仓库 Snowflake)。

大数据分析下一步的趋势是什么?

大数据分析过去几个月出现的一股趋势是,越来越关注利用人工智能(形式和风格各异)来帮助分析大规模的数据,从而获得预测性的洞察。

其实最近出现复兴的 AI 很大程度上算是大数据的产物。深度学习(最近受到关注最多的 AI 领域)背后的算法基本上是几十年前就诞生了的,但直到最近能够以足够便宜、足够快速地应用到大规模数据之后才发挥出了它的最大潜能。AI 与大数据之间的关系如此紧密,以至于业界专家现在认为 AI 已经令人懊恼地 “与大数据陷入了热恋当中”。

不过反过来,AI 现在也在帮助大数据实现后者的承诺。分析对 AI/ 机器学习越来越多的关注也符合大数据下一步演进的趋势:现在数据我都有了,但究竟从中能得到什么样的洞察呢?当然,这件事情可以让数据科学家来解决,从一开始他们的角色就是实现机器学习,否则的话就得想出模型来发现数据的意义。

但是机器智能现在正在逐渐发挥辅助数据科学家的作用—只需要倒腾数据,新兴的产品就能从中提炼出数学公式(如 Context Relevant)或者自动建立和推荐最有可能返回最佳结果的数据科学模型(如 DataRobot)。一批新的 AI 公司提供的产品能够自动识别像图像这样的复杂实体(如 Clarifai、Dextro),或者提供强大的预测性分析(如 HyperScience)。

同时,随着基于无监督学习的产品的传播和改善,看看它们与数据科学家之间的关系如何演变将非常有趣—将来这两者是敌还是友呢?AI 当然不会很快取代数据科学家的位置,但预计会看到数据科学家通常执行的更简单一点的工作越来越多的自动化,从而可以极大提高生产力。

但不管怎样,AI/ 机器学习绝不是大数据分析唯一值得关注的趋势。大数据 BI 平台的普遍成熟及其日益增强的实时能力也是一个令人兴奋的趋势(如 SiSense、Arcadia Data 等)。

大数据应用:真正的加速

随着一些核心基础设施的挑战得到解决,大数据应用层正在快速构建。

在企业内部,已经出现了各种工具来帮助跨多个核心职能的企业用户。比方说,销售和营销的大数据应用通过处理大规模的内外部数据来帮助找出哪位客户可能会购买、续约或者流失,且速度越来越实时化。客服应用帮助个性化服务。人力应用帮助找出如何吸引和挽留最好的员工等。

专门的大数据应用几乎在任何一个垂直行业都有出现,从医疗保健(尤其是基因组学和药物研究)到金融、时尚乃至于执法(如 Mark43)。

有两个趋势值得强调一下。

首先,这些应用很多都是 “大数据原生” 的,本身都是依托在最新的大数据技术基础上开发的,代表了一种客户无须部署底层大数据技术即可利用大数据的有趣方式—因为那些底层技术已经是打包的,至少对于特定功能来说是这样的。比方说,ActionIQ 就是在 Spark 基础上开发的(或者说是 Spark 的一个派生),所以它的客户能够在营销部门利用 Spark 的威力而不需要自己部署 Spark,这种情况下是没有 “装配线” 的。

其次,AI 在应用层也有很强大的存在。比方说,在猫捉老鼠的安全领域中,AI 被广泛用来对付黑客,实时识别和对抗网络攻击。去年已经出现了一个 AI 驱动的数字助手行业,支持从任务自动化到会议安排(如 x.ai)以及购物等几乎一切事情。这些解决方案对 AI 的依赖程度不一,从几乎 100%自动化到 “有人参与” 等情况各不相同,但是可以明确的是,人的能力在 AI 帮助下得到了增强。

结论

从很多方面来看,我们仍然处在大数据现象的早期发展阶段。尽管已经花费了数年时间,但减少基础设施来存储和处理大规模数据还只是第一阶段。AI/ 机器学习已经成为大数据应用层的一股迅猛趋势。大数据与 AI 的结合将会推动很多行业的惊人创新。从这个角度来说,大数据的机会也许要比大家想象的还要大。

然而,随着大数据继续走向成熟,这个术语本身可能会消失,或者变得太过时以至于没有人会再使用这个词。这就是成功赋能技术令人讽刺的命运归宿—由于技术的广泛传播,然后到达无所不在的地步,最后被人熟视无睹。

附注

1)由于不可能把大数据的所有公司都列到图表上,所以我们只能按照一定原则筛选部分公司出来,筛选原则一是进行过 1 轮或多轮 VC 融资的初创企业,二是把一些我们特别感兴趣的较早期初创企业列进去。

2)值得注意的收购包括 Revolution Analytics(微软 2015年1月 收购),Mortar(DataDog2015年2月 收购),Acunu 和 FoundationDB(2015年3月 被苹果收购),AlchemyAPI(2015年3月 被 IBM 收购),Amiato(2015年4月 被 Amazon 收购),Next Big Sound(2015年5月 被 Pandora 收购),1010Data(Advance/Newhouse 2015年8月 收购),Boundary(BMC 2015年8月 收购),Bime Analytics(Zendesk 2015年10月 收购),CleverSafe(IBM 2015年10月 收购),ParStream(2015年11月 被思科收购),Lex Machine(2015年11月 被 LexisNexis 收购),DataHero(2016年1月 被 Cloudability 收购)。

本文转载自36Kr,并已获原作者Matt turck的翻译授权。

查看英文原文:big-data-landscape


感谢杜小芳对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

评价本文

专业度
风格
13 Apr 00:37

文章: 45倍加速Spark的处理效率?!

by 侠天

Spark代表着下一代大数据处理技术,并且,借着开源算法和计算节点集群分布式处理,Spark和Hadoop在执行的方式和速度已经远远的超过传统单节点的技术架构。但Spark利用内存进行数据处理,这让Spark的处理速度超过基于磁盘的Hadoop 100x 倍。

但Spark和内存数据库Redis结合后可显著的提高Spark运行任务的性能,这源于Redis优秀的数据结构和执行过程,从而减小数据处理的复杂性和开销。Spark通过一个Redis连接器可以访问Redis的数据和API,加速Spark处理数据。

Spark和Redis结合使用到底有多大的性能提升呢?结合这两者来处理时序数据时可以提高46倍以上——而不是提高百分之四十五。

为什么这些数据处理速度的提升是很重要的呢?现在,越来越多的公司期望在交易完成的同时完成对应的数据分析。公司的决策也需要自动化,而这些需要数据分析能够实时的进行。Spark是一个用的较多的数据处理框架,但它不能做到百分之百实时,要想做到实时处理Spark还有很大一步工作需要做

图1

Spark RDD

Spark采用弹性分布式数据集(RDD),可将数据存在易变的内存中或持久化到磁盘上。 RDD具有不可变化性,分布式存储在Spark集群的各节点,RDD经过tansform操作后创建出一个新的RDD。RDD是Spark中数据集的一种重要抽象,具有良好的容错性、高效的迭代处理。

Redis

Redis天生为高性能设计,通过良好的数据存储结构能达到亚毫秒级的延迟。Redis的数据存储结构不仅仅提高内存的利用和减小应用的复杂性,也降低了网络负载、带宽消耗和处理时间。Redis数据结构包括字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets), bitmaps, hyperloglogs 和 地理空间(geospatial)索引半径查询。

下面来展示Redis的数据结构如何来简化应用的处理时间和复杂度。这里用有序集合来举例,一个以评分(score)大小排序的元素集合。

图2

Redis能存储多种数据类型,并自动的以评分(score)排序。常见的例子有,按价格排序的商品,以阅读数排序的文章名,股票价格时序数据,带时间戳的传感器读数。
有序集合依赖Redis优秀的内建操作可以实现范围查询、求交集,可以非常快地(O(log(N)))完成添加,删除和更新元素的操作。Redis内建函数不仅减少代码开发,在内存中执行也减小了网络延时和带宽消耗,可达到亚毫秒级的吞吐延迟。特别地,对时序数据集合来讲,有序集合数据结构比使用内存键值对或使用磁盘的数据库,能给数据分析带来数量级上的性能提升。

Spark-Redis connector

为了提高Spark数据分析的能力,Redis团队开发了一个Spark-Redis connector,它使得Spark可以直接使用Redis作为数据源,顺理成章的Spark也能使用Redis的各数据结构,进而显著的提升Spark分析数据的速度。

图3

为了展示Spark结合Redis所产生的效果,Redis团队拿时序数据集合做基准测试,测试了Spark在不同情况下执行时间范围查询:Spark使用堆外内存;Spark使用Tachyon作为堆外缓存;Spark使用HDFS存储;Spark结合Redis使用。

Redis团队改进了Cloudera的Spark分析时序数据的包,采用Redis有序集合数据结构加速时序数据分析,并且实现Spark访问Redis各类数据结构的接口。此Spark-Redis时序开发包主要做了两件事:

  1. 它让Redis节点与Spark集群的节点自动匹配,确保每个Spark节点都使用本地Redis节点,这样可以明显的优化延迟时间;
  2. 集成Spark DataFrame和Spark读取数据源,使得Spark SQL查询可自动转化,并能借助Redis能有效的恢复数据。

换句话说,使用Spark-Redis时序开发包意味着用户无需担心Spark和Redis两者如何使用。用户使用Spark SQL进行数据分析可以获得极大的查询性能提升。

基准测试

基准测试的时序数据集是跨度32年的1024个股票交易市场按天随机生成的数据。每个股票交易所都有有序数据集,以日期和元素属性(开盘价、最高价、最低价、收盘价等)排序,在Redis中以有序数据结构存储,采用Spark进行数据分析,描述如图4.

图4

在上述列子中,就有序集合AAPL来看,有序数据集合以天为评分(score,以蓝色表示),每天相关的值为一行(Member,以灰色表示)。在Redis中,只要执行一个ZRANGEBYSCORE操作就可以获取一个指定时间范围内的所有股票数据,并且Redis执行此查询要比其他Key/Value数据库快100倍。
从图x可以看到,横向比较各种情况的基准测试,Spark结合Redis执行时间片的查询速度比Spark使用HDFS快135倍、比Spark使用堆内内存或Spark使用Tachyon作为堆外内存要快45倍。

图5

Spark-Redis其它应用

按照“Getting Started with Spark and Redis”指南,你可以一步步安装Spark集群和使用Spark-Redis包。它提供一个简单的wordcount的例子展示如何使用Spark结合Redis。待你熟练使用后可以自己进一步挖掘、优化其他的Redis数据结构。
Redis的有序集合数据结构很适合时序数据集合,而Redis其他数据结构(比如,列表(lists), 集合(sets)和 地理空间(geospatial)索引半径查询)也能进一步丰富Spark的数据分析。当使用Spark抽取地理空间信息来获取新产品的人群偏好和邻近中心的位置,可结合Redis的地理空间(geospatial)索引半径查询来优化。

Spark支持一系列的数据分析,包括SQL、机器学习、图计算和流式数据。Spark本身的内存数据处理能力有一定的限制,而借着Redis可以让Spark更快的做数据分析。其实Spark的DataFrame和Datasets已经在做类似的优化,先把数据进行结构化放在内存里进行计算,并且Datasets可以省掉序列化和反序列化的消耗。结合Spark和Redis,借助Redis的共享分布式内存数据存储机制,可以处理数百万个记录乃至上亿的记录

时序数据的分析仅仅是一个开始,更多的性能优化可以参见:Spark-Redis

作者介绍

侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号:bigdata_ny分享相关技术文章。

感谢杜小芳对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

评价本文

专业度
风格
12 Apr 06:31

[信息图]在邮件中使用这些单词可能会让你显得粗鲁?

全球外包解决方案供应商Outsource-Philippines近日在一项公共服务公告中表示, 在邮件中使用"Fine"、"Me"、"Need"、"Sorry"、"Thanks"等在内的十个单词可能让你显得粗鲁无礼。 Outsource-Philippines建议人们在发邮件中尽量避免使用这些词语。






09 Apr 01:14

Spark在美团的实践

by 美团点评技术团队

本文已发表在《程序员》杂志2016年4月期。

前言

美团是数据驱动的互联网服务,用户每天在美团上的点击、浏览、下单支付行为都会产生海量的日志,这些日志数据将被汇总处理、分析、挖掘与学习,为美团的各种推荐、搜索系统甚至公司战略目标制定提供数据支持。大数据处理渗透到了美团各业务线的各种应用场景,选择合适、高效的数据处理引擎能够大大提高数据生产的效率,进而间接或直接提升相关团队的工作效率。
美团最初的数据处理以Hive SQL为主,底层计算引擎为MapReduce,部分相对复杂的业务会由工程师编写MapReduce程序实现。随着业务的发展,单纯的Hive SQL查询或者MapReduce程序已经越来越难以满足数据处理和分析的需求。
一方面,MapReduce计算模型对多轮迭代的DAG作业支持不给力,每轮迭代都需要将数据落盘,极大地影响了作业执行效率,另外只提供Map和Reduce这两种计算因子,使得用户在实现迭代式计算(比如:机器学习算法)时成本高且效率低。
另一方面,在数据仓库的按天生产中,由于某些原始日志是半结构化或者非结构化数据,因此,对其进行清洗和转换操作时,需要结合SQL查询以及复杂的过程式逻辑处理,这部分工作之前是由Hive SQL结合Python脚本来完成。这种方式存在效率问题,当数据量比较大的时候,流程的运行时间较长,这些ETL流程通常处于比较上游的位置,会直接影响到一系列下游的完成时间以及各种重要数据报表的生成。
基于以上原因,美团在2014年的时候引入了Spark。为了充分利用现有Hadoop集群的资源,我们采用了Spark on Yarn模式,所有的Spark app以及MapReduce作业会通过Yarn统一调度执行。Spark在美团数据平台架构中的位置如图所示:

 离线计算平台架构图

经过近两年的推广和发展,从最开始只有少数团队尝试用Spark解决数据处理、机器学习等问题,到现在已经覆盖了美团各大业务线的各种应用场景。从上游的ETL生产,到下游的SQL查询分析以及机器学习等,Spark正在逐步替代MapReduce作业,成为美团大数据处理的主流计算引擎。目前美团Hadoop集群用户每天提交的Spark作业数和MapReduce作业数比例为4:1,对于一些上游的Hive ETL流程,迁移到Spark之后,在相同的资源使用情况下,作业执行速度提升了十倍,极大地提升了业务方的生产效率。
下面我们将介绍Spark在美团的实践,包括我们基于Spark所做的平台化工作以及Spark在生产环境下的应用案例。其中包含Zeppelin结合的交互式开发平台,也有使用Spark任务完成的ETL数据转换工具,数据挖掘组基于Spark开发了特征平台和数据挖掘平台,另外还有基于Spark的交互式用户行为分析系统以及在SEM投放服务中的应用,以下是详细介绍。

Spark交互式开发平台

在推广如何使用Spark的过程中,我们总结了用户开发应用的主要需求:

  1. 数据调研:在正式开发程序之前,首先需要认识待处理的业务数据,包括:数据格式,类型(若以表结构存储则对应到字段类型)、存储方式、有无脏数据,甚至分析根据业务逻辑实现是否可能存在数据倾斜等等。这个需求十分基础且重要,只有对数据有充分的掌控,才能写出高效的Spark代码;
  2. 代码调试:业务的编码实现很难保证一蹴而就,可能需要不断地调试;如果每次少量的修改,测试代码都需要经过编译、打包、提交线上,会对用户的开发效率影响是非常大的;
  3. 联合开发:对于一整个业务的实现,一般会有多方的协作,这时候需要能有一个方便的代码和执行结果共享的途径,用于分享各自的想法和试验结论。

基于这些需求,我们调研了现有的开源系统,最终选择了Apache的孵化项目Zeppelin,将其作为基于Spark的交互式开发平台。Zeppelin整合了Spark,Markdown,Shell,Angular等引擎,集成了数据分析和可视化等功能。

 Zeppelin实例

我们在原生的Zeppelin上增加了用户登陆认证、用户行为日志审计、权限管理以及执行Spark作业资源隔离,打造了一个美团的Spark的交互式开发平台,不同的用户可以在该平台上调研数据、调试程序、共享代码和结论。

集成在Zeppelin的Spark提供了三种解释器:Spark、Pyspark、SQL,分别适用于编写Scala、Python、SQL代码。对于上述的数据调研需求,无论是程序设计之初,还是编码实现过程中,当需要检索数据信息时,通过Zeppelin提供的SQL接口可以很便利的获取到分析结果;另外,Zeppelin中Scala和Python解释器自身的交互式特性满足了用户对Spark和Pyspark分步调试的需求,同时由于Zeppelin可以直接连接线上集群,因此可以满足用户对线上数据的读写处理请求;最后,Zeppelin使用Web Socket通信,用户只需要简单地发送要分享内容所在的http链接,所有接受者就可以同步感知代码修改,运行结果等,实现多个开发者协同工作。

Spark作业ETL模板

除了提供平台化的工具以外,我们也会从其他方面来提高用户的开发效率,比如将类似的需求进行封装,提供一个统一的ETL模板,让用户可以很方便的使用Spark实现业务需求。

美团目前的数据生产主体是通过ETL将原始的日志通过清洗、转换等步骤后加载到Hive表中。而很多线上业务需要将Hive表里面的数据以一定的规则组成键值对,导入到Tair中,用于上层应用快速访问。其中大部分的需求逻辑相同,即把Hive表中几个指定字段的值按一定的规则拼接成key值,另外几个字段的值以json字符串的形式作为value值,最后将得到的对写入Tair。

 hive2Tair流程示例

由于Hive表中的数据量一般较大,使用单机程序读取数据和写入Tair效率比较低,因此部分业务方决定使用Spark来实现这套逻辑。最初由业务方的工程师各自用Spark程序实现从Hive读数据,写入到Tair中(以下简称hive2Tair流程),这种情况下存在如下问题:
每个业务方都要自己实现一套逻辑类似的流程,产生大量重复的开发工作;
由于Spark是分布式的计算引擎,因此代码实现和参数设置不当很容易对Tair集群造成巨大压力,影响Tair的正常服务。
基于以上原因,我们开发了Spark版的hive2Tair流程,并将其封装成一个标准的ETL模板,其格式和内容如下所示:

 hive2Tair  ETL模版

source用于指定Hive表源数据,target指定目标Tair的库和表,这两个参数可以用于调度系统解析该ETL的上下游依赖关系,从而很方便地加入到现有的ETL生产体系中。

有了这个模板,用户只需要填写一些基本的信息(包括Hive表来源,组成key的字段列表,组成value的字段列表,目标Tair集群)即可生成一个hive2Tair的ETL流程。整个流程生成过程不需要任何Spark基础,也不需要做任何的代码开发,极大地降低了用户的使用门槛,避免了重复开发,提高了开发效率。该流程执行时会自动生成一个Spark作业,以相对保守的参数运行:默认开启动态资源分配,每个Executor核数为2,内存2GB,最大Executor数设置为100。如果对于性能有很高的要求,并且申请的Tair集群比较大,那么可以使用一些调优参数来提升写入的性能。目前我们仅对用户暴露了设置Executor数量以及每个Executor内存的接口,并且设置了一个相对安全的最大值规定,避免由于参数设置不合理给Hadoop集群以及Tair集群造成异常压力。

基于Spark的用户特征平台

在没有特征平台之前,各个数据挖掘人员按照各自项目的需求提取用户特征数据,主要是通过美团的ETL调度平台按月/天来完成数据的提取。

但从用户特征来看,其实会有很多的重复工作,不同的项目需要的用户特征其实有很多是一样的,为了减少冗余的提取工作,也为了节省计算资源,建立特征平台的需求随之诞生,特征平台只需要聚合各个开发人员已经提取的特征数据,并提供给其他人使用。特征平台主要使用Spark的批处理功能来完成数据的提取和聚合。
开发人员提取特征主要还是通过ETL来完成,有些数据使用Spark来处理,比如用户搜索关键词的统计。
开发人员提供的特征数据,需要按照平台提供的配置文件格式添加到特征库,比如在图团购的配置文件中,团购业务中有一个用户24小时时段支付的次数特征,输入就是一个生成好的特征表,开发人员通过测试验证无误之后,即完成了数据上线;另外对于有些特征,只需要从现有的表中提取部分特征数据,开发人员也只需要简单的配置即可完成。

 特征平台 数据流向图

在图中,我们可以看到特征聚合分两层,第一层是各个业务数据内部聚合,比如团购的数据配置文件中会有很多的团购特征、购买、浏览等分散在不同的表中,每个业务都会有独立的Spark任务来完成聚合,构成一个用户团购特征表;特征聚合是一个典型的join任务,对比MapReduce性能提升了10倍左右。第二层是把各个业务表数据再进行一次聚合,生成最终的用户特征数据表。
特征库中的特征是可视化的,我们在聚合特征时就会统计特征覆盖的人数,特征的最大最小数值等,然后同步到RDB,这样管理人员和开发者都能通过可视化来直观地了解特征。 另外,我们还提供特征监测和告警,使用最近7天的特征统计数据,对比各个特征昨天和今天的覆盖人数,是增多了还是减少了,比如性别为女这个特征的覆盖人数,如果发现今天的覆盖人数比昨天低了1%(比如昨天6亿用户,女性2亿,那么人数降低了1%*2亿=2百万)突然减少2万女性用户说明数据出现了极大的异常,何况网站的用户数每天都是增长的。这些异常都会通过邮件发送到平台和特征提取的相关人。

Spark数据挖掘平台

数据挖掘平台是完全依赖于用户特征库的,通过特征库提供用户特征,数据挖掘平台对特征进行转换并统一格式输出,就此开发人员可以快速完成模型的开发和迭代,之前需要两周开发一个模型,现在短则需要几个小时,多则几天就能完成。特征的转换包括特征名称的编码,也包括特征值的平滑和归一化,平台也提供特征离散化和特征选择的功能,这些都是使用Spark离线完成。

开发人员拿到训练样本之后,可以使用Spark mllib或者Python sklearn等完成模型训练,得到最优化模型之后,将模型保存为平台定义好的模型存储格式,并提供相关配置参数,通过平台即可完成模型上线,模型可以按天或者按周进行调度。当然如果模型需要重新训练或者其它调整,那么开发者还可以把模型下线。不只如此,平台还提供了一个模型准确率告警的功能,每次模型在预测完成之后,会计算用户提供的样本中预测的准确率,并比较开发者提供的准确率告警阈值,如果低于阈值则发邮件通知开发者,是否需要对模型重新训练。

在开发挖掘平台的模型预测功时能我们走了点弯路,平台的模型预测功能开始是兼容Spark接口的,也就是使用Spark保存和加载模型文件并预测,使用过的人知道Spark mllib的很多API都是私有的开发人员无法直接使用,所以我们这些接口进行封装然后再提供给开发者使用,但也只解决了Spark开发人员的问题,平台还需要兼容其他平台的模型输出和加载以及预测的功能,这让我们面临必需维护一个模型多个接口的问题,开发和维护成本都较高,最后还是放弃了兼容Spark接口的实现方式,我们自己定义了模型的保存格式,以及模型加载和模型预测的功能。

 数据挖掘平台结构图

以上内容介绍了美团基于Spark所做的平台化工作,这些平台和工具是面向全公司所有业务线服务的,旨在避免各团队做无意义的重复性工作,以及提高公司整体的数据生产效率。目前看来效果是比较好的,这些平台和工具在公司内部得到了广泛的认可和应用,当然也有不少的建议,推动我们持续地优化。
随着Spark的发展和推广,从上游的ETL到下游的日常数据统计分析、推荐和搜索系统,越来越多的业务线开始尝试使用Spark进行各种复杂的数据处理和分析工作。下面将以Spark在交互式用户行为分析系统以及SEM投放服务为例,介绍Spark在美团实际业务生产环境下的应用。

Spark在交互式用户行为分析系统中的实践

美团的交互式用户行为分析系统,用于提供对海量的流量数据进行交互式分析的功能,系统的主要用户为公司内部的PM和运营人员。普通的BI类报表系统,只能够提供对聚合后的指标进行查询,比如PV、UV等相关指标。但是PM以及运营人员除了查看一些聚合指标以外,还需要根据自己的需求去分析某一类用户的流量数据,进而了解各种用户群体在App上的行为轨迹。根据这些数据,PM可以优化产品设计,运营人员可以为自己的运营工作提供数据支持,用户核心的几个诉求包括:

  1. 自助查询,不同的PM或运营人员可能随时需要执行各种各样的分析功能,因此系统需要支持用户自助使用。
  2. 响应速度,大部分分析功能都必须在几分钟内完成。
  3. 可视化,可以通过可视化的方式查看分析结果。

要解决上面的几个问题,技术人员需要解决以下两个核心问题:

  1. 海量数据的处理,用户的流量数据全部存储在Hive中,数据量非常庞大,每天的数据量都在数十亿的规模。
  2. 快速计算结果,系统需要能够随时接收用户提交的分析任务,并在几分钟之内计算出他们想要的结果。

要解决上面两个问题,目前可供选择的技术主要有两种:MapReduce和Spark。在初期架构中选择了使用MapReduce这种较为成熟的技术,但是通过测试发现,基于MapReduce开发的复杂分析任务需要数小时才能完成,这会造成极差的用户体验,用户无法接受。

因此我们尝试使用Spark这种内存式的快速大数据计算引擎作为系统架构中的核心部分,主要使用了Spark Core以及Spark SQL两个组件,来实现各种复杂的业务逻辑。实践中发现,虽然Spark的性能非常优秀,但是在目前的发展阶段中,还是或多或少会有一些性能以及OOM方面的问题。因此在项目的开发过程中,对大量Spark作业进行了各种各样的性能调优,包括算子调优、参数调优、shuffle调优以及数据倾斜调优等,最终实现了所有Spark作业的执行时间都在数分钟左右。并且在实践中解决了一些shuffle以及数据倾斜导致的OOM问题,保证了系统的稳定性。

结合上述分析,最终的系统架构与工作流程如下所示:

  1. 用户在系统界面中选择某个分析功能对应的菜单,并进入对应的任务创建界面,然后选择筛选条件和任务参数,并提交任务。
  2. 由于系统需要满足不同类别的用户行为分析功能(目前系统中已经提供了十个以上分析功能),因此需要为每一种分析功能都开发一个Spark作业。
  3. 采用J2EE技术开发了Web服务作为后台系统,在接收到用户提交的任务之后,根据任务类型选择其对应的Spark作业,启动一条子线程来执行Spark-submit命令以提交Spark作业。
  4. Spark作业运行在Yarn集群上,并针对Hive中的海量数据进行计算,最终将计算结果写入数据库中。
  5. 用户通过系统界面查看任务分析结果,J2EE系统负责将数据库中的计算结果返回给界面进行展现。

 交互式用户行为分析系统架构

该系统上线后效果良好:90%的Spark作业运行时间都在5分钟以内,剩下10%的Spark作业运行时间在30分钟左右,该速度足以快速响应用户的分析需求。通过反馈来看,用户体验非常良好。目前每个月该系统都要执行数百个用户行为分析任务,有效并且快速地支持了PM和运营人员的各种分析需求。

Spark在SEM投放服务中的应用

流量技术组负责着美团站外广告的投放技术,目前在SEM、SEO、DSP等多种业务中大量使用了Spark平台,包括离线挖掘、模型训练、流数据处理等。美团SEM(搜索引擎营销)投放着上亿的关键词,一个关键词从被挖掘策略发现开始,就踏上了精彩的SEM之旅。它经过预估模型的筛选,投放到各大搜索引擎,可能因为市场竞争频繁调价,也可能因为效果不佳被迫下线。而这样的旅行,在美团每分钟都在发生。如此大规模的随机“迁徙”能够顺利进行,Spark功不可没。

 SEM服务架构图

Spark不止用于美团SEM的关键词挖掘、预估模型训练、投放效果统计等大家能想到的场景,还罕见地用于关键词的投放服务,这也是本段介绍的重点。一个快速稳定的投放系统是精准营销的基础。

美团早期的SEM投放服务采用的是单机版架构,随着关键词数量的极速增长,旧有服务存在的问题逐渐暴露。受限于各大搜索引擎API的配额(请求频次)、账户结构等规则,投放服务只负责处理API请求是远远不够的,还需要处理大量业务逻辑。单机程序在小数据量的情况下还能通过多进程勉强应对,但对于如此大规模的投放需求,就很难做到“兼顾全局”了。

新版SEM投放服务在15年Q2上线,内部开发代号为Medusa。在Spark平台上搭建的Medusa,全面发挥了Spark大数据处理的优势,提供了高性能高可用的分布式SEM投放服务,具有以下几个特性:

  1. 低门槛,Medusa整体架构的设计思路是提供数据库一样的服务。在接口层,让RD可以像操作本地数据库一样,通过SQL来“增删改查”线上关键词表,并且只需要关心自己的策略标签,不需要关注关键词的物理存储位置。Medusa利用Spark SQL作为服务的接口,提高了服务的易用性,也规范了数据存储,可同时对其他服务提供数据支持。基于Spark开发分布式投放系统,还可以让RD从系统层细节中解放出来,全部代码只有400行。
  2. 高性能、可伸缩,为了达到投放的“时间”、“空间”最优化,Medusa利用Spark预计算出每一个关键词在远程账户中的最佳存储位置,每一次API请求的最佳时间内容。在配额和账号容量有限的情况下,轻松掌控着亿级的在线关键词投放。通过控制Executor数量实现了投放性能的可扩展,并在实战中做到了全渠道4小时全量回滚。
  3. 高可用,有的同学或许会有疑问:API请求适合放到Spark中做吗?因为函数式编程要求函数是没有副作用的纯函数(输入是确定的,输出就是确定的)。这确实是一个问题,Medusa的思路是把请求API封装成独立的模块,让模块尽量做到“纯函数”的无副作用特性,并参考面向轨道编程的思路,将全部请求log重新返回给Spark继续处理,最终落到Hive,以此保证投放的成功率。为了更精准的控制配额消耗,Medusa没有引入单次请求重试机制,并制定了服务降级方案,以极低的数据丢失率,完整地记录了每一个关键词的旅行。

结论和展望

本文我们介绍了美团引入Spark的起源,基于Spark所做的一些平台化工作,以及Spark在美团具体应用场景下的实践。总体而言,Spark由于其灵活的编程接口、高效的内存计算,能够适用于大部分数据处理场景。在推广和使用Spark的过程中,我们踩过不少坑,也遇到过很多问题,但填坑和解决问题的过程,让我们对Spark有了更深入的理解,我们也期待着Spark在更多的应用场景中发挥重要的作用。

02 Apr 01:22

数据库缓存管理器块替换

by triffic

学习高级数据库技术课的时候,老师提到了数据库中的buffer manager(缓存管理器),现在找出来重新整理温习下,今天要讨论的数据库缓存管理器块替换的总体结构如下图所示:

db-1

当应用程序需要从磁盘上得到一个块时,它会调用缓冲管理器,那么现在就分两种情况:

1.1、如果这个块已经在buffer中,缓冲管理器就会返回该块在主存中的地址给应用程序
1.2、如果这个块不在buffer中,那么buffer manager就会做以下几件事情:

1)在buffer中为该块分配空间,那么它是如何做到的呢?首先,如果需要的话,替换一些块以让出空间给新的块,被替换的块如果已经被修改过,那么就需要把它重新写回到磁盘上。
2)把需要的块从磁盘上读到buffer中,然后返回该块在主存中的地址。

第二部分

在上面的述说中我们了解到,当buffer的空间不足时,会选择一个块换出,这里就牵涉到了替换strategy,记得老师在课上给我们讲解了几种比较流行的替换strategy,自己整理了一下:

2.1、LRU算法(Least Recently Used 近期最少使用算法)

这个应该非常熟悉,不光是在数据库中用到,我觉得我们最早接触到这个应该是在操作系统中,这里就不多说了,给个example就很明白了。

假设现在的访问序列:1,4,8,1,5,2,3,2,4
得到如下图所示的替换过程:
db-2

但是LRU算法在一些特定的情况下,性能就不会很好,比如:

  • 1)文件共享:马上被用到的数据被一次访问的数据挤出,LRU堆栈保留了一次性访问的数据直到堆栈满的时候才会被替换出;
  • 2)环访问:假设现在LRU stack的大小为k,而现在的访问序列是:1,…,k,k+1,那么顺序访问k+1之后,就会一直miss(之前的元素已被替换出堆栈);
  • 3)不同频率的访问(混合工作负载)

频繁访问的数据会被不频繁访问的数据所替换

2.2、Clock: An approximation of LRU(一种与LRU近似的方法)

该算法的思想是把所有的块形成一个环(通过mod运算实现),环中的每一块都带一个引用位(reference_bit),也称为第二次机会位,当可用块为0时,此时就需要选择一块换出,在选择块换出时,如果当前块的reference_bit为1时,则修改成0,该块继续留在环中,如果当前块的reference_bit为0,则选择该块换出,如下图所示:

db-3

2.3、LIRS Policy

2.3.1、LIRS算法(Low Inter-Reference Recency Set Recency):表示某一块最近一次被访问与当前的间隔,可以理解为该块最近一次被访问之后,接下来访问了多少个其它块(Recency可以用这个其它块数表示)。

Reuse Distance(IRR):Buffer中某一块被连续访问时,它们之间的其它块数(在计算的时候,不算重复的块)。可以用下图进行说明:
db-4

2.3.2、LIRS的基本思想

  • 1)该算法认为IRR值高的块不会被频繁访问,所以把这些块选作将要被替换的块;
  • 2)Recency被用作第二次引用;
  • 3)把IRR值低的块保留在Buffer Cache中。

LIRS对比上面两种算法有其明显的改进,首先,它根据多种信息资源来合理地改变每个块的状态;其次,它的实现成本低。

2.3.3、LIRS的数据结构

LIRS的整个数据结构如下图所示:
db-5

我们从上图中可以看出,该算法有两个block set:LIR block set和HIR block set,同时整个物理缓存也由两部分组成,并且也可以知道,属于IRR值低的块集合中的块全部保存在物理缓存中,而属于IRR值高的块集合中的块只有一部分位于物理缓存中。

2.3.4 LIRS的块替换操作

下面我们结合具体的例子来了解使用LIRS算法之后,是如何进行块替换的,假设现在有5个块,分别是A,B,C,D,E,且当前在time 9这个位置,此时块访问序列以及每个块的Recency和IRR值如下图所示:
db-6

由上图可知,块A最近一次被访问是在time 8,所以它的R(Recency)为1;另外块A在time 6 和time 8被连续两次访问到,在这两个时刻之间,只有块D被访问到,所以块A的IRR为1。块B,C,D,E的R值和IRR值的计算方法和块A一样,如果某一块最近只有一次被访问到,那么它的IRR值为无穷大,比如块C和E。假设现在的物理缓存的空间划分如下图所示:

db-7

也就是,在该物理缓存中只能存放两个IRR值低的块,一个IRR值高的块,我们最后进行归类得到如下图所示:
db-8

那么接下来,如果在time 10访问块D,该如何进行替换,从上面得知,块D属于IRR值高的块集合,而此时属于IRR值高的块集合中只有块E驻留在物理缓存中,那么此时就需要把块E替换出去,把块D存入物理缓存中,整个过程如下图所示:

db-9

在块D进入物理缓存之后,我们接下来要做一些更新操作,看看块D应该属于哪个集合,因为在D进入物理缓存之后,各个块的R值和IRR值已经发生变化,更新完各个块的R值和IRR值之后,我们把块D的IRR值和IRR值低的块集合中的块的R值进行比较,如果块D的IRR值比IRR值低的块集合中的某块的R值小,则把该块换入到IRR值高的块集合中,而块D进入IRR值低的块集合中,过程图如下图所示:

db-10

块D在time 10被访问之后,最后的结果如下图所示:

db-11

数据库缓存管理器块替换,首发于博客 - 伯乐在线

26 Mar 09:39

如何设计实现一个LRU Cache?

by liuchi1993

1. 什么是LRU Cache?

之前,在LeetCode上看到一个LRU Cache实现的题目,题目描述是这样的:

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

简单的说,就是保证基本的get和set的功能的同时,还要保证最近访问(get或put)的节点保持在限定容量的Cache中,如果超过容量则应该把LRU(近期最少使用)的节点删除掉。

那么我们思考一个问题:如何设计实现一个LRU Cache?
那么,我们可能需要使用类似这样的数据结构去实现这个LRU Cache:

这不就是LinkedHashMap吗!
这样做的好处是,getset在不冲突的情况下可以保证O(1)的复杂度,同时,也可以通过双向链表来保证LRU的删除更新操作也能保证O(1)的复杂度。

2.实现思路

在学习了HashMap(#7 )和LinkedHashMap(#8 )后,是不是觉得这俩数据结构简直太适合做LRU Cache了!那么动手实现一下:
基于HashMap和双向链表的实现

public class LRUCache {
    class Node {
    	Node pre;
    	Node next;
    	Integer key;
    	Integer val;

    	Node(Integer k, Integer v) {
    		key = k;
    		val = v;
    	}
    }

    Map<Integer, Node> map = new HashMap<Integer, Node>();
    // The head (eldest) of the doubly linked list.
    Node head;
    // The tail (youngest) of the doubly linked list.
    Node tail;
    int cap;
    public LRUCache(int capacity) {
        cap = capacity;
        head = new Node(null, null);
        tail = new Node(null, null);
        head.next = tail;
        tail.pre = head;
    }

    public int get(int key) {
        Node n = map.get(key);
        if(n!=null) {
        	n.pre.next = n.next;
        	n.next.pre = n.pre;
        	appendTail(n);
        	return n.val;
        }
        return -1;
    }

    public void set(int key, int value) {
        Node n = map.get(key);
        // existed
        if(n!=null) {
	        n.val = value;
	        map.put(key, n);
        	n.pre.next = n.next;
        	n.next.pre = n.pre;
        	appendTail(n);
        	return;
        }
        // else {
        if(map.size() == cap) {
        	Node tmp = head.next;
        	head.next = head.next.next;
        	head.next.pre = head;
        	map.remove(tmp.key);
        }
        n = new Node(key, value);
        // youngest node append taill
        appendTail(n);
        map.put(key, n);
    }

    private void appendTail(Node n) {
    	n.next = tail;
    	n.pre = tail.pre;
    	tail.pre.next = n;
    	tail.pre = n;
    }
}

基于LinkedHashMap的实现
HashMap+双向链表?这不就是LinkedHashMap吗!

public class LRUCache {

    private int capacity;
    private Map<Integer, Integer> cache;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new java.util.LinkedHashMap<Integer, Integer> (capacity, 0.75f, true) {
            // 定义put后的移除规则,大于容量就删除eldest
            protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
                return size() > capacity;
            }
        };
    }

    public int get(int key) {
        if (cache.containsKey(key)) {
            return cache.get(key);
        } else
            return -1;
    }

    public void set(int key, int value) {
        cache.put(key, value);
    }
}

 

 

相关文章

22 Mar 03:44

JVM调优总结(1):一些概念

by liuchi1993

数据类型

Java虚拟机中,数据类型可以分为两类:基本类型引用类型。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。

基本类型包括:byte,short,int,long,char,float,double,Boolean,returnAddress

引用类型包括:类类型接口类型数组

堆与栈

堆和栈是程序运行的关键,很有必要把他们的关系说清楚。

    栈是运行时的单位,而堆是存储的单位

栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。

在Java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。

    为什么要把堆和栈区分出来呢?栈中不是也可以存储数据吗

第一,从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。这样分开,使得处理逻辑更为清晰。分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。

第二,堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。这种共享的收益是很多的。一方面这种共享提供了一种有效的数据交互方式(如:共享内存),另一方面,堆中的共享常量和缓存可以被所有栈访问,节省了空间。

第三,栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。

第四,面向对象就是堆和栈的完美结合。其实,面向对象方式的程序与以前结构化的程序在执行上没有任何区别。但是,面向对象的引入,使得对待问题的思考方式发生了改变,而更接近于自然方式的思考。当我们把对象拆开,你会发现,对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。我们在编写对象的时候,其实即编写了数据结构,也编写的处理数据的逻辑。不得不承认,面向对象的设计,确实很美。

    在Java中,Main函数就是栈的起始点,也是程序的起始点

程序要运行总是有一个起点的。同C语言一样,java中的Main就是那个起点。无论什么java程序,找到main就找到了程序执行的入口:)

    堆中存什么?栈中存什么

堆中存的是对象。栈中存的是基本数据类型和堆中对象的引用。一个对象的大小是不可估计的,或者说是可以动态变化的,但是在栈中,一个对象只对应了一个4btye的引用(堆栈分离的好处:))。

为什么不把基本类型放堆中呢?因为其占用的空间一般是1~8个字节——需要空间比较少,而且因为是基本类型,所以不会出现动态增长的情况——长度固定,因此栈中存储就够了,如果把他存在堆中是没有什么意义的(还会浪费空间,后面说明)。可以这么说,基本类型和对象的引用都是存放在栈中,而且都是几个字节的一个数,因此在程序运行时,他们的处理方式是统一的。但是基本类型、对象引用和对象本身就有所区别了,因为一个是栈中的数据一个是堆中的数据。最常见的一个问题就是,Java中参数传递时的问题。

    Java中的参数传递时传值呢?还是传引用

要说明这个问题,先要明确两点:

1. 不要试图与C进行类比,Java中没有指针的概念

2. 程序运行永远都是在栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题。不会直接传对象本身。

明确以上两点后。Java在方法调用传递参数时,因为没有指针,所以它都是进行传值调用(这点可以参考C的传值调用)。因此,很多书里面都说Java是进行传值调用,这点没有问题,而且也简化的C中复杂性。

但是传引用的错觉是如何造成的呢?在运行栈中,基本类型和引用的处理是一样的,都是传值,所以,如果是传引用的方法调用,也同时可以理解为“传引用值”的传值调用,即引用的处理跟基本类型是完全一样的。但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到堆中的对象,这个时候才对应到真正的对象。如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是堆中的数据。所以这个修改是可以保持的了。

对象,从某种意义上说,是由基本类型组成的。可以把一个对象看作为一棵树,对象的属性如果还是对象,则还是一颗树(即非叶子节点),基本类型则为树的叶子节点。程序参数传递时,被传递的值本身都是不能进行修改的,但是,如果这个值是一个非叶子节点(即一个对象引用),则可以修改这个节点下面的所有内容。

堆和栈中,栈是程序运行最根本的东西。程序运行可以没有堆,但是不能没有栈。而堆是为栈进行数据存储服务,说白了堆就是一块共享的内存。不过,正是因为堆和栈的分离的思想,才使得Java的垃圾回收成为可能。

Java中,栈的大小通过-Xss来设置,当栈中存储数据比较多时,需要适当调大这个值,否则会出现java.lang.StackOverflowError异常。常见的出现这个异常的是无法返回的递归,因为此时栈中保存的信息都是方法返回的记录点。

Java对象的大小

基本数据的类型的大小是固定的,这里就不多说了。对于非基本类型的Java对象,其大小就值得商榷。

在Java中,一个空Object对象的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。看下面语句:

Object ob = new Object();

这样在程序中完成了一个Java对象的生命,但是它所占的空间为:4byte+8byte。4byte是上面部分所说的Java栈中保存引用的所需要的空间。而那8byte则是Java堆中对象的信息。因为所有的Java非基本类型的对象都需要默认继承Object对象,因此不论什么样的Java对象,其大小都必须是大于8byte。

有了Object对象的大小,我们就可以计算其他对象的大小了。

Class NewObject {
    int count;
    boolean flag;
    Object ob;
}

其大小为:空对象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。但是因为Java在对对象内存分配时都是以8的整数倍来分,因此大于17byte的最接近8的整数倍的是24,因此此对象的大小为24byte。

这里需要注意一下基本类型的包装类型的大小。因为这种包装类型已经成为对象了,因此需要把他们作为对象来看待。包装类型的大小至少是12byte(声明一个空Object至少需要的空间),而且12byte没有包含任何有效信息,同时,因为Java对象大小是8的整数倍,因此一个基本类型包装类的大小至少是16byte。这个内存占用是很恐怖的,它是使用基本类型的N倍(N>2),有些类型的内存占用更是夸张(随便想下就知道了)。因此,可能的话应尽量少使用包装类。在JDK5.0以后,因为加入了自动类型装换,因此,Java虚拟机会在存储方面进行相应的优化。

引用类型

对象引用类型分为强引用、软引用、弱引用和虚引用

强引用:就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收

软引用:软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。

弱引用:弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

强引用不用说,我们系统一般在使用时都是用的强引用。而“软引用”和“弱引用”比较少见。他们一般被作为缓存使用,而且一般是在内存大小比较受限的情况下做为缓存。因为如果内存足够大的话,可以直接使用强引用作为缓存即可,同时可控性更高。因而,他们常见的是被使用在桌面应用系统的缓存。

相关文章

07 Mar 08:57

Notes: Spark metrics

by 四火

Below are some notes taken for future reference based on the brainstorm meeting last week, with company confidential information removed.

Background

The team use a home made workflow to manage the computation for the cost and profit, and there’s a lack of statistics for the jobs and input/output, usually SDE/oncall checks the data in Data Warehouse or TSV files on S3 manually. For EMR jobs, Spark UI and Ganglia are both powerful but when the clusters are terminated, all these valuable metrics data are gone.

Notes: Spark metrics

Typical use cases:

  • Spark metrics: status / efficiency / executor / GC …
  • EMR cluster / instance metrics: CPU / memory / IO/ network …
  • Workflow: task time cost distribution for each running, time cost comparison between different runnings of a same pipeline and task dependency graph
  • Health status: critical pipeline statuses should be monitored and presented daily, including pipeline status itself and the input / output scale
  • Metrics lib integration: We need a dedicated component to gather metrics from code, this can cover the case that the metrics is tightly connected with the code logic. It’s more like the regular “log”, but cares more about the data on metrics side.
  • (Sev-2) Ticket linkage: display on going ticket information, with related job/components links appended, and component owner should be clearly displayed

I. Metrics for Spark jobs

Metrics when cluster is running – Ganglia and Spark UI, and we can even ssh to the instance to get any information. But the main problem happens when the cluster is terminated:

1. Coda Hale Metrics Library (preferred)

In latest Spark documents there is an introduction guiding how to integrate the metrics lib. Spark has a configurable metrics system based on the Coda Hale Metrics Library (link1link2). This allows users to report Spark metrics to a variety of sinks including HTTP, JMX, and CSV files:

  • configuration: $SPARK_HOME/conf/metrics.properties or spark.metrics.conf
  • supported instances: master / applications / worker / executor / driver
  • sinks: ConsoleSink / CSVSink / JmxSink / MetricsServlet / GraphiteSink / Slf4jSink and GangliaSink (needs custom build for license)

We can also implement an S3Sink to upload the metrics data to S3. Or, call MetricsServlet to get metrics data termly and transfer the data to a data visualization system. To get the data when a Spark job is running, besides checking the Spark UI, there’s also a group of RESTful API to make full use of, which is good for long term metrics persistence and visualization.

Example: Console Sink, simply output the metrics like DAG schedule to console:

-- Gauges ----------------------------------------------------------------------
DAGScheduler.job.activeJobs
             value = 0
DAGScheduler.job.allJobs
             value = 0
DAGScheduler.stage.failedStages
             value = 0
DAGScheduler.stage.runningStages
             value = 0
DAGScheduler.stage.waitingStages
             value = 0
application_1456611008120_0001.driver.BlockManager.disk.diskSpaceUsed_MB
             value = 0
application_1456611008120_0001.driver.BlockManager.memory.maxMem_MB
             value = 14696
application_1456611008120_0001.driver.BlockManager.memory.memUsed_MB
             value = 0
application_1456611008120_0001.driver.BlockManager.memory.remainingMem_MB
             value = 14696
application_1456611008120_0001.driver.jvm.ConcurrentMarkSweep.count
             value = 1
application_1456611008120_0001.driver.jvm.ConcurrentMarkSweep.time
             value = 70
application_1456611008120_0001.driver.jvm.ParNew.count
             value = 3
application_1456611008120_0001.driver.jvm.ParNew.time
             value = 95
application_1456611008120_0001.driver.jvm.heap.committed
             value = 1037959168
application_1456611008120_0001.driver.jvm.heap.init
             value = 1073741824
application_1456611008120_0001.driver.jvm.heap.max
             value = 1037959168
application_1456611008120_0001.driver.jvm.heap.usage
             value = 0.08597782528570527
application_1456611008120_0001.driver.jvm.heap.used
             value = 89241472
application_1456611008120_0001.driver.jvm.non-heap.committed
             value = 65675264
application_1456611008120_0001.driver.jvm.non-heap.init
             value = 24313856
application_1456611008120_0001.driver.jvm.non-heap.max
             value = 587202560
application_1456611008120_0001.driver.jvm.non-heap.usage
             value = 0.1083536148071289
application_1456611008120_0001.driver.jvm.non-heap.used
             value = 63626104
application_1456611008120_0001.driver.jvm.pools.CMS-Old-Gen.committed
             value = 715849728
application_1456611008120_0001.driver.jvm.pools.CMS-Old-Gen.init
             value = 715849728
application_1456611008120_0001.driver.jvm.pools.CMS-Old-Gen.max
             value = 715849728
application_1456611008120_0001.driver.jvm.pools.CMS-Old-Gen.usage
             value = 0.012926198946603497
application_1456611008120_0001.driver.jvm.pools.CMS-Old-Gen.used
             value = 9253216
application_1456611008120_0001.driver.jvm.pools.CMS-Perm-Gen.committed
             value = 63119360
application_1456611008120_0001.driver.jvm.pools.CMS-Perm-Gen.init
             value = 21757952
application_1456611008120_0001.driver.jvm.pools.CMS-Perm-Gen.max
             value = 536870912
application_1456611008120_0001.driver.jvm.pools.CMS-Perm-Gen.usage
             value = 0.11523525416851044
application_1456611008120_0001.driver.jvm.pools.CMS-Perm-Gen.used
             value = 61869968
application_1456611008120_0001.driver.jvm.pools.Code-Cache.committed
             value = 2555904
application_1456611008120_0001.driver.jvm.pools.Code-Cache.init
             value = 2555904
application_1456611008120_0001.driver.jvm.pools.Code-Cache.max
             value = 50331648
application_1456611008120_0001.driver.jvm.pools.Code-Cache.usage
             value = 0.035198211669921875
application_1456611008120_0001.driver.jvm.pools.Code-Cache.used
             value = 1771584
application_1456611008120_0001.driver.jvm.pools.Par-Eden-Space.committed
             value = 286326784
application_1456611008120_0001.driver.jvm.pools.Par-Eden-Space.init
             value = 286326784
application_1456611008120_0001.driver.jvm.pools.Par-Eden-Space.max
             value = 286326784
application_1456611008120_0001.driver.jvm.pools.Par-Eden-Space.usage
             value = 0.23214205486274034
application_1456611008120_0001.driver.jvm.pools.Par-Eden-Space.used
             value = 66468488
application_1456611008120_0001.driver.jvm.pools.Par-Survivor-Space.committed
             value = 35782656
application_1456611008120_0001.driver.jvm.pools.Par-Survivor-Space.init
             value = 35782656
application_1456611008120_0001.driver.jvm.pools.Par-Survivor-Space.max
             value = 35782656
application_1456611008120_0001.driver.jvm.pools.Par-Survivor-Space.usage
             value = 0.3778301979595925
application_1456611008120_0001.driver.jvm.pools.Par-Survivor-Space.used
             value = 13519768
application_1456611008120_0001.driver.jvm.total.committed
             value = 1103634432
application_1456611008120_0001.driver.jvm.total.init
             value = 1098055680
application_1456611008120_0001.driver.jvm.total.max
             value = 1625161728
application_1456611008120_0001.driver.jvm.total.used
             value = 152898864

-- Timers ----------------------------------------------------------------------
DAGScheduler.messageProcessingTime
             count = 4
         mean rate = 0.13 calls/second
     1-minute rate = 0.05 calls/second
     5-minute rate = 0.01 calls/second
    15-minute rate = 0.00 calls/second
               min = 0.06 milliseconds
               max = 6.99 milliseconds
              mean = 1.78 milliseconds
            stddev = 2.98 milliseconds
            median = 0.07 milliseconds
              75% <= 0.08 milliseconds
              95% <= 6.99 milliseconds
              98% <= 6.99 milliseconds
              99% <= 6.99 milliseconds
            99.9% <= 6.99 milliseconds

Load the metrics config:

1. Create a metrics config file: /tmp/metrics.properties based on the template in bootstrap step.

2. Configure to load the file when starting Spark:

  • command line: appending –conf “spark.metrics.conf=/tmp/metrics.properties” doesn’t work, didn’t try -Dspark.metrics.conf=/tmp/metrics.properties
  • an alternative is –files=/path/to/metrics.properties –conf spark.metrics.conf=metrics.properties, have’t try that
  • in code: .set(“spark.metrics.conf”, “/tmp/metrics.properties”), this works

Metrics config file example:

*.sink.console.class=org.apache.spark.metrics.sink.ConsoleSink
*.sink.console.period=20
*.sink.console.unit=seconds
master.sink.console.period=15
master.sink.console.unit=seconds

*.sink.csv.class=org.apache.spark.metrics.sink.CsvSink
*.sink.csv.period=1
*.sink.csv.unit=minutes
*.sink.csv.directory=/tmp/metrics/csv
worker.sink.csv.period=1
worker.sink.csv.unit=minutes

*.sink.slf4j.class=org.apache.spark.metrics.sink.Slf4jSink
*.sink.slf4j.period=1
*.sink.slf4j.unit=minutes

Coda Hale Metrics Library also supports Graphite Sink (link1link2), which stores numeric time-series data and render graphs of them on demand.

Notes: Spark metrics

Graphite consists of 3 software components:

  • carbon – a Twisted daemon that listens for time-series data, which receives the data published by Graphite Sink on EMR
  • whisper – a simple database library for storing time-series data (similar in design to RRD)
  • graphite webapp – A Django webapp that renders graphs on-demand using Cairo

Notes: Spark metrics

2. Sync Existing Metrics Data to S3 (not recommended)

  • step 1, write some bootstrap scripts to sync metrics data from EMR cluster to S3 incrementally and timely
  • step 2, after that, a possible way to restore these metrics in the same visualization is having a tool (environment) to download it from S3 and start the Spark History Server on it, or we can make some simple tool to get and analyze the metrics on S3

Other metrics data:

  • OS profiling tools such as dstat, iostat, and iotop can provide fine-grained profiling on individual nodes.
  • JVM utilities such as jstack for providing stack traces, jmap for creating heap-dumps, jstat for reporting time-series statistics and jconsole.

II. EMR cluster/instance Metrics

What metrics we already have even clusters are terminated?

1. EMR cluster monitor

  • Cluster status – idle/running/failed
  • Map/Reduce – map task running/remaining …
  • Node status – core/task/data nodes …
  • IO – S3/HDFS r & w

Notes: Spark metrics

2. CloudWatch

link

Basic monitor and alert system built based on SNS is already itegrated.

Notes: Spark metrics

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接《四火的唠叨》

分享到:
02 Mar 01:36

中国房价可以闻到一股雾霾的味道

by 墙外仙

很多网友在后台留言,希望草哥能就最近火热的楼市谈谈看法。本来不想就这个话题进行评说,中国的楼市和股市,就是两朵中国特色的奇葩,是向老百姓吸血的两大合法工具,其中绑架了太多的利益关系和政治因素,其走势本来就是畸形和变态的。

很多预言房价要暴跌的经济专家,面对节节攀升的楼市,都成了炮灰和先烈,比如中国社科院的那个易宪容,五六年前,就通过晚上观察居民区的亮灯率来判断中国楼市过剩,预言房价要大跌,结果现在都被骂得不敢出来说话了。

像易宪容这样的经济学家,自己在京城有别墅还出来预警楼市泡沫的,算是有点良知,最可恨的是那些说假话的经济学家,比如瑞信某姓陶的首席经济分析师,面对中国经济不断下行的现状,竟然不言不惭地说是因为中国进入了“后工业化时代”,这是典型的为了取悦主政者而说假话了。稍微用屁股想想都知道,面对一个一半以上人口是农民,城镇化率只有50%多,经济增长只能靠盖楼来拉动的经济体,你跟我说这是“后工业化时代”?你不能因为其他工业生产和出口企业都断崖式下跌了,都死得差不多了,留下一点居民消费、网络购物和服务业,比重被动地超过了50%,就说转型成功了,就说我们是“后工业社会了”,这是自欺欺人。做经济学家预测错了没关系,但不能厚着脸皮说假话。

继续回到楼市这个话题上。尽管很多唱空楼市的专家都不敢出来说话了,但有一点我们必须承认,中国房子经过这么多年的跨越式发展确实过剩了,现在不管媒体还是官方,习惯用7亿平米来概括当下的楼市存货,但按照北师大教授钟伟的测算,这严重低估了楼市的库存,因为这7亿平米仅是待售面积,如果再加上商品房之外的保障房,公用建筑和单位自建等,那么面积可能超过80亿平米,仅商品房在建和待售就超过50亿平米。

按照2015年全国销售面积10亿多平米算,这些库存要5-7年才能消化掉。

既然房产库存这么庞大,中央政府也在不断强调房产去库存,那为何房价一直降不下来呢?即使是三四线城市的房价,也丝毫未有松动迹象。学过马克思主义经济学的应该还记得有这么一个场景,资本主义经济大萧条时,资本家宁可把牛奶倒进水沟,也不让那些穷人喝到便宜和免费的牛奶,这是资本家的本性决定的。中国的地产商比资本主义的资本家更加“为富不仁”,反正钱是银行的,地是抵押的,盖房的钱是欠着民工的,要死一起死,反正就是捂着不降价。

买房者不买,卖房者不降 ,房产销量持续低迷,房价居高不小,这样的僵持状态大概持续了2014-2015年的2年,库存去不了,土地卖不掉,这时,政府着急了,必须出大招,一方面让让房价稳中有升,一方面刺激买房者要赶紧动手买房,于是有了2016年年初的首付比例下降,税费下降,央妈放水等措施。于是我们看到了北京上海深圳南京杭州等城市房价的爆发式上涨。

当然,这些都是表象,表象背后的决策逻辑是什么?我们必须搞清楚才不至于雾里看花。草哥不想去预测房价接下来是涨是跌,“泡沫之后必然破裂,没有永远上涨的价格”,“东京香港房价泡沫破灭了,所以上海北京房价必须破灭”,这样的鬼话谁都会说,但没有任何意义,就像大家都会说“人总会死的”没有意义一样,关键在于你要知道某个人什么时间会死,那才牛X,那你就是先知了。

草哥没有做先知的天赋,所以只能从经济常识和决策者的逻辑来判断这轮房价上涨的本质。在中国,任何一个大的经济事件和经济现象背后,都有老大哥的身影,都会体现老大哥的意图和意志,相对市场,老大哥掌握的资源动员能力实在太多太丰富了。2015年的那轮5000点的牛市,就是因为老大哥想借助资本市场,拉升资产价格,以达到振兴实体经济和拉动GDP的目的,但结果失败了。这也说明一个道理,谋事在人,成事在天,虽然老大哥的资源动员能力很强大,但也不是想做什么就能做成的。

老大哥为何要在去杠杆、去库存、去产能的这个时候发动这轮房价上涨呢?说到底还是因为整个经济系统和金融系统的风险马上要爆发了。经济方面,PPI连续经历48个月的断崖式下跌;占GDP三分之一的外贸出口基本没有了,甚至成了负数;大部分制造业工厂都关闭,钢铁、化工、水泥等重化工业基本全军覆灭;消费在勉强地支撑着,但这只是人们维持基本的生存所必需,随着经济的下滑,消费早晚也会下跌的。

经济方面的下滑必然传导到金融系统,而金融系统的最大危机,不在股市,不在居民住房贷款,也不在企业贷款,而在地方政府债务。根据中国社科院的数据和估算,地方政府债务规模大概是30万亿左右,这还只是审计数据,而不是财务意义上的数据,地方政府的债务规模到底有多大,只有天知道。这是一个黑洞,也是一颗定时炸弹,一旦处理不当,真有可能爆发系统性风险。除了政府信誉受损之外,整个银行系统都可能崩溃。

而且,这些地方债务90%以上是以政府财政收入信用,或以相应资产收益作为担保或抵押的形式存在,融资对象是银行、信托、证券、保险等机构。融资渠道包括融资平台贷款、信托融资、城投债等。在平台贷监管收紧后,一些地方变相融资的行为愈演愈烈,不少地方通过信托贷款、融资租赁、售后回租、发行理财产品、垫资施工等方式变相融资。

如何化解这些风险呢?当时决策者有几种设想,一种是央行直接出钱购买地方政府的债务,印钞票给银行。毫无疑问,这就是赤裸裸的量化宽松,这样做的结果就是货币贬值,通货膨胀;第二种方案是地方政府和银行商量,债务延期。但延期并不利于债务总量的控制,也许债务会越滚越大,这个方案也被否决;第三种方案就是债务置换,发新债,还旧债,但是时限拉长,利息降低。

现在走的就是第三种方案,2015年的债务置换还比较顺利,一些银行因为第一次跟地方政府过招,都比较听话。但随着债务置换规模越来越大,银行和地方政府的博弈也进一步升级。财政部要求2016年地方债置换与新增规模将达到6万亿,三季度前完成置换。有条件的地方,可以提前置换,可以全部置换。一些地方这些乐了,赶紧把以前五花八门的债务都拿来置换。

但银行也不是傻瓜啊,很多都是上市公司,它们也有利润和绩效考核啊。这时,银行反应过来了,你地方政府这是得寸进尺啊,你违约在先,还不了债务,现在还出个主意要换成利息更低,期限更长的,而且逐渐置换还不行,要求超额提前置换,那我银行肯定不干啊。于是,很多银行在跟地方政府博弈过程中,协商说,你不要置换了,还是给你延期吧,利息还是按照原来的。通过条款设计,恩威并用,让地方政府选择延期而非置换,银行其实想走的是第二种方案。

眼看房地产不行了,土地卖不出去了,财政税收逐年下滑,债务成本越来越高,银行又不想债务置换了,这时,地方政府快扛不住了,再僵持下去也不行啊。要知道,95%的省会直辖市是靠土地偿地方债的,一大半的债务要靠土地财政来偿还。为了让债务置换顺利推进,为了让整个风险地雷不被引爆,有高人出了一计:货币放水,拉高或稳定资产价格,营造债务置换的宽松环境。

接下来就是我们看到的,1月新增贷款2.51万亿,创历史新高,大部分进入地产和投资领域;紧接着房贷首付比例下降,交易税费下降;北京上海深圳南京杭州等一线城市房价大涨。

降低首付其实就相当于增加杠杆,为什么在宏观层面降杠杆、去产能、去库存的形势下,要让房地产提高杠杆呢?从周小川在G20上海财长会议上支持房产贷款提高杠杆的表态中,你听出了其言外之意吗?

在整个银行贷款中,房贷是最优质的资产,坏账率最低,这说明中国的老百姓是最守信用的,他们宁可省吃俭用,整天吃方便面,都不愿意拖欠银行的欠款。操盘者想来想去,只有这块可以用来提高一下杠杆了,于是就各种救市措施纷纷出台。

但我们应该看明白,这次救市,救的不是房地产,而是地方政府,是地方政府的30万亿债务,是政府的信誉和中国整个金融系统。只有房产价格上升了,其他资产价格也跟着上升或保持稳定,抵押品的估价也上升了,政府的土地好卖了,财政税收也上升了,跟银行的议价能力就上升了,债务置换也就顺利了。

很多地方政府的身家性命,都取决于房地产价格,房价一旦下跌,意味着土地价格和其他资产价格也一并下跌,土地卖不出去,抵押品价格下跌,这会导致政府信用违约,银行逼债。接下来的后果大家可以想象,地方政府一旦违约,将纷纷成为被告,信用扫地,这一现象政府愿意看到吗?

有个段子很深刻地揭示了中国经济的这种尴尬:过去24个月里,我们把美利坚曾经奏效的救世良方挨个体验了一遍:1:凯恩斯主义的政府刺激需求;2:马歇尔计划的一带一路;3:克林顿的互联网加万众创新;4:弗里德曼的货币供给理论;5:里根的供给侧改革,哦还有熔断制;最后又回到我们熟悉而且的房地产拉动经济上……

只不过,老大哥的这一如意算盘,被市场充分利用了,一些房地产中介不惜做起了房产做市商的事情,搭建金融平台,勾结一些企业,通过拉高房价向银行骗取低息贷款,以缓解资金压力,你政府敢放3倍的杠杆,我就敢放到5倍、10倍,结果是促使房价迅速拉高。这一结果显然是老大哥不愿意看到的,因为任何疯牛导致的结果必然是断崖式下跌,中国经济能承受得了吗??

镜像链接:谷歌镜像 | 亚马逊镜像

相关日志

01 Mar 06:37

日本旅游印象与杂想(住房、老龄化、供给侧改革等)

by 墙外仙

陈杰(上海财经大学教授)

2月9-14日赴日本东京-京都-大阪自费旅游,走马观花,一晃而过。虽然感想比较零碎,但觉得还是有必要乘热打铁,把所见所闻所想先写出来分享一下。仅供参考。

凭着导游一路上的唠叨作为索引,结合随手查了点资料,初步整理零碎如下。

日本三少

1、垃圾桶少。日本街头很干净,但几乎看不到垃圾桶,垃圾都要拿回家或酒店房间处理。便利店门口也有垃圾分类回收箱。日本的垃圾分类做的非常细致。

2、警察少。日本治安在发达国家属于最好之列。与技术先进、到处有摄像头有关,但根本上还是平均教育程度高、收入分配平均有保障。然后跟循规蹈矩的文化也有关系。

3、小孩少。日本少子化严重,2014年生育率只有1.42,尽管有很多生育刺激政策,但都收效甚微。其实不仅日本,韩国、香港、台湾,整个东亚都面临生育率过低的困扰。

日本三多

1、自动售货机多。号称日本拥有世界上最多的自动售货机。

2、乌鸦多。乌鸦在日本视为吉祥之物和神鸟,号称日本国鸟,大城市里和乡村都乌鸦集群。

3、老人多。日本女性预期寿命为87岁,世界首位,男性为80岁,世界第四。与少子化和长寿相伴随的是老龄化。每四个日本人之中就有1个是65岁以上老人(中国目前是10%左右)。满街白发苍苍,限于劳动力不足,很多服务业从业人员都是老人。不少旅游大巴司机都是70岁以上老人。据测算,如果少子化和老龄化趋势不逆转,日本2050年65岁以上人口将占到40%,总人口也将只有9500万人,比现在的1.27亿人少了30%以上。不过日本公交上没有给老人让座的习惯,老人也没有要别人照顾的心理,日本人心理是十分讲究不给别人带来麻烦,但同时也不愿意别人给自己带来麻烦。所以是一个人情比较冷漠的社会。

日本三怪

1.猫头鹰是吉祥物,代表富裕长寿,也是学问的象征。

2.青蛙也是吉祥物,称赞有活力小孩子为青蛙。

3.猪是夸人语。猪在日本专指野猪,家猪是“豚”。野猪象征着勇敢顽强,说谁像猪就等于在夸他。据说日本人新年时喜欢用“猪突猛进”、“猪突猛伸”的新年贺词,指祝对方奋发图强有所进步。日本游戏里武将的性格属性有“猪突”这个设定项,含义是作战勇猛无前。

日本纸币(2004版)上的三个人物

1.1万日元上的头像是福泽渝吉,日本历史上著名的思想家、哲学家,明治三杰之一,“明治维新”的推动者,是日本“脱亚入欧”战略的倡导者,主张人人平等,并创立了日本著名的私立大学庆应大学。

2.5000日元上的头像是樋口一叶,一位女性,十九世纪日本著名平民作家,日本近代现实主义文学的早期开拓者之一。24岁死于肺结核,在她短短的人生中,创作了大量反映当时社会现状、关注日本女性的优秀作品,成为当时妇女社会角色变化的先驱。

3.1000日元上的头像是野口英世,日本著名的生物学家。曾在南美和非洲从事研究工作,曾获诺贝尔医学和生物学奖提名,在研究黄热病的时候感染去世。他的碑文上写着:“他毕生致力于科学,他为人类而生,为人类而死”。

日本现在的纸币上,没有天皇也没有政治家、官员的影子,而是思想家、作家和科学家的头像,其中包括一位女性。这可以说明日本当今社会的很多价值观。不过二战前日本的纸币也是大量有天皇、贵族和武将,二战后首相和政治家居多,后来学者和文人越来越多。从中也可以反映日本价值观的历史演变。

日本的三个震撼

1、物价低。日本一直给世人一个印象是一个物价极高的国家,普通中国人肯定消费不起。但近两年中国赴日本旅游持续火爆,在2014年大增84%的基础上,2015年继续翻倍,达到500万人之多,超过韩国人成为来日本旅游最多的外国人,占了日本去年1973万游客的四分之一强。据日本国家旅游局统计,2015年访日游客的旅游消费额达到创历史记录的3.47万亿日元(按近日汇率约合2000亿元人民币,100日元约合5.7元人民币),几乎可以与日本电子零部件或汽车零部件的全球出口额相仿,成为日本经济的一大支柱。中国赴日本旅游火爆有多方面原因,包括日本环境好、职业精神好、服务规范和治安好,但突然这么火爆的根本原因是日元自安倍任首相以来的持续大幅贬值,所以2015年赴日本旅游人数整体增长了47%,其中来自韩国的游客也增长了45%,全世界人都喜欢到日本旅游了。但游客增量最主要来自中国,这显然归功于中国人民币对日元的持续坚挺。2012年时候,100日元可兑换8元人民币,2013年就只有7元,2014年变成6元,2015年最低时候曾一度跌到5元,相当于人民币三年内对日元升值60%多。”爆买”成为日本2015年度热词,主要就是指中国人在日本大幅扫货。根据日本国家旅游局统计,2015年中国游客在日平均消费金额为28.1万日元(约合1.46万元人民币),远高出外国游客平均消费金额的18.7万日元(约合9700元人民币),据此推算,2015年国际游客在日本消费的2000亿元人民币中大约有1/3来自中国,这显然是因为有很多商品在日本购买比国内购买更划算。

此次旅游团里有些团员是几度来日本了,仍然大肆扫货家用电器及小家电、化妆品和医药保健品等,口里说着买买买、便宜便宜,与此对应,几乎每个大型免税商场的热销专柜都有操熟练中文的售货员或导购。自己亲身感觉日本很多日常消费的物价也便宜得远超出预期。比如商场很多衣服比中国商场便宜多,几次自费用餐,在东京、静冈县滨松市和大阪关西机场,一份普通快餐如味千拉面那样在500-650日元之间(折合人民币28-37元),上海很难比这个价格更低,尤其如果在上海的机场还要高出不少。就大家最关心的房价来看,从日本房产中介网上自己查了一下,在东京23区中最繁荣的商务区新宿区,每平方米约100-150万日元(5.7-9万元人民币),富人最集中的港区每平方米约200万日元(11万元人民币),这些价格在上海如今都不算特别天价了。而且日本其他城市就比东京便宜多了。当然,日本肯定还有不少东西比中国贵,比如出租车、理发等人工服务,蔬菜也比较贵,地铁和公交也是上海好几倍,虽然公办教育免费到高中,但如果上私立学校或补习费用都很高。各类养老和医疗保险费用也必将高。租金也比较高,在东京街头看了几个租房广告,60-70平方米的房子月租金在1.5万元人民币左右,一般刚工作的年轻人都是租房,租房也都很小,有20-30平方米就很好了。另外,虽然日本房价收入比的值比中国合理很多,但还是属于偏高的,加上生活费用高,日本人在30岁甚至35岁前能买上能买上自己房子也很少。但整体上,以日本比中国高那么多的人工成本和土地成本,却产出了大量廉价产品和消费性服务,同时还有很多独一无二的创新产品,值得中国深刻反思“供给侧”到底出了什么问题,该如何具体“降成本”。

2. 密度低。想象中日本东京等大城市中到底是像香港那样的黑压压让人窒息的高楼林立,或起码上海这样的处处水泥森林。日本国土面积37.8万平方公里,与我国云南省面积最接近。日本人口1.27亿,人口密度338人/平方公里,低于江苏(792人/平方公里),也低于中国整个东部人口密度(约460人/平方公里),在5000万以上的人口大国中也低于孟加拉(1023人/平方公里)和印度(390人/平方公里),中国大陆的人口密度是138人/平方公里。但日本山地和丘陵占全国面积的71%,平原面积只有11.02万平方公里,所以如果按照人口生理密度(总人口与可耕地面积之比)来比,日本为2924人/平方公里,在人口大国中仅略低于韩国的2988人/平方公里,但远高于中国大陆的943人/平方公里,印度则是753人/平方公里,美国是179人/平方公里。按照这个标准看,日本应该比国内人多地少多多了。但到日本一个强烈震撼是,建筑密度比国内大城市低多了。中国城市到处是高层鸽子笼式的公寓住宅。但在日本,即使在东京银座,高楼也不是很多,即使在东京、京都、大阪和名古屋的市区,居民住宅也到处是二三层独栋小楼(一户建),公寓楼不是很多,即使公寓楼也没有太多高层的,4-6楼为主。

查看了2013年日本住房与土地调查,2013年日本共有5210万套住宅,其中一户建住宅为2860万套,一户建占了55%,公寓住宅有2209万套,占了42%。在大都市圈区域,一户建比例有所下降,但也占到了41%,公寓比例上升到56%。但一户建面积明显较大,套均面积为128-130平方米(使用面积),公寓套均则只有49-53平方米(前者为全日本,后者为大都市区域),加权后日本全国所有住宅的套均面积为94.4平方米,大都市区域为86.1平方米。但一户建主要给多人家庭住,公寓主要是单身汉和无孩子家庭住,一户建套均2.81人,公寓套均就只有1.87人,全部住宅套均2.40人。简单推算,日本人均居住面积约为39平方米。跟欧洲发达国家差不多的水平。如果只算有孩子家庭通常都会居住的一户建,人均居住面积可以达到46平方米,略高于瑞典、挪威、德国等欧洲最发达国家水平(人均40-44平方米)。作为对比,中国城镇人均住房建筑面积目前大概34平方米,折算为居住面积则只有26平方米,差距很大。

有位中国社科院的前辈几次开会遇到时候都一直念叨,为什么土地资源更紧张的日本人能大部分家庭住一户建,中国人却只能住鸽子笼,愤愤不平。当时对这个说法还没有什么感觉,现在想来确实值得深思。在条件允许情况下,独栋住宅居住确实比公寓更符合人性。问题就是在日本这么稠密的土地上为什么能比中国居住条件还好,特别还能做到大多数家庭住独栋住宅。

初步几个想法或猜想:1、日本土地私人所有,自由化的土地市场对需求反应灵活,供给有弹性。对土地利用的限制也少,土地用途转换容易。另外买地后就可以自己盖房子,不需要通过开发商;2、日本已经去工业化,即使工业化时期也主要是土地节约型的精密制造业,城市建设用地基本用来居住,占60-70%,中国还处于高速工业化时期,加上地方政府主导的以工业园区这样的土地粗放型工业化,城市建设用地中工业用地比重很大,居住用地只有30%多;3、日本城市摊大饼、层层往外,市区用地物尽其用,中国城市是先圈地,看起来很大,但城市内部其实很空,市区很多土地其实闲置很厉害,有效利用不足;4、日本城市居住建筑感觉规划限制少,一眼望过去,基本上没有楼间距,密密麻麻,挨着非常紧密。中国高层住宅规划管制限制很多,日照间距、朝向要求等等,导致公寓高归高,地面空地还是很多,城市居住土地利用率不一定有日本高;5、中国公寓往往都是大型小区开发,再用围墙分割,围墙之外都是大马路,交通用地很多,交通效率还不佳。日本独栋小楼之间都是毛细血管式道路连接,大马路少,交通反而还比较通畅。6、日本公共服务比较均等化,优质教育和医疗资源不会集中在市中心少数地块,分布比较均匀,也拉开了城市居住空间的辐射式扩张。所以城市内部真实有效的住宅建筑密度,或城市居住土地的有效容积率,日本城市很可能不比中国城市低反而高。比如东京核心区面积2188平方公里,居住了1300多万人,密度达到6000人/平方公里,高于上海全市的3700人/平方公里。

3、收入差距小。日本是公认的既高度发达又贫富差距很小的国家,可以与北欧媲美。看到一个资料,日本2004年收入基尼系数0.308,不算特别低,但支出的基尼系数只有0.163,这说明税收和转移支付大大改善了收入分配。区域之间的差距也很小,据说东京和冲绳的人均收入差距也只有2倍。根据一份日本国税厅2012年的调查,日本就业者平均年收入为408万日元(按今年汇率折算约24万元人民币),但年收入高于700万日元(约42万元人民币)仅有12%,一般认为年收入超过550万日元(约合30万人民币)就算中产阶层了。不过性别差别还比较大,男性就业者平均年收入为30万人民币,但女性就只有16万元人民币了。从职业上看,根据日本厚生省2008年的调查数据,大公司高级白领、医生和大学教授的年收入都大概是1100万日元(约合63万元人民币)的水平,大学副教授、警察官和律师基本是800万日元的水平(约46万元人民币),大学讲师、中小学教师、普通公务员基本是700万日元(约40万元人民币)的水平,电车司机大概是600万日元的水平(约34万元人民币)。这些都算不错的高薪职业了。另外,日本公司里的工资严重按资排辈,普通公司的社员20-29岁的平均年收入是349万日元,30-39岁是458万日元,40-49岁是598万日元,50-59岁是756万日元,基本上每10年相差100-150万日元。

从家庭收入来看,根据日本统计年鉴,2015年日本家庭年收入中位数大概在450万日元(约26万元人民币),只有20%的家庭年收入高于800万日元(约46万元人民币),高于1000万日元(约57万元人民币)的家庭不到11%,同时也只有10%多点的家庭年收入低于200万日元(约12万元人民币),最高10%和最低10%收入家庭的收入比在5倍左右,最高20%和最低20%收入家庭的收入比大概在3倍左右。中国情况是,根据中国统计年鉴2014,2014年中国最高20%收入家庭的人均可支配收入是最低20%收入家庭的11倍(50968元vs. 4747元)。即使抛开城乡差距,城镇居民最高20%收入家庭的人均可支配收入也是最低20%收入家庭的5.5倍之多(61615元vs. 11219元)。

日本行的三个联想

1、老龄化是发展大敌。日本从1991年地产与股市泡沫破灭之后25年经济一直停滞,普通家庭的工资和收入这25来年不仅不增长反而基本在持续倒退,日本工薪家庭月收入在1991年就为54.9万日元,1997年达到历史最高的59.5万日元,此后基本上在负增长,2015年就只剩下46.9万日元,25年内以本币计算反而负增长了15%。虽然由于日本这段时间一直基本在通缩,生活质量没有明显下降,但相对日本50-80年代的高速增长,毫无疑问经济明显失去了活力。这固然有宏观政策和产业政策失误,但根本上来说是人口红利消失、人口老龄化导致经济增长失去原始动力,日元大幅升值带来地产与股市泡沫破灭只是一个触发点。日本1994年65岁以上人口超过14%,正式进入老龄社会,现在日本超过25%,称为“超老龄化”社会。2008年之后日本人口就开始负增长,由于劳动力严重短缺,2014年日本65-69岁老人41%还在工作,农业劳动力平均年龄超过67岁,连自卫队的平均年龄都超过38岁了。超老龄化给日本社会的养老、社保和医疗保障带来严重压力,消费乏力,投资衰竭,也从根本上窒息了经济发展的动力和活力。

但日本幸运的是先富后老,目前和可见未来日本老人还能享受较高水平的生活质量和福利保障。在严格计划生育带来的过度少子化驱动下,中国老龄化势头一点不比日本轻,已经是明显的未富先老。中国65岁以上人口的比例2014年为10.1%,似乎还没有进入老龄化社会,但由于中国退休年龄早,60岁以上老年人就业率很低,而且健康状况也比发达国家差,如果按照60岁人口作为老龄人,2014中国这部分人口比例已经达到15.5%。但中国明显还没有做好应付老龄化的准备。而且目前的老龄化程度只是冰山一角,更可怕的是生育率超低,预示老龄化不断加速的未来。自1991年开始中国的总和生育率已经连续24年低于世代更替水平,根据2010年第六次人口普查,中国总和生育率仅为1.18,比日本的1.42还要低很多。如果超低生育率不能根本扭转,据世界卫生组织预测,到2050年中国将有35%的人口超过60岁,成为世界上老龄化最严重的国家之一。

虽然去年放开了全面二胎政策,但多数人口学家认为已经为时已晚,低生育文化已经形成,后工业化和城市化加速的背景下,生育直接成本和机会成本又十分高昂,继续扼杀生育意愿。如果在产假、儿童看顾、幼托小学、抚养费用等方面政府没有实质扶助政策,中国生育率很难有实质反弹。但一些大城市却没有认真应对老龄化。以上海为例,反而得意洋洋大张旗鼓提出在十三五末(2020年)控制人口规模在2500万人,相对2014年的2426万只有70多万的增量。且不说这能否实现,上海2010-2014年净增加了120万人(还是在2013-2014年人口增量异乎寻常低的背景下),如果实现了只可能是统计上的胜利,就算真的实现了,带来的直接结果就是上海老龄化雪上加霜。上海目前1400多万户籍人口中每三个就有一个是60岁以上老人,已经进入“深度老龄化”社会,2020年预计户籍人口60岁以上比例将接近40%。一直以来,上海都是靠大量外来年轻人口的冲销才让常住人口的老龄化程度反而比全国还略低,同时能维持养老和社保体系不崩溃,如上海劳动力中外来人口已经超过一半。但如果严格控制人口流入,户籍老年人是不会流出去,外来年轻人又不能流入,老龄化加速,只能让上海养老和社保提前面临灾难性后果。

2、产业升级靠降成本和创新两手抓。日本和海外旅游火爆背后反映的国内与海外商品价格倒挂,已经有很多论述,一般认为有几个原因:1、国内物流和商铺经营成本高,2、国内税费高,包括奢侈品和高价品的高关税,3、知名品牌在国内海外的定价差异化策略(价格歧视),比如优衣库在中国价格比日本贵很多。但不管哪个角度都意味着国内成本太高,遏制了消费。现在流行说供给侧改革,我以为供给侧改革包括两个方面,一个是压缩成本来刺激需求,另一个是产品创新来满足潜在需求。在压缩成本方面当然还大有可为,但光降成本,如果生产出来的东西没有需求,再低成本也是没有意义。计划经济最大的一个教训是,产商如果光从自己角度出发不结合市场需求,做的再精致也没有意义。供给侧一刻不能脱离需求,必须以客户需求为导向,以市场需求为中心,迎合市场需求,发掘市场潜在需求。谁能把潜在需求发掘出来,发现蓝海,谁就为王。以电影产业为例,2013-2015年电影票房连续迈过200亿元、300亿元和440亿元大关,国产电影占六成,今年电影票房继续井喷,春节档票房35亿元,同比增长90%,今年中国电影票房有望逼近甚至超过美国票房,成为全球第一并不遥远。电影产业的高速增长是近几年中国经济不多的亮点,其原因一方面是票价稳定、相对收入比例越来越低,另一方面是大量民营资本进入,影院大幅度增加、全面走向三四线城市,同时大量人才和技术涌入,以满足市场需求为导向,质量提升、类型创新、产能优化,提供了丰富和适应市场需求的新产品。

3、消费升级提供机遇。根据中国国家旅游总局数据,2015年总计有1.2亿人次中国游客出境旅游,在世界各地的旅游消费达1940亿美元(折合1.2万亿人民币),近几年每年都是近30%的增长率。看到最新统计,光今年春节中国人就有600万人出境游,海外旅游消费达到900亿元人民币。另外中国海外代购规模现在每年也在1000亿规模。加上电影票房的持续火爆,都说明了随着中国迈入中等收入国家行列,数亿中国中产阶层正在进入消费升级时代。中国城镇居民的基尼系数已经在0.35以下,消费正在从生存型消费向享乐型消费快速升级。所以中国经济面临的问题不是需求不足,而是供给不足、供给错位。比如同样是旅游,国内旅游市场近年每年就只有最多10%的增长率,海外游客的入境游更是增长乏力,背后反映国内旅游产品与国内外需求的脱节,价高质差、缺少深度、缺乏新鲜。如果不及时实现国内产能升级和优化产能,国内蓬勃兴起的各种升级版消费需求会严重外溢到海外。不仅让人民币汇率贬值压力巨大,更让中国经济结构更加失衡、无法实现从投资驱动型向消费驱动型的经济增长模式转型。

国务院2015年11月下发《关于积极发挥新消费引领作用加快培育形成新供给新动力的指导意见》(2015[66]),指出“随着模仿型排浪式消费阶段的基本结束,个性化多样化消费渐成主流”,还指出“教育、健康、养老、文化、旅游等既满足人民生活质量改善需求、又有利于人力资本积累和社会创造力增强的服务消费迅速增长”。但偏偏教育、健康、养老、文化和旅游等产业,包括这些产业的基础设施提供,都还是国有资本垄断为主,民营资本很难进入,很难及时有效提供市场需求的产品与服务。

日本行的三个产业启发

1、循环经济产业。具体是城市垃圾回收、垃圾处理与资源再循环利用产业。日本垃圾回收的分类精细化给每位游客都留下深刻印象,一个饮料瓶要分成三部分进行回收:瓶盖、包装纸和瓶身。资源回收从业人员专业素质很高,工资也很高。而且垃圾处理多样化,经济效益高,比如东京的幕张、台场等很多新城镇新热门景点都是用垃圾填海造陆出来的。国内很多城市已经出现垃圾围城,现在已经开始注重垃圾回收与处理产业,如2月份北京控股公司以18亿欧元收购德国垃圾焚烧厂运营商EEW。垃圾回收、垃圾处理和资源再利用,潜在经济效益与社会效益都非常大,而且完全可以开发给民营资本来运作,是一大投资蓝海。

2、养老服务产业。老龄化已经不可避免,但既然来了,就顺势而为,老龄化中潜在经济效益也很大。日本有银发经济之说,老年人消费倾向不低,消费市场巨大。面对中国老龄化来临,为老年人重新改造住宅、家具、社区和道路,在互联网时代为老年人开发专门的APP,提供适合老年人的旅游、教育、文娱、医疗、理财和保险服务,包括倒按揭等形式的以房养老,都蕴育很多投资机会。

3、医疗健康产业。日本有很多养生保健产品,从食品到药品,全方面开发,品种很全,也有很多独特产品,对中国消费者吸引力很大。中国本土其实也有很多养生保健的特色资源,医疗也有很高水平,做大做强几个医院集团,放开医疗市场准入,让更多民营资本进入。大城市中好一点的三甲医院,天天人山人海,人满为患,都是优质医疗资源供给不足的表现。让医疗多层次和增加差异化,降低成本、增加品种、优化产能,满足中国居民随着收入而不断增长和不断细分的健康需求,既能为中国经济增长增加新动力,也是包容性增长的重要内容。

当然,除此之外,还有很多朝阳产业,比如文化、娱乐、教育、旅游等,都随着中国步入消费升级时代而有巨大潜力。

结语:

作为进入中等收入国家的标志,出境旅游应该成为国人的常态。读万卷书行万里路,多出去亲眼看看开阔眼界还是很重要。

镜像链接:谷歌镜像 | 亚马逊镜像

相关日志

01 Mar 03:15

Firefox 45 移除分頁群組, Tab Groups 讓你繼續使用

by esor huang
「分頁群組」是很多重度 Firefox 使用者愛用的功能之一,對於喜歡他的人來說,這是一個上網不可或缺的高效率工具,我很久之前也分享過自己利用他的心得:「Firefox 分頁群組有用嗎?重點不是分組而是隔離與專注」。

簡單的說, Firefox 的分頁群組可以幫我們在瀏覽器上「隔離出不同工作區」,我可以讓線上編輯工作、線上參考資料、社群聊天,或是工作與旅遊資料等不同的工作區分隔開來,不會所有的網頁全部擠在一條分頁列,這樣不僅雜亂也沒有效率。

可惜的是, Firefox 已經正式宣布在即將到來的 Firefox 45 穩定版中(目前是 Firefox 44 ),會移除「分頁群組」!這對使用他好幾年的用戶來說,無疑是很大的震撼,幸好,我們有非常完美的替代措施。



即將到來的 Firefox 45 中,一旦你更新,分頁群組功能會直接移除,如果你在之前版本中有使用分頁群組整理大量網頁,那麼這些網頁資料會依據你的分組匯入你的書籤,讓你還是可以保留資料(但那就不是分頁群組啦!)




那怎麼辦呢?幸好有第三方的開發者,利用分頁群組的原始碼打造了「幾乎一模一樣」的「分頁群組擴充套件」,叫做:「 Tab Groups 」

Firefox 使用者只要安裝這款「 Tab Groups 」套件,就會獲得和之前一模一樣的分頁群組功能,可以滑順拖曳把大量網頁分成不同工作區,也能幫工作區命名。

並且最新版「 Tab Groups 」中,已經內建繁體中文等語言,穩定度與速度也很棒。

我建議大家如果現在還在使用 Firefox 44 之前版本,並且有使用分頁群組功能的話,現在就先安裝「 Tab Groups 」套件,這樣到時候更新到 Firefox 45 時就能無痛轉移,不會感覺到分頁群組被移除了。




而且「 Tab Groups 」套件還有一些特色,例如可以自訂呼叫分頁群組、切換分頁群組的快捷鍵。

想要效能更快的話還可以關閉動畫功能。




「 Tab Groups 」也具備快速搜尋分頁的功能,支援中文關鍵字,對於打開大量網頁的朋友來說很有幫助。

而若是你想趁著 Firefox 45 移除分頁群組功能的時候,換一種整理大量分頁的方式,那麼我會推薦你另外一款也有在持續更新的擴充套件:「Tree Style Tab 樹狀圖分頁目錄管理,補強Firefox 4分頁群組」,他可以幫我們用側邊欄樹狀資料夾的方式整理分頁。

所以 Firefox 用戶別擔心,我們還是可以繼續善用分頁群組功能的!

延伸閱讀相關文章:

「 Tab Groups 」下載

轉貼本文時禁止修改,禁止商業使用,並且必須註明來自電腦玩物原創作者 esor huang(異塵行者),及附上原文連結:Firefox 45 移除分頁群組, Tab Groups 讓你繼續使用


喜歡這篇文章嗎?歡迎追蹤我的FacebookTwitterGoogle+,獲得更多有趣、實用的數位科技消息,更歡迎直接透過社群聯繫我。

25 Feb 03:52

2016年,大数据还是回事么?

by 董老师

本文中的Big Data Landscape图笔者分享在LinkedIn上,不晓得引起大量转发和评论,截止本周,得到6700个like,3800次share,400多条comment,笔者也觉得很神奇。这里就跟从事大数据或者投资领域的朋友推荐一下。原文作者是VC First Mark的Matt Turck,提下这一家VC,主要投资于早期阶段技术类公司的风险投资机构,包括新兴媒体、广告、游戏、教育、云计算、分析和基础设备等方向。大家熟悉的Airbnb,Pinterest,Shopify都有它的投资身影。

原文:http://mattturck.com/2016/02/01/big-data-landscape/

技术型的高科技创业公司都是喜欢闪闪发光的新东西,而“大数据”跟3年前火热程度相比反而有些凄惨。虽然Hadoop创建于2006年,在“大数据”的概念兴起到达白热化是在2011年至2014年期间,当时在媒体和行业面前,大数据就是“黑金石油”。但是现在有了某种高原感。 2015年数据世界中时尚年轻人喜欢转移到AI的相关概念,他们口味变成:机器智能,深度学习等。

2016年大数据还是“回事”么?让我们深度挖掘。

企业级技术 = 艰苦的工作

其实大数据有趣的是它不是直接可以炒作的东西。

能够获得广泛兴趣的产品和服务往往是那些人们可以触摸和感受到的,比如:移动应用,社交网络,可穿戴设备,虚拟现实等。

但大数据,从根本上说是“管道”。当然,大数据支持许多消费者或企业用户体验,但其核心是企业的技术:数据库,分析等:而这后面几乎没人能看到东西运行。

而且如果大家真正工作过的都知道,在企业中改造新技术并不大可能在一夜之间发生。

早年的大数据是在大型互联网公司中(特别是谷歌,雅虎,Facebook,Twitter,LinkedIn等),它们重度使用和推动大数据技术。这些公司突然面临着前所未有的数据量,没有以前的基础设施,并能招到一些最好的工程师,所以他们基本上是从零开始搭建他们所需要的技术。开源的风气迅速蔓延,大量的新技术与更广阔的世界共享。随着时间推移,其中一些工程师离开了大型网络公司,开始自己的大数据初创公司。其他的“数字原生”的公司,其中包括许多独角兽,开始面临跟大型互联网公司同样需求,无论有没有基础设施,它们都是这些大数据技术的早期采用者。而早期的成功导致更多的创业和风险投资。

现在一晃几年了,我们现在是有大得多而棘手的机会:数据技术通过更广泛从中型企业到非常大的跨国公司。不同的是“数字原生”的公司,不必从头开始做。他们也有很多损失:在绝大多数的公司,现有的技术基础设施“够用”。这些组织也明白,宜早不宜迟需要进化,但他们不会一夜之间淘汰并更换关键任务的系统。任何发展都需要过程,预算,项目管理,导航,部门部署,全面的安全审计等。大型企业会小心谨慎地让年轻的创业公司处理他们的基础设施的关键部分。而且,一些(大多数?)企业家压根不想把他们的数据迁移到云中,至少不是公有云。

1.jpg

(大数据分析的基本流程图)

从另一个关键点大家就明白了:大数据的成功是不是实现一小片技术(如Hadoop的或其他任何东西),而是需要放在一起的技术,人员,流程的流水线。你需要采集数据,存储数据,清理数据,查询数据,分析数据,可视化数据。这将由产品来完成,有些由人力来完成。一切都需要无缝集成。归根结底,对于这一切工作,整个公司,从高级管理人员开始,需要致力于建立一个数据驱动的文化,大数据不是小事,而是全局的事。

换句话说:这是大量艰苦的工作。

部署阶段

以上解释了为什么几年后,虽然很多高调的创业公司上线也拿到引人注目的风险投资,但只是到达大数据部署和早期成熟阶段。

2.png

更有远见的大公司(称他们为“尝鲜者”在传统的技术采用周期),在2011 - 2013年开始早期实验大数据技术,推出Hadoop系统,或尝试单点解决方案。他们招聘了形形色色的人,可能工作头衔以前不存在(如“数据科学家”或“首席数据官”)。他们通过各种努力,包括在一个中央储存库或“数据湖”倾倒所有的数据,有时希望魔术随之而来(通常没有)。他们逐步建立内部竞争力,与不同厂商尝试,部署到线上,讨论在企业范围内实施推广。在许多情况下,他们不知道下一个重要的拐点在哪里,经过几年建设大数据基础架构,从他们公司业务用户的角度来看,也没有那么多东西去显示它。但很多吃力不讨好的工作已经完成,而部署在核心架构之上的应用程序又要开始做了。

下一组的大公司(称他们为“早期大众”在传统的技术采用周期)一直呆在场边,还在迷惑的望着这整个大数据这玩意。直到最近,他们希望大供应商(例如IBM)提供一个一站式的解决方案,但它们知道不会很快出现。他们看大数据全局图很恐怖,就真的想知道是否要跟那些经常发音相同,也就凑齐解决方案的创业公司一起做。他们试图弄清楚他们是否应该按顺序并逐步工作,首先构建基础设施,然后再分析应用层,或在同一时间做所有的,还是等到更容易做的东西出现。

生态系统正在走向成熟

同时,创业公司/供应商方面,大数据公司整体第一波(那些成立于2009年至2013)现在已经融资多轮,扩大他们的规模,积累了早期部署的成功与失败教训,也提供更成熟,久经考验的产品。现在有少数是上市公司(包括HortonWorks和New Relic 它们的IPO在2014年12月),而其他(Cloudera,MongoDB的,等等)都融了数亿美元。

VC投资仍然充满活力,2016年前几个星期看到一些巨额融资的晚期大数据初创公司:DataDog(9400万),BloomReach(5600万),Qubole(3000万), PlaceIQ( 2500万)这些大数据初创公司在2015年收到的$ 66.4亿创业投资,占高科技投资总额的11%。

并购活动仍然不高(35次)。

随创业活动和资金的持续涌入,有些不错的资本退出,日益活跃的高科技巨头(亚马逊,谷歌和IBM),公司数量不断增加,这里就是2016年大数据全景图:

3.jpg

2016年2月12日修订,(本文最有价值的图,大图可以微信号donglaoshi-123回复big data下载)

很显然这里密密麻麻很多公司,从基本走势方面,动态的(创新,推出新的产品和公司)已逐渐从左向右移动,从基础设施层(开发人员/工程师)到分析层(数据科学家和分析师的世界)到应用层(商业用户和消费者),其中“大数据的本地应用程序”已经迅速崛起- 这是我们预计的格局。

大数据基础架构:创新仍然有很多

正是因为谷歌十年前的MapReduce和BigTable的论文,Doug Cutting, Mike Cafarella开发 创建Hadoop的,所以大数据的基础架构层成熟了,也解决了一些关键问题。

而基础设施领域的不断创新蓬勃发展还是通过大量的开源活动。

4.jpg

(Spark带着Hadoop飞)

2015年毫无疑问是Apache Spark最火的一年,这是一个开源框架,利用内存中做处理。这开始得到了不少争论,从我们发布了前一版本以来,Spark被各个对手采纳,从IBM到Cloudera都给它相当的支持。 Spark的意义在于它有效地解决了一些使用Hadoop很慢的关键问题:它的速度要快得多(基准测试表明:Spark比Hadoop的MapReduce的快10到100倍),更容易编写,并非常适用于机器学习。 

其他令人兴奋的框架的不断涌现,并获得新的动力,如Flink,Ignite,Samza,Kudu等。一些思想领袖认为Mesos的出现(一个框架以“对你的数据中心编程就像是单一的资源池”),不需要完全的Hadoop。即使是在数据库的世界,这似乎已经看到了更多的新兴的玩家让市场持续,大量令人兴奋的事情正在发生,从图形数据库的成熟(Neo4j),此次推出的专业数据库(时间序列数据库InfluxDB),CockroachDB,(受到谷歌Spanner启发出现,号称提供二者最好的SQL和NoSQL),数据仓库演变(Snowflake)。

大数据分析:现在的AI

在过去几个月的大趋势上,大数据分析已经越来越注重人工智能(各种形式和接口),去帮助分析海量数据,得出预测的见解。

5.jpg

最近AI的复活就好比大数据生的一个孩子。深度学习(获取了最多的人工智能关注的领域)背后的算法大部分在几十年前,但直到他们可以应用于代价便宜而速度够快的大量数据来充分发挥其潜力(Yann LeCun, Facebook深度学习研究员主管)。 AI和大数据之间的关系是如此密切,一些业内专家现在认为,AI已经遗憾地“爱上了大数据”(Geometric Intelligence)。

反过来,AI现在正在帮助大数据实现承诺。AI /机器学习的分析重点变成大数据进化逻辑的下一步:现在我有这些数据,我该怎么从中提取哪些洞察?当然,这其中的数据科学家们 - 从一开始他们的作用就是实现机器学习和做出有意义的数据模型。但渐渐地机器智能正在通过获得数据去协助数据科学家。新兴产品可以提取数学公式(Context Relevant)或自动构建和建议数据的科学模式,有可能产生最好的结果(DataRobot)。新的AI公司提供自动完成复杂的实体的标识(MetaMind,Clarifai,Dextro),或者提供强大预测分析(HyperScience)。

由于无监督学习的产品传播和提升,我们有趣的想知道AI与数据科学家的关系如何演变 - 朋友还是敌人? AI是肯定不会在短期内很快取代数据科学家,而是希望看到数据科学家通常执行的简单任务日益自动化,最后生产率大幅提高。

通过一切手段,AI /机器学习不是大数据分析的唯一趋势。令人兴奋的趋势是大数据BI平台的成熟及其日益增强的实时能力(SiSense,Arcadia)

大数据应用:一个真正的加速度

由于一些核心基础架构难题都已解决,大数据的应用层迅速建立。

在企业内部,各种工具已经出现,以帮助企业用户操作核心功能。例如,大数据通过大量的内部和外部的数据,实时更新数据,可以帮助销售和市场营销弄清楚哪些客户最有可能购买。客户服务应用可以帮助个性化服务; HR应用程序可帮助找出如何吸引和留住最优秀的员工;等

专业大数据应用已经在几乎任何垂直领域都很出色,从医疗保健(特别是在基因组学和药物研究),到财经到时尚到司法(Mark43)。

两个趋势值得关注。

首先,很多这些应用都是“大数据同乡”,因为他们本身就是建立在最新的大数据技术,并代表客户能够充分利用大数据的有效方式,无需部署底层的大数据技术,因为这些已“在一个盒子“,至少是对于那些特定功能 - 例如,ActionIQ是建立在Spark上,因此它的客户可以充分利用他们的营销部门Spark的权力,而无需实际部署Spark自己 - 在这种情况下,没有“流水线”。

第二,人工智能同样在应用程序级别有强大吸引力。例如,在猫捉老鼠的游戏,安全上,AI被广泛利用,它可以识别黑客和打击网络攻击。 “人工智能”对冲基金也开始出现。全部由AI驱动数字助理行业已经去年出现,从自动安排会议(x.ai)任务,到购物为您带来一切。这些解决方案依赖人工智能的程度差别很大,从接近100%的自动化,到个人的能力被AI增强 - 但是,趋势是明确的。

结论

在许多方面,我们仍处于大数据的早期。尽管它发展了几年,建设存储和数据的过程只是第一阶段的基础设施。 AI /机器学习出现在大数据的应用层的趋势。大数据和AI的结合将推动几乎每一个行业的创新,这令人难以置信。从这个角度来看,大数据机会甚至可能比人们认为的还大。

随着大数据的不断成熟,这个词本身可能会消失或者变得过时,没有人会使用它了。它是成功通过技术,变得很普遍,无处不在,并最终无形化。

如果大家继续对大数据感兴趣,可以参考本人的“后Hadoop时代的大数据架构”。微信公众号董老师在硅谷(ID: donglaoshi-123),转载请注明出处。