一、背景
业务反馈Java的Transfer服务有调用超时情况。
二、问题排查与优化
阅读代码发现,数据来了之后,循环处理,消息放到本地队列,是同步的方式,这里并发肯定会影响性能,果断优化;
三、优化过程
四、经验总结
1、尽量减少IO操作
尤其是网络IO,尤其是在for循环中使用网络IO,如果必须要使用,考虑使用缓存替代。
- 案例一:本次调用hbs获取uuid,其实可以从DB获取,只有获取不到的情况才从接口获取;这个调用接口问题也是服务慢的最主要原因,每次请求如果list的size是1000,且不带uuid,那么就要访问1000次接口,怎么快?
- 案例二:循环中会往kafka投递数据,这个也是网络IO,可能比调用hbs快很多,但是还是网络IO,还是影响性能的,所以后面改成了本地IO,并使用基于乐观锁的ConcurrentLinkedQueue。
2、异步处理
业务如果不关心服务响应内容,可考虑将接口修改成异步,这样业务的请求立即返回,服务端需要考虑的就是如何快速处理服务。
如本次优化,异步后最起码可以解决客户端等待问题,避免由于服务端问题影响客户端。
3、NIO理念,分段处理,减少等待
先说下优化之后的逻辑(分四段处理),每一段一个线程池,每个线程池用不同的名称,方便观察:
- --》接收请求,多线程异步处理
- --》循环中往ConcurrentLinkedQueue添加数据的时候,采用多线程异步(虽然乐观锁,但是也会碰到自旋情况)
- --》新增个定时任务,一秒钟一次消费ConcurrentLinkedQueue的数据,并将数据放到list
- --》多线程处理,当list达到200时,push到kafka
然后对每一段的线程池的缓冲队列增加监控,观察哪一步有积压,说明那一步可能比较慢,可以适当增加线程的数量。
4、第三方缓存与本地缓存合理使用
本地缓存
优点:速度快,不需要经过建立远程连接,网络传输过程等消耗;
缺点:占用本地内存,当系统处理速度慢的时候,造成本地数据积压,从而消耗本地内存,导致频繁的GC
第三方缓存
优点:不占用本地缓存
缺点:速度慢,需要经过建立远程连接,网络传输过程等消耗;
本系统采集两种方式结合:在offer数据的时候使用本地ConcurrentLinkedQueue存储,消费ConcurrentLinkedQueue数据投递到平台服务端的时候使用kafka进行缓冲,使用flink进行消费kafka数据发送到平台服务端。
为什么这么做呢?
1、在offer数据的时候使用本地ConcurrentLinkedQueue存储,主要考虑这种数据消费起来比较快,不会造成本地积压;
2、投递到平台服务,由于是要建立http连接,有可能受到此服务稳定性的影响,所以为了保障本服务的稳定性,使用flink做这个事情。
5、计算密集型服务与IO密集型服务分离
合理申请相关配置咨询,提高并发能力。
本服务是两种操作都有,如循环遍历的时候是计算密集型;
将数据推送到远端是网络IO密集型,如果放到一个服务,尤其是一个线程逻辑的时候很容易造成等待,所以此处需要分离,然后对于计算密集型的,申请cpu性能好的,对于IO密集型的选择磁盘或者网卡好的
计算机的并发瓶颈怎么估算?
对于一个2GHZ的CPU,运输速度是20亿次/秒,意味着如果是纯CPU密集型操作,对于简单的请求,理论上能达到20亿次并发,当然这当然是不可能的,因为网络请求还有很多因素的影响,如线程切换消耗,网络IO开销,连接建立过程等,仅供评估使用。