【问题标题】:Why does UDPSocket.send always call getaddrinfo in Ruby?为什么 UDPSocket.send 总是在 Ruby 中调用 getaddrinfo?
【发布时间】:2018-08-20 19:21:18
【问题描述】:

我刚刚解决了我们基础架构中的一个延迟问题,该问题被触发,因为这里的代码 sn-p 在每次运行代码时都会触发对 getaddrinfo 的调用:

sock = UDPSocket.open
sock.send("#{key}|#{value}", 0,
          GRAPHITE_SERVER,
          STATSD_PORT)
sock.close

因为我们使用statsd and graphite 进行大量事件和统计数据监控,所以我们在每次 API 调用时有效地触发了无数次调用getaddrinfo,并且每分钟可能会触发数万次。

我修改了这段代码以使用我们的石墨服务器的内部 IP 地址,而不是 DNS 名称,并且能够解决延迟问题(可能是因为内部 AWS VPC DNS 服务器没有配备处理如此高的容量请求)。

现在我的问题已经解决,我很想知道为什么 Ruby 中的 UDP 实现不使用缓存的 IP 地址值(大概基于域名条目的 TTL)。 Here is the relevant line 和完整的函数,你可以在最后看到对rsock_addrinfo 的调用:

static VALUE
udp_send(int argc, VALUE *argv, VALUE sock)
{
    VALUE flags, host, port;
    struct udp_send_arg arg;
    VALUE ret;

    if (argc == 2 || argc == 3) {
    return rsock_bsock_send(argc, argv, sock);
    }
    rb_scan_args(argc, argv, "4", &arg.sarg.mesg, &flags, &host, &port);

    StringValue(arg.sarg.mesg);
    GetOpenFile(sock, arg.fptr);
    arg.sarg.fd = arg.fptr->fd;
    arg.sarg.flags = NUM2INT(flags);
    arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0);
    ret = rb_ensure(udp_send_internal, (VALUE)&arg,
            rsock_freeaddrinfo, (VALUE)arg.res);
    if (!ret) rsock_sys_fail_host_port("sendto(2)", host, port);
    return ret;
}

我认为这个决定是故意的,并且很想了解更多原因。

【问题讨论】:

标签: ruby dns udp getaddrinfo


【解决方案1】:

getaddrinfo 不返回有关 TTL 的数据...因为实际上它可能根本没有它,因为不一定通过 DNS 完成解析(可能是 hosts 文件、LDAP 等。见/etc/nsswitch.conf)

从它的手册这里是返回的结构:

int getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res);

 struct addrinfo {
         int ai_flags;           /* input flags */
         int ai_family;          /* protocol family for socket */
         int ai_socktype;        /* socket type */
         int ai_protocol;        /* protocol for socket */
         socklen_t ai_addrlen;   /* length of socket-address */
         struct sockaddr *ai_addr; /* socket-address for socket */
         char *ai_canonname;     /* canonical name for service location */
         struct addrinfo *ai_next; /* pointer to next in list */
 };

在成功调用 getaddrinfo() 后,*res 是一个指向一个或多个 addrinfo 结构的链表的指针。

因此,“背后”getaddrinfo 是否进行缓存取决于getaddrinfo 是否已使用 DNS 检索数据。

一些特定的 DNS API,如getdnsapi 将返回给调用者一些关于 TTL 的信息,参见https://getdnsapi.net/documentation/spec/ 和示例 6.2

6·2 获取域名的 IPv4 和 IPv6 地址

这个例子和上一个例子类似,只是它检索的信息不仅仅是地址,所以它遍历replies_tree。在这种情况下,它会同时获取地址和它们的 TTL。

在任何地方都没有任何缓存层,因为 UDP 是无状态的,任何新的send 都必须以某种方式或形式触发解析。

你说:

"修改此代码以使用内部 IP 地址,而不是 DNS 名称"

您应该安装一个本地(在盒子上)递归缓存名称服务器,例如unbound。您的所有本地应用程序都将从中受益,并且 DNS 解析速度更快(取决于 /etc/nsswitch.conf/etc/resolv.conf/etc/hosts 的设置方式)。

对于@Casper 暗示的相关错误报告,它的核心似乎更多是关于 IPv6 与 IPv4 的问题,可以通过调整 /etc/gai.conf 或等效项或围绕打开连接进行一些更聪明的编程来解决,这样称为“快乐眼球算法”,您尝试同时解析 AAAAA,这意味着两个并行 DNS 查询(因为您不能根据协议将它们组合成一个)并尝试使用最快的一个回来,如果你想进入现代阵营,稍微偏爱AAAA,这样你就可以在AAAA之后的几毫秒内触发A,以捕捉你没有得到回复的情况对于AAAA 或否定的。详情请见RFC6555

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-06-17
  • 1970-01-01
  • 1970-01-01
  • 2013-03-03
  • 1970-01-01
相关资源
最近更新 更多