下面是项目大概的一个模型:
从用户角度看,完成request到接收response的时间为jvm内部处理时间+http请求Redis 网络IO时间 + https请求远程服务器 网络IO时间 +用户本身request和服务器response返回的网络IO时间。
实际本地测试,https请求响应时间大约110ms~350ms 波动。使用
REST Client工具测试。
第一阶段:
使用多线程Future模型+普通HttpClient请求。
关键代码:
将Callable提交到线程池里,返回Future对象。Future一旦有值时,就通过调用Jfinal的renderText()输出
下面就是使用HttpClient进行请求
测试结果:
通过实际测试,并发从一开始36/sec逐渐降到10/sec。在控制台看到不少链接超时的异常信息。不少远程http请求Redis服务耗时竟然超过30s。
原因分析:Http连接Redis服务,虽然在正常情况下每次都能迅速响应,大约80ms-120ms(本地环境)。生产环境可以保证Redis和项目响应速度更快,但是在大量http请求下,由于目前代码效率不高,请求若是不能及时处理完,就会导致后面http请求延迟响应。
改善:引入google guava框架,利用其中的Cache功能,因为带有expireAfterWrite功能。把guava cache服务当成1级缓存,Redis服务当成2级缓存。并且增加保证cache内各对象失效时间早于Redis内存储的失效时间,并且cache能够及时从Redis更新.这样减少了Http请求Redis的网络开销。
第二阶段:
引入guava框架后,改善显著。
关键代码:
一开始预设1000秒过期。
cache 内的对象如果过期了,需要重新设置过期时间,过期时间需要动态计算服务器当前时间和Redis内保存的时间差值,保证cache内的对象不会旧于Redis。这样在多实例中,guava的cache充当了1级缓存。
测试结果: 并发果然大大提高,大约可以上到57/sec.
本来两次Http请求,由于引入了guava缓存,变成了一次https请求。效率当然提高了。但是仍然不是很满意,因为测试过程中error率大约有0.3%。而且观察日志,不少请求费时有50s,甚至有120s。测试时发现jetty在运行过程中,会发生停顿现象,所谓的停顿就是完全没有响应。熟悉jvm垃圾回收的朋友应该知道,这个时候jvm发生了Full GC,GC过程中会对导致程序运行中断。
原因分析:
1.大量http请求堆积,需要限制访问httpClient.execute方法的数量。避免引起阻塞。
2.降低或避免Full GC,要对jvm运行参数进行调优。但是在生产环境中(微软云,Paas服务),却无法配置jvm运行参数(可能我暂时不知道吧)。
措施:
引入Java多线程信号量Semaphore
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源
通俗的讲:就是公园大门就10扇门,来了1000人都争先恐后的挤破脑袋想进去,结果都挤在一堆了乱哄哄的,没见的有多少人能进去。
Semaphore就是做了一个控制管理,比如规定每次只能放10人进行来,其它人只能等,处理完一个,后面就跟上,直到全部处理完。这样比一堆人哄在门口要有效率的多。
(Semaphore 在本公众号早些时候有专门文章讲解的)
对于本应用来说,Semaphore的值太低是不行的,太高也不行。
关键代码:
采用了公平模式
acquire() 获得锁
release() 释放锁
测试了几个数字发现当Semaphore = 200时,并发数比较高。
测试结果:公平模式大约68.7/sec,峰值可以上到110/sec,错误率0%,果然通过控制访问httpClient.execute方法的数量,能提高并发数。在测试过程中,仍然发生了好几次Full gc(),把并发数给降了下来。
第三阶段:
上Apache网站上发现HttpAsyncClient框架
HttpAsyncClient may be of interest to anyone building HTTP-aware client applications based on asynchronous, event driven I/O model.
OSChina上这样介绍的
HttpAsyncClient 的出现并不是为了替换 HttpClient,而是作为一个补充用于需要大量并发连接,对性能要求非常高的基于HTTP的原生数据通信,而且提供了事件驱动的 API。
关键代码:
改造Post请求方法
连接池初始化以及单例模式
直接返回Future对象!查看源码,还有另外一个惊喜,不需要再对SSL写一堆东西了。注意:使用HttpAsyncClient后不再使用Semaphore了。
测试结果:每秒稳定80/sec~85/sec,而且jvm几乎没有停顿过。5000个请求很快就跑完了。而且最大延迟也就7s.