Java 应用 优化手记.docx

上传人:b****5 文档编号:2871792 上传时间:2022-11-16 格式:DOCX 页数:23 大小:35.94KB
下载 相关 举报
Java 应用 优化手记.docx_第1页
第1页 / 共23页
Java 应用 优化手记.docx_第2页
第2页 / 共23页
Java 应用 优化手记.docx_第3页
第3页 / 共23页
Java 应用 优化手记.docx_第4页
第4页 / 共23页
Java 应用 优化手记.docx_第5页
第5页 / 共23页
点击查看更多>>
下载资源
资源描述

Java 应用 优化手记.docx

《Java 应用 优化手记.docx》由会员分享,可在线阅读,更多相关《Java 应用 优化手记.docx(23页珍藏版)》请在冰豆网上搜索。

Java 应用 优化手记.docx

Java应用优化手记

现在单台服务器tps可以支撑到1000并发,前端和后端压力基本平衡。

根据经验和不停的测试获得的,可能有不准确的地方,并不适用于所有java应用。

1java优化

仅仅是一些设计经验和编程技巧。

1.1Hibernate

lsmp对数据库操作采用hibernate封装对象。

但是hibernate会在后台做一些我们不知道事情。

hibernate有自己的cache机制,二级缓存需要配置默认不开启,一级缓存(查询缓存)是默认开启的,原来使用hibernate多数是查询oracle,因为是访问的介质是磁盘,所以hibernate的查询缓存可以起到提升查询速度的效果,但是lsmp使用的是timesten内存型数据库,访问速度比oracle要快的多,所以可以关闭hibernate查询缓存。

1.1.1hibernate封装对象

测试时发现,程序设计时hibernate映射对象会关联其它表,比如用户表关联了用户详细信息表,这样做的好处是方便我们查询对象,里面已经封装了我们需要的所有信息,但如果我们不需要关联的信息呢?

在查询时hibernate会自动将关联查询一起封装为sql语句,这样等于做了2次select操作,影响查询效率。

在低负载的情况下不明显,随着压力的增加会越来越明显。

解决办法:

删除映射关联或将关联设置为延迟加载。

在开发的时候对于数据库的访问应该有明确的需求分析。

1.1.2createQuery与createNativeQuery

这个问题暂时还没有明确,当hibernate使用createQuery时查询效率就会越来越低,使用createNativeQuery时就不会下降,初步判断为:

hibernate更新自己的查询缓存影响了查询速度,依据是查询不同的信息时,速度越查越慢,但是查询刚才已经查询过的数据,速度是恒定的。

createNativeQuery需要自己封装sql语句,返回的也是基础数据类型或许没有经过hibernate的一级缓存。

当然这些只是猜测。

1.2ConcurrentHashMap

ConcurrentHashMap是Java5中支持高并发、高吞吐量的线程安全HashMap实现。

按照java官方的解释,ConcurrentHashMap是由线程并发专家编写的专门用于高并发情况下的Map。

ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。

它使用了多个锁来控制对hash表的不同部分进行的修改。

ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hashtable,它们有自己的锁。

只要多个修改操作发生在不同的段上,它们就可以并发进行。

lsmp使用ConcurrentHashMap作为cache,每次购彩操作中,都会先将购彩对象存入cache中,再发送给生产系统落单,当每张彩票从生产系统正确落单返回给business后,再到cache中找到刚才的彩票对象,完成后面的购彩操作,将对象从cache中删除。

由于存在超时问题,就起了一个线程定时扫描cache,每次扫描都会遍历整个cache,将彩票对象的生命周期减一(int型数据),当减到0时,此彩票对象超时;也就是说每次定时任务都会将cache中所有对象操作一遍,要么减少生命周期操作,要么超时操作。

但是测试时发现在高并发下,经常会出现cache中数据丢失数据的问题,症状为:

在压力测试进行10分钟左右,开始出现购彩信息从生产系统正常落单回来后,在cache中找不到对应的彩票对象,造成购彩失败的情况。

低压力情况下也会发生这个问题但错误少,压力越高出错的数据就越多。

经过分析怀疑是cache操作过于频繁造成的,每次定时任务都会对cache中所有数据进行全量更新操作,并且购彩操作还在进行,购彩完成操作也在进行,长时间高并发的进行get操作的同时进行put+remove操作,ConcurrentHashMap的锁控制机制失效了。

根据上面的分析对代码进行了修改,将彩票对象的生命周期从int修改为long(System.currentTimeMillis()),每次定时任务处理cache时(10秒一次),只是将彩票对象从cache中get出来,然后与系统当前时间(System.currentTimeMillis())进行减法操作,查看结果是否大于或等于设置的超时时间,如果没有超时则遍历下一条。

这时对于cache中的数据只进行get操作并没有改变对象的内容,优化代码之前这里每次都要将生命周期减一然后再重新put到cache中,get不影响锁控制的,只有发现超时的对象才进行remove,cache的访问压力变小了;但是购彩成功的彩票后依然会实时对cache进行remove操作,cache依然长时间处于put+remover操作,错误数量虽然下降了但并没有消失。

再次优化代码,完成购彩成功后只将cache中彩票对象的状态标志位改为“购彩完成”,不实时删除cache中的数据;修改定时任务增加“判断彩票状态”功能,当定时任务遍历cache时,会判断当前彩票对象“是否超时”+“是否已经完成购彩”,如果都不是则遍历下一条,如果超时或已完成购彩则从cache中remove。

也就是说定时任务执行时彩票对象从cache中统一remove。

这样对于cache的访问就变为:

一直进行put操作,购彩成功后修改状态位重新put回cache,这时cache中的数据只增加没减少,并且cache中的对象不会发生并发访问(因为彩票号都是唯一的);当定时任务执行时(10秒一次)才集中进行remove操作,并且将remove被定义为synchronized。

定时任务的间隔时间比较长(10秒一次),所以不会导致ConcurrentHashMap的锁控制机制失效的问题。

再次测试后发现,每次定时任务执行时,cache的性能会大幅下降,所以果断的去掉了remove的synchronized,但是确保代码中只有一个地方可以对cache进行remove操作,入口也是唯一的(单例模式)。

测试结果:

在tps:

900的情况下运行8小时,全部成功,无失败记录。

定时任务可以在3秒内完成整个任务(quartz启动任务+rmi发送接收消息+cache遍历)。

对于高并发cache操作的一些总结:

1.cache是放在内存中的,速度快易丢失,会对提高编码难度,特别是高并发下,管理不好会造出数据不同步,所以再使用之前先考虑清楚:

“你确定有必要使用cache吗?

如果确定要用那么cache要承受多大的压力?

如何解决数据不同步的问题?

2.对于cache中的对象,能少修改就少修改,能不修改就不修改。

3.对于ConcurrentHashMap来说get操作不影响锁,可以放心并发使用。

4.对于生命周期来说,时间戳比递减数值的好用,它不需要频繁修改cache中对象的内容,缺点是精度不够,可以通过增加定时任务的执行频率来提高精度。

5.尽量避免长时间对cache进行put+remove操作,如果实在避免不了,就将其中一个改为批量操作。

6.在高并发下ConcurrentHashMap确实比HashMap好,用HashMap测试时购彩数据错的一塌糊涂。

但是ConcurrentHashMap的系统开销比HashMap大不少。

7.专用的cache比ConcurrentHashMap好用。

1.3session超时

session管理是web开发中很重要的一环,我们将session放入容器,到了超时时间,webserver会自动删除,也可以手动删除,一般情况下开发人员会在用户退出时调用删除session代码,但是如果用户没有点击退出,只是关闭了浏览器,那只能等到session超时了,小网站一般不会有问题,但网站访问量非常大呢?

session必须要用,那么是不是可以只往里放必要的东西,毕竟内存是有限的。

如果一定要放很多东西,能不能压缩下内容再放进去,办法其实很多。

同理我们在编程的时候有没有把各种极限情况都考虑进去呢?

就像DougLinder说的:

“一个好的程序员应该是那种过单行线都要往两边看的人。

1.4对象生存周期

编程时很多程序员习惯于先把要用到的对象声明出来,然后再写逻辑,这个习惯不错,不过java对于每个new出来的对象,都会分配内存空间并且消耗部分cpu资源。

我走查代码时,发现不少童靴new出对象放在那里,执行了好几行代码后(其中包括查询数据库这样比较耗时的操作),才用到这个对象,期间java是不会回收这个对象的,因为它的生命周期还没到,内存在这段时间被浪费了,当然在一个毫秒内浪费几十个字节的内存无所谓,不过如果这个方法在一秒钟内被调用了几百万次呢?

1.5流水线

现在的cpu架构是按流水线执行的,比如执行for循环,for里面的代码会编译为一个顺序的流水线执行,如果在for循环中加入一条System.out语句,System.out要输出信息并且不属于流水线,因为System.out需要jdk调用操作系统的IO接口,破坏了流水线操作,cpu会将流水线的指令集按顺序一条条压入堆栈(先进后出),等IO完毕后在从堆栈中顺序读取指令恢复流水线操作。

我因为测试需要,在购彩代码中加入了一句System.out测试语句,结果忘了删除,在极限压力测试下,后台报了大量线程处于blocked状态(线程阻塞),而这些线程都指向这条System.out语句;删除这条语句后再测试系统正常,压测1小时内没发现有blocked的线程。

所以我比别人更痛恨在svn上看到System.out。

1.6高低搭配

编程时经常碰到速度不匹配的代码,比如短信接口,发短信的速度慢,如果把这个接口放到购彩这样要求并发很高的代码中,就会造出极大的性能降低,这时有两个方法,第一修改短信接口,使之处理速度达到或接近购彩接口的速度(这几乎不可能);第二就需要将短信接口改为异步,购彩不再依赖短信回执信息。

1.7方法间相互调用

我们天天写各种方法、接口;调用别人给我们的各种方法、接口,但是很少有人说方法之间的调用也是耗时的,虽然耗时极短,但确实是消耗了的,当然这不能说我们把所有操作都要写在一个方法内,必要的结构有利于开发和维护,但是方法和接口泛滥不是一个好习惯,从中找到一个平衡不是一件容易的事情。

1.8伊格尔森定律

你自己的代码如果超过6个月不看,再看的时候和别人写的一样。

1.9调试

EdsgerW.Dijkstra说过:

“如果调试是除虫的过程,那么编程就一定是把臭虫放进来的过程”。

我发现很多童靴总想着开发新东西和热衷于用新技术,但很少人喜欢去调试代码,我说的调试不是像测试人员那样按照步骤一个一个功能点测试,而是更接近于白盒测试,必要时要修改bug保证测试可以顺利完成。

这项工作是十分枯燥的;调试是烦人的、苦恼的;被各种bug轮来轮去、被各种低级错误气得直揪头发,但是你却可以在调试中发现很多东西、学到很多东西,别人的思想,自己的思想。

可以说没有比调试代码更能提高程序员水平的方法了。

特别是再有性能要求的时候,比如这段代码要求并发达到1000,但是程序执行没有错误,就是速度上不去,怎么办?

找问题呗,哪里是瓶颈?

测试日志语句到底打在哪一行?

往上一行和往下一行会给测试日志带来什么改变?

哪里可以处理的再快一点?

哪里的代码质量不高?

你要一步一步去分析、去测试。

如果这样你的水平还不能提高,那我也没什么好说的了。

BrainW.Kernighan说过:

“调试代码的难度是首次编写这些代码的两倍,因此,如果你在编写代码的时候就已经发挥了全部的聪明才智,那么按照常理,你将无法凭借自己的智慧去调试这些代码。

”所以如果你能调试出比现有代码更牛叉的代码,只能说明你比写代码的人更加牛叉!

(个人观点)

2OS优化

现在使用的是64位linux,li

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 表格模板 > 合同协议

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1