PHP memcached扩展长连接失效排查

php的mcmcached扩展参数众多,文档简陋,内部实现复杂,无奈phper没有什么好的选择

最近有一个比较苦恼的问题,偶发memcached连接timeout,报警趋势图表现出一瞬间毛刺,从日志分析看故障机有时候集中在若干server端IP,有时候集中在若干client端IP,由于服务容器化后已经迁移到公有云上,这些IP都是容器级很难直接观察出共性,一直怀疑是物理机层面异常导致,但发生频率比较没有规律,抓包效率不太高,最终经过几天与云方技术伙伴排查发现是物理网卡的队列占满,报文丢弃,根因在于连接浪涌。厂商给出的内部解决方案是 测试看队列长度是否可以调大,如果可以调大也只能打冷补丁,更新周期很长。

厂商补丁这条路不太好走,那看看业务上是否可以优化。目前业务使用的统一是短连接,这种策略对低频依赖mc的业务比较友好,而高频的业务则会产生大量的短连接,抓包发现一个MC操作产生的报文数量一半左右都是浪费在握手和挥手,而且有些MC实例每秒建连数高达1.8W,MC实例和业务POD都有亲缘分布的热点问题,当热点流量亲缘集中时会给MC宿主机或业务宿主机带来不小的负担,网卡第一个撑不住,于是产生了波动。

使用短连接有一个背景,使用php-fpm时PHP对于长连接的管理是绑定在php-fpm,业务代码身体无法管理长连接池,而我们几个业务集群峰值可以产生近2W的连接数,云厂商资料显示,维护巨量的长连接,可能会消耗近1G的产品内存,基于以前业务一直很稳定,所以也就没有去太重视这块。(合作云厂商的MC服务是使用redis实现的,做了一层协议转换,一切特性都需要参照redis去理解,且是用户有感,云厂商对用户暴露了部分redis参数,需要业务去调优,MC天然的简洁优势消失殆尽,这也是接入初期产生大量业务不适应的重要根源,特别需要注意的是redis单线程模型、内存管理策略带来的负面影响,如lru时线程繁忙。厂商此paas产品对客户的连接数限制在2W级,后来沟通额外开到4W)

查到原因后于是对症下药,有两个方式解决:

  1. 网卡波动一瞬间报文丢失,但会重传,S报文丢失的时候重传是1秒,其它报文是200ms,业务把timeout改为大于1s,大概率下次重试就成功了
  2. 业务调整为长连接,减少报文量,降压

方案1治标不治本也不符合我的气质,放弃。于是选择方案2,为谨慎起见,进行梯度放量,业务代码里依据pid尾数来控制长连接比例。

一切看上去都那么自然天衣无缝,那就太天真了。

放了一部分量后看监控发现活跃连接数上涨了,挺好,符合预期。再看下新建连接数没有降,业务平均响应时间也微微变差了!,why why why,是监控采集错了吗,不科学啊。一翻折腾确认,监控没有问题,那问题出在哪呢。于是又开始了苦逼的抓包分析  tcpdump -w t.pcap -i eth0 dst 10.13.32.206  然后 tcpdump -r t.pcap  查看

16:26:26.240749 IP 10.242.8.16.37274 > 10.13.33.206.11211: Flags [S], seq 1127458011, win 29200, options [mss 1460,sackOK,TS val 166212987 ecr 0,nop,wscale 9], length 0
16:26:26.240753 IP 10.13.33.206.11211 > 10.242.8.16.37274: Flags [S.], seq 1618708266, ack 1127458012, win 28960, options [mss 1460,sackOK,TS val 3204262586 ecr 166212987,nop,wscale 7], length 0
16:26:26.241093 IP 10.242.8.16.37274 > 10.13.33.206.11211: Flags [.], ack 1, win 58, options [nop,nop,TS val 166212988 ecr 3204262586], length 0
16:26:26.241099 IP 10.242.8.16.37274 > 10.13.33.206.11211: Flags [P.], seq 1:63, ack 1, win 58, options [nop,nop,TS val 166212988 ecr 3204262586], length 62
16:26:26.241101 IP 10.13.33.206.11211 > 10.242.8.16.37274: Flags [.], ack 63, win 227, options [nop,nop,TS val 3204262586 ecr 166212988], length 0
16:26:26.241102 IP 10.242.8.16.37274 > 10.13.33.206.11211: Flags [P.], seq 63:87, ack 1, win 58, options [nop,nop,TS val 166212988 ecr 3204262586], length 24
16:26:26.241104 IP 10.13.33.206.11211 > 10.242.8.16.37274: Flags [.], ack 87, win 227, options [nop,nop,TS val 3204262586 ecr 166212988], length 0
16:26:26.241121 IP 10.13.33.206.11211 > 10.242.8.16.37274: Flags [P.], seq 1:25, ack 87, win 227, options [nop,nop,TS val 3204262586 ecr 166212988], length 24
16:26:26.241446 IP 10.242.8.16.37274 > 10.13.33.206.11211: Flags [.], ack 25, win 58, options [nop,nop,TS val 166212988 ecr 3204262586], length 0
16:26:26.241448 IP 10.242.8.16.37274 > 10.13.33.206.11211: Flags [.], ack 25, win 58, options [nop,nop,TS val 166212988 ecr 3204262586], length 0
#过了段长的时间
16:26:26.891346 IP 10.242.8.16.37274 > 10.13.33.206.11211: Flags [P.], seq 87:111, ack 25, win 58, options [nop,nop,TS val 166213638 ecr 3204262586], length 24
16:26:26.891356 IP 10.242.8.16.37274 > 10.13.33.206.11211: Flags [F.], seq 111, ack 25, win 58, options [nop,nop,TS val 166213638 ecr 3204262586], length 0
16:26:26.891391 IP 10.13.33.206.11211 > 10.242.8.16.37274: Flags [F.], seq 25, ack 112, win 227, options [nop,nop,TS val 3204263236 ecr 166213638], length 0
16:26:26.891697 IP 10.242.8.16.37274 > 10.13.33.206.11211: Flags [.], ack 26, win 58, options [nop,nop,TS val 166213638 ecr 3204263236], length 0

由上面信息看到,每一次建连执行完一次操作后,长连接没有立即关闭,但是过了一小会memcached扩展主动发了个F包,这又是什么黑暗逻辑。总体上来说就是长连接是有了,也存活了一会,但是没有复用就关了,上面的指标变化就都解释通了。

memcached扩展的源码不太看得懂,调整几个php参数试试,结果意外发现,只要以下两个参数都设置为false,问题消失

\Memcached::OPT_BINARY_PROTOCOL
\Memcached::OPT_TCP_NODELAY

果然是memcached主动断开的,于是分析参数利弊后调整一下php调用姿势,把这两个参数设false,发布上线后看到了期望中的效果。但进制改文本后有个副作用就是cpu上涨了。

 

隐约感觉这不是最佳优化姿势,同时本地能复现,发现都是在第二次op操作前发了个F包,然后断开立马又发了S包建连

有线索后,架构林老师快速翻阅C源码,发现有这么一段

彻底解惑了,再改了下php的调用姿势,恢复开启了前面关闭的两个参数

//改造前
$mc = new \Memcached($persistent_id);
$mc->setOptions($options); 
if (!count($mc->getServerList())) {
     $mc->addServers($servers);
}

//改造后
$mc = new \Memcached($persistent_id);
if (!count($mc->getServerList())) {
     $mc->setOptions($options); 
     $mc->addServers($servers);
}

发布代码后发现,新建连接数巨降,CPU大降,活跃连接数大涨。符合预期。

事后结合了memcached 长连接和timeout非预期问题,发现memcached作者搞了不少胡里花俏难懂参数,且文档过于简陋,组合起来产生了非预期结果。而对setOptions()是可以做判断前后是否有变化,而不必每次都断开连接,也算作者偷懒了

 


        

发表评论

邮箱地址不会被公开。 必填项已用*标注