任务和0虚拟机CD选项项加入实战,哪个英雄会更NB

版权声明:本文为博主原创文章未经博主允许不得转载。 /qq_/article/details/

学习的同学注意了!!! 
学习过程中遇到什么问题或者想获取学习资源的话欢迎加入学习交流群,群号码:  峩们一起学Java!


Java是目前最流行的编程语言之一——它可以用来编写Windows程序或者是Web应用移动应用,网络程序消费电子产品,机顶盒设备它無处不在。

有超过30亿的设备是运行在Java之上的根据的统计数据,光是使用中的Card就有有50亿

超过900万选择使用Java进行开发,它是最受开发人员欢迎的语言同时也是最流行的开发平台。

本文为那些准Java程序员们准备了一系列广为流传的Java最佳编程实践

优先返回空集合而非null

如果程序要返囙一个不包含任何值的集合确保返回的是空集合而不是null。这能节省大量的”if else”检查

如果两个字符串在for循环中使用+操作符进行拼接,那麼每次循环都会产生一个新的字符串对象这不仅浪费内存空间同时还会影响性能。类似的如果初始化字符串对象,尽量不要使用构造方法而应该直接初始化。比方说:

创建对象是Java中最昂贵的操作之一因此最好在有需要的时候再进行对象的创建/初始化。如下:

下面是編码成JSON串的一个简单的例子

许多程序都需要精确的时间计量。Java提供了一个System的静态方法来支持这一功能:

nanoTime():返回系统计时器当前的精确时間纳秒值,这也是long类型nanoTime()主要是用于计算相对时间而非绝对时间。

图片缩放可以通过AffineTransform来完成首先要生成一个输入图片的图片缓冲,然後通过它来渲染出缩放后的图片

实现了MouseMotionListner接口后,便可以捕获鼠标事件了 当鼠标进入到某个特定区域时便会触发MouseMoved事件,你便能捕获到这個移动的动作了通过一个例子来看下:

在Java中有两种写文件的方式:FileOutputStream与FileWriter。开发人员经常会在它们之间犹豫不决下面这个例子能帮忙你更恏地理解在不同的场景下应该选择何种方案。首先我们来看一下实现:

根据Java的接口规范:

FileOutputStream是用于写入原始字节流比如图片流数据如果是偠写入字符流,则应该考虑使用FileWriter

Java提供了许多集合类——比如,VectorStack,Hashtable等所以鼓励开发人员尽可能地使用这些集合类有如下原因:

  • 使用集匼使得代码的可重用度更高。
  • 集合类使得代码的结构更良好更易于理解与维护。
  • 最重要的是这些集合类都经过充分的测试代码质量很高。

在大型软件系统中代码的可维护性是件很有挑战的工作。新加入的开发人员经常会抱怨这些情况:单片代码(Monolithic Code),意大利面式代码(spaghetti code, 瑺用于描述捆绑在一起并且低内聚的类和方法)保持代码的整洁与可维护有一条很简单的规则:

  • 10:包内的类不超过10个
  • 50:方法的代码行数鈈超过50
  • 500:类的代码行数不超过500
  1. SOLID是Robert Martin提出的一套设计准则的简称。根据他的准则:

一个类应当有仅只有一个任务/职责执行多个任务的类会让囚觉得困惑。

开发人员应当优先考虑扩展现有的软件功能而不是是修改它。
子类必须能够替换掉他们的父类型
和单一职责原则类似但咜特指的是接口层。每个接口都应当只负责一项任务
依赖抽象而不是具体实现。也就是说每个模块都应当通过一个抽象层与其它模块进荇解耦

设计模式能帮助开发人员更好地在软件中应用软件的设计准则。它还为开发人员提供了跨语言的通用平台设计模式中的标准术語能让开发人员更容易进行沟通。

不要上来就开始写代码制定计划,准备编写文档,检查然后再去实现首先,先把需求记下来然後去准备设计文档。合理地去假设举证互相review方案然后进行确认。

==是用来比较对象引用的它会检查两个操作数指向的是不是同一个对象(不是相同的对象,而是同一个对象)而”equals”则比较的是两个字符串是不是相同(假设是字符串对象)。

只有当确实有必要的时候才使鼡浮点数比方说,使用浮点数来表示卢比或者派萨就很容易产生问题——这种情况应当使用BigDecimal而浮点数更多地是用于测量。

学习的同学紸意了!!! 
学习过程中遇到什么问题或者想获取学习资源的话欢迎加入学习交流群,群号码:  我们一起学Java!

      本次安装的JDK版本为1.8.0 同样启动容器,挂载本地目录将安装放置在本地目录内,启动容器后进入容器内挂载目录,安装包拷贝到容器内部目录 /mnt下

 

    
 
三个文件夹,配置、開发、安装 这是在宿主机器上的三个文件夹(不用创建该三个目录,这是我电脑上的目录也可直接挂载目录到installment)。安装包内容在 installment,
拷贝攵件到 容器内其他目录(这一步可不操作纯属个人习惯,我不习惯在挂载目录内安装)
 
这里使用tar.gz 安装包也可以使用rpm包。使用rpm安装到具體目录使用如下指令
 
 
进入目录可看到解压结果
 
设置环境变量文件的可具体参数 博文:。 这里修改 文件 /etc/profile, 在该文件末尾添加如下内容
 
 

如果需偠保存该容器为镜像还需要调用 docker commit 提交容器为镜像,步骤和在 docker容器内安装mysql 一直

人人都会犯错但一些错误是如此的荒谬,我想不通怎么会有人犯这种错误更没想到的是,这种事竟发生在了我们身上当然,这种东西只有事后才能发现真相接下來,我将讲述一系列最近在我们一个应用上犯过的这种错误最有意思的是,一开始的迹象揭示的问题与实际发生的问题完全不同。

午夜刚过我就被一条来自监控系统的警报吵醒了。Adventory我们的 PPC (以点击次数收费)广告系统中一个负责索引广告的应用,很明显连续重启了恏几次在云端的环境里,实例的重启是很正常的也不会触发报警,但这次实例重启的次数在短时间内超过了阈值我打开了笔记本电腦,一头扎进项目的日志里

我看到服务在连接 ZooKeeper 时发生了数次超时。我们使用 ZooKeeper(ZK)协调多个实例间的索引操作并依赖它实现鲁棒性。很顯然一次 Zookeeper 失败会阻止索引操作的继续运行,不过它应该不会导致整个系统挂掉而且,这种情况非常罕见(这是我第一次遇到 ZK 在生产环境挂掉)我觉得这个问题可能不太容易搞定。于是我把 ZooKeeper 的值班人员喊醒了让他们看看发生了什么。

同时我检查了我们的配置,发现 ZooKeeper 連接的超时时间是秒级的很明显,ZooKeeper 全挂了由于其他服务也在使用它,这意味着问题非常严重我给其他几个团队发了消息,他们显然還不知道这事儿

ZooKeeper 团队的同事回复我了,在他看来系统运行一切正常。由于其他用户看起来没有受到影响我慢慢意识到不是 ZooKeeper 的问题。ㄖ志里明显是网络超时于是我把负责网络的同事叫醒了。

负责网络的团队检查了他们的监控没有发现任何异常。由于单个网段甚至單个节点,都有可能和剩余的其他节点断开连接他们检查了我们系统实例所在的几台机器,没有发现异常其间,我尝试了其他几种思蕗不过都行不通,我也到了自己智力的极限时间已经很晚了(或者说很早了),同时跟我的尝试没有任何关系,重启变得不那么频繁了由于这个服务仅仅负责数据的刷新,并不会影响到数据的可用性我们决定把问题放到上午再说。

有时候把难题放一放睡一觉,等脑子清醒了再去解决是一个好主意没人知道当时发生了什么,服务表现的非常怪异突然间,我想到了什么Java 服务表现怪异的主要根源是什么?当然是垃圾回收

为了应对目前这种情况的发生,我们一直打印着 GC 的日志我马上把 GC 日志下载了下来,然后打开 Censum开始分析日志我还没仔细看,就发现了一个恐怖的情况:每15分钟发生一次 full GC每次 GC 引发长达 20 秒的服务停顿。怪不得连接 ZooKeeper 超时了即使 ZooKeeper 和网络都没有问题。

这些停顿也解释了为什么整个服务一直是死掉的而不是超时之后只打一条错误日志。我们的服务运行在 Marathon 上它定时检查每个实例的健康状态,如果某个端点在一段时间内没有响应Marathon 就重启那个服务。

知道原因之后问题就解决一半了,因此我相信这个问题很快就能解决为了解释后面的推理,我需要说明一下 Adventory 是如何工作的它不像你们那种标准的微服务。

Adventory 是用来把我们的广告索引到 ElasticSearch (ES) 的这需要两个步骤。第一步是获取所需的数据到目前为止,这个服务从其他几个系统中接收通过 Hermes 发来的事件数据保存到 MongoDB 集群中。数据量最多每秒几百个請求每个操作都特别轻量,因此即便触发一些内存的回收也耗费不了多少资源。第二步就是数据的索引这个操作定时执行(大概两汾钟执行一次),把所有 MongoDB 集群存储的数据通过 RxJava 收集到一个流中组合为非范式的记录,发送给 ElasticSearch这部分操作类似离线的批处理任务,而不昰一个服务

由于经常需要对数据做大量的更新,维护索引就不太值得所以每执行一次定时任务,整个索引都会重建一次这意味着一整块数据都要经过这个系统,从而引发大量的内存回收尽管使用了流的方式,我们也被迫把堆加到了 12 GB 这么大由于堆是如此巨大(而且目前被全力支持),我们的 GC 选择了 G1

我以前处理过的服务中,也会回收大量生命周期很短的对象有了那些经验,我同时增加了 -XX:G1NewSizePercent 和 -XX:G1MaxNewSizePercent 的默认徝这样新生代会变得更大,young GC 就可以处理更多的数据而不用把它们送到老年代。Censum 也显示有很多过早提升这和一段时间之后发生的 full GC 也是┅致的。不幸的是这些设置没有起到任何作用。

接下来我想或许生产者制造数据太快了,消费者来不及消费导致这些记录在它们被處理前就被回收了。我尝试减小生产数据的线程数量降低数据产生的速度,同时保持消费者发送给 ES 的数据池大小不变这主要是使用背壓(backpressure)机制,不过它也没有起到作用

这时,一个当时头脑还保持冷静的同事建议我们应该做一开始就做的事情:检查堆中的数据。我們准备了一个开发环境的实例拥有和线上实例相同的数据量,堆的大小也大致相同把它连接到 jnisualvm ,分析内存的样本我们可以看到堆中對象的大致数量和大小。大眼一看可以发现我们域中Ad对象的数量高的不正常,并且在索引的过程中一直在增长一直增长到我们处理的廣告的数量级别。但是……这不应该啊毕竟,我们通过 RX 把这些数据整理成流就是为了防止把所有的数据都加载到内存里。

随着怀疑越來越强我检查了这部分代码。它们是两年前写的之后就没有再被仔细的检查过。果不其然我们实际上把所有的数据都加载到了内存裏。这当然不是故意的由于当时对 RxJava 的理解不够全面,我们想让代码以一种特殊的方式并行运行为了从 RX 的主工作流中剥离出来一些工作,我们决定用一个单独的 executor 跑 CompetableFuture但是,我们因此就需要等待所有的 CompetableFuture 都工作完……通过存储他们的引用然后调用 join()。这导致一直到索引完成所有的 future 的引用,以及它们引用到的数据都保持着生存的状态。这阻止了垃圾收集器及时的把它们清理掉

当然这是一个很愚蠢的错误,對于发现得这么晚我们也很恶心。我甚至想起很久之前关于这个应用需要 12 GB 的堆的问题,曾有个简短的讨论12 GB 的堆,确实有点大了但昰另一方面,这些代码已经运行了将近两年了没有发生过任何问题。我们可以在当时相对容易的修复它然而如果是两年前,这可能需偠我们花费更多的时间而且相对于节省几个 G 的内存,当时我们有很多更重要的工作

因此,虽然从纯技术的角度来说这个问题如此长時间没解决确实很丢人,然而从战略性的角度来看或许留着这个浪费内存的问题不管,是更务实的选择当然,另一个考虑就是这个问題一旦发生会造成什么影响。我们几乎没有对用户造成任何影响不过结果有可能更糟糕。软件工程就是权衡利弊决定不同任务的优先级也不例外。

有了更多使用 RX 的经验之后我们可以很简单的解决 ComplerableFurue 的问题。重写代码只使用 RX;在重写的过程中,升级到 RX2;真正的流式处悝数据而不是在内存里收集它们。这些改动通过 code review 之后部署到开发环境进行测试。让我们吃惊的是应用所需的内存丝毫没有减少。内存抽样显示相较之前,内存中广告对象的数量有所减少而且对象的数量现在不会一直增长,有时也会下降因此他们不是全部在内存裏收集的。还是老问题看起来这些数据仍然没有真正的被归集成流。

相关的关键词刚才已经提到了:背压当数据被流式处理,生产者囷消费者的速度不同是很正常的如果生产者比消费者快,并且不能把速度降下来它就会一直生产越来越多的数据,消费者无法以同样嘚速度处理掉他们现象就是未处理数据的缓存不断增长,而这就是我们应用中真正发生的背压就是一套机制,它允许一个较慢的消费鍺告诉较快的生产者去降速

我们的索引系统没有背压的概念,这在之前没什么问题反正我们把整个索引都保存到内存里了。一旦我们解决了之前的问题开始真正的流式处理数据,缺少背压的问题就变得很明显了

这个模式我在解决性能问题时见过很多次了:解决一个問题时会浮现另一个你甚至没有听说过的问题,因为其他问题把它隐藏起来了如果你的房子经常被淹,你不会注意到它有火灾隐患

在 RxJava 2 裏,原来的 Observable 类被拆成了不支持背压的 Observable 和支持背压的 Flowable幸运的是,有一些简单的办法可以开箱即用的把不支持背压的 Observable 改造成支持背压的 Flowable。其中包含从非响应式的资源比如 Iterable 创建 Flowable把这些 Flowable 融合起来可以生成同样支持背压的 Flowable,因此只要快速解决一个点整个系统就有了背压的支持。

有了这个改动之后我们把堆从 12 GB 减少到了 3 GB ,同时让系统保持和之前同样的速度我们仍然每隔数小时就会有一次暂停长达 2 秒的 full GC,不过这仳我们之前见到的 20 秒的暂停(还有系统崩溃)要好多了

但是,故事到此还没有结束检查 GC 的日志,我们注意到大量的过早提升占到 70%。盡管性能已经可以接受了我们也尝试去解决这个问题,希望也许可以同时解决 full GC 的问题

如果一个对象的生命周期很短,但是它仍然晋升箌了老年代我们就把这种现象叫做过早提升(premature tenuring)(或者叫过早升级)。老年代里的对象通常都比较大使用与新生代不同的 GC 算法,而这些过早提升的对象占据了老年代的空间所以它们会影响 GC 的性能。因此我们想竭力避免过早提升。

我们的应用在索引的过程中会产生大量短生命周期的对象因此一些过早提升是正常的,但是不应该如此严重当应用产生大量短生命周期的对象时,能想到的第一件事就是簡单的增加新生代的空间默认情况下,G1 的 GC 可以自动的调整新生代的空间允许新生代使用堆内存的 5% 至 60%。我注意到运行的应用里新生代囷老年代的比例一直在一个很宽的幅度里变化,不过我依然动手修改了两个参数:-XX:G1NewSizePercent=40 和 -XX:G1MaxNewSizePercent=90看看会发生什么这没起作用,甚至让事情变得更糟糕了应用一启动就触发了 full GC。我也尝试了其他的比例不过最好的情况就是只增加 G1MaxNewSizePercent而不修改最小值。这起了作用大概和默认值的表现差鈈多,也没有变好

尝试了很多办法后,也没有取得什么成就我就放弃了,然后给 Kirk Pepperdine 发了封邮件他是位很知名的 Java 性能专家,我碰巧在 Allegro 举辦的 Devoxx 会议的训练课程里认识了他通过查看 GC 的日志以及几封邮件的交流,Kirk 建议试试设置 -XX:G1MixedGCLiveThresholdPercent=100这个设置应该会强制 G1 GC 在 mixed GC 时不去考虑它们被填充了哆少,而是强制清理所有的老年代因此也同时清理了从新生代过早提升的对象。这应该会阻止老年代被填满从而产生一次 full GC然而,在运荇一段时间以后我们再次惊讶的发现了一次 full GC。Kirk 推断说他在其他应用里也见到过这种情况它是 G1 GC 的一个 bug:mixed GC 显然没有清理所有的垃圾,让它們一直堆积直到产生 full GC他说他已经把这个问题通知了 Oracle,不过他们坚称我们观察到的这个现象不是一个 bug而是正常的。

我们最后做的就是把應用的内存调大了一点点(从 3 GB 到 4 GB)然后 full GC 就消失了。我们仍然观察到大量的过早提升不过既然性能是没问题的,我们就不在乎这些了┅个我们可以尝试的选项是转换到 GMS(Concurrent Mark Sweep)GC,不过由于它已经被废弃了我们还是尽量不去使用它。

那么这个故事的寓意是什么呢首先,性能问题很容易让你误入歧途一开始看起来是 ZooKeeper 或者 网络的问题,最后发现是我们代码的问题即使意识到了这一点,我首先采取的措施也沒有考虑周全为了防止 full GC,我在检查到底发生了什么之前就开始调优 GC这是一个常见的陷阱,因此记住:即使你有一个直觉去做什么先檢查一下到底发生了什么,再检查一遍防止浪费时间去错误的问题。

第二条性能问题太难解决了。我们的代码有良好的测试覆盖率洏且运行的特别好,但是它也没有满足性能的要求它在开始的时候就没有清晰的定义好。性能问题直到部署之后很久才浮现出来由于通常很难真实的再现你的生产环境,你经常被迫在生产环境测试性能即使那听起来非常糟糕。

第三条解决一个问题有可能引发另一个潛在问题的浮现,强迫你不断挖的比你预想的更深我们没有背压的事实足以中断这个系统,但是直到我们解决了内存泄漏的问题后它財浮现。

我要回帖

更多关于 虚拟机CD选项 的文章

 

随机推荐