首页 文章 精选 留言 我的

精选列表

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

map和reduce 个数的设定 (Hive优化)经典

一、 控制hive任务中的map数:1. 通常情况下,作业会通过input的目录产生一个或者多个map任务。 主要的决定因素有: input的文件总个数,input的文件大小,集群设置的文件块大小(目前为128M, 可在hive中通过set dfs.block.size;命令查看到,该参数不能自定义修改);2. 举例: a) 假设input目录下有1个文件a,大小为780M,那么hadoop会将该文件a分隔成7个块(6个128m的块和1个12m的块),从而产生7个map数 b) 假设input目录下有3个文件a,b,c,大小分别为10m,20m,130m,那么hadoop会分隔成4个块(10m,20m,128m,2m),从而产生4个map数 即,如果文件大于块大小(128m),那么会拆分,如果小于块大小,则把该文件当成一个块。3. 是不是map数越多越好? 答案是否定的。如果一个任务有很多小文件(远远小于块大小128m),则每个小文件也会被当做一个块,用一个map任务来完成, 而一个map任务启动和初始化的时间远远大于逻辑处理的时间,就会造成很大的资源浪费。 而且,同时可执行的map数是受限的。 4. 是不是保证每个map处理接近128m的文件块,就高枕无忧了? 答案也是不一定。比如有一个127m的文件,正常会用一个map去完成,但这个文件只有一个或者两个小字段,却有几千万的记录, 如果map处理的逻辑比较复杂,用一个map任务去做,肯定也比较耗时。 针对上面的问题3和4,我们需要采取两种方式来解决:即减少map数和增加map数;如何合并小文件,减少map数? 假设一个SQL任务: Select count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’; 该任务的inputdir /group/p_sdo_data/p_sdo_data_etl/pt/popt_tbaccountcopy_mes/pt=2012-07-04 共有194个文件,其中很多是远远小于128m的小文件,总大小9G,正常执行会用194个map任务。 Map总共消耗的计算资源: SLOTS_MILLIS_MAPS= 623,020 我通过以下方法来在map执行前合并小文件,减少map数: set mapred.max.split.size=100000000; set mapred.min.split.size.per.node=100000000; set mapred.min.split.size.per.rack=100000000; set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; 再执行上面的语句,用了74个map任务,map消耗的计算资源:SLOTS_MILLIS_MAPS= 333,500 对于这个简单SQL任务,执行时间上可能差不多,但节省了一半的计算资源。 大概解释一下,100000000表示100M, set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;这个参数表示执行前进行小文件合并, 前面三个参数确定合并文件块的大小,大于文件块大小128m的,按照128m来分隔,小于128m,大于100m的,按照100m来分隔,把那些小于100m的(包括小文件和分隔大文件剩下的), 进行合并,最终生成了74个块。 如何适当的增加map数? 当input的文件都很大,任务逻辑复杂,map执行非常慢的时候,可以考虑增加Map数,来使得每个map处理的数据量减少,从而提高任务的执行效率。 假设有这样一个任务: Select data_desc, count(1), count(distinct id), sum(case when …), sum(case when ...), sum(…) from a group by data_desc 如果表a只有一个文件,大小为120M,但包含几千万的记录,如果用1个map去完成这个任务,肯定是比较耗时的,这种情况下,我们要考虑将这一个文件合理的拆分成多个, 这样就可以用多个map任务去完成。 set mapred.reduce.tasks=10; create table a_1 as select * from a distribute by rand(123); 这样会将a表的记录,随机的分散到包含10个文件的a_1表中,再用a_1代替上面sql中的a表,则会用10个map任务去完成。 每个map任务处理大于12M(几百万记录)的数据,效率肯定会好很多。 看上去,貌似这两种有些矛盾,一个是要合并小文件,一个是要把大文件拆成小文件,这点正是重点需要关注的地方, 根据实际情况,控制map数量需要遵循两个原则:使大数据量利用合适的map数;使单个map任务处理合适的数据量; 二、 控制hive任务的reduce数:1. Hive自己如何确定reduce数: reduce个数的设定极大影响任务执行效率,不指定reduce个数的情况下,Hive会猜测确定一个reduce个数,基于以下两个设定: hive.exec.reducers.bytes.per.reducer(每个reduce任务处理的数据量,默认为1000^3=1G) hive.exec.reducers.max(每个任务最大的reduce数,默认为999) 计算reducer数的公式很简单N=min(参数2,总输入数据量/参数1) 即,如果reduce的输入(map的输出)总大小不超过1G,那么只会有一个reduce任务; 如:select pt,count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04' group by pt; /group/p_sdo_data/p_sdo_data_etl/pt/popt_tbaccountcopy_mes/pt=2012-07-04 总大小为9G多,因此这句有10个reduce2. 调整reduce个数方法一: 调整hive.exec.reducers.bytes.per.reducer参数的值; set hive.exec.reducers.bytes.per.reducer=500000000; (500M) select pt,count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04' group by pt; 这次有20个reduce 3. 调整reduce个数方法二; set mapred.reduce.tasks = 15; select pt,count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04' group by pt;这次有15个reduce4. reduce个数并不是越多越好; 同map一样,启动和初始化reduce也会消耗时间和资源; 另外,有多少个reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;5. 什么情况下只有一个reduce; 很多时候你会发现任务中不管数据量多大,不管你有没有设置调整reduce个数的参数,任务中一直都只有一个reduce任务; 其实只有一个reduce任务的情况,除了数据量小于hive.exec.reducers.bytes.per.reducer参数值的情况外,还有以下原因: a) 没有group by的汇总,比如把select pt,count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04' group by pt; 写成 select count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04'; 这点非常常见,希望大家尽量改写。 b) 用了Order by c) 有笛卡尔积 通常这些情况下,除了找办法来变通和避免,我暂时没有什么好的办法,因为这些操作都是全局的,所以hadoop不得不用一个reduce去完成; 同样的,在设置reduce个数的时候也需要考虑这两个原则:使大数据量利用合适的reduce数;使单个reduce任务处理合适的数据量; 转自http://blog.sina.com.cn/s/blog_9f48885501017dua.html 本文转自茄子_2008博客园博客,原文链接:http://www.cnblogs.com/xd502djj/p/3460205.html,如需转载请自行联系原作者。

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

Elephant:Hadoop和Spark的优化“神器”

美国加州软件公司Pepperdata的应用程序分析软件建立在Dr. Elephant(Dr. Elephant 是Hadoop和Spark的性能监视和调优工具)开源项目上。主要目的是让更多的Hadoop和Spark应用程序投入生产。 Pepperdata的应用程序分析器作为早期访问版本,基于去年推出的Apache开源项目Dr. Elephant。Dr. Elephant项目的贡献者包括Airbnb,Foursquare,Pepperdata等。 Dr. Elephant软件通过活动日志解析,适用于已知的良好部署规则,并提供有关Hadoop应用程序运行状况的报告。 这可能是试图运行多个Hadoop作业的团队的有效措施。通常,这样的团队几乎没有指导,需要深层次的Java编程技能来确定其Hadoop集群的效率。 加州LinkedIn公司的高级软件工程师Carl Steinbach表示,去年开源后,Apache Spark部署规则已被添加到Dr. Elephant中,支持Oozie和Airflow工作流调度程序。Spark历史记录器的增强功能现在也是包的一部分。 Steinbach说,实际上,Dr. Elephant承担了审查工作表现和建议调查工作的艰巨任务。Dr. Elephant使你不必学习如何调整工作,它会告诉你为什么需要作出改变。Steinbach说:“你甚至不需要Java工程师。” 基于Steinbach的经验,程序员调整Hadoop的能力是非常重要的。作为LinkedIn Hadoop开发团队的技术主管,Steinbach及其同事在许多Hadoop应用程序中投入使用。他们参加了许多代码审查会议,并提出了结合Dr. Elephant的一些最佳做法,所以更多的程序员可以在Hadoop的生产中取得成功。 及时修复问题Steinbach表示,他已经在LinkedIn上看到了积极的效果,因为Dr. Elephant的使用已经扩大。这是因为它在大数据集群上运行的其他作业的上下文中提供了单独的作业配置文件。 Steinbach说:“你不仅可以看到你的工作,也可以看到其他人的工作。而且,如果看到工作出现问题,就会给我们带来一种积极的压力,然后进行及时地更正。在这里,‘做正确的事情’意味着对工作表现进行微调。” Dr. Elephant规则或启发式在某种程度上类似于一年级医学院校的教科书,就像观察症状并提出有效的修复建议。Steinbach说。 例如,Dr. Elephant可能发现的性能瓶颈,包括MapReduce处理偏移问题或太多垃圾收集开销。它在第一种情况下的反应可能是重新分配数据,而在第二种情况下,响应可能是调整Java虚拟机中的垃圾收集设置。 Steinbach表示,自从开源以来,Dr. Elephant已经被应用到更大的大数据工作负载列表中,因此它已经涵盖了更多样化的Hadoop和Spark工作类型。 他指出,Pepperdata已经将补丁提供给了开源项目,并且能够提供重要的新功能。 跟踪群集瓶颈像Dr. Elephant这样的工具有助于转发DevOps的概念,开发人员在确保应用运行良好方面发挥更大的作用。该工具不需要系统操作员和开发人员之间进行太多繁琐的交流。 Pepperdata公司的首席执行官Ash Munshi表示,应用程序分析工具使开发人员更好地了解实际工作中对DevOps的重要作用,他说,Pepperdata是基于Dr. Elephant的应用程序分析器提供服务,并且它提供了附加的上下文数据,以更好地传达在大数据作业正在运行时在大型集群上发生的情况。它加入了一个Pepperdata软件套件,其中包括一个集群分析器,容量分析器和一个策略执行器。 根据Munshi的说法,到目前为止,Hadoop性能跟踪的很多工具都面向系统运营商,但他表示,他预计开发人员的应用程序分析工具数量将会扩大。 本文转自d1net(转载)

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

MapReduce中map并行度优化及源码分析

mapTask并行度的决定机制 一个job的map阶段并行度由客户端在提交job时决定,而客户端对map阶段并行度的规划的基本逻辑为:将待处理数据执行逻辑切片(即按照一个特定切片大小,将待处理数据划分成逻辑上的多个split),然后每一个split分配一个mapTask并行实例处理。 FileInputFormat切片机制 原文和作者一起讨论:http://www.cnblogs.com/intsmaze/p/6733968.html 1、默认切片定义在InputFormat类中的getSplit()方法 2、FileInputFormat中默认的切片机制: a)简单地按照文件的内容长度进行切片 b)切片大小,默认等于hdfs的block大小 c)切片时不考虑数据集整体,而是逐个针对每一个文件单独切片 比如待处理数据有两个文件: file1.txt 260M file2.txt 10M 经过FileInputFormat的切片机制运算后,形成的切片信息如下: file1.txt.split1-- 0~128 file1.txt.split2-- 128~260 //如果剩余的文件长度/切片长度<=1.1则会将剩余文件的长度并未一个切片 file2.txt.split1-- 0~10M 3、FileInputFormat中切片的大小的参数配置 通过分析源码,在FileInputFormat中,计算切片大小的逻辑:Math.max(minSize, Math.min(maxSize, blockSize)); 切片主要由这几个值来运算决定。 minsize:默认值:1 配置参数: mapreduce.input.fileinputformat.split.minsize maxsize:默认值:Long.MAXValue 配置参数:mapreduce.input.fileinputformat.split.maxsize blocksize:值为hdfs的对应文件的blocksize配置读取目录下文件数量的线程数:public static final String LIST_STATUS_NUM_THREADS = "mapreduce.input.fileinputformat.list-status.num-threads"; 因此,默认情况下,Math.max(minSize,Math.min(maxSize,blockSize));切片大小=blocksize maxsize(切片最大值):参数如果调得比blocksize小,则会让切片变小。 minsize(切片最小值):参数调的比blockSize大,则可以让切片变得比blocksize还大。 选择并发数的影响因素: 1、运算节点的硬件配置 2、运算任务的类型:CPU密集型还是IO密集型 3、运算任务的数据量 3、hadoop2.6.4源码解析 org.apache.hadoop.mapreduce.JobSubmitter类 //得到job的map任务的并行数量 private int writeSplits(org.apache.hadoop.mapreduce.JobContext job, Path jobSubmitDir) throws IOException, InterruptedException, ClassNotFoundException { JobConf jConf = (JobConf)job.getConfiguration(); int maps; if (jConf.getUseNewMapper()) { maps = writeNewSplits(job, jobSubmitDir); } else { maps = writeOldSplits(jConf, jobSubmitDir); } return maps; } @SuppressWarnings("unchecked") private <T extends InputSplit> int writeNewSplits(JobContext job, Path jobSubmitDir) throws IOException, InterruptedException, ClassNotFoundException { Configuration conf = job.getConfiguration(); InputFormat<?, ?> input = ReflectionUtils.newInstance(job.getInputFormatClass(), conf); List<InputSplit> splits = input.getSplits(job); T[] array = (T[]) splits.toArray(new InputSplit[splits.size()]); // sort the splits into order based on size, so that the biggest // go first Arrays.sort(array, new SplitComparator()); JobSplitWriter.createSplitFiles(jobSubmitDir, conf, jobSubmitDir.getFileSystem(conf), array); return array.length; } 切片计算逻辑,关注红色字体代码即可。 public List<InputSplit> getSplits(JobContext job) throws IOException { Stopwatch sw = new Stopwatch().start();long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); long maxSize = getMaxSplitSize(job); // generate splits List<InputSplit> splits = new ArrayList<InputSplit>(); List<FileStatus> files = listStatus(job); //遍历文件,对每一个文件进行如下处理:获得文件的blocksize,获取文件的长度,得到切片信息(spilt 文件路径,切片编号,偏移量范围) for (FileStatus file: files) { Path path = file.getPath(); long length = file.getLen(); if (length != 0) { BlockLocation[] blkLocations; if (file instanceof LocatedFileStatus) { blkLocations = ((LocatedFileStatus) file).getBlockLocations(); } else { FileSystem fs = path.getFileSystem(job.getConfiguration()); blkLocations = fs.getFileBlockLocations(file, 0, length); } if (isSplitable(job, path)) { long blockSize = file.getBlockSize(); long splitSize = computeSplitSize(blockSize, minSize, maxSize); long bytesRemaining = length; while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) { int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining); splits.add(makeSplit(path, length-bytesRemaining, splitSize, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts())); bytesRemaining -= splitSize; } if (bytesRemaining != 0) { int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining); splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts())); } } else { // not splitable splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(), blkLocations[0].getCachedHosts())); } } else { //Create empty hosts array for zero length files splits.add(makeSplit(path, 0, length, new String[0])); } } // Save the number of input files for metrics/loadgen job.getConfiguration().setLong(NUM_INPUT_FILES, files.size()); sw.stop(); if (LOG.isDebugEnabled()) { LOG.debug("Total # of splits generated by getSplits: " + splits.size() + ", TimeTaken: " + sw.elapsedMillis()); } return splits; } public static final String SPLIT_MINSIZE = "mapreduce.input.fileinputformat.split.minsize"; public static final String SPLIT_MAXSIZE = "mapreduce.input.fileinputformat.split.maxsize"; long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job)); //保证切分的文件长度最小不得小于1字节 protected long getFormatMinSplitSize() { return 1; } //如果没有在conf中设置SPLIT_MINSIZE参数,则取默认值1字节。 public static long getMinSplitSize(JobContext job) { return job.getConfiguration().getLong(SPLIT_MINSIZE, 1L); } //得到切片文件的最大长度 long maxSize = getMaxSplitSize(job); //如果没有在conf中设置SPLIT_MAXSIZE参数,则去默认值Long.MAX_VALUE字节。 public static long getMaxSplitSize(JobContext context) { return context.getConfiguration().getLong(SPLIT_MAXSIZE, Long.MAX_VALUE); } //读取指定目录下的所有文件的信息 List<FileStatus> files = listStatus(job); //如果没有指定开启几个线程读取,则默认一个线程去读文件信息,因为存在目录下有上亿个文件的情况,所以有需要开启多个线程加快读取。 int numThreads = job.getConfiguration().getInt(LIST_STATUS_NUM_THREADS, DEFAULT_LIST_STATUS_NUM_THREADS); public static final String LIST_STATUS_NUM_THREADS = "mapreduce.input.fileinputformat.list-status.num-threads"; public static final int DEFAULT_LIST_STATUS_NUM_THREADS = 1; //计算切片文件的逻辑大小 long splitSize = computeSplitSize(blockSize, minSize, maxSize); protected long computeSplitSize(long blockSize, long minSize, long maxSize) { return Math.max(minSize, Math.min(maxSize, blockSize)); } private static final double SPLIT_SLOP = 1.1; // 10% slop //判断剩余文件与切片大小的比是否为1.1. while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) { int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining); splits.add(makeSplit(path, length-bytesRemaining, splitSize, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts())); bytesRemaining -= splitSize; } map并行度 如果job的每个map或者reduce的task的运行时间都只有30-40秒钟(最好每个map的执行时间最少不低于一分钟),那么就减少该job的map或者reduce数。每一个task的启动和加入到调度器中进行调度,这个中间的过程可能都要花费几秒钟,所以如果每个task都非常快就跑完了,就会在task的开始和结束的时候浪费太多的时间。 配置task的JVM重用可以改善该问题: (mapred.job.reuse.jvm.num.tasks,默认是1,表示一个JVM上最多可以顺序执行的task数目(属于同一个Job)是1。也就是说一个task启一个JVM)。 小文件的场景下,默认的切片机制会造成大量的maptask处理很少量的数据,效率低下: 解决方案: 推荐:把小文件存入hdfs之前进行预处理,先合并为大文件后再上传。 折中:写程序对hdfs上小文件进行合并再跑job处理。 补救措施:如果大量的小文件已经存在hdfs上了,使用combineInputFormate组件,它可以将众多的小文件从逻辑上规划到一个切片中,这样多个小文件就可以交给一个maptask操作了。 作者: intsmaze(刘洋) 出处: http://www.cnblogs.com/intsmaze/ 老铁,你的--->推荐,--->关注,--->评论--->是我继续写作的动力。 微信公众号号:Apache技术研究院 由于博主能力有限,文中可能存在描述不正确,欢迎指正、补充! 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

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

Android提升篇系列:Android项目代码优化实践

Android开发中,不同的开发团队,不同的开发人员,在实际编码中会有一些不同的地方。 但是,具有一定的更普适性的编码习惯,无疑还是相当重要的。本文主要罗列项目中常见的一些编码片段,并给出相关建议。 1.数组标识符应该紧跟在数组类型后面,而非变量后面 如 int data[] = new int[1024]; 建议写成 int[] data = new int[1024]; 2.if中的条件判断在特定情况下需要合并 如 if(lastestTime > recordTime){ if(isLogin()){ //... } } 建议写成 if(lastestTime > recordTime && isLogin()){ //... } 3.if语句块在特定情况下可以简写 如 if(isExistAccount()){ return true; } else{ return false; } 建议写成 return isExistAccount(); 4.布尔型变量没必要再和true或false进行比较 如 int status = hasSubcribe == true ? 1 : 0; 建议写成 int status = hasSubcribe ? 1 : 0; 5.inteface中方法没有必要使用public修饰,常量没有必要使用public static修饰 如 public interface HostCallBack(){ public static int MODE_INSERT = 1; public static int MODE_ALL =2; public void clear(); } 建议写成 public interface HostCallBack(){ int MODE_INSERT = 1; int MODE_ALL =2; void clear(); } 6.重写equals方法需要遵守重写hashCode方法约定 如 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AccountVo accountVo = (AccountVo) o; if (id != accountVo.id) return false; return name.equals(accountVo.name); } 建议增加上重写hashCode方法 @Override public int hashCode() { int result = (int) (id ^ (id >>> 32)); result = 31 * result + name.hashCode(); return result; } 7.catch中不要再对Exception类型做判断 如 try{ //... }catch(Exception e){ if(e instanceOf IOException){ //... } else{ //... } } 建议写成 try{ //... }catch(IOException e){ //... }catch(Exception e){ //... } 8.方法体不宜太长,可以根据具体情况适当将方法体内部部分逻辑拆解出来 如 public void fixRecord(int rid, String name){ //... //方法体太长.... //... } 建议写成 public void fixRecord(int rid, String name){ //... updateRecord(int rid); //... } private void updateRecord(int rid){ //... } 9.xml元素没有内容应该采用简写形式 如 <item name="desc_tv" type="id"></item> 建议写成 <item name="desc_tv" type="id" /> 10.switch语句块需要加上break 如 switch (retCode){ case 3 // ... break; case 1: // ... break; case 2: // ... break; } 建议写成 switch (retCode){ case 3 // ... break; case 1: // ... break; case 2: // ... break; default: // ... break; } 11.变量名含义须具有唯一性 如: String password = AppAccountManager.getCurrentPassword(); password = EncryptUtil.decrypt(password); 建议写成 String password = AppAccountManager.getCurrentPassword(); String decryptPassword = EncryptUtil.decrypt(password); 12.无效的import需要删除 如果没有用到需要删除干净 13.注释不要与代码放在同一行 如: private int mState = STATE_ADD; // add record statef 建议写成 // add record statef private int mState = STATE_ADD; 14.不要犯单词拼写错误 项目中发现不少英文单词拼写错误,其实,AS默认情况下对疑似拼写错误的单词都会有波浪线等提示。 总之,在编码过程中,一些推荐的更标准的写法或风格总是没有错的,并且,一定的代码洁癖等也是一种很好的编码态度和习惯。 --------------------------------------------------------------------------------- 笔者水平有限,若有错漏,欢迎指正,如果转载以及CV操作,请务必注明出处,谢谢! 分类: Android 本文转自Windstep博客园博客,原文链接:http://www.cnblogs.com/lwbqqyumidi/p/5336707.html,如需转载请自行联系原作者

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

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

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应用均可从中受益。

用户登录
用户注册