我所在的公司付钱拉是一家金融类的创业公司,目前相关系统日处理交易额200多亿,每天平均交易量400万笔,业务类型线上线下各不相同,作为一个资金交易系统,为了保证系统高可用和资金交易安全,我们填过不少坑。本文通过真实案例和切身实战经验来描述付钱拉走过的这些坑,希望正在经历像付钱拉(创业团队或者中小初创团队)曾经一样面临这些问题的团队能够提早避免。 任何系统建设从0到1相对比较简单,但是要做到从有到好或者更好那就比较不容易,比如经常发现开发同事开发一个新功能,自己单元测试通过就提测了,到了测试那里一堆问题,主要原因是好多时候开发人员只保证了代码主分支业务没有问题,但是异常分支业务都没有考虑就提测了,代码的异常分支处理才是代码逻辑最需要考虑的点。 本文会从三个角度展开描述,一个是针对已经存在的系统如何主动发现问题,另外一个基于系统建设过程中不同项目阶段的问题描述,最后会给一些付钱拉聚合支付系统建设的最佳实践和经验。 做为技术负责人,你经常会遇到这样的问题:
通过本文的阅读,希望能帮你找到解决这些问题的答案。 墨菲定律中提到会出错的总会出错,代码的bug也一样,会发生的一定会发生,只是看在哪种场景下会触发。未运行的代码是静止的,只有运行中的代码才能能真实地体现业务系统的状况,如何不阅读代码从操作系统层面去发现正在运行的代码问题是非常必要的。 无论多复杂的系统运行在Linux之上无非就是一个进程,进程下面又由多个线程去执行每一行代码的业务逻辑,所以从操作系统层面只需要关注进程和线程即可。对于Java编写的应用主要性能关注点是操作系统CPU和内存两个指标,在运行中的代码内存比CPU更加容易出问题。 为什么Java编写的代码内存比较容易出问题呢?如今硬件效率的提升导致(CPU的速度和支持多任务)处理器运算速度和和存储设备不是同一个量级,计算机都不得不加入一层高速缓存来作为内存与处理器之间的缓冲,缓存的引入提升了效率,但是带来一致性问题,程序的复杂度也增加了,比如Java主内存和工作内存同步问题。如何从操作系统级别发现一些问题? 应用除了处理本身代码逻辑以外,往往还需要和外部服务或者接口通信。通过 netstat -anop | grep pid 就可以发现系统正在和哪些外部接口通信,比如下图中3306的端口显示系统有连接MySQL数据库,并且有11个连接。 系统有时候报错“has already more than 'max_user_connections' active connections”,这时候通过此命令就可能发现有比较多的连接数,当连接数非常多的时候最大的可能是代码在不停的操作数据库或者有慢SQL导致的连接不释放,也就是说当你发现应用连接数大于日常正常数量的时候,系统的代码可能就有问题。 同理,下面第二张图通过netstat -anop | grep 6379得知应用有连接Redis。 任何资源都是有限的,应用设计的时候需要考虑资源限制才能避免应用在某些时候因为资源过度使用而奔溃,线程数的控制就非常重要。 线程的无限制创建,最终导致其不可控,特别是隐藏在代码中的创建线程方法。当系统的SY值过高时,表示Linux需要花费更多的时间进行线程切换,Java造成这种现象的主要原因是创建的线程比较多,且这些线程都处于不断的阻塞(锁等待,IO等待)和执行状态的变化过程中,这就产生了大量的上下文切换,这时候体现在操作系统层面一种现象是下图中load average过高,这个值多少合适一般和CPU个数有关系,假如有两个CPU,这个值若大于4,系统建议报警。 除此之外,Java应用在创建线程时会操作JVM堆外的物理内存,太多的线程也会使用过多的物理内存,到了一定情况下系统应用就会报错“Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread”,这个错误是告诉我们系统创建了太多的线程导致的。这种错误的常见场景是写一个定时器去处理任务,定时器的频率1分钟一次,但是任务处理完需要10分钟,定时器浪打浪就会不停的产生新的线程,最终导致触发native OOM。 线程数过多会导致应用异常,那么如何快速统计一个进程的线程数量,有以下命令都可以使用:
系统资源瓶颈一般关注CPU、内存、硬盘、硬盘IO、网络IO,Linux操作系统总体性能可以通过top命令如下图,一般系统应用只需要关注load average、CPU、Mem、Swap、Res这些指标。
以上都是通过top命令总体检查,还有以下方法可以也可以查找系统瓶颈:
对于Java应用从操作系统层面观察,就只有进程和线程两个指标,任何东西在操作系统层面都是以文件的形式存储的,进程也不例外。Linux上部署一个Tomcat程序产生一个进程,这个进程所有的东西都在这个目录下ll /proc/pid/。 通过ll -tla /proc/pid/fd可以查看当前进程运行情况,比如想查看当前进程的socket通信,可以通过 /proc/pid/fd | grep socket,进而通过lsof | grep 3198242可以判断出这个socket通信连接的是Redis服务。 Java应用出现异常最希望的就是通过线程或者进程定位问题,然后定位到具体的代码位置,最终解决问题。对于Java程序,如果内存发生OOM,做法是通过jmap -dump:format=b,file=/tmp/dump.dat pid导出内存栈,然后通过可视化工具MAT定位出问题的代码,但是需要知道的是dump会导致full GC一次;如果是想通过线程栈定位正在执行的线程在做什么事情,可以通过jstack -l pid导出线程栈进行分析。 假如通过上面提到的top命令或pidstat等命令发现某个进程消耗的内存、CPU或者IO过高,这时候需要定位到具体代码在做什么,定位方法如下。 步骤一:首先通过top或者pidstat命令定位的具体的进程号,比如9739; 步骤二:top -p 9739后,然后按住shift H健开启Show threads on,通过观察定位到具体的线程号,比如9751: 步骤三:把9751转换为十六进制2617,因为线程栈中线程的序号是以十六进制存储的: 步骤四:jstack -l 9739 | grep 2617 来分析这个线程正在做的事情,下图显示是GC线程。 同理换一个其他线程9747,也可以发现代码正在执行的事情: 所以如果CPU或者内存消耗过高,通过以上步骤就可以准确地定位具体的业务代码。 GC会导致系统应用暂时的停顿,如果频繁的GC就会产生延迟响应,对于互联网面向用户的实时交易延迟是不可接受的。开启GC日志可以收集GC的统计信息,可以方便定位观察应用长时间停顿原因,命令如下jstat -gccause pid 1000。 JVM调优的方法都是为了尽量降低GC所导致的应用停顿时间,但是目前内存管理方面JVM已经做得非常不错了,因此如果不是有确切的理由证明GC造成性能低下,就没有必要做过多的JVM方面的调优,因为有可能反而调出问题。 一个应用做的好不好,从应用打印日志就可以看出来,好的日志打印会打的和表格一样,可以通过AWK命令快速定位问题;除了打印规范,就是检测日志中的Exception和Error,业务日志是运行期产生的,每天产生不一样的异常,一个成熟的应用发生Exception和Erro的情况非常少,但是往往好多初建的项目源于赶进度只要业务功能不影响就忽略了日志异常,从而隐藏了很多问题。对于日志部分,建议是通过工具或者脚本实时监控日志中的带有Exception或者Error的关键字,有问题就报出来,类似下图: 以上内容简单总结了如何从操作系统层面发现应用可能存在的问题,这种些方法完全不需要通过阅读业务代码,也不限定于某个具体的业务系统,对于任何应用都能简单地做一个体检。本文的第二个视角来自项目周期,项目从一开始立项开发再到持续迭代发布新功能,最后到成熟期运营,每个阶段特点不一样,需要解决的问题不一样,根据付钱拉的项目经验,不同的阶段会存在哪些问题呢? 经历过一个项目完整开发周期的同学肯定清楚每个不同的阶段都会遇到不同问题,解决问题就感觉像游戏打怪升级,并且比较有意思的现象是高级的bug一定出现在低级的bug之后,只有解决了低级的bug,才会出现更加高级的bug。 根据经验本文把项目周期定义为这五个阶段,初创阶段(协调)- |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|