http请求流程中异步发送数据的有效方法

把通过http接受的消息推送给其他服务,暂定技术方案是利用redis的消息队列机制(LPush/BRPOP)

第一印象这个需求和raven-go这个client lib很像,直接借鉴就行了。所以读了raven-go在向服务器发送log部分的代码:

  • 首先,初始化一个100大小的chan,作为buffer queue
  • 然后,启动一个worker(goroutine),消耗这个queue中的消息

上面的设计有几点需要关注:

第一个关注点:buffer的作用到底是什么?第一个想法可能是,buffer和cache很像,用于解决两方速度不匹配,导致速度快的一方受到约束这个问题。根据上面的理解得到的推论就是,用buffer能加快速度,其实正确的描述应该是“用好buffer才能加快速度”。

但是,我由于直接copy机制过来没有完整的理解raven-go中的整个发送流程,导致在压测时,发现用buffer速度1w/s,但是不用5w+/s。找问题的过程很粗暴,分析整个发送流程中的json.Marshal/newObject是不是影响性能的关键,当然结果不是。

接着,通过测试发现buffer被占满,所以有加大buffer,试图缓解问题,这个“缓解”的想法本身就是错的,所以稍微加载压测的力度就又不行了。

第二个关注点:又读了一下raven-go的client.go,发现在buffer满的情况下是直接丢弃log(这个设计是根据message重要成都定的,log一般是可以丢弃的,不过error log能压垮raven-go,我觉得就不是raven-go的问题了,可能是你系统的bug太严重了)。

随后,我又不用buffer直接写redis,发现5w+这不相当于过度优化了嘛,不过考虑一下,如果没有buffer,流量直接打到redis上相当于依赖redis和app之间的访问优化(长连接),个人其实觉得也没什么问题,但是app的该需求就依赖于redis表现的好坏,没有任何防护机制,直接影响用户的体验(因为这个需求主要是backend,所以最好要是搞成异步处理,相当于对用户的影响也就是多写一次chan内存),所以异步的方案还是要攻克。

最后,遇到了这篇每分钟支撑百万级的请求,同时要把请求异步发送到amazon s3上(耗时),才反应过来,queue阻塞是因为worker消耗速度慢,速度慢是因为只启动一个goroutine,goroutine内容一个一个的消耗消息,cpu占用率完全上不去,所以参考这个blog优化了一下,果然上去了5w+,问题解决。

上面的问题还没有结束,我觉得问题解决后,压测时发现系统卡住了,稍后发现qps非常高9w+,觉得性能肯定是没问题了,就没有过多关注系统卡顿的情况,多次的压测不断的重现这个问题,那么可能就是程序真的哪里有问题了,因为如果真的出现高并发造成系统负载过高,这个机器灾难恢复的速度也非常令人担忧,htop发现是内存消耗速度过快直接占满,同样各种粗暴的试验程序的各个细节。最后找到是上面例子中在向queue中插入数据时直接用的goroutine,如果queue队列size过小,并发量过大,大量的goroutine因为得不到queue资源不能释放导致内存问题。所以果断加了queue的大小限制。虽然性能下降了不少3.7w/s,但是仍旧能满足需求。

http请求流程中异步发送数据的有效方法
Share this