前言
这个是 2020年11月 记录的这个关于 nginx 的 dns 缓存的问题 docker 环境下面 前端A连到后端B + 前端B连到后端A
最近从草稿箱发布这个问题的时候, 重新看了一下 发现该问题的记录中仅仅是 定位到了 nginx 这边的 dns 缓存的问题, 但是 并没有到细节, 没有到 具体的 n种解决方式
这里 2024年2月, 本文的目的是 来看一下 这里的具体的细节, 从 nginx 具体的处理流程的过程中来看一下 为什么会是那样 ?
原始问题的上下文如下 docker部署了两套 前端+后端 的系统, appFront0 连接 appBackend0, appFront1 连接 appBackend1, 然后正常启动 前后端系统, 两个前端访问的各自后端系统
这时候, 不断的重启 appBackend0, appBackend1 直到复现 appFront0 访问 appBackend1 或者 appFront1 访问 appBackend0 的情况, 就复现了该问题
复现该问题的关键是 重启的过程中 appBackend0, appBackend1 的 ip 发生了变化
我们这里更侧重关注的就是 nginx 这边在 upstream 中使用域名配置了 上游服务, 然后之后 我们再 更新了 dns 服务的该域名的 ip 配置之后, 我们发现 nginx 这边使用的 ip 还是更新之前的 ip
我们这里 来梳理一下 这个流程, 然后 另外再扩展看一下 具体的 相关的系统调用这边是否有 dns 缓存
测试用例
nginx 配置如下, "/api/" 相关的服务代理到 "http://master:8081/"

http://master:8081/1.txt 的服务为一个文本文件

然后 /etc/hosts 中配置的 master 当前信息如下
当前配置为 192.168.220.2, 可以正常访问到服务

正常访问服务如下, 然后 之后将其切换为 "10.60.50.16" 一个不可用的 ip, 然后刷新页面 发现也可以正常访问到服务
我们要探究的就是 为什么 dns服务 中修改了 master 的 ip, nginx 中拿到的还是 原来映射的 192.168.220.2 呢?

问题的调试
这里会过的比较快, 大体的意思是 nginx 这边解析配置文件的时候 创建了对应的配置数据结构, 初始化的时候就进行了 dns 解析, 然后 后面的业务请求这边使用的是 这套配置
因此访问 http://localhost:82/api/1.txt 一直代理到的是 http://192.168.220.2:8081/1.txt
这里是初始化和上游服务连接的地方, 这里的 fd 就是关键, 是和上游服务关联的 socket
我们来看一下这个 socket

看一下这里的 socket 的信息如下, 下图的 sa_data 就是上游服务的 ip + port
这里 ngx_peer_connection_t pc 的数据来自于 ngx_http_upstream

这里的 sa_data 的数据解析如下, 解析为 192.168.220.2:8081

ngx_http_upstream.ngx_peer_connection_t 的数据来自于这里的 ngx_http_upstream.ngx_peer_connection_t.data 数据类型是 ngx_http_upstream_rr_peer_data_t

ngx_http_upstream.ngx_peer_connection_t.data 数据类型 ngx_http_upstream_rr_peer_data_t 来自于 upstream 这边初始化的时候
这里 ngx_inet_resolve_host 就是解析域名的地方, 然后下面 for(u.naddrs) 中将解析之后的地址信息初始化到 peer 上面去
这个就是 我们最上面和对方服务器创建连接的服务信息 192.168.220.2:8081

nginx 这边 dns 解析的方式
基于函数 getaddrinfo, 这个是 glibc 中的库函数

直接使用 getaddrinfo/gethostbyname 是否有 dns 缓存
这是 扩展的知识点
因为 我这边最开始 怀疑的缓存是在 getaddrinfo/gethostbyname 等等函数上面是否有缓存
因此 最开始的时候 做了一些测试用例, 也调试了一下 对应的函数
root@ubuntu:~/Desktop/linux/HelloWorld# cat Test31GetHostByName.c
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
int main(int argc, char** argv) {
char *name = "app0.com";
char str[16];
struct hostent* result;
result = gethostbyname(name);
printf(" official : %s \n", result->h_name);
printf(" ip : %s \n", inet_ntop(result->h_addrtype, result->h_addr, str, sizeof(str)));
sleep(10);
result = gethostbyname(name);
printf(" official : %s \n", result->h_name);
printf(" ip : %s \n", inet_ntop(result->h_addrtype, result->h_addr, str, sizeof(str)));
}
然后在 sleep(10) 的期间, 调整 app0.com 的 ip映射配置, 结果如下, 可以看到 第二次调用拿到的是最新的 ip
root@ubuntu:~/Desktop/linux/HelloWorld# ./Test31GetHostByName
official : app0.com
ip : 192.168.220.130
official : app0.com
ip : 10.60.50.16
完