一、瞬时峰值/过载的应对能力构建

应让系统在过载时仍然保持设计时的处理能力,虽然有部分新增请求会失败,但正常的处理能力还能保持正常。做到这一点的核心思想是,在入口处进行流量控制,以及资源进行预先池化。在入口处进行流量控制,减少无谓的处理能力浪费,让明知会处理失败的多余请求在入口处就被拦截,避免进入后续的处理流程消耗资源。资源预先池化的目的是为了削峰填谷:资源申请不到会被临时挂起,等待有可用的资源被唤醒再重新获得处理,其他正在进行的处理完全不受影响。
二、将linux进程绑定在特定的CPU上运行
以root用户执行如下命令
arduino
#bind <进程id> <cpu 掩码>
其中cpu掩码为十进制的形式。如果机器有4个CPU,那么用4位二进制数字中的每一位都表示一个CPU,其中0表示不使用该CPU,1表示使用该CPU。如0101(十进制为5)表示使用第一个(从左边数第一位)CPU和第三个CPU(从左边数第三位),0001(十进制为1)表示只使用第一个CPU。如果进程ID为6000,那么就表示使用第一个CPU和第三个CPU。
shell
#bind 6000 5
在某些特殊场合这个非常有用,如内核态的死循环。如果所有的CPU都在内核态死循环中,那么整个系统将挂死,不会对用户的任何命令进行响应,包括telnet等。如果通过绑定CPU的方式,留出一个CPU,那么可以保证这个系统在异常的时候,仍然有一个可用CPU供使用,此时就可以收集有用的相关信息。
三、CPU使用率过高问题的定位思路
3.1 在网络I/O中,处于runnable状态的线程几乎是不消耗CPU的
代码如下

3.2 Object.wait()处于等待状态。
正在调用sleep或者wait方法时,会处于这个状态,此时也不消耗CPU。

四、系统运行越来越慢的定位思路
4.1 GC频繁
如果存在内存泄漏,内存越来越少,Full GC就会越来越频繁
4.1 系统存在资源泄漏
资源泄漏的增多加剧了资源争用,使系统看起来越来越慢,如系统存在数据库连接泄漏的bug。当数据库连接池中可用的连接下降到一定程度时,必然导致资源争用,大多获取连接的线程会被暂时阻塞在获取连接的代码中,直到其他线程释放连接当前被挂起的线程,才可能继续运行。
五、系统挂死问题的定位思路
从线程堆栈中,通过多次打印堆栈,很容易找出挂死的线程正在执行的具体代码。有极少情况是由于虚拟机僵死导致的系统无响应。例如,有一个项目在 SUN JDK 下开发并测试正常的程序,偶然在某个商业局点上使用了 JRockit 虚拟机,运行三天到四天就出现了 Java 进程僵死的情况。
它的典型特征是,该 Java 进程无任何响应,即使通过 kill-3 这种发信号的方式,进程也不做任何响应。另外通过top进程查看工具,进程状态是工状态(跟踪/停止)
。出现这种情况的原因有很多,通过各种手段先排除自身代码的可能因素(如内存泄漏等)后问题仍然得不到解决,说明问题可能出在 JDK上,后来将这个局点的 JDK换成了 SUN的 JDK,就一切正常了。因此建议在商业局点部署时,最好采用同样的 JDK, 即使要更换JDK,也要做好充分的稳定性测试。
六、关于虚拟机core dump
l JavaCore是关于CPU的
JavaCore文件主要保存的是Java应用各线程在某一时刻的运行的位置,即JVM执行到哪一个类、哪一个方法、哪一个行上。它是一个文本文件,打开后可以看到每一个线程的执行栈,以stack trace的显示。通过对JavaCore文件的分析可以得到应用是否"卡"在某一点上,即在某一点运行的时间太长,例如数据库查询,长期得不到响应,最终导致系统崩溃等情况。
l HeapDump文件是关于内存的。
HeapDump文件是一个二进制文件,它保存了某一时刻JVM堆中对象使用情况,这种文件需要相应的工具进行分析,如IBM Heap Analyzer这类工具。这类文件最重要的作用就是分析系统中是否存在内存溢出的情况。
6.1 JNI代码中的野指针等问题。其定位方法如下:当错误发生时,使用gdb等类似的调试器来寻找错误地址(这取决于用户的操作系统)
- 在产生 core 文件的目录中启动 gdb 调试器:gdb java core;
- 如果 gdb 调试器可以读取 core 文件,它就可以分析出失效发生的地址(如Segmentation fault in function name at 0x10001234)。
- 要获得一个堆栈跟踪信息,可以使用 bt 命令:(gdb) bt。
七、连接池耗尽
bash
# 建议监控关键字
pool exhausted
八、瞬间内存泄漏的定位思路
通过设置 -XX:+HeapDumpOnOutOfMemoryError,当系统 OutOfMemory 时,就会自动对内存进行转储,然后借助输出的转储文件进行内存分析(可以手工分析或者借助 jhat 等工具)。虚拟机提供的这些事后分析工具是定位类似问题的唯一有效手段。
九、Linux下提高UDP吞吐量
Linux 下通过修改 sysctl.conf,可以增大 UDP 的缓冲区。对于处理能力特别强的机器来说,系统的瓶颈可能位于 UDP socket 上,通过修改该参数,即可大幅提高 UDP 数据的吞吐量。
ini
# 最大的接受 UDP 缓冲区大小
net.inet.udp.sendspace=65535
#最大的发送 UDP 数据缓冲区大小
net.inet.udp.maxdgram=65535
十、网络问题------TIME_WAIT状态连接不能及时释放
当客户端使用 Buffer(1024Byte)向服务端发送大批量的读/写请求时,会有大量的请求失败。
通过 gdb 跟踪,发现有时客户端连接服务器的端口(port:20005)时出现失败,错误码为 99,再查看文档,发现错误码 99 表示 Cannot assign requested address。然后分别在客户端和命令netstat -n |grep 20005 查看 socket连接情况,发现有大量状态为 TIME_WVAIT 的连接存在。这时在各服务节点可通过以下命令统计该状态下的连接数。
css
netstat -n | grep 20005 | awk '/^tcp/ {++S[$NF]} END {for (a in s) print a, S[a]}'
统计发现有的节点连接数维持在 3000个以上,更有瞬时达到 8000~10 000 个的情况发生。通过分析TCP 协议的有限状态机发现,TCP连接在主动关闭的情况下,必须经过 TIME_WAIT 状态且最终达到 CLOSED 状态,并被回收。而一般TIME WAIT 状态的时间会在 2MSL(MSL:最大生存时间,一般系统都在120s)。于是可以从两个方面尝试解决该问题。
- 加速TIME_WAIT 状态下连接的回收速度。
- 把TIME_WAIT 状态下的连接复用起来,提高资源的利用效率。
根据上述想法,着重搜索相关的解决方案,发现以上两种方案可以使用同一种方式解决,解决方法如下。

说明:net.ipv4.tcp_syncookies=1表示开启SYN Cookies。当出现 SYN 等待队列溢出时,启用 cookies进行处理,可防范少量的SYN攻击,默认为0,表示关闭;net.ipv4.tcp_tw_reuse =1 表示开启重用。允许将TIME WAIT sockets 重新用于新的 TCP连接,默认为0,表示关闭;net.ipv4.tcp_tw_recycle=1 表示开启 TCP 连接中 TIME_WAIT sockets 的快速回收。
钩子函数不应该执行任何耗时的操作,且线程应该是安全的,也不应该依赖于其他任何服务,因为整个系统都在关闭的过程中。
以上修改应用到服务器端和客户端,重启系统使修改生效,执行相同的测试用例,在服务端查看连接情况发现 TIME_WAIT 状态下的连接数量大大下降,可基本维持在 100个以内。即使偶然有上升到数百个的情况,系统也会很快回收(数秒之内)同时测试在多客户端大量连接下未再出现连接失败的情况。