search
HomeDatabaseMysql Tutoriallibmemcached的MEMCACHED_MAX_BUFFER问题

最近给服务增加了一个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
Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Memcached缓存技术对于PHP中的Session处理的优化Memcached缓存技术对于PHP中的Session处理的优化May 16, 2023 am 08:41 AM

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

PHP8.0中的缓存库:MemcachedPHP8.0中的缓存库:MemcachedMay 14, 2023 am 08:16 AM

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

PHP与Memcached数据库备份与恢复PHP与Memcached数据库备份与恢复May 15, 2023 pm 09:12 PM

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

利用Memcached缓存技术对于PHP中的音视频播放进行优化利用Memcached缓存技术对于PHP中的音视频播放进行优化May 17, 2023 pm 04:01 PM

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

使用PHP和Memcached进行缓存管理使用PHP和Memcached进行缓存管理May 23, 2023 pm 02:21 PM

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

PHP与Memcached性能监控PHP与Memcached性能监控May 15, 2023 pm 09:51 PM

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

PHP实现Memcached数据库集群的方法PHP实现Memcached数据库集群的方法May 15, 2023 pm 03:31 PM

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

使用宝塔面板进行Redis、Memcached等缓存服务器的部署使用宝塔面板进行Redis、Memcached等缓存服务器的部署Jun 21, 2023 am 09:56 AM

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

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

Hot Tools

WebStorm Mac version

WebStorm Mac version

Useful JavaScript development tools

DVWA

DVWA

Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver Mac version

Dreamweaver Mac version

Visual web development tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor