最近给服务增加了一个cache_put_latency指标,加了之后,吓了一跳。发现往memcached put一个10KB左右的数据,latency居然有7ms左右,难于理解,于是花了一些精力找原因。我分别写了一个shell和C++的测试程序。 1、shell脚本使用nc发送set命令。 #/bin/env ba
最近给服务增加了一个cache_put_latency指标,加了之后,吓了一跳。发现往memcached put一个10KB左右的数据,latency居然有7ms左右,难于理解,于是花了一些精力找原因。我分别写了一个shell和C++的测试程序。
1、shell脚本使用nc发送set命令。
#/bin/env bash let s=1 let i=0 let len=8*1024 while true do if (( i >= $len )) then break fi str=${str}1 let i++ done let i=0 begin_time=`date +%s` while true do if (( i >= 1000 )) then break fi printf "set $i 0 0 $len\r\n${str}\r\n" | nc 10.234.4.24 11211 if [[ $? -eq 0 ]] then echo "echo key: $i" fi let i++ done end_time=`date +%s` let use_time=end_time-begin_time echo "set time consumed: $use_time" let i=0 begin_time=`date +%s` while true do if (( i >= 1000 )) then break fi printf "get $i\r\n" | nc 10.234.4.22 11211 > /dev/null 2>&1 let i++ done end_time=`date +%s` let use_time=end_time-begin_time echo "get time consumed: $use_time"
2、C++程序则通过libmemcached set。
#include <iostream> #include <map> #include <string> #include <sys> #include <time.h> #include <stdlib.h> #include "libmemcached/memcached.h" using namespace std; uint32_t item_size = 0; uint32_t loop_num = 0; bool single_server = false; std::string local_ip; std::map<:string uint32_t> servers; int64_t getCurrentTime() { struct timeval tval; gettimeofday(&tval, NULL); return (tval.tv_sec * 1000000LL + tval.tv_usec); } memcached_st* mc_init() { memcached_st * mc = memcached_create(NULL); if (mc == NULL) { cout ::iterator iter; for (iter = servers.begin(); iter != servers.end(); ++iter) { if (single_server && iter->first != local_ip) { continue; } memcached_return rc = memcached_server_add(mc, iter->first.c_str(), iter->second); if(rc != MEMCACHED_SUCCESS) { cout first first <p>测试发现二者的结果是相背的。shell脚本set 1000次8KB的item,只要3s左右,平均需要3ms。而C++版本则需要39s左右,平均耗时39ms。照理说shell脚本需要不断连接服务器和启动nc进程,应该更慢才对。我用ltrace跟踪了一下,发现8KB的数据需要发送两次,两次write都是非常快的,但是等memcached返回时用了很多时间,主要的时间就耗费在这个地方。</p> <pre class="brush:php;toolbar:false"> 23:32:37.069922 [0x401609] memcached_set(0x19076200, 0x7fffdad68560, 32, 0x1907a570, 8192 <unfinished ...> 23:32:37.070034 [0x3f280c5f80] SYS_write(3, "set 29 0 600 8192\r\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 8196) = 8196 23:32:37.071657 [0x3f280c5f80] SYS_write(3, "aaaaaaaaaaaaaaa\r\n", 17) = 17 23:32:37.071741 [0x3f280c5f00] SYS_read(3, "STORED\r\n", 8196) = 8 (39ms) </unfinished>
和剑豪讨论下之后,剑豪马上去grep了一把代码,发现原来libmemcached居然有MEMCACHED_MAX_BUFFER这样一个常量,其值为8196。并且它还没有对应的memcached_behavior_set函数。在memcached_constants.h中将其直接改成81960,然后就欣喜地发现cache_put_latency从7ms降低到1ms左右。
问题完美虽然地解决了,但是意犹未尽,于是想搞明白为什么会出现这种奇怪的现象。瓶颈貌似在服务器端,于是对memcached做了一些修改。在状态切换的时候加上一个精确到微秒的时间。
static int64_t getCurrentTime() { struct timeval tval; gettimeofday(&tval, NULL); return (tval.tv_sec * 1000000LL + tval.tv_usec); } static void conn_set_state(conn *c, enum conn_states state) { assert(c != NULL); assert(state >= conn_listening && state state) { if (settings.verbose > 2) { fprintf(stderr, "%d: going from %s to %s, time: %lu\n", c->sfd, state_text(c->state), state_text(state), getCurrentTime()); } c->state = state; if (state == conn_write || state == conn_mwrite) { MEMCACHED_PROCESS_COMMAND_END(c->sfd, c->wbuf, c->wbytes); } } }
从打印的时间戳可以看出来,时间主要花在conn_nread状态处理代码中。最后定位到第二次read花费的时间非常多。
15: going from conn_waiting to conn_read, time: 1348466584440118 15: going from conn_read to conn_parse_cmd, time: 1348466584440155 NOT FOUND 98 >15 STORED 15: going from conn_nread to conn_write, time: 1348466584480099(36ms) 15: going from conn_write to conn_new_cmd, time: 1348466584480145 15: going from conn_new_cmd to conn_waiting, time: 1348466584480152
value的数据可能在conn_read中读完了,这个时候只需要memmove一下就好了。如果没有在conn_read状态中读完,那么就需要conn_nread自己来一次read了(因为套接字被设置成了异步,所以还可能需要多次read),关键就是这个read太慢了。
case conn_nread: if (c->rlbytes == 0) { complete_nread(c); break; } /* first check if we have leftovers in the conn_read buffer */ if (c->rbytes > 0) { int tocopy = c->rbytes > c->rlbytes ? c->rlbytes : c->rbytes; if (c->ritem != c->rcurr) { memmove(c->ritem, c->rcurr, tocopy); } c->ritem += tocopy; c->rlbytes -= tocopy; c->rcurr += tocopy; c->rbytes -= tocopy; if (c->rlbytes == 0) { break; } } /* now try reading from the socket */ res = read(c->sfd, c->ritem, c->rlbytes); if (res > 0) { pthread_mutex_lock(&c->thread->stats.mutex); c->thread->stats.bytes_read += res; pthread_mutex_unlock(&c->thread->stats.mutex); if (c->rcurr == c->ritem) { c->rcurr += res; } c->ritem += res; c->rlbytes -= res; break; }
折腾了好久,在libmemcached的io_flush函数前后也打了不少时间戳,发现libmemcached发送数据是非常快的。突然灵感闪现,我想起来了TCP_NODELAY这个参数,于是在libmemcached memcached_connect.c文件中的set_socket_options函数中增加了这个参数(事实上set_socket_options函数里面可以设置TCP_NODELAY,没有仔细看)。
int flag = 1; int error = setsockopt(ptr->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag) ); if (error == -1) { printf("Couldn't setsockopt(TCP_NODELAY)\n"); exit(-1); }else { printf("set setsockopt(TCP_NODELAY)\n"); }
在不改MEMCACHED_MAX_BUFFER的情况下,现在set 100KB的item也是一瞬间的事情了。不过新的困惑又出现了,Nagle算法什么情况会起作用呢?为什么第一个包没被缓存,第二个包一定会被缓存呢?
libmemcached发送一个set命令是分成三部分的,首先是header(set 0 0 600 8192\r\n,共18个字节),然后是value(8192个字节),最后是’\r\n’(两个字节),一共是8212个字节。memcached在conn_read状态一共能读取2048+2048+4096+8196=16KB的数据,因此对于8KB的数据是完全可以在conn_read状态读完的。通过在conn_read状态处理的代码中增加下面的打印语句可以发现有些情况下,conn_read最后一次只读取了4个字节(正常情况应该是2048+2048+4096+20),剩下的16个字节放到conn_nread中读了。
res = read(c->sfd, c->rbuf + c->rbytes, avail); if (res > 0) { char buf[10240] = {0}; sprintf(buf, "%.*s", res, c->rbuf + c->rbytes); printf("avail=%d, read=%d, str=%s\n", avail, res, buf);
未设置TCP_NODELAY选项时,使用netstat可以看到客户端socket的Send-Q一直会维持在8214和8215之间。
tcp 0 8215 10.232.42.91:59836 10.232.42.91:11211 ESTABLISHED 25800/t
设置TCP_NODELAY选项时,客户端socket的Send-Q就一直为0了。
tcp 0 0 10.232.42.91:59890 10.232.42.91:11211 ESTABLISHED 26554/t.quick
原文地址:libmemcached的MEMCACHED_MAX_BUFFER问题, 感谢原作者分享。

Memcached是一种常用的缓存技术,它可以使Web应用程序的性能得到很大的提升。在PHP中,常用的Session处理方式是将Session文件存放在服务器的硬盘上。但是,这种方式并不是最优的,因为服务器的硬盘会成为性能瓶颈之一。而使用Memcached缓存技术可以对PHP中的Session处理进行优化,提高Web应用程序的性能。PHP中的Session处

PHP8.0中的缓存库:Memcached随着互联网的快速发展,现代应用程序需要高效可靠的缓存技术来提高性能和处理大量数据。由于PHP的流行和开源特性,PHP缓存库已经成为了Web开发社区的一个必备工具。Memcached是一种广泛使用的开源高速内存缓存系统,它能处理数百万个同时连接的缓存请求,可以用于许多不同类型的应用程序,例如社交网络、在线

随着互联网的快速发展,大规模MySQL数据库备份和恢复成为各大企业和网站必备的技能之一。而随着Memcached的广泛应用,如何备份和恢复Memcached也成为了一个重要的问题。PHP作为Web开发的主力语言之一,在处理备份和恢复MySQL和Memcached上拥有独特的优势和技巧。本文将详细介绍PHP处理MySQL和Memcached备份与恢复的实现方法

随着网络应用的不断增加和数据量的不断膨胀,数据的读写效率成为影响应用性能的重要因素之一。而缓存技术的应用则可以很好地解决这个问题。在PHP应用中,Memcached是最常用的缓存服务器。Memcached是一个高性能的分布式内存对象缓存系统,可以将常用的数据存储在内存中,提高数据检索的效率。本文将介绍如何使用PHP和Memcached进行缓存管理,以及如何优

随着互联网技术的不断发展,音视频资源已经成为了互联网上非常重要的一种内容形式,而PHP作为网络开发中使用最广泛的语言之一,也在不断地应用于视频和音频播放领域。然而,随着音视频网站的用户日益增加,许多网站已经发现了一个问题:在高并发的情况下,PHP对于音视频的处理速度明显变缓,会导致无法及时播放或者播放卡顿等问题。为了解决这个问题,Memcached缓存技术应

随着现代互联网应用的快速发展,用户体验对于一个应用的成功至关重要。如何保证应用的高性能和高可用性,成为了开发人员需要解决的重要问题之一。PHP作为一种广泛应用的编程语言之一,它的性能监控和优化也是非常重要的。Memcached是一个高性能、分布式的内存对象缓存系统,可以帮助应用提高性能和扩展性。本文将介绍如何使用PHP和Memcached实现性能监控的方法。

随着互联网应用的快速发展,数据存储和处理变得越来越庞大和复杂。在这样的背景下,Memcached作为一款高性能、轻量级的分布式内存缓存系统,逐渐成为互联网应用领域中不可或缺的一部分。在PHP语言中,Memcached可以通过扩展内置的Memcached类实现与Memcached服务器的交互,而在实际生产环境中,我们需要通过搭建Memcached数据库集群来保

随着现代应用程序的快速增长,缓存已成为许多开发人员的至关重要的部分。缓存可以大大提高应用程序的性能并减少服务器负载。在CakePHP中,实现缓存的一种方法是使用Memcached。Memcached是一个基于内存的分布式缓存系统。它将数据存储在内存中,可以快速地读取和写入数据。在多服务器环境中,Memcached可以分布式存储数据并通过网络进行共享。不仅可以


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

PhpStorm 맥 버전
최신(2018.2.1) 전문 PHP 통합 개발 도구

에디트플러스 중국어 크랙 버전
작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

맨티스BT
Mantis는 제품 결함 추적을 돕기 위해 설계된 배포하기 쉬운 웹 기반 결함 추적 도구입니다. PHP, MySQL 및 웹 서버가 필요합니다. 데모 및 호스팅 서비스를 확인해 보세요.

Eclipse용 SAP NetWeaver 서버 어댑터
Eclipse를 SAP NetWeaver 애플리케이션 서버와 통합합니다.
