挨踢部落故事汇(32): Java深坑如何填?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了挨踢部落故事汇(32): Java深坑如何填?相关的知识,希望对你有一定的参考价值。

世上本没有坑,踩的人多了也便成了坑。每遇到一次困难,每踩一个坑,对程序员来说都是一笔财富。持续学习是程序员保持竞争力的源泉。本期将分享一个踩坑无数的Java程序猿填坑秘籍。

榆木,一个阅历无数(踩坑)的技术宅男,喜欢了解新技术却不爱太钻研新技术(因为懒,猿届反面角色一枚)。14年毕业至今,在Java开发这条道路上可谓是坑过好些人、也埋过好些坑、也被坑过好些次。因为懒,没有针对他遇到过的问题做过太多的笔记(记录一些棘手问题的解决方法还是个不错的习惯),只是习惯性的去分析为什么出现这样的问题,我们该怎么去避免重复出现。在这里榆木share一下第一次做独立需求的过程。

技术分享图片

榆木·Java开发工程师

要成为一个合格的Java程序猿,独立完成需求是一个必须经历的阶段,在这个过程中可能会遇到很多问题,要学会合理的利用资源(官方文档、社区论坛等)去解决问题。在这个阶段应该是踩坑最多、收获最多、成长最快的阶段。

在榆木入职的前3个月里,做的都是一些改bug、完善需求的活,他不需要太多思考,根据客户说的做就成了。三个月之后他的公司顺利拿下了该客户的二期项目,由于人手不够,再加上他在一期维护的时候对业务比较熟悉,老大便让榆木独自承担该项目前置子系统的全部需求。刚开始的时候榆木是很激动的,随之而来的却是不知所措。

榆木都是如何踩坑又填坑的呢?分享一下他的几点经验,希望对开发者有所帮助。

如何同时开启两个SVN服务

因为公司资源不够,老大就要求在原有的服务器上再弄一个SVN服务,于是他开始各种捣腾,可是不管怎么样就是没有办法同时起来两个服务。怎么办,只能找哥哥(google)帮忙咯,因为SVN服务的启动(/etc/init.d/svnserve start )是包含一些默认参数,如 --listen-port指定服务端口 默认是3691,如果要同时起两个SVN服务只需要在启动时指定两个不同的listen-port就OK了。

如下,问题解决:

/etc/init.d/svnserve start   -d -r /svn/repo  第一次第一个库启动  默认3691 /etc/init.d/svnserve --listen-port 9999 -d -r /svn/svndata  第二次指定端口启动

问题搞定,紧接着就是紧张的代码开发,事情有点想不到的顺利,后端接口顺利完工通过测试,榆木开始和前端对接联调,好激动,搞不好可以提前完成任务了。噼里啪啦的搞完就开始测试了。

fastJson序列化问题

所谓没有遇到过bug的程序猿就不是正常的程序猿,一点都不意外,问题来了。同一个对象赋值给HashMap中不同的key 传到前端后,第二个value竟然不能被正常解析....... 他自己写的代码必须不能怂,有问题那就解决问题,于是榆木开始找问题所在,开始模拟数据,发现返回结果如下:

{"o1":{"age":16,"name":"o1"},"2":{"$ref":"$.o1"}}

很容易就能看出来,第二value在这个返回结果中用类似指针的方法("$ref":"$.o1")表示它和“o1”的值一样,看起来像是同一个对象的循环引用哦,那是不是可以把这个循环引用禁止呢?答案是可以的。(有必要说明一下,这里使用的是fastJson)通过SerializerFeature指定禁用循环依赖就可以了。修改前代码如下:

public static void test1() {         TestObject object = new TestObject("o1", 16);         Map<String, TestObject> map = new HashMap<String, TestObject>();         map.put("o1", object);         map.put("o2", object);         System.out.println(new String(JSON.toJSONBytes(map)));     }

输出结果:{"o1":{"age":16,"name":"o1"},"o2":{"$ref":"$.o1"}}

在一个集合对象中存在多条相同数据时,将ist集合对象转化为json对象输出到前台时,JSON默认对第二条数据处理时用"$ref":"$.object".<这里object指第一条数据>,这样的json转化结果输出到前台肯定是不可以使用的,好在JSON有提供禁止关闭引用循环检测的方法,只需要在转化的时候加上SerializerFeature.DisableCircularReferenceDetect  就可以解决了。修改后代码如下:

public static void test1() {        TestObject object = new TestObject("s1", 16);        Map<String, TestObject> map = new HashMap<String, TestObject>();        map.put("o1", object);        map.put("o2", object);        SerializerFeature feature = SerializerFeature.DisableCircularReferenceDetect;        System.out.println(new String(JSON.toJSONBytes(map, feature)));    }

输出结果如下:{"o1":{"age":16,"name":"o1"},"o2":{"age":16,"name":"o1"}}

到这里问题就解决了。不久之后测试通过了,交付客户测试版本,开始和中心联调测试了。

OOM异常处理

榆木以为到这里就万事大吉了,然而是不可能的。联调测试两天之后,客户反馈说:“我们的XXX报文数据已经往中心发过了呀,可是中心说他们没有收到,你们查下是什么问题呗!”客户就是上帝呀,榆木和他的同事开始查询日志,发现有一些OOM的异常。异常产生的场景是在取数据-组报文-MQ转发这个环节,然后就开始一个一个点的排查了。

榆木首先想到的可能原因有:

1、数据取出来生成报文这个过程都是在内存中做的,会不会是这里数据太多导致?

2、会不会是报文生成过程产生了过多Object没有来得及回收?

3、会不会是数据发送慢于报文生成的速到导致等待队列爆满?

然后开始针对性的做修改测试,他将一次性取数据生成报文的过程改成批量去做,然后测试运行一段时间没有问题(排除 1);在生成保温过程中,将每一个转化后的对象置为空Object=null,以便及时回收,测试运行一段时间没有问题(排除2);在第三点上面,他最先想的是增加线程数量( 服务器开启超线程、应用中增加线程数量)去提升处理速率,运行一段时间之后还是会出现OOM。怎么办呢?再次回到了等待队列上面来,能不能在一定程度上对等待队列做个限制呢?于是榆木在每次从MQ取消息之前增加了对等待队列的深度的判断,如果深度大于最大线程数量的2倍,就放弃本次MQ队列消息的处理。然后继续测试,问题没有再出现。

查询慢怎么办?

最终项目上线了,终于可以松一口气了。可是有一天,榆木的老大说客户反映部分查询很慢,让他去处理一下。榆木心里想着,这个应该是个小问题,给数据表加索引就能搞定。到了客户现场之后发现,原来的表是有索引的,可查询还是慢,不得已只能去找原因了。不得不说,explain SQL是个不错的命令,发现索引没有生效,经过仔细的比对,发现该关联查询的关联字段在两个表中都有索引, 两个表的字符集都是UTF8,但是排序规则一个是utf-bin(二进制存储数据,大小写区分),一个是utf8_general_ci(大小写不敏感),所以把数据排序规则改成一致索引生效,查询速度也就上来了。

PS: mysql中的UTF-8编码的排序规则说明

utf8_bin将字符串中的每一个字符用二进制数据存储,区分大小写。

utf8_genera_ci不区分大小写,ci为case insensitive的缩写,即大小写不敏感。

utf8_general_cs区分大小写,cs为case sensitive的缩写,即大小写敏感。

【写在最后】

榆木整理了下这些年踩的坑,给自己也给正在和他挣扎在挨踢坑里的小伙伴们一些启发与鼓励。持续学习是保持竞争力的前提;夯实的基础是进阶的垫脚石。抬头走不独行(exchange)、埋头干(code),就算被称作屌丝,也还是要有梦想,万一逆袭了呢。

 如果你也愿意分享你的故事,请加51CTO开发者QQ交流群 114915964联系群主小官,期待你精彩的故事!

技术分享图片


以上是关于挨踢部落故事汇(32): Java深坑如何填?的主要内容,如果未能解决你的问题,请参考以下文章

挨踢部落故事汇(11):编程起步从0到1

挨踢部落故事汇:扩展新IT领域,用代码改变世界

挨踢部落故事汇(18):程序猿与代码的基情

挨踢部落故事汇(12):习惯成就技能提升

挨踢部落故事汇:程序猿的跳槽感悟

挨踢部落故事汇:入行IT自学成才