最近给服务增加了一个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是一种广泛使用的开源高速内存缓存系统,它能处理数百万个同时连接的缓存请求,可以用于许多不同类型的应用程序,例如社交网络、在线

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

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

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

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

随着互联网的发展,缓存技术在Web开发中扮演着越来越重要的角色。Redis和Memcached作为两种流行的缓存服务器,被广泛应用于各种Web应用开发中。然而,对于不熟悉Linux系统的开发人员来说,安装和配置这些缓存服务器可能会带来一些麻烦。但是,在宝塔面板的帮助下,这一过程将变得相当简单。一、什么是宝塔面板?宝塔面板是一款Linux服务器管理面板,它可以

Memcached是一种分布式内存缓存技术,被广泛应用于Web应用程序中。它可以帮助Web应用程序提高性能,减少数据库负载,加速数据访问,从而提高用户体验。在PHP中,使用Memcached技术也变得越来越流行。本文将介绍在PHP中如何使用Memcached技术。一、什么是Memcached技术Memcached是一种基于内存的缓存技术,它可以将数据缓存在内


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

PhpStorm Mac 版本
最新(2018.2.1 )专业的PHP集成开发工具

Dreamweaver Mac版
视觉化网页开发工具

SecLists
SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

DVWA
Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。