nginx 的 ngx_http_upstream_dynamic_module 动态域名解析功能的使用和源码详解

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_timeout=30s;server server1.com;server server2.com;}

2. 配置指令

该模块只有一条配置指令:

dynamic_resolve [fail_timeout=seconds] [fallback=next|stale|shutdown]

参数说明:

  • fail_timeout : 指定了当某次DNS请求失败后,后续多长的时间内DNS服务依然不可用,以减少对无效DNS的查询。
  • fallback : 如果域名解析失败的情况下采用哪种策略进行处理,包括:
    • next : 认为当前的server故障,继续选择下一个server。
    • stale : 返回旧的解析记录。
    • shutdown : 结束当前的请求,返回502。

3. 加载模块

在configure的时候需要添加ngx_http_upstream_dynamic_module来将其编译进来,

命令如下:

./configure --add-module=modules/ngx_http_upstream_dynamic_module

或者可以将ngx_http_upstream_dynamic_module编译成so库进行加载,
命令如下:

./configure --add-dynamic-module=modules/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_timeout=30s;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:44

3. 源码分析

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 doesn't 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_connect,ngx_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的保护机制,所以性能上的影响基本上是可以忽略的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/666716.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BAPI_PRODORD_CREATE-创建生产订单BAPI测试

目录 实现过程和笔记完整程序 实现过程和笔记 完整程序 *&---------------------------------------------------------------------* *& Report z_test_bapi_prodord_create_lhy *&---------------------------------------------------------------------* *&am…

【Java万花筒】Java图形库探秘:创意编程、数据可视化与用户界面设计

图形化未来&#xff1a;Java图形库全面解析与应用指南 前言 在Java开发的世界中&#xff0c;图形处理一直是一个关键领域&#xff0c;涉及从创意编程到数据可视化再到用户界面设计的多个方面。本文将深入探讨几个领域内颇具代表性的Java图形库&#xff0c;为开发者提供了解和…

广州产业园神秘顾客考察指标有哪些

随着经济的发展和产业结构的升级&#xff0c;产业园在推动区域经济增长中的地位日益凸显。为了确保产业园的持续发展与竞争力&#xff0c;对其进行神秘顾客考察显得尤为重要。产业园神秘顾客考察包括以下几点&#xff1a; 一、基础设施建设 交通便捷性&#xff1a;评估产业园…

【QT+QGIS跨平台编译】之二十五:【geos+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、geos介绍二、文件下载三、文件分析四、pro文件4.1 geos pro文件4.2 geos_c pro文件五、编译实践一、geos介绍 GEOS(Geometry Engine - Open Source)是一个开源的C++库,用于处理地理空间数据和进行地理空间分析。它提供了一系列的几何操作和算法,能够进行空间…

docker入门之更新应用程序

指南/开始使用/入门指南/第 3 部分&#xff1a;更新应用程序 更新应用程序 在 第 2 部分中&#xff0c;您容器化了一个待办事项应用程序。在此部分中&#xff0c;您将更新应用程序和映像。您还将学习如何停止和删除容器。 更新源代码 在以下步骤中&#xff0c;您将在没有任…

视频编辑场景手绘白板解决方案

传统的视频编辑方式已经不能满足现代企业的多元化需求&#xff0c;美摄科技推出了手绘白板方案&#xff0c;为企业提供了一种全新的直播和视频编辑方式&#xff0c;让手绘内容成为视频的一部分&#xff0c;增强了互动性和视觉效果。 一、手绘白板方案的优势 1、实时手绘&…

《PCI Express体系结构导读》随记 —— 第II篇 第4章 PCIe总线概述(6)

接前一篇文章&#xff1a;《PCI Express体系结构导读》随记 —— 第II篇 第4章 PCIe总线概述&#xff08;5&#xff09; 4.1 PCIe总线的基础知识 与PCI总线不同&#xff0c;PCIe总线使用端到端的连接方式&#xff0c;在一条PCIe链路的两端只能各连接一个设备&#xff0c;这两个…

RflySim | 定点位置控制器设计实验四

RflySim| 定点位置控制器设计实验四 01 设计实验 1.建立位置控制通道的传递函数模型 使用MATLAB “ControlSystemDesigner”设计校正控制器,使得加入校正环节后系统速度控制环阶跃响应稳态误差 &#xff0c;相位裕度>75截止频率>2.0rad/s。位置控制环截止频率>1rad/…

《Vue3 基础知识》 使用 GoGoCod 升级到Vue3+ElementPlus 适配处理

此篇为 《Vue2ElementUI 自动转 Vue3ElementPlus&#xff08;GoGoCode&#xff09;》 的扩展&#xff01; Vue3 适配 Vue3 不兼容适配 Vue 3 迁移指南 在此&#xff0c;本章只讲述项目或组件库中遇到的问题&#xff1b; Vue3 移除 o n &#xff0c; on&#xff0c; on&#…

蓝桥杯备战(AcWing算法基础课)-高精度-除-高精度

目录 前言 1 题目描述 2 分析 2.1 关键代码 2.2 关键代码分析 3 代码 前言 详细的代码里面有自己的部分理解注释&#xff0c;注意该博客内容实现的高精度-除-高精度是利用前面写的高精度-减-高精度实现的的时间复杂度是O&#xff08;n^2&#xff09; 1 题目描述 给定两…

第十一章[文件系统]:11.2:文件的复制/删除/移动

一,相关文档: os模块: os --- 多种操作系统接口 — Python 3.12.1 文档源代码: Lib/os.py 本模块提供了一种使用与操作系统相关的功能的便捷式途径。 如果你只是想读写一个文件,请参阅 open() ,如果你想操作文件路径,请参阅 os.path 模块,如果你想读取通过命令行给出的所…

服务器学习

云服务器通常是通过多台物理服务器协同工作来提供的。云服务提供商使用大规模的数据中心&#xff0c;这些数据中心包含许多物理服务器。这些物理服务器上运行着虚拟化技术&#xff0c;允许它们被分割成多个虚拟服务器实例。 当用户请求创建一个云服务器时&#xff0c;云服务提…

【5G SA流程】5G SA下终端完整注册流程介绍

博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持! 博主链接 本人就职于国际知名终端厂商,负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。 博客内容主要围绕: 5G/6G协议讲解 …

阿里云计算巢是什么?计算巢服务详细介绍

什么是计算巢服务&#xff1f;阿里云计算巢服务是一个开放给服务商和用户的服务管理PaaS平台&#xff0c;计算巢服务为服务商和用户提供了高效、便捷、安全的服务使用体验&#xff0c;服务商能更好地在阿里云上部署、交付和管理服务&#xff0c;用户能集中管理在阿里云上订阅的…

Postgresql PostGIS扩展

PostGIS是一个开源的PostgreSQL扩展&#xff0c;用于提供地理信息系统&#xff08;GIS&#xff09;功能。通过添加对空间数据类型、空间索引和空间函数的支持&#xff0c;PostGIS将PostgreSQL数据库转换为强大的空间数据库。在PostgreSQL中&#xff0c;PostGIS提供了对空间数据…

Python算法题集_搜索二维矩阵II

Python算法题集_搜索二维矩阵II 题41&#xff1a;搜索二维矩阵II1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【双层循环】2) 改进版一【行尾检测】3) 改进版二【对角线划分】 4. 最优算法 本文为Python算法题集之一的代码示例 题41&#xf…

var、let、const 的区别及用法示例

在 JavaScript 中&#xff0c;var、let 和 const 是用于声明变量的关键字&#xff0c;它们有一些区别和适用场景。下面是它们的主要区别和用法示例&#xff1a; var var 是在 ES5 中引入的变量声明关键字。具有函数作用域&#xff0c;而不是块级作用域。变量可以被重复声明&…

C++(20):is_invocable

C++20引入了is_invocable,用于判定某个可执行类型(函数,lambda,函数对象等)的函数签名 1.is_invocable/is_invocable_v检查函数参数类型 #include <type_traits> #include <iostream> using namespace std;auto func1(char) -> int (*)() {return nullptr…

这一年让我印象深刻的bug -- 让sql选择更合理的执行过程

1 业务场景 客户需要一个报表统计工单的各种信息&#xff0c;于是我们利用公司报表平台做了一个报表导出功能。可是当我们准备上ver环境时测试反应报表导出虽然数据正确但性能不能达标&#xff0c;导出非常缓慢。于是我就开始分析报表sql。 2 问题分析 相信有过开发经验的同学…

代码随想录训练营第三十四天|860.柠檬水找零406.根据身高重建队列

860.柠檬水找零 局部最优&#xff1a;遇到账单20&#xff0c;优先消耗美元10&#xff0c;完成本次找零。全局最优&#xff1a;完成全部账单的找零。 class Solution { public:bool lemonadeChange(vector<int>& bills) {int five 0;int ten 0;for(int bill:bills…