Почему UDPSocket.send всегда вызывает getaddrinfo в Ruby?

Я только что решил проблему задержки в нашей инфраструктуре, которая была вызвана, потому что этот фрагмент кода здесь вызвал вызов getaddrinfo при каждом запуске кода:

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

Поскольку мы используем statsd и графит для мониторинга большого объема событий и статистики, мы эффективно инициировали многочисленные вызовы. getaddrinfo на каждый вызов API, и, возможно, десятки тысяч каждую минуту.

Я изменил этот код, чтобы использовать внутренний IP-адрес, а не DNS-имя нашего графитового сервера, и смог решить проблему задержки (возможно, потому, что внутренний DNS-сервер AWS VPC не был оборудован для обработки такого большого количества запросов),

Теперь, когда моя проблема решена, я хотел бы знать, почему реализация UDP в Ruby не использует кэшированное значение IP-адреса (предположительно на основе TTL записи в доменном имени). Вот соответствующая строка и функция в полном объеме, вы можете увидеть вызов 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;
}

Я предполагаю, что это решение является преднамеренным и хотел бы узнать больше о причинах этого.

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 для получения данных, или нет.

Какой-то конкретный API для DNS, например getdnsapi вернет вызывающей стороне некоторую информацию о TTL, см. https://getdnsapi.net/documentation/spec/ и пример 6.2.

6·2 Получить адреса IPv4 и IPv6 для доменного имени

Этот пример похож на предыдущий, за исключением того, что он извлекает больше информации, чем просто адреса, поэтому он перебирает ответное дерево. В этом случае он получает как адреса, так и их TTL.

Без какого-либо слоя кэша, поскольку UDP не имеет состояния, любой новый send должен вызвать разрешение в том или ином виде.

Вы сказали:

"изменил этот код для использования внутреннего IP-адреса, а не DNS-имени"

Вместо этого вы должны установить локальный (на коробке) рекурсивный кеширующий сервер имен, такой как unbound, Все ваши локальные приложения получат выгоду от этого, и более быстрое разрешение DNS (в зависимости от того, как /etc/nsswitch.conf, /etc/resolv.conf а также /etc/hosts настроены также).

Для связанного сообщения об ошибке, на который намекает @Casper, в его основе лежит проблема IPv6 против IPv4, которую можно решить, настроив /etc/gai.conf или эквивалентный, или делать более умное программирование вокруг открытия соединения, с помощью так называемого "алгоритма счастливого глазного яблока", где вы пытаетесь разрешить оба A а также AAAA в то же время, что означает два параллельных DNS-запроса (потому что вы не можете объединить их в один для протокола) и попытаться использовать самый быстрый, возвращающийся с небольшим предпочтением для AAAA если вы хотите быть в современном лагере, чтобы вы уволили A один только некоторое заданное количество миллисекунд после AAAA поймать случай, когда вы вообще не получите ответ для AAAA или отрицательный. См. RFC6555 для деталей.

Другие вопросы по тегам