为什么 UDPSocket.send 总是在 Ruby 中调用 getaddrinfo?

Why does UDPSocket.send always call getaddrinfo in Ruby?

我刚刚解决了我们基础设施中的一个延迟问题,该问题是因为此处的代码片段在代码的每个 运行 上触发了对 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 and the function in full, you can see the call to 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;
}

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

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

从它的手册这里是结构 returned:

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 */
 };

After a successful call to getaddrinfo(), *res is a pointer to a linked list of one or more addrinfo structures.

因此,是否进行缓存取决于 getaddrinfo“背后”的事情,因为 getaddrinfo 可能使用 DNS 检索数据。

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

6·2 Get IPv4 and IPv6 Addresses for a Domain Name

This example is similar to the previous one, except that it retrieves more information than just the addresses, so it traverses the replies_tree. In this case, it gets both the addresses and their TTLs.

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

你说:

"modified this code to use the internal IP address, not the DNS name"

您应该改为安装本地(在机器上)递归缓存名称服务器,例如 unbound。您所有的本地应用程序都将受益于它,以及更快的 DNS 解析(取决于 /etc/nsswitch.conf/etc/resolv.conf/etc/hosts 的设置方式)。

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