防痴呆设计

    最痴呆的问题,就是有多个版本的相同jar包,会出现新版本的 A 类,调用了旧版本的 B 类,而且和JVM加载顺序有关,问题带有偶然性,误导性,遇到这种莫名其妙的问题,最头疼,所以,第一条,先把它防住,在每个 jar 包中挑一个一定会加载的类,加上重复类检查,给个示例:

    检查重复工具类:

    1. private Duplicate() {}
    2. public static void checkDuplicate(Class cls) {
    3. checkDuplicate(cls.getName().replace('.', '/') + ".class");
    4. }
    5. public static void checkDuplicate(String path) {
    6. try {
    7. // 在ClassPath搜文件
    8. Enumeration urls = Thread.currentThread().getContextClassLoader().getResources(path);
    9. Set files = new HashSet();
    10. while (urls.hasMoreElements()) {
    11. URL url = urls.nextElement();
    12. if (url != null) {
    13. String file = url.getFile();
    14. if (file != null && file.length() > 0) {
    15. files.add(file);
    16. }
    17. }
    18. }
    19. // 如果有多个,就表示重复
    20. if (files.size() > 1) {
    21. logger.error("Duplicate class " + path + " in " + files.size() + " jar " + files);
    22. }
    23. } catch (Throwable e) { // 防御性容错
    24. logger.error(e.getMessage(), e);
    25. }
    26. }

    检查重复的配置文件

    必填配置估计大家都会检查,因为没有的话,根本没法运行。但对一些可选参数,也应该做一些检查,比如:服务框架允许通过注册中心关联服务消费者和服务提供者,也允许直接配置服务提供者地址点对点直连,这时候,注册中心地址是可选的,但如果没有配点对点直连配置,注册中心地址就一定要配,这时候也要做相应检查。

    异常信息给出解决方案

    在给应用排错时,最怕的就是那种只有简单的一句错误描述,啥信息都没有的异常信息。比如上次碰到一个 Failed to get session 异常,就这几个单词,啥都没有,哪个 session 出错? 什么原因 Failed? 看了都快疯掉,因是线上环境不好调试,而且有些场景不是每次都能重现。异常最基本要带有上下文信息,包括操作者,操作目标,原因等,最好的异常信息,应给出解决方案,比如上面可以给出:"从 10.20.16.3 到 10.20.130.20:20880 之间的网络不通,请在 10.20.16.3 使用 telnet 10.20.130.20 20880 测试一下网络,如果是跨机房调用,可能是防火墙阻挡,请联系 SA 开通访问权限" 等等,上面甚至可以根据 IP 段判断是不是跨机房。另外一个例子,是 spring-web 的 context 加载,如果在 getBean 时 spring 没有被启动,spring 会报一个错,错误信息写着:请在 web.xml 中加入: <listener>…<init-param>…,多好的同学,看到错误的人复制一下就完事了,我们该学学。可以把常见的错误故意犯一遍,看看错误信息能否自我搞定问题,或者把平时支持应用时遇到的问题及解决办法都写到异常信息里。

    1. public void error(String msg, Throwable e) {
    2. delegate.error(msg + " on server " + InetAddress.getLocalHost() + " using version " + Version.getVersion(), e);
    3. }

    获取版本号工具类:

    kill 之前先 dump

    每次线上环境一出问题,大家就慌了,通常最直接的办法回滚重启,以减少故障时间,这样现场就被破坏了,要想事后查问题就麻烦了,有些问题必须在线上的大压力下才会发生,线下测试环境很难重现,不太可能让开发或 Appops 在重启前,先手工将出错现场所有数据备份一下,所以最好在 kill 脚本之前调用 dump,进行自动备份,这样就不会有人为疏忽。dump脚本示例:

    1. OUTPUT_HOME=~/output
    2. DEPLOY_HOME=`dirname $0`
    3. HOST_NAME=`hostname`
    4. DUMP_PIDS=`ps --no-heading -C java -f --width 1000 | grep "$DEPLOY_HOME" |awk '{print $2}'`
    5. if [ -z "$DUMP_PIDS" ]; then
    6. echo "The server $HOST_NAME is not started!"
    7. exit 1;
    8. fi
    9. DUMP_ROOT=$OUTPUT_HOME/dump
    10. if [ ! -d $DUMP_ROOT ]; then
    11. mkdir $DUMP_ROOT
    12. fi
    13. DUMP_DATE=`date +%Y%m%d%H%M%S`
    14. DUMP_DIR=$DUMP_ROOT/dump-$DUMP_DATE
    15. if [ ! -d $DUMP_DIR ]; then
    16. mkdir $DUMP_DIR
    17. fi
    18. echo -e "Dumping the server $HOST_NAME ...\c"
    19. for PID in $DUMP_PIDS ; do
    20. $JAVA_HOME/bin/jstack $PID > $DUMP_DIR/jstack-$PID.dump 2>&1
    21. echo -e ".\c"
    22. $JAVA_HOME/bin/jinfo $PID > $DUMP_DIR/jinfo-$PID.dump 2>&1
    23. echo -e ".\c"
    24. $JAVA_HOME/bin/jstat -gcutil $PID > $DUMP_DIR/jstat-gcutil-$PID.dump 2>&1
    25. echo -e ".\c"
    26. $JAVA_HOME/bin/jstat -gccapacity $PID > $DUMP_DIR/jstat-gccapacity-$PID.dump 2>&1
    27. echo -e ".\c"
    28. $JAVA_HOME/bin/jmap $PID > $DUMP_DIR/jmap-$PID.dump 2>&1
    29. echo -e ".\c"
    30. echo -e ".\c"
    31. echo -e ".\c"
    32. if [ -r /usr/sbin/lsof ]; then
    33. /usr/sbin/lsof -p $PID > $DUMP_DIR/lsof-$PID.dump
    34. echo -e ".\c"
    35. fi
    36. done
    37. if [ -r /usr/bin/sar ]; then
    38. /usr/bin/sar > $DUMP_DIR/sar.dump
    39. echo -e ".\c"
    40. fi
    41. if [ -r /usr/bin/uptime ]; then
    42. /usr/bin/uptime > $DUMP_DIR/uptime.dump
    43. echo -e ".\c"
    44. fi
    45. if [ -r /usr/bin/free ]; then
    46. /usr/bin/free -t > $DUMP_DIR/free.dump
    47. echo -e ".\c"
    48. fi
    49. if [ -r /usr/bin/vmstat ]; then
    50. /usr/bin/vmstat > $DUMP_DIR/vmstat.dump
    51. echo -e ".\c"
    52. fi
    53. if [ -r /usr/bin/mpstat ]; then
    54. /usr/bin/mpstat > $DUMP_DIR/mpstat.dump
    55. echo -e ".\c"
    56. fi
    57. if [ -r /usr/bin/iostat ]; then
    58. /usr/bin/iostat > $DUMP_DIR/iostat.dump
    59. echo -e ".\c"
    60. fi
    61. if [ -r /bin/netstat ]; then
    62. /bin/netstat > $DUMP_DIR/netstat.dump
    63. echo -e ".\c"
    64. fi
    65. echo "OK!"