首页 文章 精选 留言 我的

精选列表

搜索[基础搭建],共10000篇文章
优秀的个人博客,低调大师

03.Java基础(线程池和Callable<T>)

在以往的工作中,创建线程通常我们关注的都是Thread或者Runnable为主,其实忽略了另外一个比较有用的创建线程的方式,就是Callable接口,下边是Callable配合线程池实现异步任务 import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CallableDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService pool = Executors.newCachedThreadPool(); Future<Object> future = pool.submit(new MyCallable()); //通过get方法获取执行结果,该方法会阻塞直到任务返回结果 Object obj = future.get(); System.out.println(obj.toString()); } } class MyCallable implements Callable<Object>{ @Override public Object call() throws Exception { System.out.println("执行callable3"); return "right"; } } 在Java中,创建线程一般有两种方式,一种是继承Thread类,一种是实现Runnable接口。然而,这两种方式的缺点是在线程任务执行结束后,无法获取执行结果。我们一般只能采用共享变量或共享存储区以及线程通信的方式实现获得任务结果的目的。 不过,Java中,也提供了使用Callable和Future来实现获取任务结果的操作。Callable用来执行任务,产生结果,而Future用来获得结果。 可以看到,与Runnable接口不同之处在于,call方法带有泛型返回值V。 我们通过简单的例子来体会使用Callable和Future来获取任务结果的用法。 假设我们现在有一个任务,要计算出1-10000之间的所有数字的和,为了提升计算速度,我们使用两个线程,第一个线程计算1-5000的和,另外有一个线程计算5001-10000之间的数字的和。为了更好的展示效果,让每个线程在计算过程中休眠5s。 示例程序来自网友: import java.util.ArrayList; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class TestMain{ public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newCachedThreadPool(); ArrayList<Future<Integer>> resultList = new ArrayList<>(); //创建并提交任务1 AddNumberTask task1 = new AddNumberTask(1, 5000); Future<Integer> future1 = executor.submit(task1); resultList.add(future1); //创建并提交任务2 AddNumberTask task2 = new AddNumberTask(5001, 10000); Future<Integer> future2 = executor.submit(task2); resultList.add(future2); executor.shutdown(); int total = 0; for(Future<Integer> future : resultList){ while(true){ if(future.isDone() && !future.isCancelled()){ int sum = future.get(); total += sum; break; } else{ Thread.sleep(100); } } } System.out.println("total sum is " + total); } } class AddNumberTask implements Callable<Integer>{ private int start; private int end; public AddNumberTask(int start, int end) { // TODO Auto-generated constructor stub this.start = start; this.end = end; } @Override public Integer call() throws Exception { // TODO Auto-generated method stub int totalSum = 0; for(int i = start; i <= end; i++){ totalSum += i; } Thread.sleep(5000); return totalSum; } }

优秀的个人博客,低调大师

java基础学习_常用类01_Object类_day11总结

==========================================================================================================================================================涉及的知识点有: 1:Eclipse的概述和使用(掌握) 2:API的概述(了解) 3:Object类的概述和使用握) (1)Object类的概述 (2)Object类的构造方法 (3)要掌握的Object类的方法(掌握) A:public String toString() B:public boolean eauals(Object obj) (4)要了解的Object类的方法 A:public int hashCode() B:public final Class getClass() C:protected void finalize() D:protected Object clone() (5)两个注意问题==========================================================================================================================================================1:Eclipse的概述和使用(掌握)-----------------------------------------------------------------------------2:API的概述(了解) (1) API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数。 目的是:提供应用程序与开发人员基于某软件或硬件的以访问一组例程的能力,而又无需访问源码,或无需理解内部工作机制的细节。 (2)Java API就是Java提供给我们使用的类(也即就是JDK提供给我们的一些提高编程效率的java类), 这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用。 我们可以通过查询帮助文档(.chm文件)来了解Java提供的API如何使用。 例如:JDK_API_9.0_zh_CN.CHM JDK_API_1.6_zh_CN.CHM 1.6版本是最常用的版本,是由之前未被Oracle收购的SUN公司为我们中国程序猿写的。给SUN打call!!!-----------------------------------------------------------------------------3:Object类(掌握) (1)Object类的概述 Object n 物体;东西;对象 从JDK1.0版本开始。 Object是类层次结构的根类,所有的类都直接或者间接的继承自Object类。所有对象(包括数组)都实现了这个类的方法。 (2)Object类的构造方法 public Object() {} 有且只有一个,并且是无参构造。 这其实就是理解当时我们说过的:子类的构造方法默认访问的是父类的无参构造。--------------------------------------- (3)要掌握的Object类的方法(掌握) A:public String toString() 返回对象的字符串表示,默认是由类的全路径+@+哈希值的十六进制表示。 结果应该是一个简明扼要的表达,容易让人阅读。 上面的表示其实是没有意义的,建议一般子类都会重写该方法。 没有重写该方法前: Student s = new Student(); System.out.println(s.toString()); // cn.itcast_02.Student@424c0bc4 System.out.println(s.getClass().getName() + '@' + Integer.toHexString(s.hashCode())); // cn.itcast_02.Student@424c0bc4 这两个是等价的。 因为当我们编写了很多类的时候,经常在程序运行时要查看对象中各个属性的值,就要重写toString()方法, 那么如何快速的生成toString()方法呢?过程我也讲解过了,基本要求就是要求信息简单明了。 但是最终还是自动生成。 重写该方法后: 在ecplise中,若直接输出一个对象的名称,其实就是调用该对象的toString()方法。示例如下: Student s = new Student(); System.out.println(s.toString()); // Student [name=null, age=0] System.out.println(s); // Student [name=null, age=0] 这两个也是等价的。 Integer类下的一个静态方法: public static String toHexString(int i) 把一个整数转成一个十六进制表示的字符串--------------------------------------- B:public boolean eauals(Object obj) 比较两个对象是否相同(相等)。默认情况下,比较的是地址值是否相同。 而比较地址值是没有意义的,所以,一般子类也会重写该方法。 重写的代码优化:提高效率,提高程序的健壮性。 怎么重写呢?答:一般都是用来比较对象的成员变量值是否相同。 重写过程,我也详细的讲解和分析了。 但是最终还是自动生成。 注意: ==(等号的比较的是): 基本类型:比较的就是值是否相同。 引用类型:比较的就是地址值是否相同。 equals(该方法比较的是): 只能是引用类型:默认情况下,比较的是地址值。 不过,我们可以根据情况自己重写该方法。一般重写都是自动生成,比较的是对象的成员变量值是否相同。--------------------------------------- (4)要了解的Object类的方法 A:public int hashCode() 返回对象的哈希码值。不是实际地址值,但可以理解为地址值。 注意:哈希值是根据哈希算法计算出来的一个值,这个值和地址值有关,但是不是实际地址值。但可以理解为地址值。 B:public final Class getClass() 返回对象的字节码文件对象,反射中我们会详细讲解。 Class类的方法: public String getName() 以 String 的形式返回此 Class 对象所表示的实体名称。(实体包括:类、接口、数组名、基本类型或 void) 即:可以通过Class类中的一个方法,获取对象的真实类的全名称。 C:protected void finalize() 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。用于垃圾回收,但是什么时候回收不确定。子类重写该方法,以配置系统资源或执行其他清除。 D:protected Object clone() 创建并返回此对象的一个副本。可以实现对象的克隆,包括成员变量的数据复制,但是它和两个引用指向同一个对象是有区别的。 Cloneable接口 此类实现了 Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。 这个接口是标记接口,告诉我们只有实现该接口的类才可以实现对对象的复制了。(因为不是所有的类都可以复制的) 注意: 我们讲解的示例是一个浅度克隆,后面会学习深度克隆。 深度克隆:因为一个类可能还有父类,可能还有实现接口,那么继承体系越庞大,克隆就会变得很复杂。--------------------------------------- (5)两个注意问题: A:直接输出一个对象名称,其实是默认调用了该对象的toString()方法。 B:面试题 == 和 equals() 的区别? A:==(等号的比较的是): 基本类型:比较的是值是否相同 引用类型:比较的是地址值是否相同 B:equals(该方法比较的是): 只能比较引用类型。默认情况下,比较的是地址值是否相同。 不过,我们可以根据情况自己重写该方法。一般重写都是自动生成,比较的是对象的成员变量值是否相同。=============================================================================我的GitHub地址: https://github.com/heizemingjun 我的博客园地址: http://www.cnblogs.com/chenmingjun 我的蚂蚁笔记博客地址: http://blog.leanote.com/chenmingjun Copyright ©2018 黑泽明军 【转载文章务必保留出处和署名,谢谢!】

优秀的个人博客,低调大师

VR界的神器WakingApp让你零基础制作VR内容

WakingApp最新更新的系统版本,让任何人都可以为Oculus和Vive头显创作内容。 去年11月份完成C轮融资的WakingApp,最近发布了最新版本的VR/AR内容创作平台ENTiTi,将支持程序小白用户为Oculus Rift和HTC Vive头显创作属于自己的内容和游戏。 WakingApp是来自以色列的一家公司,他们的核心产品是一款基于云的创作平台ENTiTi,用户可通过ENTiTi平台上传文档、图片、视频、3D模型等内容,通过3D视角对上述原材料以及平台自带的工具进行查看编辑;其次通过可视化的逻辑系统建立VR/AR领域所特有的交互关系;最后所生产的内容还可通过WakingApp及合作平台进行分享与体验。简单地说,ENTiTi有点类似H5制作工具里大家熟知的易企秀、人人秀等,又有点像游戏《我的世界》,不需要任何特殊技能即可完成创建内容,并可以和朋友分享成果。 WakingApp在今年上半年就已经开放了相关平台,让所有人都可以为GearVR,Cardboard等头显创作内容。现在该应用再次更新,可以支持Oculus和Vive的内容创作。 WakingApp的董事长Alon Melchner表示:“我们的目标是为大家带来像虚拟现实、增强现实和物联网这样的未来科技,让每个人都可以参与其中,成为内容创作者,而不是当一个单纯的观众。”Alon Melchner还说,WakingApp下一次更新将会让VR和AR作为物联网的用户界面。 据了解到现在为止,并没有人通过WakingApp创作出一个完整的VR内容系统,但是WakingApp的出现,让VR和AR变得不再那么遥不可及,它会激发更多的人加入到内容创作,对于推动VR和AR产业是有很大的益处的。 原文发布时间: 2016-06-17 14:44 本文作者: 巫盼 本文来自云栖社区合作伙伴镁客网,了解相关信息可以关注镁客网。

优秀的个人博客,低调大师

系统架构师-基础到企业应用架构系列之--介绍篇

什么是系统架构 软件架构的起源 软件中的系统架构,其实是从建筑行业中的架构设计参考过来的,但是软件中的系统架构又有很大的特殊性。特殊性表现在,软件的架构可以在设计完毕后,项目进行的过程中进行相应的变化,或者可以推到重来,但是建筑行业中却不能这么做。软件行业有着很大的变化性。 什么是架构 架构总体来说就是实现需求功能的较复杂组件的设计与不能精简的较复杂组件。ISO与IEEE对系统架构的定义:一致认为软件密集型系统的架构分为主要模块,组织模块与支撑模块3部分。 系统架构的目标 功能:功能上必须满足需求。 可靠性:系统系统对于用户的商业经营和管理来说极为重要,因此软件系统必须非常可靠。 可用性:系统必须可用。 可维护性:系统的维护包括两方面,一是排除现有的错误,二是将新的需求反映到现有系统中去。一个易于维护的系统可以有效地降低技术支持的花费 安全性:系统所承担的交易的商业价值极高,系统的安全性非常重要。 可扩展性:必须能够在用户的使用率、用户的数目增加很快的情况下,保持合理的性能。只有这样,才能适应用户的市场扩展得可能性。 系统架构师的职责 系统分析员通过与项目干系人的沟通或者是与客户的沟通整理出来功能需求,形成需求文档,然后将文档交付给系统架构师,系统架构师负责将分析员整理的需求转换为详细设计说明书。这个详细设计书作为与开发人员沟通的主要工具。 系统架构师可以通过使用UML建模工具,在详细设计说明书中书写每个组件的设计用力图或者流程图,或者是通过IBM的relations Rose工具来实现相应的建模。 系统架构师在设计时遵循的原则是: 1、低耦合:就是要求不同的组建之间的耦合度最低。每个组建的功能应尽量的分离,他们直接的调用关系进行把对象的引用转换为接口的引用。 2、高内聚:功能相像的功能放在一个组件中,对外提供接口调用的方式来整理。 系统架构师作为需求分析师与软件开发工程师之间的核心关键。好的系统架构不但可以满足软件的功能需求有更好的扩展性,可维护性,安全性,可靠性的等等。 系统架构师必须参与到开发的全过程。 系统的需求 一般来说。系统的需求分为:功能性需求与非功能性需求。 系统架构设计影响的原因 系统架构师在进行系统设计的时候一般采用的方式是通过逻辑分层将功能相像的模块放在单独的层中,然后通过分层来实现系统的功能分离与低耦合、高内聚的原则。 系统架构的设计与系统采用的开发方式有关,软件的开发方式可简单分为:传统方式与敏捷开发。 传统方式:大家熟知的瀑布型模式,软件的过程严格的按照上级步骤执行完毕后开始下一步骤。 敏捷开发:则是递增需求的迭代开发。每个阶段都会新增一个需求,完成这个需求之后变发布软件,虽然软件的功能不全,但是至少是可以使用的。 系统架构师的设计方案,最后由项目干系人进行确定,确定采用哪个设计方案,当然最后也可能不采用,造成这样的结果,可能的原因是非功能性需求的原因。非功能性的需求:例如与之前系统的继承,硬件设备,网络环境等等。 系统架构的设计方案 系统架构可以简单的分为隐式架构与显示架构,例如:盖一个小狗的棚子,有经验的架构师在自己的脑海中就已经有了如何去架构。这就是隐式架构。有些情况下:系统的功能比较复杂和细化,我们不可能在脑海中就能想出来如何架构,需要通过做demo或者模块化的划分来设计,这就是显示架构。 系统的架构设计,必须考虑设计的可行性,可扩展性,健壮性,可用性,高效性,安全性等方面,还要考虑软件所处的环境等等,各方面的因素都要考虑,否则等软件开发的过程中发现系统架构设计中的不足再去修改,代价是很昂贵的。 系统架构说明 因为本人初次写博。部分描述不清楚的部分在所难免,错字方面还请见谅,还请大家多多提出交流意见,大家共同提高。 下一篇将对系统架构的具体实践展开讲解 最后CallHot会尽心尽力写好这个系列,同时由于是自己对这些知识的使用总结和心得体会,错误之处在所难免,所以希望大家能够多多指点,这样在使一部分人受益的同时也能纠正我的错误观点,以便和各位共同提高,后续文章敬请关注! 本文转自何戈洲博客园博客,原文链接:http://www.cnblogs.com/hegezhou_hot/archive/2010/09/07/1820327.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

分布式基础学习【二】 —— 分布式计算系统(Map/Reduce)

二. 分布式计算(Map/Reduce) 分布式式计算,同样是一个宽泛的概念,在这里,它狭义的指代,按Google Map/Reduce框架所设计的分布式框架。在Hadoop中,分布式文件系统,很大程度上,是为各种分布式计算需求所服务的。我们说分布式文件系统就是加了分布式的文件系统,类似的定义推广到分布式计算上,我们可以将其视为 增加了分布式支持的计算函数。从计算的角度上看,Map/Reduce框架接受各种格式的键值对文件作为输入,读取计算后,最终生成自定义格式的输出文件。而从分布式的角度上看,分布式计算的输入文件往往规模巨大,且分布在多个机器上,单机计算完全不可支撑且效率低下,因此Map/Reduce框架需要提供一套机制,将此计算扩展到无限规模的机器集群上进行。依照这样的定义,我们对整个Map/Reduce的理解,也可以分别沿着这两个流程去看。。。 在Map/Reduce框架中,每一次计算请求,被称为 作业。在分布式计算Map/Reduce框架中,为了完成这个作业,它进行两步走的战略,首先是将其拆分成若干个 Map任务,分配到不同的机器上去执行,每一个Map任务拿输入文件的一部分作为自己的输入,经过一些计算,生成某种格式的中间文件,这种格式,与最终所需的文件格式完全一致,但是仅仅包含一部分数据。因此,等到所有Map任务完成后,它会进入下一个步骤,用以合并这些中间文件获得最后的输出文件。此时,系统会生成若干个 Reduce任务,同样也是分配到不同的机器去执行,它的目标,就是将若干个Map任务生成的中间文件为汇总到最后的输出文件中去。当然,这个汇总不总会像1 + 1 = 2那么直接了当,这也就是Reduce任务的价值所在。经过如上步骤,最终,作业完成,所需的目标文件生成。整个算法的关键,就在于增加了一个中间文件生成的流程,大大提高了灵活性,使其分布式扩展性得到了保证。。。 I. 术语对照 和分布式文件系统一样,Google、Hadoop和....我,各执一种方式表述统一概念,为了保证其统一性,特有下表。。。 II. 基本架构 与分布式文件系统类似,Map/Reduce的集群,也由三类服务器构成。其中 作业服务器,在Hadoop中称为 Job Tracker,在Google论文中称为 Master。前者告诉我们,作业服务器是负责管理运行在此框架下所有作业的,后者告诉我们,它也是为各个作业分配任务的核心。与HDFS的主控服务器类似,它也是作为单点存在的,简化了负责的同步流程。具体的 负责执行用户定义操作的,是 任务服务器,每一个作业被拆分成很多的 任务,包括 Map任务和 Reduce任务等,任务是具体执行的基本单元,它们都需要分配到合适任务服务器上去执行,任务服务器一边执行一边向作业服务器汇报各个任务的状态,以此来帮助作业服务器了解作业执行的整体情况,分配新的任务等等。。。 除了作业的管理者执行者,还需要有一个 任务的提交者,这就是客户端。与分布式文件系统一样,客户端也不是一个单独的进程,而是一组API,用户需要自定义好自己需要的内容,经由客户端相关的代码,将作业及其相关内容和配置,提交到作业服务器去,并时刻监控执行的状况。。。 同作为Hadoop的实现,与HDFS的通信机制相同,Hadoop Map/Reduce也是用了协议接口来进行服务器间的交流。实现者作为RPC服务器,调用者经由RPC的代理进行调用,如此,完成大部分的通信,具体服务器的架构,和其中运行的各个协议状况,参见下图。从图中可以看到,与HDFS相比,相关的协议少了几个,客户端与任务服务器,任务服务器之间,都不再有直接通信关系。这并不意味着客户端就不需要了解具体任务的执行状况,也不意味着,任务服务器之间不需要了解别家任务执行的情形,只不过,由于整个集群各机器的联系比HDFS复杂的多,直接通信过于的难以维系,所以,都统一由作业服务器整理转发。另外,从这幅图可以看到,任务服务器不是一个人在战斗,它会像孙悟空一样招出一群宝宝帮助其具体执行任务。这样做的好处,个人觉得,应该有安全性方面的考虑,毕竟,任务的代码是用户提交的,数据也是用户指定的,这质量自然良莠不齐,万一碰上个搞破坏的,把整个任务服务器进程搞死了,就因小失大了。因此,放在单独的地盘进行,爱咋咋地,也算是权责明确了。。。 与分布式文件系统相比,Map/Reduce框架的还有一个特点,就是 可定制性强。文件系统中很多的算法,都是很固定和直观的,不会由于所存储的内容不同而有太多的变化。而作为通用的计算框架,需要面对的问题则要复杂很多,在各种不同的问题、不同的输入、不同的需求之间,很难有一种包治百病的药能够一招鲜吃遍天。作为Map/Reduce框架而言,一方面要尽可能的抽取出公共的一些需求,实现出来。更重要的,是需要提供良好的可扩展机制,满足用户自定义各种算法的需求。Hadoop是由Java来实现的,因此通过反射来实现自定义的扩展,显得比较小菜一碟了。在 JobConf类中,定义了大量的接口,这基本上是Hadoop Map/Reduce框架所有可定制内容的一次集中展示。在JobConf中,有大量set接口接受一个 Class<? extends xxx>的参数,通常它都有一个默认实现的类,用户如果不满意,则可自定义实现。。。 III. 计算流程 如果一切都按部就班的进行,那么整个作业的计算流程,应该是作业的提交 -> Map任务的分配和执行 -> Reduce任务的分配和执行 -> 作业的完成。而在每个任务的执行中,又包含输入的准备 -> 算法的执行 -> 输出的生成,三个子步骤。沿着这个流程,我们可以很快的整理清晰整个Map/Reduce框架下作业的执行。。。 1、作业的提交 一个作业,在提交之前,需要把所有应该配置的东西都配置好,因为一旦提交到了作业服务器上,就陷入了完全自动化的流程,用户除了观望,最多也就能起一个监督作用,惩治一些不好好工作的任务。。。 基本上,用户在提交代码阶段,需要做的工作主要是这样的: 首先,书写好所有自定的代码,最起码,需要有Map和Reduce的执行代码。在Hadoop中,Map需要派生自 Mapper<K1, V1, K2, V2>接口,Reduce需要派生自 Reducer<K2, V2, K3, V3>接口。这里都是用的泛型,用以支持不同的键值类型。这两个接口都仅有一个方法,一个是map,一个是reduce,这两个方法都直接受四个参数,前两个是输入的 键和 值相关的数据结构,第三个是作为 输出相关的数据结构,最后一个,是一个 Reporter类的实例,实现的时候可以利用它来统计一些计数。除了这两个接口,还有大量可以派生的接口,比如分割的 Partitioner<K2, V2>接口。。。 然后,需要书写好主函数的代码,其中最主要的内容就是实例化一个 JobConf类的对象,然后调用其丰富的setXXX接口,设定好所需的内容,包括输入输出的文件路径,Map和Reduce的类,甚至包括读取写入文件所需的格式支持类,等等。。。 最后,调用 JobClient的 runJob方法,提交此JobConf对象。runJob方法会先行调用到 JobSubmissionProtocol接口所定义的 submitJob方法,将此作业,提交给作业服务器。接着,runJob开始循环,不停的调用JobSubmissionProtocol的 getTaskCompletionEvents方法,获得 TaskCompletionEvent类的对象实例,了解此作业各任务的执行状况。。。 2、Map任务的分配 当一个作业提交到了作业服务器上,作业服务器会生成若干个Map任务,每一个Map任务,负责将一部分的输入转换成格式与最终格式相同的中间文件。通常一个 作业的输入都是基于分布式文件系统的文件(当然在单机环境下,文件系统单机的也可以...),因为,它可以很天然的和分布式的计算产生联系。而对于一个Map任务而言,它的输入往往是输入文件的一个数据块,或者是数据块的一部分,但通常, 不跨数据块。因为,一旦跨了数据块,就可能涉及到多个服务器,带来了不必要的复杂性。。。 当一个作业,从客户端提交到了作业服务器上,作业服务器会生成一个 JobInProgress对象,作为与之对应的标识,用于管理。作业被拆分成若干个Map任务后,会预先挂在作业服务器上的任务服务器拓扑树。这是依照分布式文件数据块的位置来划分的,比如一个Map任务需要用某个数据块,这个数据块有三份备份,那么,在这三台服务器上都会挂上此任务,可以视为是一个预分配。。。 关于任务管理和分配的大部分的真实功能和逻辑的实现,JobInProgress则依托 JobInProgressListener和 TaskScheduler的子类。TaskScheduler,顾名思义是用于任务分配的策略类(为了简化描述,用它代指所有TaskScheduler的子类...)。它会掌握好所有作业的任务信息,其 assignTasks函数,接受一个 TaskTrackerStatus作为参数,依照此任务服务器的状态和现有的任务状况,为其分配新的任务。而为了掌握所有作业相关任务的状况,TaskScheduler会将若干个JobInProgressListener注册到 JobTracker中去,当有新的作业到达、移除或更新的时候,JobTracker会告知给所有的JobInProgressListener,以便它们做出相应的处理。。。 任务分配是一个重要的环节,所谓任务分配,就是 将合适作业的合适任务分配到合适的服务器上。不难看出,里面蕴含了两个步骤,先是选择作业,然后是在此作业中选择任务。和所有分配工作一样,任务分配也是一个复杂的活。不良好的任务分配,可能会导致网络流量增加、某些任务服务器负载过重效率下降,等等。不仅如此,任务分配还是一个无一致模式的问题,不同的业务背景,可能需要不同的算法才能满足需求。因此,在Hadoop中,有很多TaskScheduler的子类,像Facebook,Yahoo,都为其贡献出了自家用的算法。在Hadoop中,默认的任务分配器,是 JobQueueTaskScheduler类。它选择作业的基本次序是:Map Clean Up Task(Map任务服务器的清理任务,用于清理相关的过期的文件和环境...) -> Map Setup Task(Map任务服务器的安装任务,负责配置好相关的环境...) -> Map Tasks -> Reduce Clean Up Task -> Reduce Setup Task -> Reduce Tasks。在这个前提下,具体到Map任务的分配上来。当一个任务服务器工作的游刃有余,期待获得新的任务的时候,JobQueueTaskScheduler会按照各个作业的优先级,从 最高优先级的作业开始分配。每分配一个,还会为其留出余量,已被不时之需。举一个例子:系统目前有优先级3、2、1的三个作业,每个作业都有一个可分配的Map任务,一个任务服务器来申请新的任务,它还有能力承载3个任务的执行,JobQueueTaskScheduler会先从优先级3的作业上取一个任务分配给它,然后再留出一个1任务的余量。此时,系统只能在将优先级2作业的任务分配给此服务器,而不能分配优先级1的任务。这样的策略,基本思路就是 一切为高优先级的作业服务,优先分配不说,分配了好保留有余力以备不时之需,如此优待,足以让高优先级的作业喜极而泣,让低优先级的作业感慨既生瑜何生亮,甚至是活活饿死。。。 确定了从哪个作业提取任务后,具体的分配算法,经过一系列的调用,最后实际是由 JobInProgress的 findNewMapTask函数完成的。它的算法很简单,就是 尽全力为此服务器非配且尽可能好的分配任务,也就是说,只要还有可分配的任务,就一定会分给它,而不考虑后来者。作业服务器会从离它最近的服务器开始,看上面是否还挂着未分配的任务(预分配上的),从近到远,如果所有的任务都分配了,那么看有没有开启多次执行,如果开启,考虑把未完成的任务再分配一次(后面有地方详述...)。。。 对于作业服务器来说,把一个任务分配出去了,并不意味着它就彻底解放,可以对此任务可以不管不顾了。因为任务可以在任务服务器上执行失败,可能执行缓慢,这都需要作业服务器帮助它们再来一次。因此在Task中,记录有一个 TaskAttemptID,对于任务服务器而言,它们每次跑的,其实都只是一个Attempt而已,Reduce任务只需要采信一个的输出,其他都算白忙乎了。。。 3、Map任务的执行 与HDFS类似,任务服务器是通过心跳消息,向作业服务器汇报此时此刻其上各个任务执行的状况,并向作业服务器申请新的任务的。具体实现,是 TaskTracker调用 InterTrackerProtocol协议的 heartbeat方法来做的。这个方法接受一个 TaskTrackerStatus对象作为参数,它描述了此时此任务服务器的状态。当其有余力接受新的任务的时候,它还会传入 acceptNewTasks为true的参数,表示希望作业服务器委以重任。 JobTracker接收到相关的参数后,经过处理,会返回一个 HeartbeatResponse对象。这个对象中,定义了一组TaskTrackerAction,用于指导任务服务器进行下一步的工作。系统中已定义的了一堆其TaskTrackerAction的子类,有的对携带的参数进行了扩充,有的只是标明了下ID,具体不详写了,一看便知。。。 当TaskTracker收到的TaskTrackerAction中,包含了 LaunchTaskAction,它会开始执行所分配的新的任务。在TaskTracker中,有一个 TaskTracker.TaskLauncher线程(确切的说是两个,一个等Map任务,一个等Reduce任务),它们在痴痴的守候着新任务的来到。一旦等到了,会最终调用到Task的 createRunner方法,构造出一个 TaskRunner对象,新建一个线程来执行。对于一个Map任务,它对应的Runner是TaskRunner的子类 MapTaskRunner,不过,核心部分都在TaskRunner的实现内。TaskRunner会先将所需的文件全部下载并拆包好,并记录到一个全局缓存中,这是一个全局的目录,可以供所有此作业的所有任务使用。它会用一些软链接,将一些文件名链接到这个缓存中来。然后,根据不同的参数,配置出一个JVM执行的环境,这个环境与 JvmEnv类的对象对应。 接着,TaskRunner会调用 JvmManager的 launchJvm方法,提交给JvmManager处理。JvmManager用于管理该TaskTracker上所有运行的Task子进程。在目前的实现中,尝试的是池化的方式。有若干个固定的槽,如果槽没有满,那么就启动新的子进程,否则,就寻找idle的进程,如果是同Job的直接放进去,否则杀死这个进程,用一个新的进程代替。每一个进程都是由JvmRunner来管理的,它也是位于单独线程中的。但是从实现上看,这个机制好像没有部署开,子进程是死循环等待,而不会阻塞在父进程的相关线程上,父线程的变量一直都没有个调整,一旦分配,始终都处在繁忙的状况了。 真实的执行载体,是Child,它包含一个main函数,进程执行,会将相关参数传进来,它会拆解这些参数,并且构造出相关的Task实例,调用其run函数进行执行。每一个子进程,可以执行指定个数量的Task,这就是上面所说的池化的配置。但是,这套机制在我看来,并没有运行起来,每个进程其实都没有机会不死而执行新的任务,只是傻傻的等待进程池满,而被一刀毙命。也许是我老眼昏花,没看出其中实现的端倪。。。 4、Reduce任务的分配与执行 比之Map任务,Reduce的分配及其简单,基本上是所有Map任务完成了,有空闲的任务服务器,来了就给分配一个Job任务。因为Map任务的结果星罗棋布,且变化多端,真要搞一个全局优化的算法,绝对是得不偿失。而Reduce任务的执行进程的构造和分配流程,与Map基本完全的一致,没有啥可说的了。。。 但其实,Reduce任务与Map任务的最大不同,是Map任务的文件都在本地隔着,而Reduce任务需要到处采集。这个流程是作业服务器经由此Reduce任务所处的任务服务器,告诉Reduce任务正在执行的进程,它需要的Map任务执行过的服务器地址,此Reduce任务服务器会于原Map任务服务器联系(当然本地就免了...),通过FTP服务,下载过来。这个隐含的直接数据联系,就是执行Reduce任务与执行Map任务最大的不同了。。。 5、作业的完成 当所有Reduce任务都完成了,所需数据都写到了分布式文件系统上,整个作业才正式完成了。此中,涉及到很多的类,很多的文件,很多的服务器,所以说起来很费劲,话说,一图解千语,说了那么多,我还是画两幅图,彻底表达一下吧。。。 首先,是一个时序图。它模拟了一个由3个Map任务和1个Reduce任务构成的作业执行流程。我们可以看到,在执行的过程中,只要有人太慢,或者失败,就会增加一次尝试,以此换取最快的执行总时间。一旦所有Map任务完成,Reduce开始运作(其实,不一定要这样的...),对于每一个Map任务来说,只有执行到Reduce任务把它上面的数据下载完成,才算成功,否则,都是失败,需要重新进行尝试。。。 而第二副图,不是我画的,就不转载了,参见 这里,它描述了整个Map/Reduce的服务器状况图,包括整体流程、所处服务器进程、输入输出等,看清楚这幅图,对Map/Reduce的基本流程应该能完全跑通了。有这几点,可能图中描述的不够清晰需要提及一下,一个是在HDFS中,其实还有日志文件,图中没有标明;另一个是步骤5,其实是由TaskTracker主动去拉取而不是JobTracker推送过来的;还有步骤8和步骤11,创建出来的MapTask和ReduceTask,在Hadoop中都是运行在独立的进程上的。。。 IV. Map任务详请 从上面,可以了解到整个Map和Reduce任务的整体流程,而后面要啰嗦的,是具体执行中的细节。Map任务的输入,是分布式文件系统上的,包含键值对信息的文件。为了给每一个Map任务指定输入,我们需要掌握文件格式把它分切成块,并从每一块中分离出键值信息。在HDFS中,输入的文件格式,是由 InputFormat<K, V>类来表示的,在JobConf中,它的默认值是 TextInputFormat类(见 getInputFormat),此类是特化的 FileInputFormat<LongWritable, Text>子类,而 FileInputFormat<K, V>正是InputFormat<K, V>的子类。通过这样的关系我们可以很容易的理解,默认的文件格式是 文本文件,且键是 LongWritable类型(整形数),值是 Text类型(字符串)。仅仅知道文件类型是不够的,我们还需要将文件中的每一条数据,分离成键值对,这个工作,是 RecordReader<K, V>来做的。在TextInputFormat的 getRecordReader方法中我们可以看到,与TextInputFormat默认配套使用的,是 LineRecordReader类,是特化的 RecordReader<LongWritable, Text>的子类,它将每 一行作为一个记录,起始的位置作为键,整行的字符串作为值。有了格式,分出了键值,还需要切开分给每一个Map任务。每一个Map任务的输入用 InputSplit接口表示,对于一个文件输入而言,其实现是 FileSplit,它包含着 文件名、起始位置、长度和存储它的一组服务器地址。。。 当Map任务拿到所属的InputSplit后,就开始一条条读取记录,并调用用于定义的Mapper,进行计算(参见MapRunner<K1, V1, K2, V2>和MapTask的run方法),然后,输出。MapTask会传递给Mapper一个OutputCollector<K, V>对象,作为输出的数据结构。它定义了一个collect的函数,接受一个键值对。在MapTask中,定义了两个OutputCollector的子类,一个是MapTask.DirectMapOutputCollector<K, V>,人如其名,它的实现确实很Direct,直截了当。它会利用一个RecordWriter<K, V>对象,collect一调用,就直接调用RecordWriter<K, V>的write方法,写入本地的文件中去。如果觉着RecordWriter<K, V>出现的很突兀,那么看看上一段提到的RecordReader<K, V>,基本上,数据结构都是对应着的,一个是输入一个是输出。输出很对称也是由RecordWriter<K, V>和OutputFormat<K, V>来协同完成的,其默认实现是LineRecordWriter<K, V>和TextOutputFormat<K, V>,多么的眼熟啊。。。 除了这个非常直接的实现之外,MapTask中还有一个复杂的多的实现,是MapTask.MapOutputBuffer<K extends Object, V extends Object>。有道是简单压倒一切,那为什么有很简单的实现,要琢磨一个复杂的呢。原因在于,看上去很美的往往带着刺,简单的输出实现,每调用一次collect就写一次文件,频繁的硬盘操作很有可能导致此方案的低效。为了解决这个问题,这就有了这个复杂版本,它先开好一段内存做 缓存,然后制定一个比例做 阈值, 开一个线程监控此缓存。collect来的内容,先写到缓存中,当监控线程发现缓存的内容比例超过阈值,挂起所有写入操作,建一个 新的文件,把缓存的内容批量 刷到此文件中去,清空缓存,重新开放,接受继续collect。。。 为什么说是刷到文件中去呢。因为这不是一个简单的照本宣科简单复制的过程,在写入之前,会先将缓存中的内存,经过排序、合并器(Combiner)统计之后,才会写入。如果你觉得Combiner这个名词听着太陌生,那么考虑一下Reducer,Combiner也就是一个Reducer类,通过JobConf的setCombinerClass进行设置,在常用的配置中,Combiner往往就是用用户为Reduce任务定义的那个Reducer子类。只不过,Combiner只是服务的范围更小一些而已,它在Map任务执行的服务器本地,依照Map处理过的那一小部分数据,先做一次Reduce操作,这样,可以压缩需要传输内容的大小,提高速度。每一次刷缓存,都会开一个新的文件,等此任务所有的输入都处理完成后,就有了若干个有序的、经过合并的输出文件。系统会将这些文件搞在一起,再做一个多路的归并外排,同时使用合并器进行合并,最终,得到了唯一的、有序的、经过合并的中间文件(注:文件数量等同于分类数量,在不考虑分类的时候,简单的视为一个...)。它,就是Reduce任务梦寐以求的输入文件。。。 除了做合并,复杂版本的OutputCollector,还具有 分类的功能。分类,是通过 Partitioner<K2, V2>来定义的,默认实现是 HashPartitioner<K2, V2>,作业提交者可以通过JobConf的 setPartitionerClass来自定义。分类的含义是什么呢,简单的说,就是将Map任务的输出,划分到若干个文件中(通常与Reduce任务数目相等),使得每一个Reduce任务,可以处理某一类文件。这样的好处是大大的,举一个例子说明一下。比如有一个作业是进行 单词统计的,其Map任务的中间结果应该是 以单词为键,以单词数量为值的文件。如果这时候只有一个Reduce任务,那还好说,从 全部的Map任务那里收集文件过来,分别统计得到最后的输出文件就好。但是,如果单Reduce任务无法承载此负载或效率太低,就需要多个Reduce任务并行执行。此时,再沿用之前的模式就有了问题。每个Reduce任务从 一部分Map任务那里获得输入文件,但最终的输出结果并不正确,因为同一个单词可能在不同的Reduce任务那里都有统计,需要想方法把它们统计在一起才能获得最后结果,这样就没有将Map/Reduce的作用完全发挥出来。这时候,就需要用到分类。如果此时有两个Reduce任务,那么将输出分成两类,一类存放字母表排序较高的单词,一类存放字母表排序低的单词,每一个Reduce任务从 所有的Map任务那里获取一类的中间文件,得到自己的输出结果。最终的结果,只需要把各个Reduce任务输出的,拼接在一起就可以了。本质上,这就是将Reduce任务的输入, 由垂直分割,变成了水平分割。Partitioner的作用,正是接受一个键值,返回一个分类的序号。它会在从缓存刷到文件之前做这个工作,其实只是多了一个文件名的选择而已,别的逻辑都不需要变化。。。 除了缓存、合并、分类等附加工作之外,复杂版本的OutputCollector还支持错误数据的跳过功能,在后面分布式将排错的时候,还会提及,标记一下,按下不表。。。 V. Reduce任务详情 理论上看,Reduce任务的整个执行流程要比Map任务更为的罗嗦一些,因为,它需要收集输入文件,然后才能进行处理。Reduce任务,主要有这么三个步骤: Copy、 Sort、 Reduce(参见ReduceTask的run方法)。所谓Copy,就是从执行各个Map任务的服务器那里,收罗到本地来。拷贝的任务,是由 ReduceTask.ReduceCopier类来负责,它有一个内嵌类,叫 MapOutputCopier,它会在一个单独的线程内,负责某个Map任务服务器上文件的拷贝工作。远程拷贝过来的内容(当然也可以是本地了...),作为MapOutput对象存在,它可以在内存中也可以序列化在磁盘上,这个根据内存使用状况来自动调节。整个拷贝过程是一个动态的过程,也就是说它不是一次给好所有输入信息就不再变化了。它会不停的调用 TaskUmbilicalProtocol协议的 getMapCompletionEvents方法,向其父TaskTracker询问此作业个Map任务的完成状况(TaskTracker要向JobTracker询问后再转告给它...)。当获取到相关Map任务执行服务器的信息后,都会有一个线程开启,做具体的拷贝工作。同时,还有一个内存Merger线程和一个文件Merger线程在同步工作,它们将新鲜下载过来的文件(可能在内存中,简单的统称为文件...),做着归并排序,以此,节约时间,降低输入文件的数量,为后续的排序工作减负。。。 Sort,排序工作,就相当于上述排序工作的一个延续。它会在所有的文件都拷贝完毕后进行,因为虽然同步有做着归并的工作,但可能留着尾巴,没做彻底。经过这一个流程,该彻底的都彻底了,一个崭新的、合并了所有所需Map任务输出文件的新文件,诞生了。而那些千行万苦从其他各个服务器网罗过来的Map任务输出文件,很快的结束了它们的历史使命,被扫地出门一扫而光,全部删除了。。。 所谓好戏在后头,Reduce任务的最后一个阶段,正是Reduce本身。它也会准备一个 OutputCollector收集输出,与MapTask不同,这个OutputCollector更为简单,仅仅是打开一个 RecordWriter,collect一次,write一次。最大的不同在于,这次传入RecordWriter的文件系统,基本都是 分布式文件系统,或者说是HDFS。而在输入方面,ReduceTask会从JobConf那里调用一堆getMapOutputKeyClass、getMapOutputValueClass、getOutputKeyComparator等等之类的自定义类,构造出Reducer所需的键类型,和值的迭代类型Iterator(一个键到了这里一般是对应一组值)。具体实现颇为拐弯抹角,建议看一下 Merger.MergeQueue, RawKeyValueIterator, ReduceTask.ReduceValuesIterator等等之类的实现。有了输入,有了输出,不断循环调用自定义的Reducer,最终,Reduce阶段完成。。。 VI. 分布式支持 1、服务器正确性保证 Hadoop Map/Reduce服务器状况和HDFS很类似,由此可知,救死扶伤的方法也是大同小异。废话不多说了,直接切正题。同作为客户端,Map/Reduce的客户端只是将作业提交,就开始搬个板凳看戏,没有占茅坑的行动。因此,一旦它挂了,也就挂了,不伤大雅。而任务服务器,也需要随时与作业服务器保持心跳联系,一旦有了问题,作业服务器可以将其上运行的任务,移交给它人完成。作业服务器,作为一个单点,非常类似的是利用还原点(等同于HDFS的镜像)和历史记录(等同于HDFS的日志),来进行恢复。其上,需要持久化用于恢复的内容,包含作业状况、任务状况、各个任务尝试的工作状况等。有了这些内容,再加上任务服务器的动态注册,就算挪了个窝,还是很容易恢复的。 JobHistory是历史记录相关的一个静态类,本来,它也就是一个干写日志活的,只是在Hadoop的实现中,对日志的写入做了面向对象的封装,同时又大量用到观察者模式做了些嵌入,使得看起来不是那么直观。本质上,它就是打开若干个日志文件,利用各类接口来往里面写内容。只不过,这些日志,会放在分布式文件系统中,就不需要像HDFS那样,来一个SecondXXX随时候命,由此可见,有巨人在脚下踩着,真好。JobTracker.RecoveryManager类是作业服务器中用于进行恢复相关的事情,当作业服务器启动的时候,会调用其recover方法,恢复日志文件中的内容。其中步骤,注释中写的很清楚,请自行查看。。。 2、任务执行的正确和速度 整个作业流程的执行,秉承着木桶原理。执行的最慢的Map任务和Reduce任务,决定了系统整体执行时间(当然,如果执行时间在整个流程中占比例很小的话,也许就微不足道了...)。因此,尽量加快最慢的任务执行速度,成为提高整体速度关键。所使用的策略,简约而不简单,就是 一个任务多次执行。当所有未执行的任务都分配出去了,并且先富起来的那部分任务已经完成了,并还有任务服务器孜孜不倦的索取任务的时候,作业服务器会开始炒剩饭,把那些正在吭哧吭哧在某个服务器上慢慢执行的任务,再把此任务分配到一个新的任务服务器上,同时执行。两个服务器各尽其力,成王败寇,先结束者的结果将被采纳。这样的策略,隐含着一个假设,就是我们相信,输入文件的分割算法是公平的,某个任务执行慢,并不是由于这个任务本身负担太重,而是由于服务器不争气负担太重能力有限或者是即将撒手西去,给它换个新环境,人挪死树挪活事半功倍。。。 当然,肯定有哽咽的任务,不论是在哪个服务器上,都无法顺利完成。这就说明,此问题不在于服务器上,而是任务本身天资有缺憾。缺憾在何处?每个作业,功能代码都是一样的,别的任务成功了,就是这个任务不成功,很显然,问题出在输入那里。输入中有非法的输入条目,导致程序无法辨识,只能挥泪惜别。说到这里,解决策略也浮出水面了,三十六计走位上,惹不起,还是躲得起的。在MapTask中的MapTask.SkippingRecordReader<K, V>和ReduceTask里的ReduceTask.SkippingReduceValuesIterator<KEY,VALUE>,都是用于干这个事情的。它们的原理很简单,就是在读一条记录前,把当前的位置信息,封装成SortedRanges.Range对象,经由Task的reportNextRecordRange方法提交到TaskTracker上去。TaskTracker会把这些内容,搁在TaskStatus对象中,随着心跳消息,汇报到JobTracker上面。这样,作业服务器就可以随时随刻了解清楚,每个任务正读取在那个位置,一旦出错,再次执行的时候,就在分配的任务信息里面添加一组SortedRanges信息。MapTask或ReduceTask读取的时候,会看一下这些区域,如果当前区域正好处于上述雷区,跳过不读。如此反复,正可谓,道路曲折,前途光明啊。。。 VII. 总结 对于Map/Reduce而言,真正的困难,在于提高其适应能力,打造一款能够包治百病的执行框架。Hadoop已经做得很好了,但只有真正搞清楚了整个流程,你才能帮助它做的更好。。。 本文转自 duguguiyu 51CTO博客,原文链接:http://blog.51cto.com/duguguiyu/363397,如需转载请自行联系原作者

优秀的个人博客,低调大师

VMware虚拟化技术培训(1) 虚拟化的基础知识

最近几年的每个暑假都会去参加一些培训,而且每次的收获都不少。今年参加的是由山东省组织的省内高职院校师资培训,培训由省财政专项拨款。计算机类的培训项目一共有十几个,我选择了其中的“VMware虚拟化技术应用与管理”,培训地点在东营职业学院,时间从7月23日到8月2日,培训讲师主要是来自企业的虚拟化产品工程师。当然,培训仅仅只靠听课是远远不够的,我自己准备的学习资料主要是由岳雷老师力荐的《虚拟智慧VMware_vSphere运维实录》一书,以及由yeslab现任明教教主主讲的vmware系列视频教程。在接下来的几天里仍会将培训资料和学习心得全程实录,以保留第一手资料。 1.了解什么是虚拟化 虚拟化以及因之而起的云计算毫无疑问是计算机网络技术发展的一大方向,据我了解,即使在烟台,很多企业里也都已经应用了虚拟化技术。 凡事皆有因果,虚拟化技术的起因很简单,当初主要是为了解决服务器硬件资源使用率过低的问题。有些服务器比如DNS服务器、DHCP服务器等,CPU使用率通常都在5%以下,这对于那些花大价钱购买来的服务器,无疑是很大的浪费。如果能够将这些服务器合并到一起,那就可以有效地提高服务器硬件的使用率。但是如果只是简单的将所有的服务都安装在同一个操作系统中也是一个非常不明智的选择,比如将Web服务器和SQL安装在同一个操作系统中,此时操作系统、Web、SQL任何一方蓝屏都将导致整个系统崩溃。对于大部分的企业服务,都强调一个操作系统中只安装一个服务。因此,服务器合并必须要基于虚拟化技术,让每一台虚拟机运行一个单独的服务。 采用虚拟化技术最明显的优点就是可以减少IT成本以及电费等运营支出;另外也使服务器的管理维护变得更为简单,甚至一个管理员就可以管理上千台服务器;此外在高可用性、冗余、负载均衡等方面,虚拟化技术也都提供了无与伦比的优势。 毕业于麻省理工学院以及加州大学伯克利分校(这是IT技术界的2个圣地)的DianeGreene女士把握住了这其中的商机,她于1998年与其他一些伙伴联合创建了VMware公司,并开发出了世界上第一款虚拟化产品。至今,VMware已发展成为全球第三大软件公司,其产品也在虚拟化领域占据了绝对的优势地位。 2.虚拟化的两种架构 虚拟化主要指的是操作系统平台的虚拟化,即让一台物理计算机能并发运行多个OS(操作系统),并且要让每个OS都觉得自身好像拥有独立的机器,而不是跟别的OS分享。 提供这种虚拟化功能的机制就被称为VMM(VirtualMachineMonitor),它更常用的名称是Hypervisor。Hypervisor是虚拟机厂家的最高机密,也是不同虚拟机产品之间的主要差别。 从是否存在宿主操作系统的角度,Hypervisor分为两种不同的架构:原生架构和寄居架构。 2.1寄居架构寄居架构的Hypervisor被看成一个应用软件或是服务,必须在已经安装好的操作系统上才能运行,最典型的产品就是我们所熟知的VMware公司的VMwareWorkstation以及微软的VirtualPC。 寄居架构的好处是硬件的兼容性,只要宿主操作系统能使用的硬件,虚拟机中的操作系统都能使用到。另外它对物理硬件的要求也很低,基本上所有的PC都可以运行VMwareWorkstation或VirtualPC。 然而寄居架构的缺点更加明显,首先最致命的是当宿主操作系统出现任何问题时,虚拟机中的操作系统都将无法使用。比如我们在Win7中安装的VMwareWorkstation,如果Win7蓝屏了,那VMwareWorkstation当然也就无法使用了。另外,寄居架构的虚拟机性能和物理主机相去甚远,因此无法用于高负荷的生产环境。所以这种寄居架构的虚拟化产品只能适用于个人用户,对于企业用户是远远无法满足需求的。 2.2原生架构(裸金属架构)原生架构又称为裸金属架构,它将Hypervisor直接安装在硬件上,将所有的硬件资源接管。由于Hypervisor层极小,而且不管理太复杂的事项,仅负责和上层的虚拟机操作系统沟通及资源协调,因而蓝屏的概率很低。而且在其上的任何一个虚拟操作系统蓝屏了,都不会影响其它的客户端。另外,原生架构的虚拟机性能与物理主机基本相当,这是寄居架构的虚拟机所远远无法比拟的。 目前,原生架构的典型产品是VMware的VMwarevSphere和微软的Hyper-V。 原生架构的虚拟机产品也有缺点,这就是为了保持稳定性及微内核,它不可能将所有硬件产品的驱动程序都放入,因此最大的问题就是硬件兼容性。但是大部分的原生架构产品都支持主流服务器及存储设备,但一般PC所使用的硬件,则很多都无法在原生架构的虚拟机下运行。 在这方面,vSphere和Hyper-V有着很大的区别。 VMwarevSphere采用的是胖管理层,也就是把底层物理硬件的驱动程序都整合到Hypervisor管理层中,所以管理层显得比较胖。很显然,这种架构的性能比较好,但是对于底层物理硬件的要求比较高,兼容性和安全性的挑战比较高。 Hyper-V采用的则是瘦管理层,Hypervisor管理层仅用于管理CPU和内存,而不包含底层物理硬件的驱动程序(Hyper-V本来就集成于Windows系统中,可以通过系统直接管理支配硬件设备),所以管理层显得比较瘦。由于不包含硬件驱动,所以代码量比较小,Hyper-V仅有300多K,因此安全性和兼容性要更好一些,但是效率和胖管理层相比有所不如。 另记: 这种官方培训比较程式化,培训第一天主要是开班仪式以及由VMware负责销售的一位美女做产品方面的介绍,没有太多有价值的信息。以上内容主要是由个人根据资料自己整理 本文转自 yttitan 51CTO博客,原文链接:http://blog.51cto.com/yttitan/1255803

优秀的个人博客,低调大师

Android基础:Sqlite中INTEGER PRIMARY KEY AUTOINCREMENT和rowid的使用(转载)

转载地址:Sqlite中INTEGER PRIMARY KEY AUTOINCREMENT和rowid的使用:里面还有很多可以学习的内容 感觉很整个客很整洁,值得学习。。。嘿嘿 在用sqlite设计表时,突然想到一个问题,就是我设计的表中,每个表都有一个自己的整形id值作为主键,其实可以不指定这么一个id值,sqlite内部本来就会为每个表加上一个 rowid,这个rowid可以当成一个隐含的字段使用,但是由sqlite引擎来维护的,在3.0以前rowid是32位的整数,3.0以后是64位的整数,为什么不直接使用这个内部的rowid作为每个表的id主键呢。 想到就立即先查找一下sqlite的文档,看看用指定INTEGER PRIMARY KEY AUTOINCREMENT 和不指定自增长字段用rowid有什么区别。相关的文档在这里:http://www.sqlite.org/autoinc.htmlhttp://www.sqlite.org/faq.html 使用自增长字段为主键有不少问题,比如维护或是在大型分布应用中主键冲突的解决等。在一些大型分布应用中主键一般选用guid,这可以有效的避免主键冲突,减少对主键维护的工程。当然,对于中小型的应用,自增长字段的好处更多一些,简单、快速。 Sqlite中,一个自增长字段定义为INTEGER PRIMARY KEY AUTOINCREMENT ,那么在插入一个新数据时,只需要将这个字段的值指定为NULL,即可由引擎自动设定其值,引擎会设定为最大的rowid+1。当然,也可以设置为非NULL的数字来自己指定这个值,但这样就必须自己小心,不要引起冲突。当这个rowid的值大于所能表达的最大值9223372036854775807 (3.0及以后版本的rowid最大值)后,rowid的新值会这个最大数之前随机找一个没被使用了的值。所以在rowid达到最大值前,rowid的值是严格单调增加的。 INTEGER PRIMARY KEY AUTOINCREMENT 自增长字段的算法与rowid稍微有些不同。 第一,在达到最大值后,rowid会找已被删除的字段对应的rowid作为新值,而自增长字段则会丢出一个SQLITE_FULL的错误。 第二,自增长字段在增加新值时,是找一个从没被使用过的rowid作为新值,而rowid则是找最大已存在的rowid+1。这里对应用的影响会比较大,尤其是一些对id值有依赖的元记录,只适合使用自增长字段而不能用rowid。 比如,我们设计一个元记录表: Create table meta_struct(id INTEGER PRIMARY KEY AUTOINCREMENT, name varchar, type Integer); 然后,定义一个一级表,来描述其它表的结构: Create table meta_table(tableid INTEGER, table_field integer) 最后,我们的应用可以根据这个一级表来产生实际使用的二级表。 这样为保证兼容性meta_struct中的id必须是唯一的,如果有字段被删除,也不能重复使用这个字段的id值,不然,在数据库合并时,一级表和二级表就会混乱。所以meta_struct表中的主键只能使用自增长字段,而不能用rowid。 第三,使用自增长字段,引擎会自动产生一个sqlite_sequence表,用于记录每个表的自增长字段的已使用的最大值,用户可以看到,并可以用使用Update、Delete和Insert操作,但不建议这么使用,这会让引擎混乱。如果使用rowid,也会有这么一个内部表,用户可以维护rowid值,但看不到。 这么看来,如果直接使用rowid来代替自增加字段,根据两者的细微的差别,需要注意是否与自己的应用冲突,如果没有冲突,那么用rowid会更快一点。 本文转自demoblog博客园博客,原文链接http://www.cnblogs.com/0616--ataozhijia/archive/2012/11/06/2757991.html如需转载请自行联系原作者 demoblog

优秀的个人博客,低调大师

系统架构师-基础到企业应用架构-系统建模[上篇]

一、摘要 本文主要从系统架构中的建模开始讲解,本文讲述的内容主要是我在工作和学习过程中的总结和经验,不足之处还请大家多多批评指出,有更好的建议也可以留言 说明。本意主旨是为不熟悉系统架构建模过程和不知道如何使用建模工具,或者不熟悉如何根据需求去建立模型的角度出发,简单的阐述了在系统架构的过程中我们应 该从什么样的角度出发去分析需求并且建立抽象模型。这应该说是架构师必备的技能。 本文由浅入深,本篇将简单的介绍如何使用使用UML建模中的各个结构图与行为图,去完成抽象模型的建立。 二、本章内容 1、摘要。 2、本章内容。 3、建模工具介绍及使用。 4、建模中的抽象模型图。 5、本质总结。 6、系列进度。 7、下篇预告。 三、建模工具介绍 介绍建模工具之前,我们先来简单介绍下建模语言的定义。建模语言就是基于一系列规则、符号、图表、关键字的图形化或文本语言。建模语言的主要作用是对模 型的结构与行为进行描述。并且能够将知识和信息通过模型传递给熟悉该描述语言的人。 当今的建模语言其实并不少,其中比较有规模的如下图: 不过最流行、最常用的当属UML建模语言(Unified Modeling Language) 统一建模语言。经过不断的发展,目前UML已成为业界公认的标准的建模语言。 我们先来了解下UML建模语言的起源: 回顾20世纪晚期--准确地说是1997年,OMG组织(Object Management Group对象管理组织)发布了统一建模语言(Unified Modeling Language, UML)。UML的目标之一就是为开发团队提供标准通用的设计语言来开发和构建计算机应用。UML提出了一套IT专业人员期待多年的统一的标准建模符号。通过使用 UML,这些人员能够阅读和交流系统架构和设计规划--就像建筑工人多年来所使用的建筑设计图一样。 到了21世纪--准确地说是2003年,UML已经获得了业界的认同。在我所见过的专业人员的简历中,75%都声称具备UML的知识。然而,在同绝大多数求职人员面 谈之后,可以明显地看出他们并不真正了解UML。通常地,他们将UML用作一个术语,或对UML一知半解。大家对UML缺乏理解的这种状况,促进我撰写这篇关于UML 建模。当阅读完本文时,您还不具备足够的知识可以在简历上声称自己掌握了UML,但是您已具有了进一步钻研该语言的良好起点。 四、建模中的抽象模型 既然UML语言如此流行,本系列中也只用UML语言来进行建模,本系列中的后续章节也将基于UML建模图来完成相应的设计。 学习过UML语言的开发人员都知道UML分为以下几类模型图: 通过上图我们知道UML的分类,分为结构型与行为型建模图形。下面的内容将详细的讲述每种建模图形的使用场景及如何使用。 行为型: 我们先从行为型的建模图形来开始讲起: 1、用例图: 我想用例图大家都应该基本上有所了解,只要使用过UML建模的除了基本的流程图基本上大家都会的使用外,用例图用过是最常见的一种建模图形。 用例图中主要包含的元素:系统、参与者、用例、关系。 用例图主要的应用场景:一般用例图用来描述需求中的系统应具有的功能,系统参与者(使用者,维护者、外部系统或者用户等)与系统如何交互进行一个模 型话的描述。 用例图的目的:帮助开发团队以一种可视化的方式理解系统的功能需求。 一般使用如下方式来进行操作: 用来标识系统的参与者,任何与系统交互的对象,都可以叫参与者。 是用来描述系统中的某个模块与参与者的一次交互过程。 系统参与者与用例之间的具体关系通过如下连线标示: 这几类不同的连线来标识不同的用例之间或者用例与参与者或者2个参与者直接直接的关系。 UML定义了3类标准的关系: 第一种:包含,通过一条直线链接2个用例,因此是用例之间的关系链接,表述了箭头的开始一端包含箭头指向的一端的用例。 例如: 第二种:扩展,通过一个反向的直线来标识某个用例扩展了另外一个用例的行为,一般情况下箭头指向的用例即是被扩展的用例。 例如: 第三种:泛化,用来标识具有同质关系的参与者与参与者或者用例与用例之间的关系,泛化类似继承关系。箭头指向的为父元素。 例如: 除了以上的3中关系还有一种未列在规范关系的我们把它叫做关联关系。这种关系是用来描述用例与参与者直接的关系的。是通过一条直线来完成链接的,泛化关系 描述了链接的2个部分存在某种程度的交付。一般情况下,我们可以系统的功能情况分析出系统中的主动发和被动方。 如何使用用例图: 第一步:先把系统按照功能进行划分,比如一个简单的内容管理系统。先把他细化,细化成多个模块功能。每个模块的功能相对独立,但是可能又与另外一个有交 互。 第二步:把功能需求抽象,达到高内聚,低耦合的标准,然后分析出该模块功能的参与者是什么,例如用户是谁?或者细分成角色,与该模块交互还可能是数据库? 等,把所有交互的对象分析出。 第三步:把系统模块中的每个功能模块看是否能再按照子功能进行细分,细分后形成具体的用例。 第四步:分析用例与参与者之间的关系,分析同质对象(参与者与参与者、用例与用例)之间的关系。 第五步:根据以上四步完成建模。在建模的过程如果发现某块功能不清晰或者参与者不清晰,可重复前4步。 2、类图: 类图也是UML建模中最常用的一种结构图,类图用来标示系统的静态结构。静态结构是由类型及关系构成。 类图表示不同的实体(人、事物和数据)如何彼此相关;换句话说,它显示了系统的静态结构。类图可用于表示逻辑类,逻辑类通常就是业务人员所谈及的事物种 类--摇滚乐队、CD、广播剧;或者贷款、住房抵押、汽车信贷以及利率。类图还可用于表示实现类,实现类就是程序员处理的实体。实现类图或许会与逻辑类图显示一 些相同的类。然而,实现类图不会使用相同的属性来描述,因为它很可能具有对诸如Vector和HashMap这种事物的引用。 类图其实就是一个长方形,内部分成3个区域。每个区域的含义不同。 类图中也有命名空间的概念,是通过包来实现的如果想定义该类在某个命名空间中,则在定义类名时按照如下类似格式标示 命名空间 :: 类名 [必须按照这样的形式才可以]。 类图中的有3类修饰符,每种修饰符标示的含义不同。 具体用法如下: 理解成具体的类代码的格式如下: public class Product { Public string ProductName; public void GetProductLists(string sWhere) { //TODO…. } } 如果在类图中的属性定义与函数成员的定义是斜体表示的话,则表名该成员是虚成员。 虚成员 如果在类图中的属性定义与函数成员的定义是带下划线的话,则表名该成员是静态成员。 静态成员 当然这是最基本的类图,还有一种特殊的,类图支持参数化类型即是.NET中的特殊类型[泛型格式]标示。 参数化类图 具体的表示形式如:该符号在类的右上角有个长方形其中可输入类型如上图。 类图中属性包含的元素: 访问修饰符:Public、Protected、Private 特性/属性名称:特性/属性名称 类型:可以是自定义类型或者是系统类型。 默认值:即特性/属性的默认值,如果有的话。 重复性:可以用来定义多个对象的集合,特性值中包含的对象个数。 类图中操作包含的元素: 访问修饰符:Public、Protected、Private 操作名称:函数名称 操作列表:函数的参数列表。 返回值:函数的返回值,如果有的话。 函数参数列表中的参数方向: 类图之间的关联关系 首先我们知道,我们在设计类的时候就是把独立的功能放在一个类中,不同的类之间进行交互,那么我们在类图中如何去表述这样的类之间的关系呢? 类图直接的关系: 1、关联关系:关联标识2个类直接存在关系。是通过一条线来表示,关联关系中包含了2种特殊的关系:聚合和组合 聚合代表的2个类直接是has-a的关系,即部分与整体的关系,具体的图标通过一条虚线带有菱形箭头,箭头指向的方向即是整体的部分,代表该类包含另一部分。 聚合例如:代表产品中具有ProductName这个成员。 组合举例:组合关系的标示与聚合比较类似,唯一区别实心的菱形。 组合例如: 组合与聚合的区别: 在聚合关系中被包含对象可能不完全依赖容器对象,也就是说ProductName不完全依赖Product。如果Product对象销毁,但是可能ProductName对象没有被销 毁。可以这么想想产品的分类不会因为产品销毁而不存在。 组合关系中则是比聚合的关联程度更高,Product完全包含ProductName。如果销毁Product时,那么ProductName也一定被销毁。产品从数据库被删除了,那 么与产品相关的的数据列属性也被删除了,这里只是举例子,可能不太合适。 类图之间的泛化关系 泛化关系:存在2个类之间。一个类是另外一个类的子类,表示一个类是另外一个类的特例。 表示方法:通过一个带有空的三角形箭头的线段标识,箭头指向父类型。 表示火车和汽车是交通工具的子类型。 类图之间的依赖关系 依赖关系描述为:一个类型必须依靠另外一个类才能实现相应的功能。最简单的理解方式:依赖注入中的构造函数注入。 具体的表示方法:一个带有箭头的虚线段。箭头方向标示被依赖的类型。 例如: 五、本章总结。 本章主要是对UML有个简单的介绍及详细介绍了如何构建UML图形中的用例图与类图。这是我们在建模时常用的2类图形。也是必须掌握的建模图形。 同时通过本质我们应该大脑中对UML有个新的认识,UML建模可以让我多个角度的去分析问题,然后不断的改进设计,同时能很清晰的表达功能需求功能的分离和组合 关系。本文只是简单的抛砖引玉,不足之处,在所难免,请大家批评指出。 六、系列进度。 七、下篇预告。 下一篇中将介绍UML建模过程中其他的比较常用的UML建模图形:顺序图、组件图、状态图等。 版权声明:原创作品,如需转载,请注明出处。否则将追究法律责任 本文转自 hot的fans 51CTO博客,原文链接:http://blog.51cto.com/2435232/601861

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册