欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  数据库

memcached优化始末

程序员文章站 2022-06-11 08:09:17
...

我们线上服务一直使用memcached作为cache服务器,客户端使用的是libmemcached,示意图如下。 问题现象 我们发现item平均大小仅为10KB左右,结果memcached set的平均延时就有7ms之多。我们之前已经在libmemcached里面设置了TCP_NODELAY。 libmemcached在set/g

我们线上服务一直使用memcached作为cache服务器,客户端使用的是libmemcached,示意图如下。

memcached优化始末

问题现象

我们发现item平均大小仅为10KB左右,结果memcached set的平均延时就有7ms之多。我们之前已经在libmemcached里面设置了TCP_NODELAY。

memcached优化始末

libmemcached在set/get之后会接收服务器返回的数据,如果一次没有收到,就会循环调用poll等待服务器返回数据。

      while (1) 
      {   
        int enable = 1;
        setsockopt( ptr->fd, IPPROTO_TCP, TCP_QUICKACK, (void *)&enable, sizeof(enable));
        data_read= recv(ptr->fd, ptr->read_buffer, MEMCACHED_MAX_BUFFER, 0); 
        setsockopt( ptr->fd, IPPROTO_TCP, TCP_QUICKACK, (void *)&enable, sizeof(enable));
        if (data_read > 0)
        {   
          break;
        }   
        else if (data_read == SOCKET_ERROR)
        {   
          ptr->cached_errno= get_socket_errno();
          memcached_return_t rc= MEMCACHED_ERRNO;
          switch (get_socket_errno())
          {   
          case EWOULDBLOCK:
#ifdef USE_EAGAIN
          case EAGAIN:
#endif
          case EINTR:
#ifdef TARGET_OS_LINUX
          case ERESTART:
#endif
            if ((rc= io_wait(ptr, MEM_READ)) == MEMCACHED_SUCCESS)
              continue;
            /* fall through */
          default:
            {   
              memcached_quit_server(ptr, true);
              *nread= -1; 
              return rc; 
            }   
          }   
        }

剑豪使用systemstap发现经常有超过40ms的poll,这是一个很重要的线索,我使用strace也可以看到很多超过40ms的poll。

memcached优化始末

经过一系列测试发现,流量越小,延时反而越高。但是为什么延时又随着QPS的波动而波动呢?我想应该是这样的,memcached_st对象池使用stack保存,使用的顺序是后进先出。当流量小的时候,会一直使用栈最上面的一两个memcached_st对象,所以对某个memcached服务器的压力可能不必流量高峰低。

问题缘由

经过来回折腾,我发现客户端和服务器端都存在DELAYACK的情况,客户端会对服务器的响应做DELAYACK,不会影响性能。真正奇怪的是客户端设置了NODELAY,但是有时候它会等待服务器发送ACK,才接着继续发数据。并且由于服务器都开启了TSO、GRO等特性,使用tcpdump看不到网卡做的事情,tcp经常push一个远大于MSS的包出去,似乎也根本不考虑服务端的接收窗口大小。

下图中487、488、489三行在做一个get,没有命中cache之后,从490行开始做set,结果服务器的ack delay了,导致客户端发送第二个包的时候等了40ms。

memcached优化始末

我甚至怀疑TCP_NODELAY没有设置上,但是在代码中使用getsockopt将TCP_NODELAY的值打印出来,发现确实是1。

NOREPLY

无意中看到memcached支持NOREPLY选项(这个选项libmemcached同样也支持,要不然一个巴掌拍不响),真是如获重宝啊,实际线上的效果如下,现在set的latency已经可以忽略不计了。我想世界上所有非关键的服务都应该支持NOREPLY这个选项。

memcached优化始末

一点小结

1、TCP_NODELAY之所以没起作用,应该和gro有关系。我做了一个测试,发现关闭gro之后,poll超过40ms的情况减少了很多。如果不能理解其中的奥义,就很难解释为什么流量大,延时反而会降下来这个现象。

2、libmemcached这种每个memcached_st对象对每个memcached服务器都有自己连接的方式,特别容易导致每个连接压力都很小,导致延时变得特别高。这种压力能小到什么程度呢?假设有30台memcached服务器,客户端set的QPS是1K,多线程导致一共有30个memcached_st对象,那么一个socket的set QPS就是1K/(30*30)=1。

淘宝自己的tbnet/anet网络通讯库是复用socket的,每个包通过channel id来标识自己,可以使socket保持高压力,不容易出现上面这个问题。