持续更新线上问题及解决方案

     最近线上遇到几个小问题,排查代码发现基本都是些细节问题,作些总结提示你们不要掉到坑中。前端

1、fastjson的序列化SerializerFeature使用注意                                       java

    咱们都知道Integer、Double、Boolean等包装类型的字段默认值是null。若是不对这些字段设置值,那么在反序列化时获得的相应的值也应该是null。mysql

一、本次服务接口升级后致使调用方业务逻辑判断失败,好比Integer type 在升级以前是返回的null,而升级后返回了数字0。究其缘由发现同事在序列化时设置了SerializerFeature.WriteNullNumberAsZero,而致使Number类型(Boolean,Integer,Float,Double等)都转成了0。sql

二、null属性不显示数据库

咱们先来看一段代码json

Map <String , Object > map = new HashMap<String , Object>();
map.put("a",1);
map.put("b","");
map.put("c",null);

String str = JSONObject.toJSONString(map);
System.out.println(str);//输出结果:{"a":1,"b":""}

属性c,怎么就凭空消失了哪?这里不讲述缘由,只说明解决方案。vim

解决方案:使用fastjson的SerializerFeature序列化属性,即JSONObject.toJSONString(Object object, SerializerFeature... features)windows

所以上面的代码就能够改写成后端

String str = JSONObject.toJSONString(map,SerializerFeature.WriteMapNullValue);

 

咱们经常使用的SerializerFeature:数组

  • SerializerFeature.WriteMapNullValue, //输出空置的字段
  • SerializerFeature.WriteNonStringKeyAsString,//若是key不为String 则转换为String 好比Map的key为Integer
  • SerializerFeature.WriteNullListAsEmpty,//list为null时输出[] 
  • SerializerFeature.WriteNullNumberAsZero,//number为null时输出0
  • SerializerFeature.WriteNullStringAsEmpty,//String为null时输出""
  • SerializerFeature.WriteNullBooleanAsFalse,//boolean为null时输出false
  • SerializerFeature.QuoteFieldNames,//输出key时是否使用双引号,默认为true
  • SerializerFeature.DisableCheckSpecialChar//一个对象的字符串属性中若是有特殊字符如双引号,将会在转成json时带有反斜杠转移符。若是不须要转义,可使用这个属性。默认为false 

    总结:

    一、服务接口的测试用例覆盖率仍是过低

    二、对于通用处理工具,在作修改时对工具类的来龙去脉要清楚

2、java.util.List.subList的陷阱                                                             

     咱们通常都会使用java.util.List中有一个subList方法,返回一个以fromIndex为起始索引(包含),以toIndex为终止索引(不包含)的一部分的视图(List)。    

List<E> subList(int fromIndex, int toIndex);

    之因此说是视图,是由于实际上,返回的list是靠原来的list支持的。原来的list和返回的list作的“非结构性修改”(non-structural changes),都会影响到彼此对方。

所谓的“非结构性修改”,是指不涉及到list的大小改变的修改。相反,结构性修改,指改变了list大小的修改。

若是发生结构性修改的是返回的子list,那么原来的list的大小也会发生变化;

若是发生结构性修改的是原来的list(不包括因为返回的子list致使的改变),那么会是抛出一个ConcurrentModificationException。

  • 如何删除一个list中的某个值   
list.remove(index)
  • 如何删除一个list的某个区段
list.subList(int fromIndex, int toIndex).clear();
  • 如何修改子list视图而不影响原来的list或修改原list而不影响子list视图
List<Integer> subList2 = new ArrayList<Integer>(list2.subList(2, list2.size()));

 那么ArrayList的remove的底层是怎么作的?

      AbstractList中有一个属性modCount,这个属性是跟踪list中数据被修改的次数,任何对list的add/remove操做,都将致使modCount++。

在AbstractList中还有一个内部类Itr implements Iterator,Itr是一个list遍历的工具类。固然list.iterator()方法也是返回Itr对象,在Itr中有一个校验位属性expectedModCount;对于一个itr对象,其初始时expectedModCount=modCount。

      Iterator是list一个视图,其最终仍是操做list的存储结构。在使用iterator遍历时,remove()操做,会致使modCount++(AbstractList.remove()),可是还有expectedModCount=modCount,即在iterator中remove数据,会带来expectedModCount与modCount值的同步

在Iterator遍历时,next(),remove()方法会校验expectedModCount与modCount值是否一致,若是不一致,就意味着这list数据在iterator外部被修改,此时iterator遍历将会形成ConcurrentModificationException.

      AbstractLlist不只支持普通的iterator,还支持ListIterator(ArrayList,LinkedList均支持),ListIterator增长了遍历时双向游标能力(previous,next),增长了add方法。add方法和remove方法同样也作了expectedModCount和modCount一致性校验.

咱们来看下面四个对list数据删除的代码的区别

1)
for(int i=0;i<list.size();i++){
     list.remove(i);
}

2)
for(int i=list.size()-1;i>=0;i--){
    list.remove(i);
}

3)
int size = list.size();
for(int i=size-1;i>-1;i--){
     list.remove(i);
}

4)
for(Object i : list){
   //若是list中存在多个Object互相equals时,此方法仍然有效.注意list.remove(Object)内部使用了遍历操做,并使用equals来比较对象并删除.
   list.remove(i);
}

5) 
Iterator it = list.iterator()
while(it.hasNext()){
        it.next();
        it.remove();
}

1),2),3)是最普通的遍历方式,可是在遍历并有删除操做时,彷佛它们执行的结果还有些差距,根据坐标删除,

那么1)实事上只会有一半被删掉,1)中每删除一次,计算一次list.size(),可是当前i++,且前端删除会形成数组结构copy。

2)后端删除,不会形成copy,每次都是删除最后一个位置,直至结束

3)由于size没有从新计算,在删除一半数据后,抛出IndexOutOfBoundsException

4)/5)正常

  

3、illegal character: \65279                                                               

   问题产生的操做过程,同事使用SVN提交java文件,发现有冲突使用UltraEdit进行了修改,从新编译时却报了异常

java:[1,0] illegal character: \65279 

   解决方法

   将文件从新保存成UTF-8 无BOM便可。

               

具体缘由参看高人的解释

http://blog.csdn.net/shixing_11/article/details/6976900

 

4、Java线程池任务执行完毕后线程回收的问题 

咱们知道ThreadPoolExecutor解决了两个重要的问题:

一、因为减小了每一个任务调用的开销,它们一般能够在执行大量异步任务时提供加强的性能

二、还能够提供绑定和管理资源(包括执行任务集时使用的线程)的方法。 

当使用java中的ThreadPoolExecutor,给咱们的工做带来方便的同时,若是不当使用一样也带来巨大潜在危险。

最近在review代码时,发现线程池中的全部任务执行完毕后,线程并无被销毁。咱们知道初始化ThreadPoolExecutor会有构造参数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

咱们看下TreadPoolExecutor是怎样工做的

注意

      一、核心线程即core线程,只有当前线程数小于等于corePoolSize时,这时的线程才叫核心线程。

      二、在新任务被提交时,若是运行的core线程少于corePoolSize,才建立新core线程。并非一开始就建立corePoolSize个core线程。

      三、若是运行的线程多于corePoolSize 而少于 maximumPoolSize,则仅当队列满时才建立新线程。

      
按需构造
      核心线程最初只是在新任务到达时才被ThreadPoolExecutor建立和启动的,可是也能够手动调用方法 prestartCoreThread() 或 prestartAllCoreThreads()来的提早启动核心线程。
    若是构造带有非空队列的池,这时则可能但愿预先启动线程。


保持活动时间

      若是线程池中当前线程数大于corePoolSize ,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止。
      若是后来线程池中线程变得更为活动,则能够建立新的线程。也可使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数,若是把值设为Long.MAX_VALUE TimeUnit.NANOSECONDS 的话,空闲线程不会被回收直到ThreadPoolExecutor为Terminate。
      默认状况下,此种活动策略只在有多于corePoolSize Threads的线程时才会应用。可是只要 keepAliveTime 值非 0,也能够经过allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。
注意1:setKeepAliveTime(long, java.util.concurrent.TimeUnit)用于设置空闲线程最长的活动时间,即若是空闲时间超过设定值,就中止该线程,对该线程进行回收。
    该策略默认只对非内核线程有用(即当前线程数大于corePoolSize),能够调用allowCoreThreadTimeOut(boolean)方法将此超时策略扩大到核心线程
注意2:若是把值设为Long.MAX_VALUE TimeUnit.NANOSECONDS的话,空闲线程不会被回收直到ThreadPoolExecutor为Terminate。

 

线程终止

      若是ThreadPoolExecutor在程序中没有任何引用且没有任何活动线程,线程池也不会自动 shutdown。
      若是但愿确保回收线程(即便用户忘记调用 shutdown()),则必须安排未使用的线程最终终止,设置适当保持活动时间,设置 allowCoreThreadTimeOut(boolean)。

    

关键函数

  • public void shutdown()

    按过去执行已提交任务的顺序发起一个有序的关闭,可是不接受新任务。若是已经关闭,则调用没有其余做用。
    抛出:
        SecurityException - 若是安全管理器存在而且关闭此 ExecutorService 可能操做某些不容许调用者修改的线程(由于它没有 RuntimePermission("modifyThread")),或者安全管理器的 checkAccess 方法拒绝访问。

  • public List<Runnable> shutdownNow()

    尝试中止全部的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表。在今后方法返回的任务队列中排空(移除)这些任务。
    并不保证可以中止正在处理的活动执行任务,可是会尽力尝试。 此实现经过 Thread.interrupt() 取消任务,因此没法响应中断的任何任务可能永远没法终止。
    返回:
        从未开始执行的任务的列表。 
    抛出:
        SecurityException - 若是安全管理器存在而且关闭此 ExecutorService 
        可能操做某些不容许调用者修改的线程(由于它没有 RuntimePermission("modifyThread")),
        或者安全管理器的 checkAccess 方法拒绝访问。

  • public int prestartAllCoreThreads()

    启动全部核心线程,使其处于等待工做的空闲状态。仅当执行新任务时,此操做才重写默认的启动核心线程策略。
    返回:
        已启动的线程数

  • public boolean allowsCoreThreadTimeOut()

    若是此池容许核心线程超时和终止,若是在 keepAlive 时间内没有任务到达,新任务到达时正在替换(若是须要),则返回 true。当返回 true 时,适用于非核心线程的相同的保持活动策略也一样适用于核心线程。当返回 false(默认值)时,因为没有传入任务,核心线程不会终止。
    返回:
        若是容许核心线程超时,则返回 true;不然返回 false

  • public void allowCoreThreadTimeOut(boolean value)

    若是在保持活动时间内没有任务到达,新任务到达时正在替换(若是须要),则设置控制核心线程是超时仍是终止的策略。当为 false(默认值)时,因为没有传入任务,核心线程将永远不会停止。当为 true 时,适用于非核心线程的相同的保持活动策略也一样适用于核心线程。为了不连续线程替换,保持活动时间在设置为 true 时必须大于 0。一般应该在主动使用该池前调用此方法。
    参数:
        value - 若是应该超时,则为 true;不然为 false 
    抛出:
        IllegalArgumentException - 若是 value 为 true 而且当前保持活动时间不大于 0。

咱们了解了ThreadPoolExecutor后,确保线程的回收就能够经过如下方式

// 在allowCoreThreadTimeOut设置为true时,ThreadPoolExecutor的keepAliveTime参数必须大于0。
executor.allowCoreThreadTimeOut(true);
// 在任务执行完后,调用shutdown方法,将线程池中的空闲线程回收
executor.shutdown();

5、Eclipse下maven项目自动打war包丢失jar包问题解决方法 

 因为本地没有maven仓库和强大的“墙”,使用maven命令clean package却打包失败。没有办法使用了Eclipse最原始的Export命令,打好包后发现依赖的jar都没有打进去,真是一波三折呀。现说下完整的打包过程和解决方法:

一、选择相应的profile,没有配置略过

二、.project文件

检查<buildSpec>节点中是否包含以下节点

<buildCommand>                    
     <name>
         org.eclipse.m2e.core.maven2Builder
     </name>
     <arguments></arguments>
</buildCommand>

检查<natures>节点中是否包含以下节点

<nature>org.eclipse.m2e.core.maven2Nature</nature> 

三、.classpath文件

检查是否包含以下节点

<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
        <attributes>
            <attribute name="maven.pomderived" value="true"/>
            <attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
        </attributes>
</classpathentry>

正由于没有这个属性<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>,才致使打的包中没有jar。

上述三步骤检查完毕后,从新clean工程,再打包就能够正常了。

6、Linux系统下忽略代表大小写

坑爹的DBA将线上的一个数据库重作后,致使了某些服务抛出了大量的异常

select name,type,updateTime from VIDEO_INFO where 1=1  ExecuteData Table 'MS.VIDEO_INFO' doesn't exist

信息提示找不到代表。

咱们都知道Linux是区分代表大小写,windows不区分代表大小写的。DBA重作了新的数据库后却忽略设置代表大小写。

解决方法很简单:

编辑mysql配置文件:vi /etc/my.cnf

添加:lower_case_table_names=1 一句到文件中。

重启MySQL,服务一切正常。

小结:重要的事情说一遍,检查一遍。

7、swap file "*.swp" already exists!

Linux下咱们使用vi或vim对文件编辑, 当打开文件或保存文件可能会出现:

swap file "*.swp" already exists!

[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:

缘由:

使用vim编辑文件实际是先copy一份临时文件并映射到内存给你编辑, 编辑的是临时文件, 当执行:w后才保存临时文件到原文件,执行:q后才删除临时文件。

每次启动检索式否有临时文件, 有则询问如何处理,就会出现如上情景。

解决方法:

一、显示隐藏文件 ll -a或ls -a

二、删除隐藏文件 rm -f *.swp

 

8、20880端口被占用

问题描述:
在部署线上服务时,出现20880端口被占用而没法启动。

问题分析:
20880端口被该服务器上的客户端随机选取源端口给占用掉了。

解决方案:

一、使用net.ipv4.ip_local_port_range参数,规划出一段端口段预留做为服务的端口,这种方法是能够解决当前问题,可是会有个问题,端口使用量减小了,当服务器须要消耗大量的端口号的话,好比反代服务器,就存在瓶颈了。
二、将服务监听的端口以逗号分隔所有添加到ip_local_reserved_ports中,TCP/IP协议栈从ip_local_port_range中随机选取源端口时,会排除ip_local_reserved_ports中定义的端口,所以就不会出现端口被占用了服务没法启动。、

推荐使用第二种方法

$ cat /proc/sys/net/ipv4/ip_local_port_range
32000 61000
$ cat /proc/sys/net/ipv4/ip_local_reserved_ports
8080,9148 

 

 

 

 

因为本人经验有限,文章中不免会有错误,请浏览文章的您指正或有不一样的观点共同探讨!

相关文章
相关标签/搜索