
摘要:也许你并不知晓5月17日是“国际电信日”,但大家最近一定看到过天猫517通信狂欢节!阿里通信与各大运营商联手举办了2018年度的517通信狂欢节,本文就带领大家一起了解517通信狂欢节的各种玩法! 数十款阿里云产品限时折扣中,赶紧点击这里,领劵开始云上实践吧! 以下内容根据演讲嘉宾PPT以及视频整理而成(云栖社区做了不修改原意的编辑)。 首先大家一起了解一下“5.17”这个节日。大家都知道,阿里巴巴是一个善于去造节的公司,也许你并不知晓5月17日是“国际电信日”,但大家最近一定看到过天猫517通信狂欢节!两年前,5月17日第一次作为通信行业的双11,到2017年,宽带的受理业务总数超2016年两倍以上,收入达到20亿。在通信狂化节中,也有很多合作伙伴和运营商一起加入狂欢联盟。在To B这一块,阿里云认为也可以模仿To C的玩法,在通信狂欢节向客户提供业务,同时,今年也是阿里云第一次尝试做To B方面的狂欢。阿里通信在今年处在采购季的三月份进行第一次试水,云通信的收入在阿里通信所有的产品中排名第一。基于以上试水的结果,阿里通信对“517云通信狂欢”的影响力抱有极大的信心。下面介绍一下“517云通信狂欢”的玩法。 “517云通信狂欢节”的活动时间是从4月24日至5月17日,今年参加“517云通信狂欢节”的产品主要有:短信服务套餐包,物联网连接服务;企业流量套餐包;国际消息服务。 营销有多种玩法,玩法一如下图所示。玩法一会提供一些满减券,在购买套餐包时,如果用户购买的套餐价格符合一定额度,可以用优惠券做直接抵扣。 玩法二采用抽奖的形式,如下图。在活动期间,用户每完成一笔云通信的订单,该用户就可以获得一次抽奖的机会,且100%中奖。用户订单只要满10000,就可以参与抽iPhone X的活动,订单金额越大,获奖的几率也就越高,此外,用户还有机会获得超级代金券。 玩法三如下图所示,表现形式为新人体验专区和专属礼包。如果新客户不了解云通信产品的流程,可以申请去官网进行体验在特定环境下,整个产品的使用流程。同时,阿里通信也会向新人提供专属礼包。 下面为大家简单介绍流量资源这一块的具体情况,详情如下图所示。在去年的“517云通信狂欢节”,阿里通信做了非常多的流量导流。对于站内资源,其中包括阿里云站内资源,阿里云站内资源又可以分为阿里云官网首焦,阿里云解决方案,注册成功或者登陆页,产品导航小喇叭,产品控制台,云通信官网。在以上提及的站内资源中,用户都可以从中了解“517云通信狂欢节”的各种玩法。站内资源还包括集团内资源,分为1688诚信通,支付宝企业征信,淘宝任意门。从这些站内资源,用户也可以点击链接进入“517云通信狂欢节”的专场。在站外资源这一块,阿里通信投放了大量的官微官博以及科技媒体的广告,此外,阿里通信在SEM/EDM上也做了大量准备工作。 在渠道通路方面,除了阿里云提供的官网之外,由于阿里云采用的是立体CBM销售网络,所以也会提供纵向的行业线的CBM,同时还有区域性的CBM。不得不提的是,在整个狂欢节中,阿里通信认为合作伙伴是最重要的一类渠道通路。下面,为大家介绍一下在合作伙伴方面,阿里通信会采取什么激励措施,如下图所示。合作伙伴通路活动期间,短信套餐包业务量正常纳入返佣体系,即短信套餐包新购收入月度返佣15%,季度不返佣。同时,阿里通信也会对PCBM达量奖设置两个奖项,一个是突出表现奖,数量为1名,奖品为iPhoneX,销售门槛为150000,除此之外,还设置20名的达量奖,销售门槛为30000元。 本文由云栖志愿小组沈金凤整理
摘要:在云栖大会南京峰会智能制造专场上,南京泰治自动化技术有限公司副总经理陆晓杰和南京泰治自动化技术有限公司副总经理钱国兵分享了他们与阿里云的合作过程以及成果。高效的阿里云平台联合专业领域经验丰富的企业,进一步提升了制造业的智能化水平。在阿里云提供的高效算法和模型下,多个企业实现了生产信息的实时监控,无纸化的生产过程,清晰的物料标识和追溯,以及高度协同的人机集成系统。同时,拥抱云平台之后,项目管理过程,包括投资,融资,建设等过程也变得尤为高效。 数十款阿里云产品限时折扣中,赶紧点击这里,领劵开始云上实践吧! 以下内容根据演讲嘉宾视频分享以及PPT整理而成。 本文主要介绍以下两个案例分享。 一、集成电路行业智能制造案例分享 二、云上项目管理案例分享 一、集成电路行业智能制造案例分享 “智能化“工厂的目标在于提升制造业的智能化水平,建立具有高度灵活性和资源效率的智能工厂,集成并整合产业价值链中包括客户、供应商、合作方、物流等所有环节,所有智能化的工厂并不仅仅表现在工厂内部的智能化,向上延伸到客户以及供应商,包括公司的合作方以及物流环节,所以智能工厂延伸到了整个供应链。此外,智能化工厂延续了“传统意义”上的工厂自动化的广泛应用,现在一谈到智能化,就能想到智能车间,智能工厂包括智能制造。智能化工厂深化了横向和纵向集成(基于标准化和软件)。智能化工厂实现从中央控制式转换到智能分布式,原来工厂的智能化是基于工厂内部的,如果想要在企业内构建类似于大数据,流计算等环境,成本较高。所以在完成工业智能化的基础上,想要根据采集的工业数据,向智能制造发展时,可以依托阿里云提供的算法,模型进行工业上的智能制造。 下图是南京泰治自动化公司智能化工厂发展的过程。整个过程分为五个步骤,即组织化,数字化,工业互联网,数据分析,智能工厂。国内大部分的公司停留在组织化和数字化,并且对于工业互联网正在不断探索。南京泰治自动化公司聚焦的是高端制造业,即芯片的IC封装,PC板制造等,处于工业互联网阶段,之后在数据分析和智能工厂这两步上还有很长的路要走,仍然在与客户的交流中不断进行探索。 智能化工厂共有4个目标,如下图所示,即生产信息的实时监控,无纸化的生产过程,清晰的物料标识和追溯,以及高度协同的人机集成系统。前三个目标实现起来较为容易,第四个目标对应于上面的第四,第五阶段,是大家需要一起探索研究来实现的目标。 下图展示了集成电路企业系统的横向集成与纵向集成,首先实现了设备自动化,通过一些中间平台,智能MES系统等搭建了集成电路集成架构。 下图展示了集成电路企业智能化的升级路线,对应于上文提到的四个目标,无纸化,可追踪性,产线和产能管控以及大数据分析,大数据分析方面会有很多阿里云的合作,在大数据分析之前公司会做一些数据信息的采集,在特定的领域中,公司有比较强的专业性,但在算法或者模型建立上,需要依托于阿里云提供的一些技术支撑,在强强联合后,阿里云能够对公司提出的特性,功能要求,进行算法预测,模型建立得到企业想要的数据信息。这是南京泰治自动化公司与阿里云合作的一个点,在不久之后,公司会将这些想法寻找用户落地实现。 下图展现了工业互联网平台的本质,如果我们仅仅只有数据,但是数据没有经过有效的分析,这样子就无法为企业提供真正的价值。目前,数据来自一下几点,设备数据,产品数据,系统数据以及生产数据,从订单开始到产品的出库都能提供大量的数据,这点是南京泰治自动化公司体现的强项,公司能够基于企业的生产,生成所有的数据,采集这些数据。 之后,公司基于阿里云提供的模型和算法,包括整体的微服务架构,对于制造业来说模型的使用在于以下几点,云端部署,优化算法,系统微服务架构等。对于定制化的生产,工业如何去支撑,这是生产工艺所关心的。另外,设备,能源这些都是成本,如何去尽可能的优化也是企业关心的问题。产品生命周期的追溯是很重要的,例如,对于芯片来说,不是单一存在的,芯片与现在很多的电子产品密切相关,如果芯片出现问题,对于某个设备而言,影响非常巨大,因此对产品生命周期的管理是非常重要的。 下图展现了工业互联网平台的基本架构,底层是终端层,进行数据的采集,上面依次是网络层,平台层,应用层和服务层。阿里云目前关注于平台层,南京泰治自动化公司关注于网络层,终端层中数据的采集,此外还关注于服务层的应用,这体现了南京泰治自动化公司和阿里云在中间平台上的合作。 下面介绍的是南京泰治自动化公司在集成电路行业中的具体案例分析。如下图所示,这里采用了平时我们所说的找痛点方式,也是阿里云尝试用的一种方式,公司能够采取什么样的方式帮助客户解决他们的痛点,这是公司关注的内容。在集成电路领域,帮助客户解决痛点的方式如下图所示,一般来说就是人,机,料,法,环。公司会在这五个环节中寻找客户可能存在的痛点,公司如何从智能制造方面去解决这些痛点是亟待解决的。 下图是智能制造平台的整体架构,展现了公司目前的核心产品,底层和设备端相接,具备了不同的通讯模式,如PLC,TCP/IP等模式。数据采集一般分为以下几个方面,recipe,这是公司设置的工艺制成参数;status,指的是设备状态;alarm;event;和生产相关的信息,这些是进行设备联网的基本数据。有了这些数据,通过智能化平台,和上面的智能软件做对接,有对recipe,alarm,参数,电子图档的管控。如果有非常复杂的系统,保证recipe参数正确下载,以及通过大数据分析,对alarm进行管控,对生产设备进行维护。PMS实现了对参数的管控包括对参数对核心品质的追溯。下图中智能仓储,智能物料柜,智能上下料是公司的核心产品。现在所处的高端制造业,已经满足了设备的高度自动化,但是设备与设备之间是孤立的,未来将会研究智能化的上下料实现智能车间整个工序的自动化。未来的智能工厂并不仅仅管理生产过程,还包括对于人机料法环环境的管控。 下图展现了智能化的MES系统,首先抽象产品与建模,所有工序的卡工点都是通过过程建模得来的。在整个生产过程中,无论生产前还是生产后,系统根据Lot与设备关联,关联之后,每个工序自动生成它们的卡工点。 下图展现了生产数据的分析看板,点击所有的设备,能够看到设备当前生产的产量,有没有alarm,alarm产生和清除的时间,这些都能检索到。除此之外,还能看到生产任务,运行状态,生产效率等。下图展现的是一个车间的状况,在未来能够实现多个车间的信息呈现。 下图展现了多个维度上的分析结果,在未来,数据源提供的数据并不只是单一的一种数据,需要有不同种类的数据结合在一起进行分析。目前,公司有三大主题,分为生产计划,设备运行状态,设备生产效率,通过这些给不同的决策者提供决策依据。实际生产过程中,停机一个小时对生产者的影响是很大的,通过集成电路系统,运用大数据分析,能够告诉生产者alarm的产生过程。生产者在改善这些记录的alarm之后,对产能来说是一个质的飞跃。系统能够对数据进行分析,同时指导他们生产过程的优化。 下图展现了工业云平台架构,底层是数据采集层,数据采集是基础,系统能够构建精准、实时、高效的数据采集体系,设备同时包括测试数据,如果测试数据发生异常,需要对前面的生产过程进行干预。通过传感器能够获得资源的使用情况,这也作为数据采集层生成的数据。在这之上,IaaS是支撑,实现了计算、存储网络等资源池化。工业PaaS是核心,构建一个可扩展的操作系统,为应用软件开发提供一个基础平台。工业APP是关键,形成满足不同生产场景、不同管理需求的应用服务体系。 工业云平台使用带来的变化如下图所示。起初,智能制造的数据来源于生产数据,加上与平台之后,来源于业务系统,产品系统,运行环境,生产过程以及互联网等。在数据使用方面,从本地孤立的系统分析统计转换到在云端,实现互联,互通,互操作等。在数据处理方面,有了云平台之后,提高了性能,满足了良好的吞吐性能,实现了可视化实时数据流程管理。资源优化方面,智能制造加云平台使得资源优化从描述,诊断到预测,决策不断的深入。 下图展现了云平台效益的评估,主要体现在生产工艺优化效率的提升,设备性能优化,生产品质追溯,生产系统决策,无纸化几个方面。 二、云上项目管理案例分享 首先介绍一下投-融-建-管-营方案,如下图所示。针对几个方面,建设投资,融资,建设,管理,最后到运营的整个一体化的项目管理云平台。投资过程包括投资决策,项目评审,多维度,科学评分体系;融资过程包括融资安排,资金规划,收支偿付动态平衡;建设过程包括项目的建设过程,进度成本质量的管理;在管的过程,包括投资集团,大型企业,多层级项目组织结构管理等;最后在运营过程中,包括项目公司的运营管理,项目汇报分析与监控。 如下图所示,在“投”这一过程中,从项目获取,假设分析,资产评估到相应的财务预测,系统对这些过程都有对应的数据模型。假设公司对一个自来水厂投资20个亿,想要知道自来水厂多少年能回本,每年的收益率是怎么样的,这些信息可以从投资决策这个角度进行分析。 在有了以上的投资决策后,企业可能会有融资需求,不管是从银行还是其他渠道获取资金,企业都需要进行融资资金的管控。如下图所示,首先是银行授信管理,之后项目的资金使用方和银行是需要对接的,有一些项目贷款记录,包括资金的拆借情况,融资协议查询、银行提款情况查询、还贷应还/实还查询、资金拆借还款查询;融资总体情况、融资还款情况、国资委报表统计等。 现在很多大型的项目,例如最早的三峡工程,奥运,世博,对这些项目中的计划,成本等过程进行管理非常复杂,如果没有很好的建设管理平台,管理项目就会变成企业的痛点。 系统提供多级的管控平台,把多组织,多单位的方式在一个平台上完成,如下图所示。 项目建设成功之后,会有一个运营的工程,运营期间的管理以及运营期间的资产转换等都是在运营过程中需要展现的。 下面介绍一个轻量级的平台,科研项目管理。如今一些企业中都会有一些科研的需求,有一些科研的立项。设计思路如下图所示。 以下的图片展示了基于以上设计思路的设计成果,下图阐述了严格项目审批流程,强调科研团队组织和成果预期价值。 下图展现了完善科研经费管理,增强资金使用的规范性和自主性。 下图展现了加强对项目进度的考核管理。 下图展现了项目实施过程的管理进展跟踪。 下图展现了加强科研信息整合,推进知识库建设。 下图展现了强化科研成果管理,将成果转化纳入管理过程。 本文由云栖志愿小组沈金凤整理,编辑百见
摘要:POLARDB是阿里云ApsaraDB数据库团队研发的基于云计算架构的下一代关系型数据库,其最大的特色是计算节点与存储节点分离,借助优秀的RDMA网络以及最新的块存储技术。POLARDB不但满足了公有云计算环境下用户业务快速弹性扩展的刚性需求,同时也满足了互联网环境下用户对数据库服务器高可用的需求。本文就带领大家了解什么是“云原生数据库”,云原生数据库的标准是什么,如何定义以及为何如此定义?为大家介绍下一代云原生数据库POLARDB的架构、产品设计、未来工作等内容。 以下内容根据演讲嘉宾视频分享以及PPT整理而成,PPT下载链接。 数十款阿里云产品限时折扣中,赶紧点击这里,领劵开始云上实践吧! 演讲嘉宾简介:蔡松露(子嘉),阿里云云数据库总架构师,主要负责阿里云POLARDB、NoSQL技术以及阿里云数据库整体架构等工作。在搜索引擎、NoSQL数据库、分布式系统、操作系统内核等领域有深厚积累与丰富的经验。 本文主要内容有: 一、什么是云原生数据库 二、云原生数据库POLARDB架构实现 三、云原生数据库POLARDB产品设计 一、什么是云原生数据库 POLARDB是一个云原生数据库,关于云原生,演讲者团队在ICDE上做了相关阐述。本文通过视频整理,从架构和产品设计方面介绍POLARDB的架构和实现。 首先介绍实现云原生的门槛(PPT内容如下图所示),一个云原生的数据库必须拥有出色的性能,有上百万的QPS,规模很容易扩展到上百TB,同时在版本升级时尽量满足零宕机,最重要的一点是百分百兼容开源生态。门槛的定义,我们可以通过下面例子理解,一辆车可能有很拉风的外观,又有很快的速度,但是这辆车不能被直接称为跑车,也有可能是山寨车。也就是说以上的四点只是达到了云原生数据库的门槛值,还并不代表是这一个云原生的数据库。 下面介绍实现云原生的标准,首先我们看下图中所展示的,这些年数据库的演变。从数据库的规模来看,我们现如今处在一个数据爆炸的时代,从线性增长到如今指数级别的增长,数据库领域的核心理论也在发生变化,分布式系统领域中的CAP理论是指导我们设计系统的原则和基石,但是这个理论在最近几年也在发生改变,同时,最近也出现了很多的理论算法,例如paxos,raft等,如何应用这些算法到数据库架构的设计中是一个问题。另外,客户也在发生变化,以前的数据库客户来自于银行,政府或者全世界前500强企业,但现在的形式已经发生了巨大的转变,现在数据库的主体变成了互联网+,IOT等公司。此外,基础设施也在发生变化,以前用的是IDC等,现在很多新兴的业务都往云上迁移,而且在这个过程中一切都是在线的,包括用户与数据。 下图很好地展示了如今的数据爆炸形势。下图出自互联网女皇米克尔的互联网形势报告,通过报告,下图将互联网大概分为三个时代,第一是PC互联网时代,数据主要由PC产生;第二是移动互联网时代,数据产生自衣食住行,社交,工作等多个方面;第三是物联网时代,数据由传感器和终端设备产生,数据量从以前的线性增长变成了指数级别的增长。数据爆炸使得处理数据的成本越来越大,怎么采集数据,怎么存储数据,怎么搬运分析数据,都变得愈加复杂。操作数据的复杂性直接带来的后果就是,数据很难再被利用。但是,在这个新时代,数据像是石油,价值非常之大。 下图解释了CAP理论是怎么变化的。CAP中C代表一致性,A代表可用性,P代表分区容忍性,CAP的核心在于指出了当网络分区发生时,一致性和可用性是无法被完美地保证,无法同时被满足。C和A不是0和1的关系,而是99%和1%的关系,也就是说C和A不是互斥关系,它们是可以无限逼近的。在有些场景下,P问题和A问题可以建模成相同的问题,谷歌大神Jeff Dean有篇论文中对这个问题做了很好的阐述,他认为在某些场景下,P问题本质上就是A问题。P产生可能有两种情况,第一种,可能是网卡宕机了导致机器发生了网络分区,也可能是交换机挂掉导致一堆机器也挂了。网卡挂掉了,看上去像机器在系统中消失了,但本质上和宕机没有区别,因为宕机看上去也是机器突然消失了,所以在这种情况下,P问题就是A问题。第二种,机器的硬件不稳定,比如磁盘很卡导致响应请求很慢,这时候取决于怎么建模, P或A问题都可以解释。Paxos的核心在于每做一个决定时,多数派同意就行,可以容忍少数派不同意,所以Paxos对网络分区是有容忍性的,如果三个副本中的一个副本写的比较慢或者出现了问题,在Paxos下不会影响其他两个副本,仍然会正确返回结果。当发生大规模的宕机时,如果系统中使用Paxos利用拓扑容忍单个交换机挂掉的情况。如果多个交换机挂掉,甚至出现了3-4个网络分区,作为一个数据库,追求的是百分百的C,其次才是A。但是,时间上,多个交换机全部挂掉的几率非常小,相反,几台机器出问题的概率非常大,所以应该着重于解决常见问题,之后使得C和A无限逼近。 下面介绍客户发生的变化,如下图所示。客户对数据库的需求正不断演变,首先客户希望数据库更灵活,尤其对一些创业公司来说,机会是非常重要的,例如,当出现热点新闻,或者举办双十一的活动,公司很不希望数据库成为效率的瓶颈。此外,客户希望降低使用数据库的成本,也希望数据库更高效,能够花更少的钱买到更多的能力。同时,客户希望数据库更敏捷,假设一个公司在举办双十一活动时,系统挂1个小时或1分钟是完全不同的概念,也就是说客户希望在有故障发生时,数据库是灵活的,自治的,能快速从从故障中恢复过来。总结一下,现阶段,客户对数据库的要求是,弹性,低成本,高性能,业务永续性。 在新时代,数据是实时在线产生,收集,清洗,存储,分析的(即Everything is Online),再实时的应用到算法训练模型上。在中国,大概有70%的新兴公司都遇到了数据化的挑战,数据化的挑战也影响到了客户的业务。如下图中列出了遇到的一些挑战,主要有高成本,能力不足(没有专业的工程师,无法实现数据的备份,数据挖掘等功能),数据孤岛化(数据散落在各个IDC或自建的机房中,没有被很好的利用),数据规模很大(难以存储,搬运,分析,利用)。 以上提到的挑战促使我们设计云原生的数据库,根据总结的挑战,得出了设计云原生数据库的标准,如下图所示。首先,云原生数据库必须是HTAP的,是一整套解决方案,不仅满足TP的需求也满足AP的需求,使得TP和AP不需要远程同步,再做数据的转换,数据之间没有延迟,同时,能用一份存储同时完成TP和AP,明显降低了用户的存储成本;另外,云原生数据库应是serverless的,可以将存储进行分级,将成本降到最低,并且在serverless下的升降配非常简单;最后,云原生数据库必须是智能化的,能提供一些SQL优化,索引等,能实时监控诊断,也能提供管理系统方便成本控制。 下面将详细介绍做HTAP的原因,如下图所示。首先,HTAP对于分析来说,不存在任何延迟,对于实时性要求较高的业务是非常重要的,比如说实时反欺诈,过海关时需要调查的信息。同时,在架构中不需要同步,共用一份存储后,成本也会降低,不需要额外复制副本。AP和TP在计算层是被分开的,物理上完全隔离,可以在不同的维度扩展AP和TP,当AP的需求多,TP需求少时,可以扩展AP的结点,反之,扩展TP的结点,同时,AP也对TP不会造成干扰。 下面介绍实现Serverless的原因,如下图所示。原因主要在于两个方面,一个是成本,客户只为使用或存储付费,而且客户可以根据自己的业务模型定制不同的存储级别,比如说冷存储或热存储。这使得用户的消费呈现阶梯性,不会出现很大的跃迁。用户在刚办网站,流量还很少时,这时候可以采用serverless架构,在存储层使用冷存储,虽然延迟可能会大一些,但这是最经济的做法。随着业务的扩大,也可以在计算层继续使用Serverless架构,在存储层将冷存储换成热存储,业务再次扩大时,可以在计算层加一些结点,这样很大的提高了灵活性。 下面介绍提供智能化的原因,如下图所示。很多创业公司一开始支出较少,各方面的人才配置并不会齐全,云原生数据库的智能化能够告诉这些创业公司,该如何应对遇到的一些问题。同时,系统需要告诉用户此时此刻全链路的状况,存在哪些问题,如何解决。有了这些功能之后,能帮助用户从小白成为数据库专家,分布式系统专家,财务安全专家。 二、云原生数据库POLARDB架构实现 下文从架构,产品设计与未来工作介绍POLARDB。下图展现了POLARDB的整体架构,蓝色的线代表数据流,红色的线为控制流。控制流主要负责POLARDB生命周期的管理,数据流展现数据在整个系统中流转的情况。在设计POLARDB时遵循以下四个原则,第一为存储计算分离,全用户态,零拷贝。在架构的存储层使用三副本,采用变种raft算法,允许乱序的提交确认和应用,乱序也会引入一些问题。在设计POLARDB时,大量采用新硬件,例如RDMA,3D XPOINT等。 下面介绍进行存储计算分离的原因,如下图所示,上面一层为计算层,下面一层为存储层,两层使用RDMA连接。在计算层有一个主结点负责读写请求,还有一些备结点,只负责接收读请求。存储计算分离的好处在于对一体化架构的数据库进行水平切分,相当于切成了两层,对于这两层以前必须使用相同的硬件,现在可以根据这两层不同的特点定制不同的硬件策略。例如,在计算层更关注CPU和内存,在存储层更关注I/O响应时间和I/O成本,所以分离之后,针对这两层做出的硬件差别是很大的,这种差别又会带来新的红利,这些红利又可以释放给用户,这就是有时候技术优秀,成本还低的原因。在计算层,计算不持有数据,很方便进行迁移,在存储层,从原来大一统的架构中拆分出来,可以针对存储(分布式文件系统)有自己的复制策略,高可用的策略。相较于以前大一统的架构设计,如果对存储做一些策略会干扰到计算,对计算做策略可能干扰到存储。存储分离出来后,很方便进行池化,池化的好处在于没有碎片,也不会有不均衡的情况出现。如果有不均衡,存储层可以自己进行迁移。存储计算分离也能方便实现serverless。 下图展示了全用户态的设计,有用户态的文件系统,有Libpfs(分布式文件系统),有本地类似于网关的polarswitch,有用户态的IO栈,用户态的网络。POLARDB性能的提升很大一部分来自于全用户态和对新硬件的利用。消除进程切换,以及内存拷贝带来的收益非常大。 下图是对文件系统的详细解释,文件系统的特点是使用POSIX API,对DB层的侵入较小。同时,它是一个静态库,直接链接到数据库进程中。分布式系统的元数据是通过PAXOS进行同步的,带来的好处是,多台机器看到的是同一个目录,当用户去操作目录时,PAXOS可以在底部做一个串行,所以不会存在数据冲突的问题。在每个计算层的节点上,都会有对元数据的缓存,目的是做访问加速。 下图展示了ParallelRaft算法,乱序会让写入加速,带来接近翻倍的性能提升。 架构也用到了大量的新硬件,如下图所示,包括RDMA,3D XPOINT,演讲者团队正在研究的Open-Channel SSD。虽然SSD已经工业化很多年了,主流的存储都是SSD,但是目前对SSD的应用还存在很多问题。因为SSD的软件和硬件并不是非常匹配,导致我们对SSD的使用存在浪费,浪费一方面来自性能以及寿命。Open-Channel SSD方式对IO性能和寿命的影响最终反映到成本上,都比以前有了很大的提升。 下图展现了POLARDB和MySQL的对比结果,读性能相较于MySQL提高了5-6倍,写性能提高了3倍左右。同时,性能也在不断提升。 三、云原生数据库POLARDB产品设计 接下来介绍一些产品设计的特点,主要从五个维度设计产品,如下图所示,首先是性能,应该很方便地就能扩展到上百万QPS,而且RT很低;存储可以很方便的扩展到100TB,也很方便的缩回来;弹性上,版本升级时,尽量做到零宕机,在存储层,计算层可以方便进行scale-up以及scale-out;目前100%能兼容MySQL5.6;在可用性方面,承诺99.95%的可用性,99.999%的可靠性。在数据安全方面,演讲者团队会做定时的snapshot,并实时上传到OSS,提供物理备份和逻辑备份。在可用性上,主结点和readonly结点可以很方便的进行角色切换。 产品设计上是读写分离的,如下图所示,有一个结点是主结点接收读写请求,可以很方便地进行scale-up,其他结点都是只读结点,只读结点可以很方便的进行scale-out。读能力和只读结点的数量呈线性正比。 可扩展性方面,如下图所示,可以在计算层做scale-out和scale-up,从用户看来存储层是在做scale-up,但因为底层是分布式文件系统,当存储水位比较高时可以很方便的加入新的存储结点,所以本质上是scale-out。 在数据迁移方面,如下图所示,假设你是一个RDS的用户,通过备份到OSS,在POLARDB的实例里加载OSS上的备份的数据新生成POLARDB的实例,也可以通过DTS进行数据实时的迁移。在未来还可以提供一种方式,将POLARDB做成slave,直接挂到RDS的结点上,把数据实时的同步。如果用户使用的是第三方商用的数据库,因为DTS支持的数据库类型非常多,所以建议使用DTS。 在数据可靠性上,如下图所示,目前在官网上购买到的版本是在一个AZ里面的三副本。 未来工作中,在DB引擎层未来会提供多写的能力,而且数据库引擎层会引入新的组件例如CacheFusion,最大提升计算层的性能。未来会支持更多的数据库类型,在存储层会应用更多新的硬件,对某些IO进行加速,会用Open-Channel SSD对性能进一步提升,成本进一步降低。在扫描时,做计算下推,使回到计算层的数据尽量少。演讲者希望现有的分布式文件系统和数据库联系的更紧密,能感知InnoDB的语义。 本文由云栖志愿小组沈金凤整理,编辑百见 POLARDB https://www.aliyun.com/product/polardb?spm=5176.8142029.388261.347.62136d3etcPz5x HBASE https://www.aliyun.com/product/hbase?spm=5176.155538.765261.355.57227e0dLAlXGl 云数据库RDS PPAS 版 https://www.aliyun.com/product/rds/ppas?spm=5176.54432.765261.351.6e1e28f5UFqADw
摘要:阿里云工业大数据总监杨斌在2018云栖大会·深圳峰会中介绍了他们团队的ET工业大脑,通过大数据以及人工智能,创建一个工业的最强大脑,协助制造业实现关键工序智能化、生产过程智能优化控制等方面的转型升级。 数十款阿里云产品限时折扣中,赶紧点击这里,领劵开始云上实践吧! 以下内容根据演讲嘉宾PPT以及视频整理而成(云栖社区做了不修改原意的编辑)。 实际上,在云计算和大数据的驱动下,智能制造是工业升级,改造的核心内容,其中工业大数据在整个升级改造中处在重要地位。对于如何利用海量的大数据进行分析挖掘,提高生产效益这一研究问题上,杨斌团队做了大量的项目实践。 数据是工业系统的核心要素,但在中国工业文献中,工业1.0,2.0,3.0,4.0中的内容非常层次不齐的,很多数据没有被充分使用,即便数据被使用了,它的价值也没有被充分发挥。以车间制造系统为例,只有个位数比例的数据被用于辅助工业决策。阿里云将数据分为质检数据,仪器数据,设备参数等。对于阿里云来说,他们积累了很多数据分析的经验,再加上阿里数据智能的技术,这两者结合起来足以解决使用工业大数据面临的诸多难题,比如数据的深度整合,数据的分析挖掘,以及数据的使用。数据使用的场景一般是存,通,用。存和通的关键在于数据上云,数据上云之后,构建机器神经网络,汲取和吸纳行业的经验和社会的运行特征参数以及规律,在这之上统筹归纳为人工智能。所以,整个人工智能服务分为三步走,如下图所示,首先是看见数据,对应于企业深度数据洞察,把实时的数据和离线的数据在线统一;之后在第一步的基础上进一步做数据处理,将数据按照各个维度关联起来,形成工业领域的知识图谱,在一个个项目中沉淀出行业模型;最终这些行业知识图谱可以对标具体的工业业务(包括工业的业务,工业研发的业务,营销的业务等),在原有的工业信息系统,工业的制造执行系统基础上进行拔高和叠加,然后给每个企业创造自己的工业大脑。以上是整个产品设计的核心思想。 阿里云将整体工业大脑的设计称为三舱一体,如下图所示。第一舱为数据舱,利用工业数据集成套件,在云上进行数据处理,归纳为知识中心,信息中心以及数据中心,这几个中心实现都对应不同管理体系下的不同子系统。这些整体的数据系统进行重组,打散业务的需求,整理成为工业企业的大数据总线。在数据舱之上,定义了应用舱,应用舱是一个开放的智能算法服务平台,阿里云围绕着“供,研,产,销,能,环”沉淀了一系列供用户使用的官方数据智能服务。同时,阿里云希望引入更多的合作伙伴,利用应用舱提供的开发接口把他们的算法服务拉到数据智能服务中,共同服务客户解决问题。对企业和工业来说,管理者也好,一些员工也好,他们常常会面临非实时,非集成的数据。阿里云希望把数据舱的实时数据,知识图谱以及应用舱的智能服务暴露的服务接口,在可视化的条件下进行集成,然后面对不同业务角色形成满足业务需求的业务指挥舱,供企业进行监控与指导。对工业和企业的用户来说,为了降低大家使用技术的门槛,希望用户更多的了解自身的业务,通过AI创作间平台,通过拖拉拽的方式,把数据舱的数据模型,组件和应用舱的算法服务组件结合起来满足业务需求,所以用户不需要懂很多专业的技术,只需要专注于自己的业务,工艺。 阿里云提供了工业数据集成套件,如下图所示。工业数据采集上云一直是一个非常具有挑战性的问题。本身设备的多样性,工业协议的不一致性,对数据安全的顾虑都是我们要充分重视的。对于工业大脑的设计来说,通过工业数据集成套件,依托阿里云的数据安全技术,给数据稳定地搭建安全通道,处理好数据管道安全,云端数据的安全。为了提高用户体验,提供了数据设备服务,支持用户将他的设备注册管理,这里的设备是面向对象的。然后通过智能网关,对接工业上的不同协议,例如OPC,CDT等,同时提供多源异构适配去适配不同的数据源。最后,数据从云上转成通用的格式,通过实时和离线的方式,根据不同的业务场景,最后形成归档。 下面为大家介绍一下数据舱中的领域知识图谱,如下图所示。数据上云的第一步是要打破物理的数据孤岛,打破物理的数据孤岛有三个一的概念,即工业数据集成套件,自己通过多个项目踩过的坑形成的数据上云的解决方案以及工业的合作伙伴。工业的合作伙伴在数据采集领域有多年的经验,每天都在和不同的工业设备打交道。第二步需要打破逻辑的数据孤岛,基于工业业务的供,研,产,销进行纵向梳理,在横向上打散数据,依托数据舱中的工具建立工业的服务自身需求的数舱体系。在信息仓库的基础上,基于初步的工业数据进行数据关联,形成知识图谱。将这些知识图谱作为标准的数据土壤,搭载人工智能算法模型以及深度学习模型,形成神经网络,优化工业制造。以上过程类似与大脑的思考,首先是信息汲取,之后信息的分析,最后进行信息的仿真。 下面为大家展示一下工业数据门户,如下图所示。用户对个人的精品资产进行盘点,可以根据不同的维度进行筛选,了解原始数据,数据标签,数据模型,而且可以定位到哪张表格等更多的细节信息。这些数据模型将底层的原始数据和底层的智能应用链接起来,用户可以载入行业数据模型,看看能否一键部署直接建立自己的模型体系。也可以在现有的载入之后,进行一定配置和修改之后重新部署。让用户在不同的数据层面了解到数据现有状况。用户能知道哪些数据是缺失的。 下面介绍应用舱的设计,如下图所示。应用舱的设计目标是达到工业智能的普惠化,应用舱提供了一系列算法服务。一般企业的开发人员在使用算法服务时都遇到了很多挑战,需要花很多时间将算法和业务进行整合,所以在这个层面上,阿里云希望明显降低算法服务的使用门槛,希望用户在使用具体算法模型时,能够直接载入具体的算法服务,通过简单的定制修改就可以算法服务的搭建。不管哪个行业都可能涉及到关键参数识别,功能推荐等实现场景,阿里云在过去的项目实践中对这些领域进行了算法沉淀,用户只需要选择具体的数据源,具体的算法之后进行配置修改生成算法服务,最后执行调试。整体上,应用舱提供了供,研,产,销,能,环全链路算法服务,在这基础上,希望最大限度地降低用户的使用门槛,达到易用会用的目标。比如说在智能研发套件中,用户可以获取海量的设备信息,使用数据进行深度的挖掘,提供关键参数识别,良品率预测,产能分析等算法服务。之后,阿里云希望引入更多的合作伙伴,在应用舱合作提供更多的算法服务供用户使用。 下面介绍指挥舱中的设计内容,如下图所示。在产品领域,指挥舱提供了多种模板,有关于生产监控的模板,有关于设别监控的模板以及整个企业关键信息的展示模板。针对不同角色的工业企业提供不一样的功能,比如说,对于企业的管理者而言,更关心企业生产的成本,效益等内容;对于车间主任来讲,他们更想看到生产的总体情况以及团队的绩效情况;对单个个人来说,他们关注与设备运行的健康状况。所以,阿里云量身定做了一套模板,用户取到模板后就可以将这些数据应用起来。 新能源生产及技术研究副总刘建平说过一句话:“观念转变:以前是讲逻辑,现在是看数据,讲事实,通过计算与挖掘发现问题,很‘野蛮’。”在某种程度上,经过项目的经验沉淀,杨斌团队发现工业智能在一定程度上有跨行业的可能性,在具体的细分行业,细分场景下,例如在工艺优化,关键因素识别,推荐上都会有涉及的。工业场景的多变复杂性需要我们考虑到效率,安全,成本等更多因素,所以往往一些沉淀出来的解决方案一旦被搬到现实场景下,起到的效果是非常有限的,只有通过实时的数据分析,数据挖掘去洞察背后的影响。杨斌团队所做的事情并不是和以前的一些技术进行对抗,更多的是和原有的技术专家进行技术的结合产生一些期望的“化学效果”。工业大脑的推出是企业的财富,由于专家的脑海中的知识,在平时工作的工程中,因为外界原因无法完整被记录下来,所以如果通过引入工业大脑的解决方式,所有的经验都会沉淀为数据,这些数据通过挖掘,分析又可以产生新的规律,因此工业大脑可以说是实现了工业技术传承的目标。对于工业企业来说,数据是一笔宝贵的财富,上亿次的生产,成百上千的工业曲线本身就是历史经验,人工智能技术通过分析历史,找到规律。为了更好地应用数据,依托工业大脑为底座,形成了属于团队的一套解决方案,如下图所示。杨斌团队的思路是和现有的系统结合在一起形成联动,并不希望对现有的系统长期的改造。在工业大数据中,这个大字并非仅仅体现在数据量的大,更多的体现在解决问题的思路比较多,并非是数据量一定要大,一定要完备才能获取知识。 在多个领域,阿里云ET工业大脑都有涉及,有很多实践案例,通过实打实的技术效果体现技术的价值。如下图所示,主要提升体现在运维成本的降低,工作效率的提升,稳定性的提升等等。 本文由云栖志愿小组沈金凤整理,编辑百见
摘要:阿里巴巴高级技术专家木洛在2018云栖大会·深圳峰会中就Feed流的概念介绍、概念架构以及TableStore场景的Timeline模型等方面的内容做了深入的分析。本文带领大家一起了解并学习如何通过存储系统的性能特性,通过Feed流中的消息存储和推送机制支撑起千万Feed流的并发。 数十款阿里云产品限时折扣中,赶紧点击这里,领劵开始云上实践吧! 以下内容根据演讲嘉宾PPT以及视频整理而成(云栖社区做了不修改原意的编辑)。 本次的分享主要围绕以下三个方面: 一、概念介绍 二、Feed流系统架构 三、TableStore Timeline 一、概念介绍 下图为比较抽象的Feed流系统,系统被分为Feed,Feed流以及Feed订阅三个层次。Feed可以看作消息体,消息体就像我们发送的信息,平时发布的动态,新闻App中的一条新闻,或者推荐的内容,我们将这些统称为Feed。Feed产生之后,通过Feed流被推送到Feed订阅端,这个过程就是典型的Feed三层系统。Feed是一种实时消息,由于消息是实时产生,实时消费,实时推送的,因此满足实时性是关键。另外消息来自于很多不同的消息源,消息的产生属于海量级别。Feed流是实时推送的有序的可扩散的消息流,Feed产生之后,我们通常会将这些Feed推送给Feed的订阅端,推送的过程中需要满足有序,可扩散的原则。消息顺序有时间线类型,即按照消息产生的时间对消息进行排序;也有Rank类型,这种类型主要出现在推荐信息中,根据某些算法或消息所处的类别找到最相关的订阅端。一个消息可能是由单个消息体产生的,而这个消息体可以扩散到很多的订阅端,从消息产生到消息消费产生巨大的读写比。 下图展示了常见的Feed应用,朋友圈中的Feed就是每个人发布的动态,动态通过朋友圈的Feed流扩散到好友,同时每个人也可以在朋友圈中订阅到好友发布的动态。微博也是较为常见的Feed应用。Pinterest是国外关于图片的社交系统,与其它Feed系统不同的是,Pinterest的Feed就是图片。雪球的动态广场是一个讨论组,大家可以在讨论组里发布消息,讨论组的人也可以订阅组内消息。 二、Feed流系统架构 下面为大家举例介绍主要Feed流整体架构的实现。在讲架构实现的时候,本文会先介绍一个简单且为大家所熟知的案例。如下图所示,朋友圈是大家常用的社交平台,首先定义某个功能实现需要满足什么需求。第一点是朋友圈中的动态:在消息发送方面,消息会存到个人相册,每个人能在个人相册中看到自己发布的所有动态,并且这些动态是永久保存的,我们可以翻到N多年前自己发布的消息,所以这些消息有一个很长时间的保存状态;另外消息会扩散到好友的朋友圈,消息保存在持有好友关系的朋友圈;同时我们的朋友圈也保存着好友发的动态,作为消息保存。朋友圈中不仅仅只有发布的动态,也可能有一些广告,这些广告是由广告系统推荐过来的。以上是朋友圈简单的Feed流功能需求分析。总结功能需求如下图中所示: 人与人之间组建好友关系,朋友圈用于查看好友圈内的人发送的所有消息,按照动态发布的时间进行时间排序;广告系统可向朋友圈内插入广告消息,这是一个rank的消息;个人相册用于查看个人发送的所有消息。并且,个人相册和朋友圈是有所区别的,个人相册中需要永久保存个人发布的动态,但是朋友圈可以只看最近一段时间的消息,这点在我们之后设计消息系统时,会有所区别。 针对上文所说的朋友圈场景,下文为大家介绍一个简单的Feed流系统架构大概会分为哪几个模块。如下图所示,首先会设计一个端,用来发送消息或者接收消息,这就是消息的入口。其次,会有接入层,我们通常把它叫做消息服务器,这些消息服务器通常是由一组无状态的服务器组成的,主要职能是接收消息,处理消息以及当端读消息时的同步消息。接入层算是比较简单的实现,通常都是一些API网关,提供特定消息处理的API,并且涉及到消息协议中的特定实现,包括数据加密,优化的通信等。最核心的是后台服务,如下图所示,把后台服务简单化为两块,第一块是消息系统,消息系统是整个Feed流架构中最核心的部分,负责对消息进行持久化,个人的消息会先持久化都自己的库中;其次负责对消息进行同步,将消息同步给自己的好友或者抽取某些好友订阅自己的消息。广告系统会找到和广告最关联的人群,向这些人推送消息。消息系统下面最核心的是两个库的设计,一个是消息存储库,另外一个是消息同步库。这两个库的区别在于,消息存储库往往是用于永久存储所有人发布的所有消息;消息同步用来将一条消息向该同步的人同步。因为广告系统需要将广告推动给关联的人群,所以广告系统会和消息同步库打交道,同时,由于广告是不需要永久存储的,所以不需要和消息存储打交道。下图中最核心的是消息系统的设计,它的设计与实现和Feed流中的消息发送,订阅以及特征非常相关。 接下来我们看一下,当我们设计一个消息系统时,一定要足够了解系统的场景,了解在这个场景里面,这个消息系统需要满足什么要求。 如下图所示,将要求归为三: 类,第一类我们需要了解Feed流系统中消息的特征是什么:第一个特征是典型的读多写少,读多写少的含义就是我们在朋友圈中通常是读动态,很少人去发消息,读写比大概为100:1;第二个消息模型比较简单,就是消息体,或者消息,消息里面可能会有发送人,接收人,消息时间等属性。在消息确定时模型中的数据已经确定了,而且消息与消息之间是没有关联的;第三个是弱关系型数据,就是消息不会和其他数据产生关联,只是流式产生的一条条消息;第四个是波峰波谷式访问,指消息系统承载的流量呈现出波峰波谷式访问的特征,因为Feed流系统通常属于社交类系统,社交系统的活跃度取决于人群的活跃度,人群的活跃度会有很明显的波峰波谷。上述四种特征对于我们以后设计消息系统时选择消息存储的数据库是至关重要的,也具有参考意义。 第二类是关于消息存储,个人相册是需要永久存储的,存储量随着时间的推移会越来越大。因为消息的读写是在线的,不可能用一个本地存储,所以消息存储要求拥有在线永久存储的能力。同时,朋友圈中的动态需要保证高可靠性,不能轻易丢失。当应用达到很大的用户量时存储的消息容量会很巨大,处在PB级,属于万亿行的规模。 第三类关于消息同步,消息的产生与推送都对消息库产生很大的压力,需要能够应对海量消息;需要实现实时同步,多端同步;同步模型包括写扩散和读扩散。下面举一个例子理解一下写扩散和读扩散。一个人有100个好友,他在朋友圈中发布了一条动态,动态是如何同步到100个好友的朋友圈的呢?解决方案有两种,第一,每个人都有一个收件箱,向每个人的收件箱中投递一个消息。这种方案的好处在于,因为每个人都有一个收件箱,所有我们只需要查看收件箱,就可以看到别人给我传递的消息,对读有很大的优化,相对而言缺点是消息的写操作是需要扩散的,扩散的比例就是好友的个数,以上是写扩散的优劣。第二,采用读扩散,读扩散的好处在于数据只需要写一份,比如说将消息存储在我们自己的库中就行了,坏处是当我们需要拉取新的消息时,假设要获取100个好友的新信息,我们就需要从100个人那边拉取新消息,这对系统产生较大的读压力。以上是写扩散和读扩散的主要区别。 实际上,Feed流系统读写比例是100:1,读操作是非常多的,在这种数据特征下,如果我们继续采用读扩散,将会把读写比拉的更大,有可能将100:1拉到1000:1,这会导致在设计消息系统容量时,取读或者写的最高上限,上限拉高之后,对系统要求的能力也会变高,对系统的成本要求也越高。这时,如果采用写扩散,将把写的比例拉高,使读写比取得均衡,对系统的预留资源要,所以在Feed流系统中通常采用写扩散这个模式。但是,写扩散有一个缺点,写会扩散很多份,当朋友圈中有1万个好友,写扩散缺点会很明显,因此可以采用混合模式进行优化,例如大部分消息采用写扩散,对于扩散比例很高的消息可以采用读扩散。有一个优化方式, 在了解了消息的特征,存储和同步后,我们接下来就要选择合适的数据库,下图展示了NoSQL的解决方案。NoSQL的解决方案关注容量,服务能力,分布式支持,售卖模式以及数据模型。在容量方面,传统关系型数据库的容量在TB级别,而NoSQL采用分布式,所以容量在10PB级别。服务能力方面,十万TPS对应于千万TPS。分布式支持方面,对于这样海量数据的系统来说,必须采用分布式,在图中,分库分表对原生支持。售卖模式方面,按规格计费对按量计费,由于Feed流系统是一个具有波峰波谷式特征的系统,按量模式的售卖模式能帮助节约很多成本。数据模型方面,关系型对弱关系型,弱关系型在Feed流系统下也是满足需求的。 三、TableStore Timeline TableStore为了简化Feed流系统的开发,之后就推出了TableStore Timeline。下图是Timeline简单逻辑模型。如下图所示,消息会有一个发送端(A),消息会有订阅端(B1,B2,B3),消息同步通过队列,是一个虚拟的队列,消息需要同步给谁,就将消息发到队列中。队列提供的功能有以下几个: 每个消息都有一个顺序ID,用来完成消息的同步,由于每个接收端同步的速度是不同的,每个接收端会保留自己的同步位点,在进行消息同步时,只需要通过同步位点,到消息列表中随机定位,定位之后拉取所有需要同步的消息,这是一个简单的模型。对于每个人的收件箱以及发件箱,都是一个独立的Timeline,在底层数据库中存储的内容就变成了每个人的收件箱以及发件箱,如果有1000万的用户,数据库中就需要存储1000万的timeline,在这种情况下,数据库就要达到千亿万亿的规模。 下面我们一起看下,基于Timeline的消息系统的架构实现,如下图所示。图中有几条关键路径,首先是消息的写操作,消息是如何写的,写完如何做同步。;第二个是消息的读。对于消息的写,首先端上发送一个消息到消息服务器;通常消息服务器为了异步化批量处理消息,都会将消息写进消息队列;消息队列中的消息会进行专门的消息处理;每个消息上传到服务器之后,将消息存储到个人相册中,个人相册存储在消息存储库中,其中存储着每个人的timeline,每个人读自己对应的timeline就可以读取自己的动态;在存储timeline之后,需要将消息同步到自己所有的好友,采取写扩散的消息同步模型;最后,选定所有的好友,将自己的消息发送到所有好友的收件箱,因此消息同步库中存储的是所有好友的收件箱。同步消息通常会看两个地方,一个是个人相册,从消息存储库中根据同步位点拉取内容。同时,我们只需要从消息同步库中拉取自己的收件箱,就可以看到还有发送的所有消息。消息存储库与消息同步库有不同的要求,消息存储库要求PB级数据低成本永久存储,消息同步库需要提供高并发及低延迟读,另外,需要存储每个用户的收件箱数据,数据生命周期为半年。TableStore提供的功能为容量型提供低成本存储,高性能提供低延迟读,以及PB级存储,毫秒级延迟。 下图展示了Timeline性能的实验分析结果,可以看到在同步100万扩散写时,比如说我将消息发给100万个人,在8核机器上,只需要3.7s的时间。在16核机器上,并发读取数据能达到4万QPS。 本文由云栖志愿小组沈金凤整理,编辑百见
摘要:本文介绍了Lambda表达式的起源以及基本语法,并提供代码实例帮助大家理解Lambda表达式的使用。另外,本文介绍了Java开发中常用的多线程技术,详细介绍多线程涉及到的概念以及使用方法。 数十款阿里云产品限时折扣中,赶紧点击这里,领劵开始云上实践吧! 演讲嘉宾简介: 吕德庆(花名:嵛山),阿里巴巴高级开发工程师,武汉大学地信硕士,有丰富的系统开发经验,使用过Java,C++、Go、Python、Javascript、.Net等多种语言,目前主要精力在Java,就职于阿里巴巴代码中心团队,负责后端开发。 本次直播视频精彩回顾,戳这里! PPT地址:https://yq.aliyun.com/download/2657 以下内容根据演讲嘉宾视频分享以及PPT整理而成。 本次的分享主要围绕以下两个方面: 一、Lambda入门 二、多线程技术 一、Lambda入门 Lambda起源于数学中的λ演算中的一个匿名函数,从它的起源我们可以知道,Lambda本身就是一个匿名函数,是Java8才推出的亮点,体现了函数式编程的思想。现在主流的编程语言都包含了函数式编程的特性,Java8在进化过程中吸收了该特性,作为面向编程对象的补充。 Lambda基本语法如下图所示,Lambda语法较为简单,和普通函数相比,没有返回值以及函数名,它的参数和执行语句之间通过->连接,表示参数将传递到语句中执行。Lambda表达式还有两种简化表达式的方法,当表达式中只有一个执行语句时,可以省略语句的{};如果接口的抽象方法只有一个形参,()可以省略,只需要参数的名称即可。Lambda可以替代特定匿名内部类,Lambda表达式不能单独存在,在使用时必须继承函数式接口。 下图示例中的第一个Lambda表达式,形参列表的数据类型会自动推断,只需要参数名称。 代码示例: package lambda; public class Lambda { public static void main(String[] args) { Flyable flyable = new Flyable() { @Override public void fly(int a) { System.out.println("I can fly by anonymous class"); } //@Override //public void landing() { // System.out.println("I can landing by anonymous class"); //} }; flyable.fly(1); flyable = (t) -> System.out.println("I can fly by lambda"); flyable.fly(1); Bird bird = new Bird() { @Override void fly() { System.out.println("I can fly by bird"); } }; bird = () -> System.out.println("I can fly by lambda"); } @FunctionalInterface interface Flyable { void fly(int a); //void landing(); } abstract static class Bird { abstract void fly(); } } 在上图展示的代码中,代码中的匿名内部类继承了Flyable接口,实现了接口中的fly()方法。代码准备了Lambda表达式重新实现了Flyable接口。根据代码中的输出命令,执行结果显示Lambda表达式起到了和匿名内部类相同的作用。代码中,并没有定义Lambda表达式的参数类型,但是我们也可以在Lambda表达式中定义符合要求的类型flyable=(int t)->System.out.println(“I can fly by Lambda”),如果参数类型与接口中方法参数类型不一致flyable=(String t)->System.out.println(“I can fly by Lambda”),编译器就会报错。 假如接口实现了两个方法,匿名内部类可以重写新的方法。但是,Lambda表达式没法做到这一点,编译后,将会提示发现有多个需要重写的抽象方法。因此,Lambda表达式在实现接口时,只允许接口中有一个抽象方法,我们将这样的接口称为函数式接口,Java8中提供了注解@FunctionalInterface检验接口是否为函数式接口,如果不是,注解将会报错。另外,代码尝试使用Lambda表达式替代抽象类的匿名内部类的写法,但会报错,提示必须继承函数式接口。因此,Lambda可以替代特定匿名内部类,简化代码,但是必须继承函数式接口。 二、多线程技术 1.进程与线程 进程是具有一定独立功能的程序,关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。线程是进程的一个实体,是CPU分配调度的基本单位,代码的执行体。从概念上,我们可以知道进程是程序的一次运行活动,需要系统进行分配和调度的;线程是最终代码的执行体,是CPU分配调度的基本单位。同一个进程中可以包括多个线程,并且线程共享整个进程的资源,一个进程至少包括一个线程。如果在理解概念时很费解,想要充分理解这些概念,我们可以采用反抽象的方法,即联系,我们需要在实际生活中寻找符合概念描述的事物。举例说明:我们经常说安卓手机比较卡,手机上App跑的太多,导致内存不足,那么我们在手机上看到的这些App,就是一个个程序;在手机卡顿时,双击home键,看到有App在后台运行,这是我们看到的这些app就是进程。进程是需要系统分配资源的,资源相当于手机的内存。通过这个例子,我们可以加深对进程和程序概念上的理解。另外,我们也可以通过反抽象的方法理解进程与线程的概念。举例说明:公司运转与员工工作,这里的公司,我们可以对应到程序;进程是程序的运行活动,这里的进程,我们可以理解为公司的正常运转;同时,公司想要正常运转,离不开员工的工作,员工是公司运转不可分割的实体,只有员工才是真正做事的人,因此我们可以将线程类比员工。 2.线程的生命周期 下图为线程的状态图。所谓的生命周期,指的是线程从出生到死亡过程中,经历的一系列状态。线程通过创建Thread的一个实例new Thread()进入new新建状态;之后调用start()方法进入等待被分配时间片,进入runnable状态;之后,线程获得CPU资源执行任务,进入running状态;当线程执行完毕或被其它线程杀死,线程就进入dead死亡状态;如果由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入blocked堵塞状态,在多种条件下,blocked状态可以恢复成runnable状态,最终在线程重新拿到时间片后,就可以进入running状态重新运行。在running状态下,如果时间片用完了或者线程主动放弃CPU的使用,线程重新回到runnable状态。 时间片指的是CPU的时间片段,CPU将它的可执行时间分成很多片段,每个片段随机分配给处在runnable状态下的线程,这样可以达到并发的效果。假设我有一个单核的CPU,通过分割很多的时间片,每个程序都有机会运行,仍然可以跑很多的程序,宏观上看是并发的,但是由于只有一个CPU,实际上程序还是串行的。 我们可以通过阅读JDK的Thread类注释,创建并使用线程,如下图所示。 按照JDK的注释,下述代码中使用了两种创建线程的方法。由于Runnable是一个函数式接口,因此代码中使用Lambda表达式替代匿名内部类,再将runnable传递给Thread,使用start()启动线程。 public class ThreadTest { public static void main(String[] args) { PrimeThread thread = new PrimeThread(); thread.setName("Thread "); thread.start(); Runnable runnable = () -> System.out.println(Thread.currentThread().getName() + " runnable run."); Thread t = new Thread(runnable); t.run(); System.out.println(Thread.currentThread().getName()); } static class PrimeThread extends Thread { @Override public void run() { System.out.println(Thread.currentThread().getName() + " Thread run"); } } } 上述代码结果如下图所示。在下图代码中,如果我们将t.start();替换成t.run(),打印结果将会变成: Thread Thread run Main runnable run. Main 这说明run()方法并没有真正启动线程,run()方法只是在当前的线程中执行了run中的函数。 3. 线程协作 并行与协作:线程在并发的过程中更多的是协作关系,就像之前的概念中所提到的,进程是系统资源分配的单位,线程本身并没有多少分配资源,除了维护自己必须的内存开销之外,线程的所有资源都是在进程中。多线程在使用竞争中资源时,存在抢占或者说是共享的关系。 这时,多线程之间该如何协作,是需要我们去解决的。我们通过下面的代码,学会使用关键字synchronized,以及理解临界区,锁的概念。 public class Tickets { int tickets = 10; /** * 重复卖票 */ void sell() { while (tickets > 0) { System.out.println(Thread.currentThread().getName() + " sell ticket:" + tickets); tickets--; } System.out.println(Thread.currentThread().getName() + " sell out."); } public static void main(String[] args) { Tickets tickets = new Tickets(); Thread sellerA = new Thread(tickets::sell); sellerA.setName("sellerA"); Thread sellerB = new Thread(tickets::sell); sellerB.setName("sellerB"); Thread sellerC = new Thread(tickets::sell); sellerC.setName("sellerC"); sellerA.start(); sellerB.start(); sellerC.start(); } } 上述代码模拟售票操作。一共有10张票,三个售票员sellerA,seller,sellerC一起去售票,sell( )方法模拟售票行为。代码启动线程之后,运行结果如下图所示。售票员sellerA在一个时间片内将sell方法中的代码全部跑完,票售空,但是sellerB与sellerC在线程并发时,也售出了第10张票,存在重复售票,这样的操作是不合理的。 为了解决重复售票的问题,我们可以使用Java中提供的同步关键字synchronized修饰sell( )方法,代码如下述所示。使用关键字synchronized修饰后,多线程在访问sell( )方法时,能保证只有一个线程执行这个方法,当前线程执行完sell( )方法后,其他线程才能执行sell( )方法。 /** * sync之后,导致独占资源 */ synchronized void sell() { while (tickets > 0) { System.out.println(Thread.currentThread().getName() + " sell ticket:" + tickets); tickets--; } System.out.println(Thread.currentThread().getName() + " sell out."); } 执行上述代码后,输出结果如下图所示。从下面结果可以看到,代码解决了重复售票的不合理问题,但是仍然只有sellerA一个在售票。原因在于,通过关键字synchronized修饰sell( )方法后,sellerA在拿到sell( )方法的执行权时,把里面的代码一口气执行完了,也就是将票全部卖出,等sellerA执行完后,sellerB和sellerC再执行sell( )方法时,票数已经为0,自然会出现下图中没有卖出一张票的现象。我们将方法sell( )中的内容叫做临界区,当一个线程进入临界区后,其他线程必须等待该线程执行完临界区内容后,才能进入该临界区。 下述代码改善了上述sellerA一口气卖完所有票的现象。代码在方法体内使用关键字synchronized,括号中的this表示一个对象或者一个类。代码相较于上面的解决方法,将临界区从整个方法缩小到两行代码。也就是说多线程在执行这两行代码时是同步的。 /** * 改善后,资源没有独占 */ void sell() { while (tickets > 0) { synchronized (Tickets.class) { System.out.println(Thread.currentThread().getName() + " sell ticket:" + tickets); tickets--; } //do something try { TimeUnit.MILLISECONDS.sleep(50L); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " sell out"); } 上述代码执行结果如下图所示。从图中我们可以发现,不再是只有sellerA在卖票。并且代码每次执行结果都是不一样的,因为CPU的时间片是随机给出的。上述代码中的try catch方法块使线程睡50ms,延长售票操作的时间,在这段时间内可以执行其他的操作(比如,将该票给某个顾客)。代码改善过后,保证资源不是被独占的,使资源分配均匀。 从运行结果来看,存在无效票,原因在于:假设当前票数为1,A进入临界区售票,而此时B已经进行判断,在临界区外等待了。当A卖完票后,票数为0,但是B还是会进入临界区进行售票操作,因此,出现无效票-1的情况。这说明代码需要进一步改善。改善后的代码如下所示。代码在临界区内加入判断条件,只有票数大于0时,才会进行售票操作,这是常用的双重检验方法。经过双重检验后,运行代码就不会出现无效售票。 /** * 改善后,资源没有独占, 修复卖出无效票的问题 */ void sell() { while (tickets > 0) { synchronized (this) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + " sell ticket:" + tickets); tickets--; } } //do something try { TimeUnit.MILLISECONDS.sleep(50L); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " sell out"); } 下面介绍另外一种单线程同步的方法。代码如下。代码通过Lock接口定义了一个锁,使用ReentrantLock实现。锁和上面提到的关键字synchronized作用是一样的,都是定义出一个临界区,让线程进入临界区时实现线程同步。代码通过lock.lock( )定义临界区的初始点,使用在try语句块中定义临界区执行内容, finally语句块中采用unlock( )方法进行解锁。在unlock后线程才算真正走出临界区。使用try,finally的原因在于:如果try中抛出异常,如果没有finally中的解锁,线程不会调用unlock方法,永远占用这把锁,导致其他线程无法进入临界区执行代码。在finally中调用unlock( )方法保证无论什么情况下,锁终将被释放。避免死锁。 private Lock lock = new ReentrantLock(); /** * 使用锁 */ void sell() { while (tickets > 0) { lock.lock(); try { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + " sell ticket:" + tickets); tickets--; } } finally { lock.unlock(); //锁必须在finally块中释放 } //do something try { TimeUnit.MILLISECONDS.sleep(50L); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + " sell out"); } 在上述展示的代码中,如果线程遇到售卖同一张票,锁没有被释放,线程将会等待。改善这种情况的方法是,我们使用10把锁,使得每张票都有一把锁,当线程A售卖某张票时,其他线程可以跳过这张票,无需等待去卖其他未售出的票。或者,使用两把锁,五张票一把锁,这种分段锁的策略进一步提高了并发的效率。 4. 线程池 线程虽然不占用进程中的资源,但在Java中,如果每当一个请求到达就创建一个新线程,开销是相当大的。并且,如果在一个JVM里创建太多的线程,可能会导致系统由于过度消耗内存导致系统资源不足,为了防止资源不足,应该尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量复用已有对象来进行服务,这就线程池技术产生的原因。如果想要实现线程的复用,我们需要继承线程,在run方法中通过循环不断从外部获取runnable的实现,以此达到线程复用的目的。有了复用后,可以提供线程池,管理线程,线程池可以控制线程的并发度,同时,通过对多个任务重用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。 下面介绍一下线程池的使用。下图代码中展示了ThreadPoolExecutor的构造方法,下面介绍一下方法中包含的参数。 corePoolSize:表示线程池的核心线程数,指线程池中常驻线程的数量,核心线程数会一直在线程池中存活,除非线程池停止使用被资源回收了。 maximumPoolSize:指线程池所能容纳的最大线程数量,当活动线程数到达这个数值后,后续的新任务将会被阻塞。 keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。 Unit:用于指定keepAliveTime参数的时间单位。 workQueue:表示线程池中的任务队列(阻塞队列),通过线程池的execute方法提交Runnable对象会存储在这个队列中。 threadFactory:表示线程工厂,为线程池提供创建新线程的功能。 RejectExecutionHandler:这个参数表示当ThreadPoolExecutor已经关闭或者已经饱和时(达到了最大线程池大小而且工作队列已经满),提供以下几个策略考虑是否拒绝到达的任务。DiscardPolicy:直接忽略提交的任务 AbortPolicy:忽略提交的任务,在拒绝的同时抛出异常,通知调用者拒绝执行 CallerRunsPolicy:让线程池的使用者所在的线程运行提交的任务调用者 DiscardOlderestPolicy:忽略最早放到队列中的任务 下面代码自定义了一个线程池。通过线程池的submit( )方法提交runnable的实现,最终通过线程池的shutdown( )方法关闭线程池。 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(16, 30, 30L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(false); t.setUncaughtExceptionHandler((thread, e) -> System.out.println(e.getMessage())); return t; } }, new DiscardOldestPolicy()); threadPoolExecutor.submit(() -> System.out.println(Thread.currentThread().getName())); threadPoolExecutor.shutdown(); //ExecutorService executorService = Executors.newFixedThreadPool(); findJavaExecutorsBug(); Java包中预置的线程池有以下几种:newSingleThreadExecutor;newFixedThreadPool:newCachedThreadPool: newScheduledThreadPool: 但在阿里巴巴的Java开发中是不建议甚至禁止使用Java预置线程池的。下图中的代码目的是寻找SingleThreadExecutor的bug。 static void findJavaExecutorsBug() { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (;;) { executorService.submit(() -> { try { TimeUnit.SECONDS.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } }); } } 上述代码的运行结果如下图所示。代码利用循环,无限添加runnable的实现,但是由于单一线程的阻塞队列是没有边界的,会导致添加的对象过多,耗尽内存资源。因此阿里巴巴开发手册是明确禁止使用Java预置线程池的。 本文由云栖志愿小组沈金凤整理,编辑百见
摘要:本文介绍了C++对象模型的特殊之处,包括与C兼容的朴素模型,以及能支持多态的虚表模型,同时还带大家了解了构造函数与析构函数相关的一些特性与陷阱。这些内容能够帮助大家更好地学习和使用C++。 数十款阿里云产品限时折扣中,赶紧点击这里,领劵开始云上实践吧! 演讲嘉宾简介: 付哲(花名:行简),阿里云高级开发工程师,哈尔滨工业大学微电子学硕士,主攻方向为分布式存储与高性能服务器编程,目前就职于阿里云表格存储团队,负责后端开发。 以下内容根据演讲嘉宾视频分享以及PPT整理而成。 本次的分享主要围绕以下两个方面: 一、C++对象模型的特殊之处 二、构造函数与析构函数相关的一些特性与陷阱 大家都知道,做好云服务,项目首先要保持稳定,不能总出现bug,另外,还要提供好的性能,而要达到这两点,项目开发人员需要深入理解C++这门语言以及语言背后的实现原理,即编译器如何实现这些C++语言带来的特性。 下面首先介绍一下什么是对象模型。 第一个问题,什么是对象模型? C++ Primer的一位作者Lippman在他的另一部非常经典的作品《深入探索C++对象模型》中有如下总结:有两种概念可以解释C++对象模型,一是语言中直接支持面向对象的程序设计的部分;二是对于各种支持的底层实现机制。说得直白一点,对象模型就是要在我们脑海中建立起一种模型,这种模型能告诉我们C++对象为什么要设计成这个样子,这种设计带来的优缺点以及编译器在背后做了什么事情。 上面介绍了对象模型的含义,那么学习对象模型的意义是什么呢?——当我们脑海里建立起了这个模型,对象就不再神秘,它的行为我们自己都可以推理出来。总而言之,对象模型能告诉我们如何正确而高效地使用对象。另一点,C++是一门多范式语言,不只有面向对象这一块。但其它部分也还是建立在对象模型基础上的,因此了解对象模型,对学习其它C++特性也很有帮助。 一、C++对象模型的特殊之处 现在流行的面向对象语言非常多,比如Java、C#、Python、Ruby、JavaScript,以及先行者SmallTalk,这些语言都有自己的对象模型。那么相比这些模型,C++对象模型有什么特殊之处吗? C++在最开始的时候,目标是成为C with class,这体现了它的两大卖点:一是与C兼容,二是面向对象,也就是要有多态。而另一方面,C++的一个设计哲学就是零开销抽象,即开发人员做一个抽象,或者说一个特性,不会给不使用它的用户带来额外的开销。C++对象模型就深受这三条的影响,形成了它的特点——既要与C兼容;同时要能表达面向对象的理念,即体现多态;也要保证零开销抽象。因此C++的对象既可以像C的结构体,又可以像Java/C#中的对象。它既可以有复杂的功能,又可以在不同场景下都保持很高的执行效率,这就是它的特殊之处。 下面先从先从兼容C那部分介绍:Niklaus Wirth有句非常著名的话,叫“数据结构加算法等于程序”,这也是他的一本书的名字。C的抽象数据结构就体现了这句话,它就是数据结构加上操作这个数据结构的若干个函数。这就相当于是C的对象模型。把它搬到C++里,就是C++的朴素对象模型,数据结构,也就是C++的类,加上成员函数,就等于对象。朴素对象模型是完整的C++对象模型的一个子集,也是基础,它体现了面向对象中封装的部分。下图说明了C++的数据结构。 我们看TreeNode类型,它代表了红黑树中的一个节点。它既是C的结构体,也是C++的类,两者是兼容的,那么也能够分配在栈上,也不会初始化成员变量的值。对于这么一个类,没有任何成员函数,只关心两个东西,一个是它占多少空间,一个是它的空间结构是什么样的,或者说它的每个成员变量在类里的偏移是多少。在64位环境中执行下图左侧的代码,右侧展示了代码的输出结果。 输出结果显示,这个对象占32个字节,其中mValue的偏移是0,mLeft的偏移是8,mIsRed的偏移是16,mRight的偏移是24。 是不是看出问题了,为什么每个成员的偏移都比上个成员大8字节?我们知道64位环境中指针是占8个字节的,int32_t是占4个字节的,bool是占1个字节的,为什么后一个成员的地址减去前一个成员的地址,结果不等于前一个成员占的空间大小呢?这是因为编译器为我们做了对齐和填充。编译器为什么要做这个事情?cpu有个概念叫字长,是它处理数据的一个单位。有些cpu甚至要求访问内存时,内存地址必须是字长的整数倍,否则就报错。有些cpu不报错,但访问跨字长的数据时,会将一次操作拆成多次操作,影响性能。x86-64下这些问题倒是没有,但编译器为了兼容以前的代码,还是会按x86的标准调整每个变量的位置,使其能对齐。这样一来不同变量之间就不是紧凑布局了,就会有空隙。x86的标准是,大小为1、2、4、8字节的变量,内存地址也要能被1、2、4、8整除。结构体的话如果没有成员,是个空结构体,那么就按1处理,如果有成员,就按里面对齐标准最严格的那个处理。 因此我们看到的TreeNode类,它的内存布局实际是这样的,如下图所示。编译器在mValue和mIsRed后面都加了填充,以此来保证每个成员都按各自的标准是对齐的。 可以看到,TreeNode的成员排列似乎不太合理,填充太多,如果调整一下成员的顺序,把mIsRed前提,与另一个比较小的成员mValue排在一起(如下图所hi),这样填充就变少了, TreeNode对象也变小了,从32个字节降到了24个字节。如下图所示。 如果想添加一个前序遍历树节点的函数Visit,有两种方式,一种是像C一样,定义一个全局函数,它的参数是TreeNode的指针。这种我们叫非成员函数,意思是它不是任何类的成员。而另一种方式是把Visit加到TreeNode类里面去,这样它就不需要有参数了。这种我们叫成员函数。如下图所示。 加完了成员函数后,我们重新跑一下前面的main函数,结果就不贴出来了。可以看到TreeNode对象仍然占24个字节,这说明成员函数不占用对象的体积。换句话说,成员函数不位于对象内部,也是在外部。 我们在gcc5.4.0下编译这两个函数,打开优化,对比它们的汇编指令,结果有点长,就不贴了。结论是成员函数版本的Visit与非成员函数版本的Visit汇编指令是完全相同的。这说明成员函数与非成员函数是等效的,我们把函数移到类里面不会给对象的使用带来任何额外的开销。 以上两条结论,充分显示了C++的朴素对象模型就是一种零开销抽象。 朴素对象模型就介绍到这里。接下来我们开始探索支持多态的对象模型。 本文说到的多态指的是一种运行期的多态,C++中是通过继承和虚函数来支持的。一般模式是基类有虚函数,然后派生类继承自基类,并改写这个虚函数。之后可以把派生类的指针或者引用传给需要基类的指针或者引用的地方,这些地方去调用这个虚函数时,会去执行派生类改写的那个版本。这就是运行期多态。 刚才本文在朴素对象模型中讲的是非虚的成员函数,编译器在编译时就能决定到底调用哪个函数,这叫做静态绑定。而参与多态的虚函数,编译时是不知道具体要调用哪个函数的,是基类的还是派生类的,要在运行期才能确定,这叫做动态绑定。 另外我们要明确的是,继承不一定意味着多态。多态有两个触发条件,一个是必须要通过虚函数触发,那么如果我们调用的基类成员函数不是虚函数,就只会有静态绑定。下图的例子中,Base有个成员函数F,不是虚函数,那么Derived就没办法改写它,只能重载它,这里就不会有多态。 多态的另一个触发条件是它一定要通过指针或者引用去调用,如果直接通过对象的话是没办法触发多态的。这个的原因是C++不同类型有着不同的大小,如果直接传对象的话,它的大小是固定的,类型也就固定了,基类就是基类,不会是派生类。而指针和引用就不同了,无论什么类型的指针和引用都是8个字节,这样我们才能让一个指针或引用可能扮演多个类型的对象。 下图的例子中,三个函数里,第一个就没办法触发多态,而后两个就可以。 我们再回到数据结构。当我们让一个派生类去继承自一个基类,这个过程中派生类的数据结构发生了什么变化呢?C++标准规定,派生类的对象里面,每个基类都要对应有一个基类子对象。它就像是派生类自己的一个成员,要求是这个基类子对象要与一个独立的基类对象完全相同。 我们可以看下图的例子: Point2D是一个表示二维的点的类型,而Point3D扩展了它,加了一个维度,变成了三维的点。为了重用代码,我们选择Point2D作为基类,而Point3D继承自它。Point3D对象里面就可以认为有一个Point2D的子对象,且与一个单独的Point2D对象在各方面都是相同的。 每个基类子对象在派生类对象中的偏移在编译期就固定下来了。这个值标准中没有规定,但常见的编译器都选择把基类子对象放到派生类对象的头部,如果有多个基类,那么它们的子对象按声明顺序依次排列。第0个基类子对象的偏移是0,也就是它与派生类对象共享相同的内存地址。 这样当我们通过派生类对象调用基类的成员函数时,编译器只需要简单的计算一下偏移,移到对应的基类子对象地址,再调用函数就可以了。相比独立的基类对象去调用函数,没有什么多出来的开销。 而且无论我们继承几层,都只是计算一次偏移,不会随着继承层数的增加而导致调用开销也跟着增加。这又是一个体现C++对象模型是零开销抽象的地方。 接下来,我们真正进入面向多态的对象模型。 首先我们要明确的是,一种发生在运行期的特性,一定是有运行期开销的,尤其是对于C++这种几乎把运行开销降到最低的语言。那么对象模型要支持发生在运行期的多态,就需要付出一些代价。比如在对象中记录一些信息,比如函数调用路径变长,等等。但C++保证了,如果开发人员不用多态,就不会有这些运行期开销,也就是前面介绍的朴素对象模型。即使有了多态,它也尽量将这种开销降到了最低。下面我们来看三个可以支持多态的模型。我们以下图中这个类为例: 这种模型很简单,它认为对象就是一个表格,其中每个位置都是一个指针,指向一个成员,这个成员包括成员变量和成员函数,我们在访问成员时就是根据这个表格中这个成员的位置,找到这个指针,再跳转过去访问真正的成员。这种模型很简单,编译器实现难度很低。而且我们注意到,这个模型可以不需要区分虚函数或非虚函数,因为大家都要跳转一次,都是动态绑定。它的缺点也很明显,首先是不与朴素模型兼容,其次是访问成员要至少一次间接寻址,开销比较大。因此没有编译器真的采用过简单对象模型。 接下来这种模型类似于简单对象模型,但它把成员变量和成员函数分到了两个表格中。这么做的好处是这样无论什么类型的对象,大小和布局都一样了。模型如下图所示。当然双表格模型也有着与简单模型相同的缺点,就是不兼容朴素模型,且开销较大。 实际上,所有编译器都采用了虚表模型。它相比朴素模型,只有一个区别:对象里会增加一个虚表指针。这个虚表指针实际会指向一个表格,这个表格有N+1个位置,分别放指向这个对象本身的信息的指针,类型为type_info*,和N个指向虚函数的指针。模型如下所示,每个类型都有自己的虚表,其中派生类的虚表会与基类的虚表兼容,但指向类型信息那个指针会指向派生类自己的类型信息。这个模型的优点就是首先与朴素模型兼容,如果类没有虚函数,它的对象中就不会有虚表指针,此时就与朴素模型完全相同了。 第二个优点是这是一个平铺的结构,如果我们多次继承,多次改写一个虚函数,它的访问开销始终只会有一次间接寻址,不会随着继承层数增加而增加。 它的缺点实际上也是朴素模型的缺点,就是每个派生类都要知道它的每个基类的内存布局,无论哪个基类修改都会导致派生类也要跟着重新编译和链接。 下面,我们介绍一下多基类下的虚表对象模型,如下图所示中的例子: 这里面Derived有两个基类,且都有虚函数,因此两个基类子对象都需要一个虚表指针。这里值得注意的是这两个虚表指针实际指向的是同一个虚表的不同位置。派生类的虚表会同时包含每个基类的虚表,只要指向不同位置,就能兼容不同基类的虚表。因此这个多基类例子里,Derived的对象实际如下所示。 我们知道C++有四种转换,static_cast、dynamic_cast、const_cast和reinterpret_cast,另外C++还允许用C风格的转换,就是圆括号里写上目标类型。在支持多态的对象模型下,我们有两种转换需求,一种是派生类的指针或引用到基类的指针或引用的转换,称为up-cast,一种是基类的指针或引用到派生类的指针或引用的转换,称为down-cast。 up-cast与派生类指针或引用到基类的指针或引用的隐式转换实际是相同的,这里我们应该用的是static_cast,它会根据基类子对象的偏移,应用到源对象的地址上,生成一个指向这个基类子对象的指针或引用。C风格的转换也会修正偏移,但我们还是推荐用static_cast,因为它更醒目。dynamic_cast不适用于这种场景,而reinterpret_cast用在这里是错的,它不会考虑基类子对象的偏移,在多基类时实际会得到一个错误的地址。 down-cast场景应该用的是dynamic_cast,它首先会根据基类子对象的偏移,计算出目标的派生类地址,并且还会拿出这个地址对应的类型信息,看到底能不能转换成功,如果转换失败就返回一个空指针。而static_cast和C风格的转换只能做到前面一点,它会计算偏移量,但识别不出来转换失败。reinterpret_cast就更不能用了,它连偏移量都不会算。 二、构造函数与析构函数相关的一些特性与陷阱 接下来我们简单介绍一下构造函数与析构函数。构造函数会在一个对象被构造时触发,这个对象可能构造在栈上,也可能构造在堆上,后者一般就是通过new来构造。而析构就是与构造相对,当一个栈上对象离开定义的作用域时,这个对象会自动析构,而对于堆上构造的对象,我们也可以通过delete来手动析构。 构造函数会依次做这么几件事情: * 按声明顺序依次构造每个基类子对象。 * 设置虚表指针,指向正确的虚表位置。 * 按声明顺序依次构造每个成员变量。 * 依次调用构造函数体中的语句。 而析构函数则是反过来,做这几件事: * 依次调用析构函数体中的语句。 * 按声明逆序依次析构每个成员变量。 * 按声明逆序依次析构每个基类子对象。 构造函数和析构函数有一个特别的地方,它们的执行过程中没有多态,虚函数的调用也会走静态绑定。如果不这么做,当构造基类子对象时,我们调用到了一个被派生类改写过的虚函数,它可能会访问到派生类自己的成员变量,但此时这些成员变量还没有构造,产生的行为是不可预期的。因此构造函数期间不能有动态绑定。而析构函数也是相同原因。 一般来说对象的构造时间都是比较好确定的,而它的析构时间则有些值得注意的点: * 全局/静态变量在main函数之后析构。 * 局部变量在出定义的作用域后析构。 * 临时变量在其所在的最外层表达式执行完成后析构。 * 被赋值给const引用的临时变量,在引用出作用域后析构。 * 被赋值给右值引用的临时变量,在引用出作用域后析构。 接下来我们来看几个构造函数和析构函数的陷阱。 1. 成员初始化列表要按声明顺序 第一个陷阱是在写构造函数的初始化列表时成员顺序与声明顺序不同,如果其中还有声明靠前的成员依赖声明靠后的成员的值,那会产生未定义行为。下图展示一个例子。原因是成员会按声明顺序而非列表顺序构造。如果初始化列表顺序不同于声明顺序,编译器会警告。因此不能依赖声明顺序靠后的其它成员。为了避免这个问题,推荐严格按照成员的声明顺序写初始化列表。 2. 尽量在初始化列表中构造成员 第二个陷阱是对于有构造函数的成员,如果我们在构造函数体内去做赋值,可能会浪费之前的构造。如下图中展示的例子。看这个例子: 原因是:进入构造函数体前所有成员都已构造完,另外,构造函数体中再赋值会浪费一次构造。为了避免这个问题,推荐成员的构造都通过初始化列表来做,尽量不要放到构造函数体中做赋值。 3.不同编译单元全局变量构造不能相互依赖 第三个陷阱是一个cpp文件中的全局变量,用于构造它的值来自另一个cpp文件中的全局变量,也可能会产生未定义行为。我们来看下图中的例子: 原因是:全局变量在main函数前串行构造;不同编译单元的构造顺序随机;A可能早于依赖的B构造,结果未定义。静态变量本质上也是全局变量,因此也会遇到这个问题。 我们并不推荐程序中使用全局变量或静态变量,但如果真要用的话,可以参考上图箭头下方的用法。 4. 单参数构造函数应声明为explicit 第四个陷阱是如果我们写了一个单参数的构造函数,但未声明为explicit,那么可能会产生预期之外的隐式转换。如下图所示: 因此我们推荐所有单参数的构造函数都声明为explicit,如上图箭头下方所示。 5. 析构函数的陷阱 第五个陷阱是析构函数的陷阱,它实际包含两部分。一是类有虚函数,但析构函数不是虚的,它的问题是当一个基类指针指向派生类对象,我们去delete这个指针,它实际只会调用基类的析构函数,因为析构函数不是虚的,没办法有多态行为。这会导致资源泄漏。例子如下图所示: 二是一个纯虚的基类,析构函数也是纯虚的。这会导致派生类析构时,它调用到了这个纯虚基类的析构函数,但没有定义。这会导致构建时链接失败。 因此我们推荐,有虚函数的类,就要有一个虚的析构函数,且要给一个定义,而不管类本身是不是纯虚类。 对于默认构造函数与析构函数,有一个特性,就是编译器在必要时会为没有的类型生成这两个函数。 生成构造函数的条件是:没有自定义的构造函数,且代码中调用了默认构造函数。 生成析构函数的条件是:没有自定义的析构函数,且代码中调用了析构函数。 本文由云栖志愿小组沈金凤整理,编辑百见
摘要: 深度学习是指多层神经网络上运用各种机器学习算法解决图像,文本等各种问题的算法集合。卷积神经网络(CNN)是深度学习框架中的一个重要算法,本文介绍了CNN主流模型结构的演进过程,从一切的开始LeNet,到王者归来AlexNet,再到如今的CNN模型引领深度学习热潮。本文也将带领大家了解探讨当下与CNN模型相关的工业实践。 演讲嘉宾简介: 周国睿(花名:逐水),毕业于北京邮电大学模式识别实验室,目前是阿里妈妈事业部算法专家,服务阿里妈妈精准定向广告的排序相关业务;致力于深度学习在广告排序的应用和研究,涉及模型结构设计、模型压缩、深度学习框架开发,相关工作发表于AAAI等会议中。 以下内容根据演讲嘉宾视频分享以及PPT整理而成。 本次的分享主要围绕以下三个方面: 1.CNN Architectures 演进过程 2.网络从浅至深的思考 3.工业界的选择 一、CNN Architectures 演进过程 熟悉技术的发展历史,能帮助大家掌握当下的技术,同时也能对未来的技术更富想象力。接下来,为大家分享CNN-based model主流模型结构的演进过程。 下图中横轴表示时间线,纵轴表示模型的深度(即神经网络的层数)。用最简单的视角概括图中CN的发展就是,层数愈来愈深了。下面,为大家简单介绍图中涉及的模型。 上图为LeNet的网络结构,虽然该模型层数较浅,但是麻雀虽小,五脏俱全。模型中包含的卷积网络,降采样,全连接网络等,这些都是现代CNN网络的基本组件。LeNet依靠多个领域相关的filter, 采用单filter参数共享(即参数复用,降采样),以此保障一定程度的平移,形变扭曲的不变性。LeNet试图通过这种模型结构,让模型自己提取输入信号中特征的关系。 2012年,AlexNet 在当年的ImageNet数据集的分类竞赛中,取得了第一名,而且在性能方面远超过当年的第二名,使得深度学习再一次获得人们的关注,回到了历史的舞台。下图为AlexNet的模型结构,AlexNet成功的原因有以下两点: 1. ReLU激活函数 首先,常用的激活函数Sigmoid,在输入非常大时,神经元的梯度是接近于0的,存在饱和现象。而Alex采用的ReLU激活函数在输入信号小于0时,输出都为0,在输入信号大于0时,输出等于输入,一定程度上降低了过拟合,梯度爆炸的风险,减少了反向传播的计算量。 2. Heavy Data augmentation,Dropout AlexNet在训练时,对于256*256的图片进行随机提取其中224*224的图片,然后允许水平翻转,相当于将样本扩大到了((256-224)^2)*2=2048倍。在测试时,固定了五个位置,即左上、右上、左下、右下、中间,对这几个位置提取五个224*224的图片,然后翻转,共10个图片,之后对结果求平均作为最后的预测目标。以上的技术极大的增强了模型的泛化能力。另外,AlexNet对图像的RGB空间做PCA,然后对主成分做一个(0, 0.1)的高斯扰动,使得错误率又下降了1%。AlexNet也采用了Dropout的技术,它做的就是以0.5的概率,将每个隐层神经元的输出设置为零。 AlexNet相对于LeNet,虽然看起来只是增加了计算量,层数变深了,但是提出AlexNet的作者依靠对CV领域相关技术的长时间积累,做了很多细节工作,将许多技术综合起来才取得了这一突破性的成果。AlexNet的提出是一个新时代开始的标志,非常值得我们尊敬。 VGGNet 探索了卷积神经网络的深度与其性能之间的关系,通过反复堆叠3*3的小型卷积核,VGGNet 成功地构筑了16~19层深的卷积神经网络,在训练时,VGGNet输入是大小为224*224的RGB图像,预处理只有在训练集中的每个像素上减去RGB的均值。 VGGNet选择3*3滤波器尺寸的原因如下: 1. 这是能捕捉到各个方向(即上下,左右,中间)的最小尺寸; 2. 如下图所示从感知视野的角度,一个5*5可以用两个3*3来近似代替,一个7*7可以用三个3*3的卷积核来代替; 3. 对于3个3*3的卷积核,通道数为C,则参数为3×(3×3×C×C)=27C^2,而一个7*7的卷积核,通道数也为C,则参数为7×7×C×C=49C^2,不仅增强了模型的泛化能力,而且还减少了参数。 VGGNet性能相对于AlexNet提升了,但是VGGNet在模型接近输入的前几层,需要大量的内存。同时,VGGNet,AlexNet两者模型在最后几层的全连接上,需要的参数非常多,计算量也非常大。 在GoogleNet出现之前,CV领域常用的模型如果通过粗暴地扩充层数或者增加神经元的个数,以此加深模型的层数,这是很浪费计算机资源的,并且由于模型是串联的,所以会直接增加计算代价,同时并不能保证这些计算都是必要的。GoogleNet的模型结构与之前提出的模型结构不一样,GoogLeNet采用了模块化的结构,方便增添和修改;模型移除了全连接,减少了大量的参数;为了让梯度更好的回传,模型额外的增加了两个辅助的分类器的loss。 下面具体介绍GoogleNet模型中Inception模块的设计。如下图所示,Inception模块由滤波器和几个采样器并联组成,简单的说就是共享输入,连接合并输出。这个设计的假设是基于底层的相关性高的单元,通常会聚集在图像的局部区域,所以使用尺寸为1*1的滤波器,同时为了保证信号的接收范围,模块之后使用更大尺寸的卷积学习即可。另外,网络越到后面,特征越抽象,而且每个特征所涉及的信号视野也更大了,因此随着层数的增加,3*3和5*5卷积的比例也要增加。 上图的Inception模块,如果使用5*5的卷积核仍然会带来巨大的计算量,因此GoogleNet的作者采用1*1卷积核来进行降维,降低输入维度的信号。改进后的Inception模块如下图所示,Inception技术既能保持网络结构的稀疏性,又能利用密集矩阵的高计算性能。 ResNet提出的动机是“退化”问题的发现,即当模型的层次加深时,错误率却提高了,如下图所示。但是模型的深度加深,学习能力增强,因此更深的模型不应当产生比它更浅的模型更高的错误率。而这个“退化”问题产生的原因归结于优化难题。 ResNet的作者提出了一个残差结构,如下图所示。结构增加一个identity mapping(恒等映射),将原始所需要学的函数H(x)转换成F(x)+x,这样的结构让模型有了一个显式的F(x)+x的计算逻辑,模型的学习难度大幅度下降。ResNet依靠这个技术极大的扩展了模型的深度,ResNet作者发明的残差网络,将之前的GoogleNet和VggNet等不到40层的网络直接提升到151层甚至到201层,也让模型性能获得了极大提高,这是模型结构上的突破性贡献。 二、网络从浅至深的思考 为什么更深的网络性能会更好呢?在这个问题上,人们已经做了很多研究。最早在1989年,George Cybenko提出了如下的理论:一个神经网络的层数可以很浅,只要在有足够多的隐层结点的前提下,这个神经网络就具有拟合任何函数的能力。但是,较浅的神经网络在大多数情况下,它的表现都是弱于深的神经网络,原因在于,浅的神经网络虽然存在一个拟合特定函数的解,但是求解的过程可能是很困难的,简单的梯度优化算法可能无法找到函数的解。此外,在2013年,Eigen等人提出在CNN-based的模型中,通过叠加模型的层数,就能提高模型的计算能力。在一篇论文《SNN-MIMIC:Do Deep Nets Really Need to be Deep?》中,作者用真正的实验数据证明了浅的神经网络有能力和深的神经网络学习到一样的函数。SNN-MMIC的作者尝试让浅层网络模拟深度网络的表现,下图为SNN-MIMIC使用的Teacher-Student学习框架。Teacher是一个更深的网络,Student是一个更浅但可能更宽的网络,先训练好Teacher直至收敛之后,用Teacher的输出结果作为Soft Target,并用这个Soft Target监督Student。通过这样的训练框架,SNN-MIMIC的作者成功证明在实际情况下,是有方法使浅层网络达到和更深的网络差不多的性能。SNN-MIMIC的作者也解释了在下图训练框架中,浅层模型模拟深层模型相较于单独训练浅层模型,训练性能来的好的原因。原因总结如下: 1. 假设训练样本中的label是错的(例如将猫标注成了狗),在这种情况下,如果用label进行训练,预测会显示这是一条狗,因此带来噪声,误导模型的训练。但是Teacher的预测结果,会得到这是一条狗或者一只猫,输出可能和label相反,让Teacher的预测结果监督Student的训练,会将错误的那部分信息抹掉。这种假设在作者的实验环境中是不存在的,但至少这种假设在现实生活中是存在的。 2. Teacher网络更深,通过浅层网络模拟深层网络,可以解决Student不能解决的更难的问题。 3. Soft target本身更容易学习,拟合平滑的波相对于拟合尖锐的波更简单。 增加模型的深度提高了模型的性能,但是,更深的网络在gradient-based训练方法下,更难训练(FIT-Nets的提出早于ResNet)。FIT-Nets作者的目标是通过更浅的网络(即student)指导更深的网络(即teacher),以此训练一个比Teacher性能更好的网络。Teacher中间隐层的信息叫做hints,框架通过将Teacher与Student中间的隐层降到同一个维度,以此对Student的信息重构出Teacher的表达。 三、工业界的选择 下面介绍工业界对神经网络的应用。模型的精度与响应延迟存在一个天然的tradeoff,模型越复杂,精度可能越高,但响应时间越长。虽然这两者是相互矛盾的,但是幸运的是,工业界可以用离线训练的复杂化换取更高效的模型精度,也就是说把离线训练过程做的复杂,而在线的时候保证inference的高效同时提升模型的精度。解决上述问题的思路可以分为两种,一种是硬性的压缩(hard compress),比如说mobilenet直接减少了卷积的计算,把模型结构变得更简单,直接降低了计算复杂度。另一种是soft tradeoff,离线的时候利用训练出来的复杂模型辅助简单模型的学习,提升简单模型的精度,在线的时候只需要使用简单模型,例如SNN-MIMIC,Knowledge Distillation,FitNet,Rocket Launching等。这两种思路就体现了离线时训练复杂化,在线时就可以使用更高效,精度也能得到保证的模型这样的一个过程。 下面介绍一下演讲者周国睿他们团队在这方面的工作,即Rocket Launching。训练框架如下图所示,Rocket Launching算法也属于Teacher-student算法的范畴,算法训练一个Teacher及Student网络,Teacher网络辅助Student网络的学习,虽然训练的网络更浅,但是性能更高,inference速度快,因此可以满足上线的需要。 不同于之前的算法,周国睿团队认为模型可以拆解为输入的表达层和最后的决策层,通过参数共享更直接地将Teacher学习到的信息传递给Student。此外,之前Teacher-Student的训练框架先是固定Teacher,之后使用Teacher输出结果指导Student的学习,不同于以往Teacher-Student训练框架,Rocket Launching让Teacher网络与Student网络同时学习,最后Student网络不仅能学习到Teacher拟合的结果,还能学习到整个Teacher收敛过程中的路径。同时,Rocket Launching对Teacher网络独有的参数做一个Gradient Block,让Teacher网络的学习不受Student网络和它共同学习的影响。Rocket Launching训练的过程类似于火箭的发射,卫星与推进器组合起来才称为火箭,使用Teacher网络与Student网络一起学习(类似于卫星与推进器一起发射),最后在线时只需要使用Student网络(类似于推进器将卫星送上天),因此周国睿团队将训练框架称为Rocket Launching。 周国睿团队在公开数据集CIFAR-10,CIFAR-100等上,将Rocket Launching模型与属于Teacher-Student范畴的方法进行对比,效果取得了明显的提高。同时,周国睿团队还做了非常仔细的Ablation Study,以此验证之前提到的三个改进点(即协同训练,参数共享,Gradient Block) 都提高了模型的训练效果。此外,周国睿团队将Rocket Launching中协同训练方法与KD结合,结果显示结合了KD后,Rocket Launching 模型取得了进一步提高。使用Rocket Launching后,使在线的计算量减少了8倍,性能也没有发生损耗。 本文由云栖志愿小组沈金凤整理,编辑百见