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的保护机制,所以性能上的影响基本上是可以忽略的。
相关文章:
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 域名解析回调处理…...

前端vue/react项目压缩图片工具@yireen/squoosh-browser
想要在前端项目中压缩图片,然后再上传到后端保存,就需要一个压缩工具的帮助,暂时有两个依赖库可以选择:image-conversion和yireen/squoosh-browser,看了官方仓库地址和更新时间等详情,发现还是yireen/squoo…...
悬而未决:daterangepicker设置默认选择日期时间后点确认无值的BUG
daterangepicker有两个BUG: 1、startDate和endDate对设置默认日期没有问题,但对设置默认时间的支持有BUG!比如设为 moment().add( 1, day ).hours(8).minutes(20).seconds(0), //如果现在是9点,则设置的时间8:20因为比…...

composer常用命令
查看全局配置信息 composer config -gl 设置镜全局像地址 composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ 去掉-g,即表示只有当前项目使用该镜像 批量安装composer项目依赖 composer install 执行该命令后,会读取当…...

2024年1月27日~2月2日周报
一、前言 上周主要完成了SeisInvNet加强版论文的阅读,并尝试跑了一下代码。 本周阅读师兄的论文《DD-Net》,并尝试思考新的点子修改网络架构。 二、DD-Net阅读情况 标题:Dual decoder network with curriculumlearning for full waveform in…...
红黑树,以及其在C++的set、map等数据结构中应用
红黑树介绍: 红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,它在插入和删除操作后通过一系列的旋转和着色操作来维持平衡。红黑树的命名来自于节点上的额外颜色属性,每个节点要么是红色,要么是黑色。 红…...

C++(11)——内存管理
C内存分布 我们先看一段代码以及相关问题。 这道题的答案是多少呢? 答案在这里哦,看一下有没有问题呀。如果这么简单的题做错了,怕不是要被电击一下。 C内存管理方式 我们知道C语言中动态内存管理的方式是 malloc realloc calloc free 这几…...

《C++ Primer Plus》《3、数据处理》
文章目录 0 前言1 简单变量1.1变量名1.2整型1.3整型short,int,long和long long1.4无符号类型1.5选择整型类型1.6整型字面值1.7C如何确定常量的类型1.8char类型:字符和小整数1.9bool类型 2 cost限定符3浮点数3.1书写浮点数3.2浮点类型3.3浮点常量3.4浮点数的优缺点 4…...

Java 正则匹配sql
文章目录 正则匹配sql表名称insert intoupdate 正则表达式什么时候要加^$ 在线正则校验 正则匹配sql表名称 insert into insert into PING_TABLE (CODE, NAME) VALUES(0, 待提交),(1, 审核中),(2, 审核通过),(3, 已驳回); regex -> insert\sinto\s(\w)\s*\(?update upda…...
服务器入门
入门服务器管理涉及到一系列基础概念和技能,这包括操作系统、网络配置、安全性、远程访问等。以下是一些建议,可以帮助你开始学习服务器管理: ### 1. **选择合适的操作系统:** - 大多数服务器使用 Linux 操作系统,…...

云端录制直播流视频,上传云盘
前言 哪一天我心血来潮,想把我儿子学校的摄像头视频流录制下来,并保存到云盘上,这样我就可以在有空的时候看看我儿子在学校干嘛。想到么就干,当时花了一些时间开发了一个后端服务,通过数据库配置录制参数,…...

【靶场实战】Pikachu靶场XSS跨站脚本关卡详解
Nx01 系统介绍 Pikachu是一个带有漏洞的Web应用系统,在这里包含了常见的web安全漏洞。 如果你是一个Web渗透测试学习人员且正发愁没有合适的靶场进行练习,那么Pikachu可能正合你意。 Nx02 XSS跨站脚本概述 Cross-Site Scripting 简称为“CSS”ÿ…...

蓝桥杯每日一题-----数位dp
前言 今天浅谈一下数位dp的板子,我最初接触到数位dp的时候,感觉数位dp老难了,一直不敢写,最近重新看了一些数位dp,发现没有想象中那么难,把板子搞会了,变通也会变的灵活的多! 引入…...

sklearn 计算 tfidf 得到每个词分数
from sklearn.feature_extraction.text import TfidfVectorizer# 语料库 可以换为其它同样形式的单词 corpus [list(range(-5, 5)),list(range(-6,4)),list(range(12)),list(range(13))]# corpus [ # [Two, wrongs, don\t, make, a, right, .], # [The, pen, is, might…...

Qt拖拽事件,实现控件内项的相互拖拽
文章目录 1拖拽演示2 步骤3 实现 这里主要以QTableview控件为例,实现表格内数据的相互拖拽。 1拖拽演示 2 步骤 自定以QTableView类,在自定义类中重写拖拽事件: void dropEvent(QDropEvent *event); void dragEnterEvent(QDragEnterEvent *…...

基于MATLAB实现的OFDM仿真调制解调,BPSK、QPSK、4QAM、16QAM、32QAM,加性高斯白噪声信道、TDL瑞利衰落信道
基于MATLAB实现的OFDM仿真调制解调,BPSK、QPSK、4QAM、16QAM、32QAM,加性高斯白噪声信道、TDL瑞利衰落信道 相关链接 OFDM中的帧(frame)、符号(symbol)、子载波(subcarriers)、导频…...

Redis核心技术与实战【学习笔记】 - 21.Redis实现分布式锁
概述 在《20.Redis原子操作》我们提到了应对并发问题时,除了原子操作,还可以通过加锁的方式,来控制并发写操作对共享数据的修改,从而保证数据的正确性。 但是,Redis 属于分布式系统,当有多个客户端需要争…...

17.Golang channel的基本定义及使用
目录 概述实践无缓冲 channel代码结果 缓冲 channel代码结果 channel的关闭特点代码结果range代码结果 select channel代码结果 结束 概述 此篇文章介绍 channel 的用法 无缓冲 channel缓冲 channelchannel的关闭特点range channelselect channel 每一种,配上完整…...

Linux - iptables 防火墙
一. 安全技术和防火墙 1.安全技术 入侵检测系统(Intrusion Detection Systems):特点是不阻断任何网络访问,量化、定位来自内外网络的威胁情况,主要以提供报警和事后监督为主,提供有针对性的指导措施和安全…...

如何在FBX剔除Lit.shader依赖
1)如何在FBX剔除Lit.shader依赖 2)Unity出AAB包(PlayAssetDelivery)模式下加载资源过慢问题 3)如何在URP中正确打出Shader变体 4)XLua打包Lua文件粒度问题 这是第371篇UWA技术知识分享的推送,精…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...