免费网站建设入门,潍坊网站建设价,wordpress主题汉化软件,建设企业网站得花多少钱tengine ngx_http_upstream_dynamic_module 动态域名解析功能的代码详细解析 1. 为什么需要域名动态解析2. 配置指令3. 加载模块3. 源码分析3.1 指令解析3.2 upstream负载均衡算法的初始化3.3 upstream负载均衡上下文的初始化3.4 获取upstream的服务器地址3.5 域名解析回调处理… tengine ngx_http_upstream_dynamic_module 动态域名解析功能的代码详细解析 1. 为什么需要域名动态解析2. 配置指令3. 加载模块3. 源码分析3.1 指令解析3.2 upstream负载均衡算法的初始化3.3 upstream负载均衡上下文的初始化3.4 获取upstream的服务器地址3.5 域名解析回调处理 4. 总结 1. 为什么需要域名动态解析
众所周知nginx可以配置成代理后端web服务器的模式运行如下配置upstream{server server1.com;server server2.com;}
但是有一个问题就是这里用到的server1.com 和server2.com的域名是在nginx启动的时候通过域名解析的方式解析成IP并将其存储起来的如果在nginx运行的过程中server1.com或者server2.com域名的解析记录变化了nginx是感知不到的这就导致了无法通过域名解析记录的切换来实现upstream中的real server的切换。不过tengine提供了ngx_http_upstream_dynamic_module来满足这个需求。
在tengine编译的时候添加了ngx_http_upstream_dynamic_module模块之后可以通过dynamic_resolve指令来开启动态域名解析如下upstream{dynamic_resolve fall_back_stable faile_timeout30s;server server1.com;server server2.com;}
2. 配置指令
该模块只有一条配置指令dynamic_resolve [fail_timeoutseconds] [fallbacknext|stale|shutdown]
参数说明
fail_timeout : 指定了当某次DNS请求失败后后续多长的时间内DNS服务依然不可用以减少对无效DNS的查询。fallback : 如果域名解析失败的情况下采用哪种策略进行处理包括 next : 认为当前的server故障继续选择下一个server。stale : 返回旧的解析记录。shutdown : 结束当前的请求返回502。
3. 加载模块
在configure的时候需要添加ngx_http_upstream_dynamic_module来将其编译进来命令如下
./configure --add-modulemodules/ngx_http_upstream_dynamic_module
或者可以将ngx_http_upstream_dynamic_module编译成so库进行加载 命令如下
./configure --add-dynamic-modulemodules/ngx_http_upstream_dynamic_module
nginx的相关配置如下# 如果编译成动态库模式则在nginx的配置文件头部增加这条指令
load_module objs/ngx_http_upstream_dynamic_module.so;upstream backup {ip_hash;dynamic_resolve fall_back_stable faile_timeout30s;server www.baidu.com:80;server 2.2.2.2:80;
}
需要注意的是iphash 和 dynamic_resolve 这两行代码顺序不能交换因为在初始化调用ngx_http_upstream_init_dynamic的时候ngx_http_upstream_dynamic_module需要ngx_http_upstream_module已经设置好相应的负载均衡模块否则nginx启动的时候会出现以下警告信息nginx: [warn] load balancing method redefined in /opt/nginx/conf/nginx.conf:443. 源码分析
3.1 指令解析 static ngx_command_t ngx_http_upstream_dynamic_commands[] {{ ngx_string(dynamic_resolve),NGX_HTTP_UPS_CONF|NGX_CONF_TAKE12|NGX_CONF_NOARGS,ngx_http_upstream_dynamic,0,0,NULL },ngx_null_command
};
从以上代码知道dynamic_resolve指令只能在upstream块里面进行配置一旦nginx发现dynamic_resolve指令就调用ngx_http_upstream_dynamic函数进行配置解析。ngx_http_upstream_dynamic本身还是非常好理解的具体可以看代码ngx_http_upstream_dynamic函数中需要特别说明一下的是dcf-original_init_upstream uscf-peer.init_upstream? uscf-peer.init_upstream: ngx_http_upstream_init_round_robin;uscf-peer.init_upstream ngx_http_upstream_init_dynamic;这段代码的意思是保存ngx_http_upstream_module设置的init_upstream函数指针并用ngx_http_upstream_dynamic_module模块的ngx_http_upstream_init_dynamic函数来代替。
这个就是我们常用的系统钩子函数的方法。这样子当nginx需要初始化upstream负载均衡算法的时候就会转而调用ngx_http_upstream_init_dynamic进行初始化。3.2 upstream负载均衡算法的初始化
下面来分析ngx_http_upstream_init_dynamic函数逻辑这个函数会在nginx初始化的时候被回调用于初始化upstream负载均衡上下文static ngx_int_t
ngx_http_upstream_init_dynamic(ngx_conf_t *cf,ngx_http_upstream_srv_conf_t *us)
{ngx_uint_t i;ngx_http_upstream_dynamic_srv_conf_t *dcf;ngx_http_upstream_server_t *server;ngx_str_t host;ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf-log, 0,init dynamic resolve);dcf ngx_http_conf_upstream_srv_conf(us,ngx_http_upstream_dynamic_module);/** Keep one static address for each server to resolve name only one* time. And server[].addrs should not be used in this case.*//* 对于每个upstream中用域名配置的server强制将其IP地址数量设置为1 */if (us-servers) {server us-servers-elts;for (i 0; i us-servers-nelts; i) {host server[i].host;if (ngx_inet_addr(host.data, host.len) INADDR_NONE) {if (server[i].naddrs 1) {server[i].naddrs 1;}}}}/* 调用原始的init_upstream函数进行初始化 */if (dcf-original_init_upstream(cf, us) ! NGX_OK) {return NGX_ERROR;}/* 如果upstream中配置的server都不是域名形式给出的那么禁用本模块即设置dcf-enabled 0*/if (us-servers) {server us-servers-elts;for (i 0; i us-servers-nelts; i) {host server[i].host;if (ngx_inet_addr(host.data, host.len) INADDR_NONE) {break;}}if (i us-servers-nelts) {dcf-enabled 0;return NGX_OK;}}/* 再次拦截peer.init回调函数, 用于在请求进入的时候在进行负载均衡的前进行回调 */dcf-original_init_peer us-peer.init;us-peer.init ngx_http_upstream_init_dynamic_peer;dcf-enabled 1;return NGX_OK;
}
3.3 upstream负载均衡上下文的初始化
这个初始化过程是在请求过来的时候进行的在以上ngx_http_upstream_init_dynamic函数里面设置了拦截函数ngx_http_upstream_init_dynamic_peer所以程序会运行到ngx_http_upstream_init_dynamic_peer函数里面来。static ngx_int_t
ngx_http_upstream_init_dynamic_peer(ngx_http_request_t *r,ngx_http_upstream_srv_conf_t *us)
{ngx_http_upstream_dynamic_peer_data_t *dp;ngx_http_upstream_dynamic_srv_conf_t *dcf;ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r-connection-log, 0,init dynamic peer);dcf ngx_http_conf_upstream_srv_conf(us,ngx_http_upstream_dynamic_module);dp ngx_palloc(r-pool, sizeof(ngx_http_upstream_dynamic_peer_data_t));if (dp NULL) {return NGX_ERROR;}/* 调用原始的init_peer函数进行负载均衡上下文的初始化 */if (dcf-original_init_peer(r, us) ! NGX_OK) {return NGX_ERROR;}/* 拦截peer.get和peer.free函数如果开启了ssl则同时需要拦截peer.set_session和peer.save_session*/dp-conf dcf;dp-upstream r-upstream;dp-data r-upstream-peer.data;dp-original_get_peer r-upstream-peer.get;dp-original_free_peer r-upstream-peer.free;dp-request r;r-upstream-peer.data dp;r-upstream-peer.get ngx_http_upstream_get_dynamic_peer;r-upstream-peer.free ngx_http_upstream_free_dynamic_peer;#if (NGX_HTTP_SSL)dp-original_set_session r-upstream-peer.set_session;dp-original_save_session r-upstream-peer.save_session;r-upstream-peer.set_session ngx_http_upstream_dynamic_set_session;r-upstream-peer.save_session ngx_http_upstream_dynamic_save_session;
#endifreturn NGX_OK;
}3.4 获取upstream的服务器地址
peer.get 被拦截后nginx在调用ngx_event_connect_peer发起向上游服务器进行连接的时候会执行以下代码rc pc-get(pc, pc-data);if (rc ! NGX_OK) {return rc;}这里pc-get指向的正好就是ngx_http_upstream_get_dynamic_peer。
pc-get这个调用的目的就是要求负载均衡模块把上游服务器的IP和端口设置到pc-sockaddr中。static ngx_int_t
ngx_http_upstream_get_dynamic_peer(ngx_peer_connection_t *pc, void *data)
{ngx_http_upstream_dynamic_peer_data_t *bp data;ngx_http_request_t *r;ngx_http_core_loc_conf_t *clcf;ngx_resolver_ctx_t *ctx, temp;ngx_http_upstream_t *u;ngx_int_t rc;ngx_http_upstream_dynamic_srv_conf_t *dscf;ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc-log, 0,get dynamic peer);/* The get function will be called twice if* one host is resolved into an IP address.* (via ngx_http_upstream_connect if resolved successfully)** So here we need to determine if it is the first* time call or the second time call. *//* 在域名resolve完成后已经设置好了目标upstream的地址 */if (pc-resolved NGX_HTTP_UPSTREAM_DR_OK) {return NGX_OK;}dscf bp-conf;r bp-request;u r-upstream;if (pc-resolved NGX_HTTP_UPSTREAM_DR_FAILED) {ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc-log, 0,resolve failed! fallback: %ui, dscf-fallback);switch (dscf-fallback) {/* 解析失败返回老的解析记录 */case NGX_HTTP_UPSTREAM_DYN_RESOLVE_STALE:return NGX_OK;/* 解析失败shutdown模式直接结束请求返回502 */case NGX_HTTP_UPSTREAM_DYN_RESOLVE_SHUTDOWN:ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);return NGX_YIELD;/* 让负载均衡逻辑找下一个上游server */default:/* default fallback action: check next upstream */return NGX_DECLINED;}return NGX_DECLINED;}/* 这里判断如果在最近一次域名解析失败的时间内则不再请求域名解析,因为当前请求第一次进入到这个函数的时候pc-resolved NGX_HTTP_UPSTREAM_DR_INIT但是dscf-fail_check可能因为最近有一次域名解析失败而设置了失败的时间所以会进入到这段代码的逻辑中*/if (dscf-fail_check (ngx_time() - dscf-fail_check dscf-fail_timeout)){ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc-log, 0,in fail timeout period, fallback: %ui, dscf-fallback);switch (dscf-fallback) {/* 直接调用设定的负载均衡模块返回对应的upstream server的ip地址 */case NGX_HTTP_UPSTREAM_DYN_RESOLVE_STALE:return bp-original_get_peer(pc, bp-data);/* shutdown模式直接结束请求返回502 */case NGX_HTTP_UPSTREAM_DYN_RESOLVE_SHUTDOWN:ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);return NGX_YIELD;/* next模式在本函数第一次被调用的时候也是直接调用设定的负载均衡模块返回对应的upstream server的ip地址 */default:/* default fallback action: check next upstream, still need* to get peer in fail timeout period*/return bp-original_get_peer(pc, bp-data);}return NGX_DECLINED;}/* NGX_HTTP_UPSTREAM_DYN_RESOLVE_INIT, ask balancer *//* 通过负载均衡获取到使用哪个server然后对该server进行域名解析 */rc bp-original_get_peer(pc, bp-data);if (rc ! NGX_OK) {return rc;}/* resolve name */if (pc-host NULL) {ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc-log, 0,load balancer doesnt support dyn resolve!);return NGX_OK;}/* host是ip地址直接连接不需要解析 */if (ngx_inet_addr(pc-host-data, pc-host-len) ! INADDR_NONE) {ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc-log, 0,host is an IP address, connect directly!);return NGX_OK;}clcf ngx_http_get_module_loc_conf(r, ngx_http_core_module);if (clcf-resolver NULL) {ngx_log_error(NGX_LOG_ERR, r-connection-log, 0,resolver has not been configured!);return NGX_OK;}/* 分配并设置异步域名调用的上下文 */temp.name *pc-host;ctx ngx_resolve_start(clcf-resolver, temp);if (ctx NULL) {ngx_log_error(NGX_LOG_ERR, r-connection-log, 0,resolver start failed!);return NGX_OK;}if (ctx NGX_NO_RESOLVER) {ngx_log_error(NGX_LOG_ERR, r-connection-log, 0,resolver started but no resolver!);return NGX_OK;}ctx-name *pc-host;/* TODO remove */// ctx-type NGX_RESOLVE_A;/* END */ctx-handler ngx_http_upstream_dynamic_handler;ctx-data bp;ctx-timeout clcf-resolver_timeout;/* 发起异步域名解析, 解析完成后会回调函数ngx_http_upstream_dynamic_handler*/u-dyn_resolve_ctx ctx;if (ngx_resolve_name(ctx) ! NGX_OK) {ngx_log_error(NGX_LOG_ERR, pc-log, 0,resolver name failed!\n);u-dyn_resolve_ctx NULL;return NGX_OK;}/* tengine 定制的返回标记即直接返回等待epoll事件发生待域名解析完成后将重新调用ngx_http_upstream_connectngx_event_connect_peer的时候还会进入到本ngx_http_upstream_get_dynamic_peer,以便返回目标服务器地址 */return NGX_YIELD;
}3.5 域名解析回调处理 static void
ngx_http_upstream_dynamic_handler(ngx_resolver_ctx_t *ctx)
{ngx_http_request_t *r;ngx_http_upstream_t *u;ngx_peer_connection_t *pc;
#if defined(nginx_version) nginx_version 1005008socklen_t socklen;struct sockaddr *sockaddr, *csockaddr;
#elsestruct sockaddr_in *sin, *csin;
#endifin_port_t port;ngx_str_t *addr;u_char *p;size_t len;ngx_http_upstream_dynamic_srv_conf_t *dscf;ngx_http_upstream_dynamic_peer_data_t *bp;bp ctx-data;r bp-request;u r-upstream;pc u-peer;dscf bp-conf;if (ctx-state) {/* 解析失败 */ngx_log_error(NGX_LOG_ERR, r-connection-log, 0,%V could not be resolved (%i: %s),ctx-name, ctx-state,ngx_resolver_strerror(ctx-state));/* 设置解析失败的时间 */dscf-fail_check ngx_time();pc-resolved NGX_HTTP_UPSTREAM_DR_FAILED;} else {/* dns query ok */
#if (NGX_DEBUG) /* 这里只是debug模式下打印解析到的IP地址列表 */{u_char text[NGX_SOCKADDR_STRLEN];ngx_str_t addr;ngx_uint_t i;addr.data text;for (i 0; i ctx-naddrs; i) {addr.len ngx_sock_ntop(ctx-addrs[i].sockaddr, ctx-addrs[i].socklen,text, NGX_SOCKADDR_STRLEN, 0);ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r-connection-log, 0,name was resolved to %V, addr);}}
#endifdscf-fail_check 0;
#if defined(nginx_version) nginx_version 1005008csockaddr ctx-addrs[0].sockaddr; /* 取解析到的第一个地址 */socklen ctx-addrs[0].socklen;/* 如果peer_connection中的地址和解析出来的地址一致就直接返回OK否则要重新分配一个sockaddr最后赋值给peer_connection*/if (ngx_cmp_sockaddr(pc-sockaddr, pc-socklen, csockaddr, socklen, 0) NGX_OK){pc-resolved NGX_HTTP_UPSTREAM_DR_OK;goto out;}sockaddr ngx_pcalloc(r-pool, socklen);if (sockaddr NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}ngx_memcpy(sockaddr, csockaddr, socklen);port ngx_inet_get_port(pc-sockaddr);switch (sockaddr-sa_family) {
#if (NGX_HAVE_INET6)case AF_INET6:((struct sockaddr_in6 *) sockaddr)-sin6_port htons(port);break;
#endifdefault: /* AF_INET */((struct sockaddr_in *) sockaddr)-sin_port htons(port);}p ngx_pnalloc(r-pool, NGX_SOCKADDR_STRLEN);if (p NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}len ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 1);addr ngx_palloc(r-pool, sizeof(ngx_str_t));if (addr NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}addr-data p;addr-len len;pc-sockaddr sockaddr; /* 设置upstream服务器目标地址 */pc-socklen socklen;pc-name addr;#else/* for nginx older than 1.5.8 *//* 以下仅仅针对 1.5.8 版本以前的代码 */sin ngx_pcalloc(r-pool, sizeof(struct sockaddr_in));if (sin NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}ngx_memcpy(sin, pc-sockaddr, pc-socklen);/* only the first IP addr is used in version 1 */csin (struct sockaddr_in *) ctx-addrs[0].sockaddr;if (sin-sin_addr.s_addr csin-sin_addr.s_addr) {pc-resolved NGX_HTTP_UPSTREAM_DR_OK;goto out;}sin-sin_addr.s_addr csin-sin_addr.s_addr;len NGX_INET_ADDRSTRLEN sizeof(:65535) - 1;p ngx_pnalloc(r-pool, len);if (p NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}port ntohs(sin-sin_port);len ngx_inet_ntop(AF_INET, sin-sin_addr.s_addr,p, NGX_INET_ADDRSTRLEN);len ngx_sprintf(p[len], :%d, port) - p;addr ngx_palloc(r-pool, sizeof(ngx_str_t));if (addr NULL) {ngx_http_upstream_finalize_request(r, u,NGX_HTTP_INTERNAL_SERVER_ERROR);return;}addr-data p;addr-len len;pc-sockaddr (struct sockaddr *) sin;pc-socklen sizeof(struct sockaddr_in);pc-name addr;
#endifngx_log_debug1(NGX_LOG_DEBUG_HTTP, r-connection-log, 0,name was resolved to %V, pc-name);pc-resolved NGX_HTTP_UPSTREAM_DR_OK;}out:ngx_resolve_name_done(ctx); /* 释放域名解析上下文 */u-dyn_resolve_ctx NULL;/* 这里重新发起上游服务器的连接, 会重新进入ngx_event_connect_peer函数并在ngx_event_connect_peer函数里面重新调用ngx_http_upstream_get_dynamic_peer*/ngx_http_upstream_connect(r, u);
}4. 总结
ngx_http_upstream_dynamic_module 主要采用了钩子函数的方式拦截了负载均衡模块的对应处理函数进行了动态域名解析的处理实现上还是非常巧妙的。
虽然开启动态解析虽然会对系统性能或多或少有一些影响但是由于它利用了nginx 的异步域名解析的能力同时nginx本身具备域名解析的cahce能力而且本模块在解释失败的时候还会有fail_timeout的保护机制所以性能上的影响基本上是可以忽略的。