首页 > 嗟来之食 > 海量并发下充电业务优化实践
2017
08-07

海量并发下充电业务优化实践

摘要
目前在进行充电业务开发时,面对的是充电终端上报的海量并发数据。访问缓存的TPM可达120w,访问数据库的TPM在3w左右,高峰时段面对的是近二十万终端上传的百万条并发的实时数据。在这样的场景下有些无伤大雅的小漏洞最终酿成了生产环境的大问题,正所谓千里之堤毁于蚁穴,面对这样的场景,必须深入理解系统所使用的技术并对于常见问题有必备运维经验和分析能力。本文针对这些问题及解决过程进行分析,总结过往,以飨未来。
海量并发时遇到的问题
本文中以问题为驱动,以故障分析为轴线。在分析问题的过程中总结海量并发场景中对于充电业务的优化实践经验。这些问题的产生原因大多并不复杂,但是正如谚语所说:会者不难,难者不会。如果没有合适的分析手段,往往对于故障问题束手无策。希望在阅读完本文后,可以对于以后可能发生的问题有些分析手段和分析方向有些许帮助。
首先来看下本文中所涉及的生产环境问题:

服务器CPU持续过高
访问存储的频率过高
高并发时的数据乱序
其他导致实时数据处理能力下降的问题

服务器CPU过高
服务器的CPU使用率是重要的性能指标。一般的情况下,CPU在10~20%足以应对数据处理的需要,但是在生产环境中多次发生CPU因为某种原因持续超过75%并且无法自行下降的情况。CPU异常高企常常会导致数据处理能力下降的问题。针对这种情况有必要进行深入分析,查找问题原因并采取有效措施防止类似事故的发生。
首先需要排除由于上传数据的波动引起的性能变化。如果物联网设备离网又上线,云端首先需要获取一些数据进行初始化的工作,批量涌入的数据会导致服务器的性能压力问题,但通常不需要人为干预即可恢复正常。
解决办法:针对这个问题的分析需要强大的监控图表的支持,使用监控图还原故障发生时的场景并针对海量数据的突变规模和业务系统的影响程序进行分析。当然如何进行数据接口的实时监控也是个很大的问题,如果这个做不到,故障现场就无法恢复,原因分析也就无从谈起。对于海量数据的监控是足以写一篇论文的,在此不再展开,有兴趣的读者可以看下influxDB(参考资料5)和grafana的相关资料。
其次需要检查的是相关的服务运行是否正常。如果云端的后台是由一系列的微服务构成,而服务之间是通过同步的RPC调用相互串联起来的,那么在涌入海量数据时由于某些服务的响应速度问题,造成实时数据处理模块的大量线程被占用,在.NET中如果有大量线程被唤醒,同时在进行业务处理的话,也是很有可能会造成CPU的升高问题。
解决办法:针对这个问题的分析办法是抓取程序的dump,最好能间隔1~2分钟连续抓取多个dump,并通过dump分析线程池中的线程堆栈,并通过堆栈查找导致线程积压的原因(参考资料2),是调用别的服务?还是查询数据库?等等。找到线程大量积压的原因,基本上就成功了大半了。
最后的办法是检查性能计数器。性能计数器常常被人忽略,但是其在分析CPU问题时常常能起到意想不到的巨大帮助。
案例一:某次实时数据处理程序CPU预警,排除掉前文所述的可能原因后,针对.NET CLR计数器进行分析,发现在CPU高企的阶段# of Exceps Thrown/Sec这个指标也相应的升高,而该指标代表的是某个时刻的程序抛出异常的统计。后来通过统计日志,也印证了前文所述,确实在那个时刻的异常非常多。
CPU和该指标的关系:

CPU(%)
指标值(%)

45
5

16
5

73
11

82
11

66
7

32
5

MSDN的解释如下(参考资料3):
显示每秒引发的异常的数目。它包括.NET异常和转换成.NET 异常的非托管异常。 例如,从非托管代码返回的HRESULT转换为托管代码中的异常。
此计数器包括已处理和未经处理的异常。此计数器不是一段时间内的平均值;它显示在最后两个样本(以取样间隔持续时间来划分)中观察到的值之间的差异。
案例二:某次实时数据处理程序CPU预警,排除掉前文的可能原因并排除异常相关指标后,在针对.NET CLR计数器的分析过程中发现,在CPU高企的阶段% Time in GC这个指标也比较高,该指标代表GC时间在整个CPU时间中的占比。在发生预警的时段内,该指标的值基本上位于30%~60%的区间内,而非预警时段该指标的值在10%左右。
MSDN的解释如下(参考资料3):
显示自上次垃圾回收周期后执行垃圾回收所用运行时间的百分比。此计数器通常指示垃圾回收器代表该应用程序为收集和压缩内存而执行的工作。只在每次垃圾回收结束时更新此计数器。此计数器不是一个平均值;它的值反映了最近观察所得值。
针对GC造成的性能问题,首先需要通过dump分析是否是由于程序本身导致了GC过于频繁,比如不合适的字符串操作,频繁分配大对象等等。
如果从dump中无法得知GC频繁的原因,可以尝试使用Server GC模式(参考资料4)。通过压力测试发现,在使用Server GC前后,程序的处理能力相差超过一倍,例如,假设原来的时候单节点TPM200会导致CPU高企,那么在使用Server GC后,TPM达到450时CPU也不到40%,相对于以前的表现有很大改善。因此使用Server GC可以有效缓解GC过于频繁导致的性能问题,显著提升程序的数据处理能力。
访问存储媒介的频率过高
经过统计,在充电高峰期,处理实时状态的程序访问缓存的TPM可以达到120w,访问数据库的TPM在3w左右。这个数据比其他所有模块的总和还要高。特别是访问缓存的频率非常高,导致对于缓存性能的依赖非常强,在缓存性能不稳定时非常容易影响到实时数据处理模块的性能表现。
造成该问题的原因是多方面的,总结起来有以下几个方面的原因:
1、程序架构原因。因为终端数据上传至云端后,是随机分配到不同的服务器上的程序进行处理,因此在处理数据之前必须进行上下文的恢复工作,导致访问存储的频率过高。
2、物联网通讯协议原因。受到通讯协议的限制,终端只能上传当前时刻状态的数据,而没有采用重要状态转化的事件通知机制,无法表达数据本身所代表的业务含义。导致云端的程序必须通过查询终端之前的相关数据获知数据的准确意图。
3、业务逻辑的需要。充电终端上传的数据是分类按照不同频率上传的,在处理这些实时数据的过程中,经常涉及到状态转化相关的逻辑处理。如果需要判断状态转化则必须知道是由什么状态转换到当前状态的,所以需要经常性的从缓存或数据库中查询相关数据。
针对以上各种原因,可以采取如下办法:
1、优化架构设计。通过适当的方法,保证终端数据能稳定传输到某个运算节点。可以有效利用服务器本身的资源缓存终端的相关数据,避免每次都需要恢复上下文。
2、优化物联网协议。减少依赖实时数据的状态转化判断逻辑,而采用事件通知机制确保重点状态的处理过程,对于实时数据则更多的用于状态监控而不是业务逻辑判断。
海量并发时的数据乱序
终端上传数据是随机分配到不同的服务器由不同的线程进行处理,因此不能保证数据的处理顺序跟数据的上传顺序保持一致。因此就导致了实时数据的乱序问题,即可能新的数据被老数据覆盖的问题。目前在生产环境中使用的实时数据处理程序是运行在Thrift上的,当其比较繁忙,特别是CPU占用较高时,经常出现数据延迟和数据乱序的问题,并因此导致了严重的程序逻辑错误。
对于该问题有两个备选方案:
方案一:通过严格保证数据处理的串行化来避免该问题的发生。但是这个办法有个最大的问题是导致了程序性能的下降,本来可以并行处理的业务被迫串行化实际上对于性能影响比较大。
方案二:优化通讯协议,在物联网传输的报文都应该增加时间戳,通过检查时间戳判断当前数据是否有效并采取相关逻辑。该方法的最大好处是不影响业务数据的处理性能。当然该方案也有坏处,那就是在确保程序并发时还需要保证逻辑的正确性,因此需要在并发编程方面投入更多的精力。
实时数据处理能力下降
尽管CPU繁忙会导致数据处理能力下降,但是本章节所述内容与此无关。有时候虽然CPU占用率并不高,内存也很充足,但是数据处理能力莫名其妙的下降,并导致了许多业务方面的问题。
该问题的原因比较隐蔽,通过对于业务模块的分析,发现该问题与对外推送业务有关。
正常的流程推送是:
终端数据变化->实时数据处理模块->互联互通模块->第三方
而问题的出现与第三方服务接口响应较慢有关,其传导过程如下:
第三方接口响应慢
->互联互通大量线程等待HTTP返回结果
->互联互通模块线程池无可用线程
->实时数据处理模块的线程同步等待互联互通模块的返回结果
->实时数据处理模块线程池也逐步用光
->数据处理能力下降
当实时数据处理模块判定状态状态变化时会通过互联互通模块进行对外推送,部分外部接口无法承接巨大的推送量而出现了访问超时等问题。由于互联互通模块设计对外推送时设置的超时时间是60s,导致大量的线程在等待外部接口的返回结果。而线程池的数量时有限制的,当互联互通模块的线程池用光后,无法再处理外部的请求,所以请求都积压在互联互通模块的数据队列中得不到处理,而由于实时数据处理模块当时是直接同步调用互联互通模块进行推送,导致其本身的大量线程也在等待互联互通接口的返回结果,进而造成由外而内的传导效应。当实时数据处理模块线程池可用线程用完,也就没法处理新来的实时数据并导致了严重的性能问题。
对于该问题的解决办法也相当简单,通过dump完全可以看到线程的堆栈,通过堆栈确定了问题原因后,推送方和接收方使用消息队列相互解耦,最终避免了此类事故的再次发生。
总结

# of Exceps Thrown / Sec指标较高(>5)时,很容易导致CPU出现性能问题;
在比较繁忙的业务中使用抛出异常的方式要慎重;
% Time in GC指标持续较高(>10)时,很容易导致CPU出现性能问题;
使用Server GC有助于缓解GC导致的性能问题;
在处理实时数据的场景中,数据必须包含时间戳;
在处理实时数据的场景中,频繁恢复数据的上下文会导致较高的压力;
在处理实时数据的场景中,不推荐使用无状态的处理机制;
数据推送的两端有最好能通过MQ进行解耦,减少相互影响;

参考资料

Lock and Thread Performance Counters
网站High CPU分析
Performance Counters in the .NET Framework
<gcServer> Element
互联网级监控系统必备-时序数据库之Influxdb技术
互联网级监控系统必备-时序数据库之Influxdb集群及踩过的坑

最后编辑:
作者:
这个作者貌似有点懒,什么都没有留下。

留下一个回复