zoukankan      html  css  js  c++  java
  • 持续更新线上问题及解决方案

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

    一、fastjson的序列化SerializerFeature使用注意                                       

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

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

    2、null属性不显示

    我们先来看一段代码

    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,怎么就凭空消失了哪?这里不讲述原因,只说明解决方案。

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

    因此上面的代码就可以改写成

    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 

        总结:

        1、服务接口的测试用例覆盖率还是太低

        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)正常

      

    三、illegal character: 65279                                                               

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

    java:[1,0] illegal character: 65279 

       解决方法

       将文件重新保存成UTF-8 无BOM即可。

                   

    具体原因参看高人的解释

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

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

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

    1、由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能

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

    当使用java中的ThreadPoolExecutor,给我们的工作带来方便的同时,如果不当使用同样也带来巨大潜在危险。

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

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

    我们看下TreadPoolExecutor是怎样工作的

    注意

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

          2、在新任务被提交时,如果运行的core线程少于corePoolSize,才创建新core线程。并不是一开始就创建corePoolSize个core线程。

          3、如果运行的线程多于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();

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

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

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

    2、.project文件

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

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

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

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

    3、.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工程,再打包就可以正常了。

    六、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,服务一切正常。

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

    七、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后才删除临时文件。

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

    解决方法:

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

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

    八、20880端口被占用

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

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

    解决方案:

    1、使用net.ipv4.ip_local_port_range参数,规划出一段端口段预留作为服务的端口,这种方法是可以解决当前问题,但是会有个问题,端口使用量减少了,当服务器需要消耗大量的端口号的话,比如反代服务器,就存在瓶颈了。
    2、将服务监听的端口以逗号分隔全部添加到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 

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

  • 相关阅读:
    ld: warning: PIE disabled. Absolute addressing (perhaps -mdynamic-no-pic) not allowed in code signed PIE, but used in _ff_h264_decode_mb_cabac from
    关于屏幕旋转的几种情况
    AppCan应用打包关于缺少XMPP的依赖库libresolv.dylib--升级引擎版本到3.1以上
    "_dns_parse_resource_record", referenced from:......
    export IPHONEOS_DEPLOYMENT_TARGET=6.0 .......
    CodeSign error: code signing is required for product type Application in SDK iOS
    Receiver type ‘X’ for instance message is a forward declaration
    uva299
    uva152
    uva10474
  • 原文地址:https://www.cnblogs.com/exceptioneye/p/4887290.html
Copyright © 2011-2022 走看看