最近在和小伙伴们作充电与通讯程序的架构迁移。迁移前的架构是,通讯程序负责接收来自充电集控设备的数据实时数据,经过Thrift调用后端的充电服务,充电服务收到响应后放到进程的Queue中,而后在管理线程的调度下,启动多线程进程数据处理。后端
随着业务规模的不断扩大和对系统可用性的逐步提升。如今这个架构存在不少的问题,好比:缓存
1.充电服务重启,可能会丢数据。多线程
2.充电服务重启会波及影响通讯服务。架构
3.充电服务与通讯服务面对的需求和变化是不同,强依赖的架构带来不少的问题。并发
为了解决上述的这些问题,项目组决定借助Kafka对程序进行改造 。整体思路是,通讯服务收到数据后,把数据存储到kafka,而后经过一个异步任务处理框架实时消费Kafka数据,并调用业务插件处理。框架
经过上面思路咱们能够看到,系统总体架构仅是引入了一个MQ中间件,业务逻辑并无发生本质的变化。可是在实际的压测中,却发现新架构下的程序性能比原来要慢不少。顺便说一下,压测场景是模拟10万充电终端离网上下线,短期内会生成大约32万的消息量,遥信:10万,遥测:10万,电量10万,其余:2万。异步
经过ANTS分析相关进程,发现MonitorDataUploader.AddToLocalCache方法占用了78%左右的CPU。此方法不是业务方法,是为了监控程序的运行状况而加入的埋点监控。经过进一步分析看,在30多万消息量下,会产生约1000万甚至更高的监控消息。在如此高的并发下,这部分程序存在很严重的性能问题,致使系统的资源占用很高,系统运行变慢。函数
OK。既然问题已经清楚,那就开始优化吧。虽然能够把监控埋点屏蔽,临时解决程序的性能问题。可是,这对一个互联网应用来讲是要不得的。没有监控,系统的运行健康情况就一无所知,这对一个SLA要求99.95%的系统来讲,是不现实的。因此,必须全力优化监控程序在上报海量监控日志上的性能问题。性能
为了便于验证问题,写了一个模拟程序.经过模拟程序,很容易的再现了CPU占用很高的状况。测试
代码实现中,监控消息的存储是经过BlockingCollection存储的,而且设置了Collection大小为1000万。
var cache = new BlockingCollection<MonitorData>(boundedCapacity);
经过阅读BlockingCollection 的说明,能够看到空构造函数能够不设置Collection的上限。看到这个解释,怀疑是限制了上线的Collection存在性能问题。与是把代码中对BlockingCollection 的构造改为空构造,再次测试。测试结果大出意料,性能表现有了很是好的提高。
为了进一步验证问题,把对BlockingCollection 的构造改了限制大小,并设置上线为1个亿。测试时消息总量为5000万,验证一下是不是BlockingCollection 达到上限后,引发的严重性能问题。经过测试数据看,CPU消耗与不限制时基本一致。经过此能够肯定,BlockingCollection 在设置了容量上限后,若是消息超过容量,性能将会很是差。
经过上面的调优,在发送5000万监控消息的状况下,程序的CPU在60% 左右持续30s左右。虽然性能有所改善,可是还不是很尽如人意。 有没有更好的解决方案呢?经过不算的思考和尝试,终于找到了一个更好的解决方案:基于双缓存+线程级多桶式Collection。此种模式下性能表现以下,CPU平均在30%左右,持续时间在15s左右。性能又有近一倍的提高。具体实现方案下次再分享。