Почему 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 для деталей.