当前位置: 首页 > news >正文

详解 APISIX Lua 动态调试插件 inspect

作者罗锦华,API7.ai 技术专家/技术工程师,开源项目 pgcat,lua-resty-ffi,lua-resty-inspect 的作者。

原文链接

为什么需要 Lua 动态调试插件?

Apache APISIX 有很多 Lua 代码,如何在运行时不触碰源代码的情况下,检查代码里面的变量值?

修改 Lua 源码来调试有如下缺点:

  • 生产环境不允许也不应该修改源码
  • 修改源码需要 reload,使得业务功能失效
  • 容器环境难以修改源码
  • 产生的临时代码容易忘记回滚,导致维护问题

很多时候我们不仅仅需要在函数开始或结束的时候去检查变量,而且需要在满足一定条件,例如某个循环体被循环到了一定次数, 或者某个条件判断为真的时候我们才查看变量值,并且也不仅仅是简单打印变量值,有时候还可能需要将相关信息发送到外围系统。 并且,这个过程如何做到动态化呢?而且,开启调试后,能否不影响程序运行的性能呢?

Lua 动态调试插件就是辅助你完成以上需求的插件,该插件被命名为 inspect 插件。

  • 断点处理可定制
  • 断点设置动态化
  • 多个断点
  • 断点可被定义为只生效一次
  • 可控制性能影响范围

插件原理

它充分利用了 Lua 提供的 Debug API 来实现功能。解释器模式执行的每一个字节码都可以对应到它所属的文件以及行号,我们只需要判断行号是否等于期望值,然后执行我们定义的断点函数,对该行对应的上下文信息,包括 upvalue ,局部变量,还有一些元信息,例如堆栈,进行处理即可。

APISIX 使用的是 Lua 的 JIT 实现:LuaJIT,很多热点代码路径会被编译成机器码执行,而它们是不受 Debug API 的影响的,所以我们需要在开启断点前清空 JIT 缓存。关键就在这里了,我们可以选择只清空某个具体 Lua 函数的 JIT 缓存,减小对全局性能的影响。一个程序运行起来,会有很多 JIT 编译代码块,在 LuaJIT 里被称为 trace,这些 trace 跟 Lua 函数是关联起来的,一个 Lua 函数可能包括多个 trace ,指代函数内不同的热点路径。

对于全局函数、模块级别的函数,我们可以指定它们的函数对象,清空它们的 JIT 缓存。但是如果某行号对应的是其他函数类型,例如匿名函数,我们无法在全局获取函数的对象,那么只能清空所有 JIT 缓存了。在调试开启期间,新的 trace 无法被生成,但是已有的未被清理的 trace 还继续运行,所以只要控制的好,程序性能不会受到影响,因为一个已经运行很久的线上系统,基本不会有新 trace 的生成。当调试结束后,也就是所有断点都被撤销后,系统会恢复正常的 JIT 模式,被清理掉的 JIT 缓存,一旦重新进入热点,会被重新生成 trace。

安装与配置

该插件默认被启用。

配置好 conf/confg.yaml 启用插件:

plugins:
...- inspectplugin_attr:inspect:delay: 3hooks_file: "/usr/local/apisix/plugin_inspect_hooks.lua"

插件默认每隔3秒从文件 /usr/local/apisix/plugin_inspect_hooks.lua 读取断点定义,想调试就编辑该文件即可。

建议创建软链接到该路径,这样比较方便地存档不同历史版本的断点文件。

注意每次该文件的更改时间有变,插件会清空所有旧的断点,并且启用断点文件所定义的所有新断点。断点将在所有工作进程生效。

一般情况下不需要删除该文件,因为定义断点的时候,可以定义什么时候撤销断点。

删除文件会取消所有工作进程的所有断点。

断点的启停都会通过 WARN 日志级别打印日志。

定义断点

require("apisix.inspect.dbg").set_hook(file, line, func, filter_func)
  • file 文件名,可以是任何无歧义的文件名部分,可包含路径
  • line 文件的行号,注意断点跟行号是密切挂钩的,所以如果代码变了,行号就得跟着变。
  • func 要清除哪个函数的 trace,如果为 nil,则清除 luajit vm 里面所有 trace
  • filter_func 处理该断点的自定义 Lua 函数
    • 函数的入参为一个 table,包含以下内容
      • finfo: debug.getinfo(level, "nSlf")的返回值
      • uv: upvalues hash table
      • vals: local variables hash table
    • 函数的返回值为 true,则该断点自动注销,返回为 false,则该断点继续生效

例子:

local dbg = require "apisix.inspect.dbg"dbg.set_hook("limit-req.lua", 88, require("apisix.plugins.limit-req").access,
function(info)ngx.log(ngx.INFO, debug.traceback("foo traceback", 3))ngx.log(ngx.INFO, dbg.getname(info.finfo))ngx.log(ngx.INFO, "conf_key=", info.vals.conf_key)return true
end)dbg.set_hook("t/lib/demo.lua", 31, require("t.lib.demo").hot2, function(info)if info.vals.i == 222 thenngx.timer.at(0, function(_, body)local httpc = require("resty.http").new()httpc:request_uri("http://127.0.0.1:9080/upstream1", {method = "POST",body = body,})end, ngx.var.request_uri .. "," .. info.vals.i)return trueendreturn false
end)--- more breakpoints ...

注意到 demo 这个断点,它将一些信息整理后发送到外部的服务器上,使用的 resty.http 库是基于 cosocket 的异步库。

凡是调用 OpenResty 的异步 API ,必须使用 timer 延迟发送,因为在断点上执行函数是同步阻塞的,不会再返回到 nginx 的主程序做异步处理,所以需要延后发送。

使用示例

根据请求体的内容来决定路由

假设我们有个需求,如何设置让某个路由仅接受请求体中携带了 APISIX: 666 的 POST 请求?

路由配置里面有个 vars 字段,是用来检查 nginx 变量的值来判断是否匹配该路由的, 而 $request_body 则是 nginx 提供的变量,包含请求体的值,那我们可以利用这个变量来实现我们的需求?

让我们来尝试一下,先配置一下路由:

curl http://127.0.0.1:9180/apisix/admin/routes/var_route \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{"uri": "/anything","methods": ["POST"],"vars": [["request_body", "~~", "APISIX: 666"]],"upstream": {"type": "roundrobin","nodes": {"httpbin.org": 1}}
}'

然后我们尝试一下:

curl http://127.0.0.1:9080/anything
{"error_msg":"404 Route Not Found"}curl -i http://127.0.0.1:9080/anything -X POST -d 'hello, APISIX: 666.'
HTTP/1.1 404 Not Found
Date: Thu, 05 Jan 2023 03:53:35 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.0.0{"error_msg":"404 Route Not Found"}

奇怪,为什么匹配不上这个路由呢?

我们再查看一下 NGINX 对该变量的文档说明:

The variable’s value is made available in locations processed by the proxy_pass, fastcgi_pass, uwsgi_pass, and scgi_pass directives when the request body was read to a memory buffer.

也就是说,使用该变量前需要先读取 request body 。

那是不是匹配路由的时候,这个变量为空呢?我们可以使用 inspect 插件来验证一下。

我们找到了匹配路由的代码行:

apisix/init.lua

...
api_ctx.var.request_uri = api_ctx.var.uri .. api_ctx.var.is_args .. (api_ctx.var.args or "")router.router_http.match(api_ctx)local route = api_ctx.matched_route
if not route then
...

我们就在 515 行,也就是 router.router_http.match(api_ctx) 这行验证一下变量 request_body 吧。

设置断点

编辑文件 /usr/local/apisix/example_hooks.lua

local dbg = require("apisix.inspect.dbg")
dbg.set_hook("apisix/init.lua", 515, require("apisix").http_access_phase, function(info)core.log.warn("request_body=", info.vals.api_ctx.var.request_body)return true
end)

创建软链接到断点文件路径:

ln -sf /usr/local/apisix/example_hooks.lua /usr/local/apisix/plugin_inspect_hooks.lua

检查日志看看确认断点生效:

2023/01/05 12:02:43 [warn] 1890559#1890559: *15736 [lua] init.lua:68: setup_hooks():
set hooks: err: true, hooks: ["apisix\/init.lua#515"], context: ngx.timer

再触发一次路由匹配:

curl -i http://127.0.0.1:9080/anything -X POST -d 'hello, APISIX: 666.'

查看日志:

2023/01/05 12:02:59 [warn] 1890559#1890559: *16152
[lua] [string "local dbg = require("apisix.inspect.dbg")..."]:39:
request_body=nil, client: 127.0.0.1, server: _,
request: "POST /anything HTTP/1.1", host: "127.0.0.1:9080"

果然,request_body 是空的!

解决方案

既然我们知道需要读取请求体才能用 request_body 变量,那么我们就不能通过 vars 来做了,那我们可以通过路由里面的 filter_func 字段来实现需求。

curl http://127.0.0.1:9180/apisix/admin/routes/var_route \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{"uri": "/anything","methods": ["POST"],"filter_func": "function(_) return require(\"apisix.core\").request.get_body():find(\"APISIX: 666\") end","upstream": {"type": "roundrobin","nodes": {"httpbin.org": 1}}
}'

验证一下:

curl http://127.0.0.1:9080/anything -X POST -d 'hello, APISIX: 666.'
{"args": {},"data": "","files": {},"form": {"hello, APISIX: 666.": ""},"headers": {"Accept": "*/*","Content-Length": "19","Content-Type": "application/x-www-form-urlencoded","Host": "127.0.0.1","User-Agent": "curl/7.68.0","X-Amzn-Trace-Id": "Root=1-63b64dbd-0354b6ed19d7e3b67013592e","X-Forwarded-Host": "127.0.0.1"},"json": null,"method": "POST","origin": "127.0.0.1, xxx","url": "http://127.0.0.1/anything"
}

问题解决!

打印一些被日志级别屏蔽的日志

生产环境一般不会开启 INFO 级别的日志,但是有时候我们又需要检查一些详细信息,那怎么办呢?

我们一般不会直接设置 INFO 级别然后 reload,因为这样做有两个缺点:

  • 日志太多,影响性能和加大检查难度
  • reload 导致长连接被断开,影响在线流量

一般我们只需要检查具体某个点的日志,例如我们都知道 APISIX 使用 etcd 作为配置分发数据库,那么可否看看什么时候路由配置被增量更新到了数据面呢?更新了什么具体数据呢?

apisix/core/config_etcd.lua

local function sync_data(self)
...log.info("waitdir key: ", self.key, " prev_index: ", self.prev_index + 1)log.info("res: ", json.delay_encode(dir_res, true), ", err: ", err)
...
end

增量同步的lua函数是 sync_data(),但是它是通过 INFO 级别来打印从 etcd watch 到的增量数据的。

那么我们来试一下使用 inspect plugin 来显示一下?只显示路由资源的变化。

编辑 /usr/local/apisix/example_hooks.lua

local dbg = require("apisix.inspect.dbg")
local core = require("apisix.core")
dbg.set_hook("apisix/core/config_etcd.lua", 393, nil, function(info)local filter_res = "/routes"if info.vals.self.key:sub(-#filter_res) == filter_res and not info.vals.err thencore.log.warn("etcd watch /routes response: ", core.json.encode(info.vals.dir_res, true))return trueendreturn false
end)

这个断点处理函数的逻辑很好表达了过滤能力,如果 watch 的 key/routes,以及 err 为空的情况下,就打印 etcd 返回的数据,并且打印一次就够了,就取消断点。

注意 sync_data() 是局部函数,所以无法获取它的引用,我们只能设置 set_hook 的第三个参数为 nil,这样做的副作用就是它会清空所有 trace

上面例子我们已经创建了软链接,所以编辑后保存文件即可。等几秒钟后,断点就会被启用,可观察日志确认。

检查日志,我们可以得到我们需要的信息,而这些信息用 WARN 日志级别打印,并且也显示了我们在数据面获取到 etcd 增量数据的时间。

2023/01/05 14:33:10 [warn] 1890562#1890562: *231311
[lua] [string "local dbg = require("apisix.inspect.dbg")..."]:41:
etcd watch /routes response: {"headers":{"X-Etcd-Index":"24433"},
"body":{"node":[{"value":{"uri":"\/anything",
"plugins":{"request-id":{"header_name":"X-Request-Id","include_in_response":true,"algorithm":"uuid"}},
"create_time":1672898912,"status":1,"priority":0,"update_time":1672900390,
"upstream":{"nodes":{"httpbin.org":1},"hash_on":"vars","type":"roundrobin","pass_host":"pass","scheme":"http"},
"id":"reqid"},"key":"\/apisix\/routes\/reqid","modifiedIndex":24433,"createdIndex":24429}]}}, context: ngx.timer

结论

Lua 动态调试是很重要的辅助功能。我们可以通过 APISIX inspect 插件来做很多事情,例如:

  • 排查问题,定位原因
  • 打印一些被屏蔽的日志,按需获取各种信息
  • 通过调试来学习 Lua 代码

更多详情请查阅相关文档介绍。

关于 API7.ai 与 APISIX

API7.ai 是一家提供 API 处理和分析的开源基础软件公司,于 2019 年开源了新一代云原生 API 网关 -- APISIX 并捐赠给 Apache 软件基金会。此后,API7.ai 一直积极投入支持 Apache APISIX 的开发、维护和社区运营。与千万贡献者、使用者、支持者一起做出世界级的开源项目,是 API7.ai 努力的目标。

相关文章:

详解 APISIX Lua 动态调试插件 inspect

作者罗锦华,API7.ai 技术专家/技术工程师,开源项目 pgcat,lua-resty-ffi,lua-resty-inspect 的作者。 原文链接 为什么需要 Lua 动态调试插件? Apache APISIX 有很多 Lua 代码,如何在运行时不触碰源代码的…...

#科研筑基# python初学自用笔记 第五篇 函数

调用函数python有很多内置函数,我们可以直接调用,详见python官方文档:内置函数 — Python 3.11.2 文档,也可以在命令行中输入help(函数名)来查看该函数的使用法则。函数名的本质就是指向一个函数对象的引用,完全可以用…...

设计模式之策略模式

一.基本内容1 . 实例有各种鸭子(野鸭,北京鸭子,水鸭等,鸭子有各种行为,比如飞,叫等显示鸭子的信息传统方法解决:鸭子为抽象类,具体鸭子继承抽象类2.传统方法的不足:其他鸭…...

dbdeployer 使用札记

https://github.com/datacharmer/dbdeployer默认配置文件为当前用户的$HOME/.dbdeployer/config.json作为配置文件,可以通过dbdeplyoer defaults export导出并修改配置或者直接通过dbdeployer defaults update来更新默认文件,配置文件包含MySQL初始信息。…...

MATLAB算法实战应用案例精讲-【图像处理】数字图像模糊化(附Java、python和matlab代码实现)

目录 前言 几个相关概念 噪声 滤波器 算法原理 算法思想 噪...

搭建Hexo博客-第1章-Git和GitHub以及Coding的简单用法

搭建Hexo博客-第1章-Git和GitHub以及Coding的简单用法 搭建Hexo博客-第1章-Git和GitHub以及Coding的简单用法 Coding GitHub Hexo Markdown 搭建博客 大家好,这是我第一次写博客。使用 GitHub Hexo 创建最基本的博客很容易,网上有很多现成的教程。…...

【C++修行之路】C/C++内存管理

文章目录程序区域内存划分C语言动态内存分配:new和delete:new、delete和malloc、free的区别:程序区域内存划分 C/C程序内存区域划分非常相似。 C语言动态内存分配: malloc、calloc、realloc都是C语言动态开辟内存的常用函数 其中 malloc 开…...

spring cloud alibaba Sentinel(四)

服务雪崩 在分布式系统中,由于网络原因或自身的原因,服务一般无法保证 100% 可用。 如果一个服务出现了问题,调用这个服务就会出现线程阻塞的情况, 此时若有大量的请求涌入,就会出现多条 线程阻塞等待,进而导致服务瘫痪。 由于服…...

Redis第三讲

目录 三、Redis03 3.1 Redis持久化之RDB 3.1.1 什么是RDB 3.1.2 备份是如何执行的 3.1.3 Fork 3.1.4 RDB持久化流程 3.1.5 dump.rdb文件 3.1.6 配置rdb文件生成位置 3.1.7 如何触发RDB快照以及保持策略 3.2 Redis持久化之AOF 3.2.1 什么是AOF 3.2.2 AOF持久化流程 …...

JAVA线程池的使用

一、池化思想和JAVA线程池 池化是很重要的思想;池化的好处是提供缓冲和统一的管理。这个笔者在本人的数据库连接池的博客中已经提到过了(JAVA常用数据库连接池_王者之路001的博客-CSDN博客 )。 线程池是另一种池化思想的运用,把…...

力扣56.合并区间

文章目录力扣56.合并区间题目描述排序合并力扣56.合并区间 题目描述 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中…...

代码随想录二刷Day03链表: 24.两两交换链表中的节点,19.删除链表的倒数第N个节点,面试题 02.07. 链表相交,142.环形链表||

24.两两交换链表中的节点 文章链接:代码随想录 (programmercarl.com) 思路: (1)首先如果要处理相邻两个节点的话,一定需要操作两个节点的前一个节点才可以,因此,本题需要设定一个虚拟头节点 …...

我应该在我的博客上写什么? 介绍如何撰写初学者容易担心的文章

我想有很多人开了博客,但想不起来写作,无法取得进展。 博客的主题和文章的内容不会仅仅通过写你想做的事情来工作。 重要的是要了解用户想要阅读的内容以及人们可能收集的内容,并将其与您想要编写的内容很好地匹配。 这一次,我…...

嵌入式C语言设计模式 --- 外观模式

1 - 什么是外观模式? 外观模式(Facade Pattern),是一种比较简单的结构型模式,它存在的目的,也是为了简单。 外观模式隐藏了一系列接口的复杂性,旨在为外部客户端提供一个更高层次且统一简单的接口,简化了客户端调用某些模块的一系列操作。 外观模式应该是软件工程师…...

若依ruoyi——手把手教你制作自己的管理系统【三、代码生成】

昨天情人节一( ̄︶ ̄*)) 送给赛利亚一((* ̄3 ̄)╭ ********* 专栏略长 爆肝万字 细节狂魔 请准备好一键三连 ********* 修改后的页面: 干干净净贼舒服一Ψ( ̄∀ ̄)Ψ——Ψ( ̄∀&#x…...

SCI论文写作神器集合 —— 超级实用

特此声明: 本文拷贝多处别人的内容,并给出具体的链接 本文所提到的软件都为博主在文章撰写过程中发掘的比较实用的工具,旨在帮助小伙伴们更快更有效率的完成文章发表,如果其他好用的工具,欢迎各位交流~~ 一、文献搜索神…...

MAC 系统安装多版本 JDK 并任意切换

1、背景 在进行 Java 开发的过程中,我们可能需要使用不同版本的 JDK。例如:一些旧的 Java 应用程序只能在旧版本的 JDK 上运行,而一些新的 Java 应用程序需要较新的 JDK 才能运行。 在 MAC 系统上,如何安装多个版本的 JDK 并配置…...

配置 Smart Link 接口时需注意的互斥命令

配置 Smart Link 接口时需注意的互斥命令 一、接口加入Smart Link组功能与以下功能互斥一、接口加入Smart Link组功能与以下功能互斥 注:当接口已经加入Smart Link组,则不能再配置以下功能;反之,当接口已经配置以下功能&#xff…...

QT的下载和安装

这里介绍的是QT官方方式下载,每次都让我很糊涂,就记载一下。先是下载QT online installerhttps://www.qt.io/download 在下方有Go Open Sourcehttps://www.qt.io/download-open-source 在下方有Download the Qt Online installerhttps://www.qt.io/downl…...

nacos配置中心与服务注册中心

文章目录 目录 文章目录 前言 一、服务注册与发现中心 二、配置中心 总结 前言 Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是 Spring Cloud Alibaba 组件之一,负责服务注册发现和服务配置. [服务治理的作用和微服务配置管理] Na…...

UE4 手把手教你做插件(1) 从代码引用插件

0,前言 我看的是 技术宅阿棍儿 的视频,B站有。 系列视频:从代码引用插件_哔哩哔哩_bilibili 看不懂,只能边查资料边看,讲的顺序有点乱 1,根据视频提示创建第三方插件 注意:如果只有空白插件的情…...

【Mybatis源码解析】一级缓存和二级缓存源码解析

文章目录缓存使用缓存源码测试代码上一篇《【Mybatis源码解析】mapper实例化及执行流程源码分析》,主要讲解了Mybatis的基本原理一级执行的流程,这一章来讲一下Mybatis的两个缓存:一级缓存和二级缓存。 因为网上大部分都是使用xml配置的方式…...

你知道MES实施的要点吗?

随着国家行动纲领:中国制造2025(智能制造)的发布,MES系统在制造业的工厂中所占比重越来越大,越来越多的工厂选择使用MES完成工厂的信息化、数字化、智能化生产。伴随着企业对MES的需求不断增大,生产MES的厂…...

告诉你为什么为什么 SELECT COUNT(*) FROM table 在 InnoDB 引擎中比 MyISAM引擎中的速度慢

统计一张表的总数量,是我们开发中常有的业务需求,通常情况下,我们都是使用 select count(*) from table SQL 语句来完成。随着业务数据的增加,你会发现这条语句执行的速度越来越慢,为什么它会变慢呢? 为什…...

Redis 命令和Redis key键

Redis 命令 Redis 命令用于在 Redis 服务器上执行一些操作,而命令运行的方式是通过客户端命令行来执行的,这种方式也被称为“命令行模式”。因此想要在 Redis 服务器上运行命令,您首先需要开启一个 Redis 客户端。操作方法如下: …...

如何入侵服务器

根据中华人民共和国刑法: 第二百八十六条违反国家规定,对计算机信息系统功能进行删除、修改、增加、干扰,造成计算机信息系统不能正常运行,后果严重的,处五年以下有期徒刑或者拘役;后果特别严重的&#xff…...

在Windows10上安装虚拟机---VMware 17 Pro下载与安装

在Windows10上安装虚拟机---VMware下载与安装0 前言1 下载VMware 17 pro2 安装VMware 17 Pro3. 打开Vmware0 前言 电脑原生系统:Windows10虚拟机软件:VMware 17 pro准备好安装虚拟机的文件夹路径 1 下载VMware 17 pro 下载网址:VMware 官网…...

生命周期函数、组件

1. 生命周期函数 beforeCreate : 无法通过 vm 访问data 中的数据、methods 中的方法created :可以访问 vm 中的 data 的数据, methods 中的方法beforeMount:为经 Vue 编译的 dommounted:经过 vue 编译的 dom &#x…...

蓝桥杯 stm32 PWM 测量频率

本文代码使用 HAL 库。 文章目录 前言一、PWM 原理图:二、CubeMX 创建工程:三、PWM 单路测频:四、详细代码:1. 获取 CNT函数。2. 设置CNT为 0 函数3. 开启TIM2_CH1的输入捕获中断函数4. TIM 回调函数5. 在 LCD 上显示 R40 和 R39 的频率。总结前言 一、PWM 原理图: 参考…...

Docker CPU 资源控制

01-本章背景知识 在生产环境里运行服务的一个主要问题是如何公平有效的进行资源分配。 1、Docker 容器使用核心操作系统的 Cgroups 管理容器的 CPU资源分配。 2、Docker 容器资源竞争时,默认使用简单均分(CFS)算法。 3、Docker 容器也可以根…...

互联网保险行业发展报告/淘宝seo是什么

载自:http://blog.csdn.net/l461269717/article/details/8008063 我用的是OK6410开发板,自带的两个内核(2.6.32和3.0.1都测试通过) 首先讲一下制作logo的方法: Linux Logo制作工具 : LogoMaker.tgz 4.7MB http://www…...

网站弹窗设计/河北seo人员

最近在一个公众号看到滚动视差效果,觉得挺有意思的,相比传统滚动的显示效果让人耳目一新。 来源:http://web.jobbole.com/95068/ 主要是运用background-attachment属性,该属性有四个值,分别是scroll(默认…...

wordpress 怎么传网站/seo 网站排名

Qt是目前最先进、最完整的跨平台C开发工具。它不仅完全实现了一次编写,所有平台无差别运行,更提供了几乎所有开发过程中需要用到的工具。如今,Qt已被运用于超过70个行业、数千家企业,支持数百万设备及应用。 点击获取Qt组件下载 …...

做网站的计划概要/简述网络推广的方法

1.javaScript 中const、var、let区别 const 定义的变量不可修改 而且必须初始化 >解决闭包变量污染问题 var 定义的变量可以修改 如果不初始化则默认值为undefined let 是块级作用域 函数内部使用let定义后 对函数外部不影响 2.浅拷贝 object.assign(target,source) 等…...

redis网站开发教程/山东企业网站建设

题目链接:http://poj.org/problem?id2728 01规划问题,二分prim。注意这里的图是稠密图,应该用prim而不是kruskal。堆优化的prim不适合稠密图,也不适合邻接矩阵建图,否则基本上没怎么优化O(n*(lgnn)),因为每…...

私人做网站/互动营销公司

使用pandas to_csv乱码情况 在我们向csv文件中编写内容时,总是出现乱码情况,我们只需要在写入文件时在后面加入:encoding"utf_8_sig" 防止乱码,读取数据库写入csv文件时加入 代码:df_sql.to_csv("E:/t…...