net-http-transport 引发的句柄数(协程)泄漏问题
Reference
- 关于 Golang 中 http.Response.Body 未读取导致连接复用问题的一点研究
- https://manishrjain.com/must-close-golang-http-response
- https://www.reddit.com/r/golang/comments/13fphyz/til_go_response_body_must_be_closed_even_if_you/?rdt=35002
- https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779
- https://stackoverflow.com/questions/17948827/reusing-http-connections-in-go
- 内存泄漏排查:https://lessisbetter.site/2019/05/18/go-goroutine-leak/
最近在工作中遇到一个句柄数泄漏的问题,在排查的过程中学习了很多关于 net/http 源码库的一些使用和原理
问题描述
背景:一个负责告警规则判断的服务,vmAlert,主要流程是根据用户配置的规则,查询对应的指标是否满足告警的阈值条件,从而进行告警
现象:vmAlert,在执行一段时间后,提示panic掉了,提示” too many open file “ 相关的错误
进程数:
分析来看,vmalert 在频繁地与 某个服务 建立 TCP 连接,并且频繁地打开和关闭 socket 套接字,由于建立连接的操作与关闭连接的操作频率不 一致,从而使得连接数持续增加,最后超过 ulimit -Sn 和 ulimit -Hn 65535 的限制 [[Linux 的最大TCP连接数]];大胆猜测可能是任意一个HTTP请求的问题
排查过程
从现象看本质,HTTP 请求本质上是建立一个TCP连接,每建立一个TCP连接,则需要打开对应的文件,而 fd 则是控制这些文件的一个数据结构,fd 泄漏了,则说明几个可能性:
- 某个地方连接未被释放
- 打开fd的速度远远快于释放的速度
从这个思路排查,找一下服务里面调用外部的HTTP请求
- 检查 vm 的相关请求的连接和代码 使用的自定义的连接池,并且连接都正常建立连接,看起来没啥问题
- 检查心跳接口相关的连接和代码
在 vmalert 运行了一段时间后,通过 lsof -p [进程号] 查看其打开的文件连接,发现 vmselect 的TCP连接数较为稳定,连接数与 vmalert 的在执行的 组数基本是相同的 检查发现存在大量的 sock 连接,HTTP请求会建立TCP连接,从而打开socket进行读写,从这个角度去排查;
检视 心跳相关的代码,每30s执行一次,使用timer.ticker 进行定时调用,错误也处理了,一个简单的post心跳上报请求; resp 也不读取,直接 _ 进行忽略即可,也不用close调,看起来没啥问题
在 lsof -p [进程号] 中偶然发现与一个后端服务Backend存在 CLOSE_WAIT 的连接,并且 src port 也不一样,也就是意味着心跳接口的 TCP 连接在重建?
大胆猜测,http.Post 这个调用没有使用到长连接? 减少interval时间为1s,执行10m,再次 lsof 看下,发现存在大量的 sock 连接,整体打开的fd数上涨到了 711
并且出现了大量的 close_wait 连接
基本就能定位到是这个段代码的问题了,线上增长缓慢是因为interval为30s,fd会慢增加; 查看容器配置的最大可打开文件数为 65535,超过就会进行报错;
回看心跳的代码,只是使用了http.Post 发送一个简单的上报心跳请求,为什么会导致句柄泄漏的问题呢?难道每个HTTP请求建立了新的TCP连接吗?看下 net/http 的代码 [[net-http-transport]]
// DefaultClient is the default [Client] and is used by [Get], [Head], and [Post].
var DefaultClient = &Client{}func Post(url, contentType string, body io.Reader) (resp *Response, err error) { return DefaultClient.Post(url, contentType, body)
}func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response, err error) { req, err := NewRequest("POST", url, body) if err != nil { return nil, err } req.Header.Set("Content-Type", contentType) return c.Do(req)
}func (c *Client) do(req *Request) (retres *Response, reterr error) {// ... 省略不关键的部分if resp, didTimeout, err = c.send(req, deadline); err != nil { // c.send() always closes req.Body reqBodyClosed = true if !deadline.IsZero() && didTimeout() { err = &timeoutError{err.Error() + " (Client.Timeout exceeded while awaiting headers)"} } return nil, uerr(err) }
}// didTimeout is non-nil only if err != nil.
func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) { if c.Jar != nil { for _, cookie := range c.Jar.Cookies(req.URL) { req.AddCookie(cookie) } } resp, didTimeout, err = send(req, c.transport(), deadline) if err != nil { return nil, didTimeout, err } if c.Jar != nil { if rc := resp.Cookies(); len(rc) > 0 { c.Jar.SetCookies(req.URL, rc) } } return resp, nil, nil
}func (c *Client) transport() RoundTripper { if c.Transport != nil { return c.Transport } return DefaultTransport
}// DefaultTransport is the default implementation of [Transport] and is// used by [DefaultClient]. It establishes network connections as needed
// and caches them for reuse by subsequent calls. It uses HTTP proxies
// as directed by the environment variables HTTP_PROXY, HTTPS_PROXY
// and NO_PROXY (or the lowercase versions thereof).
var DefaultTransport RoundTripper = &Transport{ Proxy: ProxyFromEnvironment, DialContext: defaultTransportDialContext(&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }), ForceAttemptHTTP2: true, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second,
}
可以看到,http.Post的方法,使用了默认的连接池,也就是会有TCP连接被 reuse 的,但从实际的情况(大量的close_wait)来看,又没复用连接,为什 么呢? 现在能大概猜测是没复用到长连接的问题,所以修改一下代码,使用短连接 connection为close看一下效果;
改完跑一段时间, lsof -p [进程号] 发现很正常,都是与 vmselect 建立的 ESTABLISH 连接,并且连接数固定在 50 左右;
实在是没什么思路了,范围就能确定是长连接没复用,导致不断建立新的TCP连接,导致socket被不断打开的问题,TCP 连接建立的速度比关闭的速度要快导致的;把心跳的代码丢到gpt问一下,一下子发现了新世界,重点: ”未关闭响应体可能导致fd泄漏“
?!没有使用到 resp 进行 io.Read 也需要关闭吗?这个时候就得 google 一下了,果然,找到了大佬们的一些解答
TIL: Go Response Body be MUST closed, even if you don’t read it
https://manishrjain.com/must-close-golang-http-response
文章中提到了几个点:
- net/http 源码包中的 response.body 的描述,如果上一个body没有被读取完毕并且close掉,则这个 TCP 连接,不会被 reuse
- 永远不要使用 HTTP.GET 以及 HTTP.DefaultClient,而应该使用自己创建的 httpclient
- 无论是否需要(处理
Response
),都必须始终读Body
并将其关闭
http.Response.Body
的注释:
The http Client and Transport guarantee that Body is always non-nil, even on responses without a body or responses with a zero-length body. It is the caller’s responsibility to close Body. The default HTTP client’s Transport may not reuse HTTP/1.x “keep-alive” TCP connections if the Body is not read to completion and closed.
上述三点的一些个人理解:
- 第一点官方已经给出说明了,如果你不读取完body并且关闭,则下一次请求过来,tcp 会认为上一个请求还未结束,则不会resue 这个连接,更详细的可以继续往下看根因分析
- 第二点则是因为,使用这些方法时,defaultHttpClient 初始化未 var DefaultClient = &Client{},默认的 timeout 为 zero,即没有过期时间,这样客户端就不会主动去关闭这个连接,完全交给服务端决定是否断开,这样只要故障服务器决定等待,它们就会继续挂起。由于 API 调用是为了满足用户请求,这会导致满足用户请求的 goroutine 也挂起。一旦有足够多的调用,则应用程序就会崩溃;
- 第三点,则是一个建议,但实际上有一定的隐患,可以参考这篇文章;关于 Golang 中 http.Response.Body 未读取导致连接复用问题的一点研究,主要提到了当网络出现阻塞时,读取body也会被阻塞,需要看实际的使用场景,如果你不需要resp的内容,不建议读,
- 额外提一嘴
- 如果你只进行 defer close,而不读取 resp,则tcp连接也不会复用
- 如果你只进行读取 resp,而不 close 连接,则 tcp 连接可以复用(具体原因,可以看后续根因分析)
找到原因后,修改代码进行测试,发现 src port一直不变,说明复用的是同一个tcp连接
按照实际场景测试,30s触发一次心跳,发现tcp连接又不复用了,调整为5s又复用了,再次调整为 10s,发现有时候复用有时候不复用? 猜测是跟客户端的连接时间有关,检查一下客户端的初始化代码。检查了 transport 的idleconnTime 为 90s,也没啥问题,只能抓包看是哪边把连接关了,发现服务端 Backend 给 vmalert 发送了一个 FIN 关闭连接的报文,证明:是服务端主动关闭的连接
检查服务端的代码,发现设置了http的read和write的timeout为 10s, 破案
深挖根因
大量 fd 的根因
到底是net-http中哪一段代码导致的fd泄漏呢?这个问题还是不知道,我们继续深挖一下
在复现大量 close_wait 请求后,我们通过 pprof 看下服务的一些信息,主要看 top,trace,list
[root@vmalert-84cf77d57c-dwqzv nocalhost-dev]# go tool pprof http://0.0.0.0:xxxx/debug/pprof/goroutine
File: vmalert
Type: goroutine
Time: Jan 6, 2025 at 12:01pm (UTC)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 5474, 99.91% of 5479 total
Dropped 107 nodes (cum <= 27)
Showing top 10 nodes out of 29flat flat% sum% cum cum%5474 99.91% 99.91% 5474 99.91% runtime.gopark0 0% 99.91% 77 1.41% bufio.(*Reader).Peek0 0% 99.91% 77 1.41% bufio.(*Reader).fill0 0% 99.91% 80 1.46% internal/poll.(*FD).Read0 0% 99.91% 82 1.50% internal/poll.(*pollDesc).wait0 0% 99.91% 82 1.50% internal/poll.(*pollDesc).waitRead (inline)0 0% 99.91% 82 1.50% internal/poll.runtime_pollWait0 0% 99.91% 79 1.44% main.(*AlertingRule).Exec0 0% 99.91% 170 3.10% main.(*Group).start0 0% 99.91% 79 1.44% main.(*Group).start.func2(pprof) traces
File: vmalert
Type: goroutine
Time: Jan 6, 2025 at 11:52am (UTC)
-----------+-------------------------------------------------------2144 runtime.goparkruntime.selectgonet/http.(*persistConn).writeLoop
-----------+-------------------------------------------------------2060 runtime.goparkruntime.selectgonet/http.(*persistConn).readLoop(pprof) list writeLoop
Total: 4486
ROUTINE ======================== net/http.(*persistConn).writeLoop in /usr/local/go/src/net/http/transport.go0 2144 (flat, cum) 47.79% of Total. . 2516:func (pc *persistConn) writeLoop() {. . 2517: defer close(pc.writeLoopDone). . 2518: for {. 2144 2519: select {. . 2520: case wr := <-pc.writech:. . 2521: startBytesWritten := pc.nwrite. . 2522: err := wr.req.Request.write(pc.bw, pc.isProxy, wr.req.extra, pc.waitForContinue(wr.continueCh)). . 2523: if bre, ok := err.(requestBodyReadError); ok {. . 2524: err = bre.error(pprof) list readLoop
Total: 5479. . 2322: // Before looping back to the top of this function and peeking on. . 2323: // the bufio.Reader, wait for the caller goroutine to finish. . 2324: // reading the response body. (or for cancellation or death). 2562 2325: select {. . 2326: case bodyEOF := <-waitForBodyRead:. . 2327: alive = alive &&. . 2328: bodyEOF &&. . 2329: !pc.sawEOF &&. . 2330: pc.wroteRequest() &&. . 2331: tryPutIdleConn(rc.treq). . 2332: if bodyEOF {. . 2333: eofc <- struct{}{}. . 2334: }. 1 2335: case <-rc.treq.ctx.Done():. . 2336: alive = false. . 2337: pc.cancelRequest(context.Cause(rc.treq.ctx)). . 2338: case <-pc.closech:. . 2339: alive = false. . 2340: }
... # 后面省略了
可以得到以下信息:
top命令
- 总计:1450 个 goroutine,占总 goroutine 数量的 99.86%
- 主要函数:
- runtime.gopark:1450 次,几乎占据了所有的 goroutine
- 其他函数如
bufio.(*Reader).Peek、internal/poll.(*FD).Read
等也有一定的调用次数,但相对较少
分析:
- runtime.gopark:这个函数用于将当前 goroutine 暂停,直到某个条件满足。它通常在等待某些事件(如 I/O 操作、锁等)时被调用
bufio.(*Reader).Peek 和 bufio.(*Reader).fill
:这些函数与缓冲 I/O 操作有关,表明有 goroutine 正在等待读取数据- internal/poll:这些函数与网络 I/O 操作有关,表明有 goroutine 正在等待网络连接的读操作
trace 命令
net/http.(*persistConn).writeLoop 和 net/http.(*persistConn).readLoop
- 在协程暂停之前,最多调用就是这两个函数
list 命令
- 可以看到具体是在哪个地方阻塞住了
那么 writeLoop 和 readLoop 到底是干嘛的呢?有兴趣详细了解可以参考这篇文章 [[net-http-transport]],下面就简单说一下,附带一个流程图
- http 请求本质还是去发起一个 tcp 连接,net-http库通过transport来实现http请求的过程,通过一个请求池来控制空闲连接数量,transport 会尝试去申请一个 persistConn 持久连接,persistConn 负责控制读写过程
- 如果存在空闲TCP连接则直接复用,否则通过 net-dial 重新请求一个连接;每个 persistConn 都会启动两个协程,也就是writeLoop 和 readLoop
- persistConn 发送 request 信号,writeLoop 监听 writech 读取 request 并通过 bufio.Writer 写入 bw,也就是真正发送请求,服务端处理完,会把结果写回到 br
- persistConn 与 readLoop 是通过 reqch 传递 request,readLoop 不断从br.Peek 检查是否有数据,有则读取然后写入到 respAndErr.ch ,persistConn 则进行最后的返回 resp
按照我们上面的 list 分析,我们可以知道具体是哪行代码阻塞了
readLoop 阻塞根因
先看下readLoop,可以发现其阻塞在了 waitForBodyRead;
go的select用法,在没有default的case情况下,会一直阻塞,直到满足某个case;
通过其注释我们能知道,readLoop退出的几个条件:
- body 读取完毕
- request 主动 cancel
- request context Done 状态 true
- 当前的 persistConn 关闭
// Before looping back to the top of this function and peeking on
// the bufio.Reader, wait for the caller goroutine to finish
// reading the response body. (or for cancellation or death)
select {. . 2326: case bodyEOF := <-waitForBodyRead:}
继续看下 waitForBodyRead 是哪里传输的,发现是body中的一个回调函数 earlyCloseFn,这个函数又是bodyEOFSignal 这个结构体的变量,从注释可知, bodyEOFSignal作用是确保在读取到响应体的末尾EOF之前,不会继续在连接上进行其他读取操作
我们继续溯源
waitForBodyRead := make(chan bool, 2)// bodyEOFSignal is used by the HTTP/1 transport when reading response
// bodies to make sure we see the end of a response body before
// proceeding and reading on the connection again.
body := &bodyEOFSignal{body: resp.Body,earlyCloseFn: func() error {waitForBodyRead <- false<-eofc // will be closed by deferred call at the end of the functionreturn nil},fn: func(err error) error {isEOF := err == io.EOFwaitForBodyRead <- isEOFif isEOF {<-eofc // see comment above eofc declaration} else if err != nil {if cerr := pc.canceled(); cerr != nil {return cerr}}return err},
}
...func (es *bodyEOFSignal) Close() error {es.mu.Lock()defer es.mu.Unlock()if es.closed {return nil}es.closed = trueif es.earlyCloseFn != nil && es.rerr != io.EOF {return es.earlyCloseFn()}err := es.body.Close()return es.condfn(err)
}
Close 方法存在太多引用了,无法通过idea直接查询,但是我们发现 resp 会被 bodyEOFSingal 包裹一层,这个Close是 resp.body 的,也就是调用 resp.body.Close() 肯定能触发这段逻辑;详细看下 earlyCloseFn 这个函数用来干嘛的,从注释知道,这个函数就是在未读到EOF之前,也可通过这个channel信号,提早关闭conn,结合 readLoop 的阻塞逻辑,可以分析出,这里就是 resp.body.Close 关闭操作前的处理,通知 readLoop ,我读取完了,你继续执行吧
earlyCloseFn // optional alt Close func used if io.EOF not seen// readLoop
2326: case bodyEOF := <-waitForBodyRead:
并且我们还看到另外一个channel变量,eofc,注释中介绍,这个变量用于阻塞caller协程(也就是close函数),读取到EOF结束符时,进行阻塞,知道连接放回到空闲连接池中,这也是httpClient维护连接池的一些处理逻辑;
// eofc is used to block caller goroutines reading from Response.Body
// at EOF until this goroutines has (potentially) added the connection
// back to the idle pool.
eofc := make(chan struct{})
defer close(eofc) // unblock reader on errors// close 回调函数
earlyCloseFn: func() error {waitForBodyRead <- false<-eofc // will be closed by deferred call at the end of the functionreturn nil
}// readLopp 循环逻辑
select {case bodyEOF := <-waitForBodyRead:alive = alive &&bodyEOF &&!pc.sawEOF &&pc.wroteRequest() &&tryPutIdleConn(rc.treq)if bodyEOF {eofc <- struct{}{}}case <-rc.treq.ctx.Done():alive = falsepc.cancelRequest(context.Cause(rc.treq.ctx))case <-pc.closech:alive = false
}rc.treq.cancel(errRequestDone)
所以,resp.body.close 会给watiForBodyRead 发送 false 值,readLoop 接收到后alive 的判断就为false,从而跳出了 readLoop 的 for alive {} 循环,这个函数退出前会进行defer ,pc.Close 则最终会调用 net.conn.close 将 fd 打开的 fd 给关掉
defer func() {pc.close(closeErr)pc.t.removeIdleConn(pc)
}()// Close closes the connection.
func (c *conn) Close() error {if !c.ok() {return syscall.EINVAL}err := c.fd.Close()if err != nil {err = &OpError{Op: "close", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}}return err
}
所以,当我们没有调用 resp.body.Close 时,readLoop 就会一直阻塞住,那为什么不会走到 rc.treq.ctx.Done或者closech呢,因为 rc.treq.cancel(errRequestDone) 在select 之后,另外一个调用的地方则是其他情况,不做讨论;而 closech 则是 conn关闭时才会走到;因此,我们得出结论:
如果不主动调用 resp.body.Close() 则 readLoop 会阻塞在 waitForBodyRead
总结:
- 我们通过分析 readLoop 的各个退出条件,分析出 resp.body.Close 会回调 earlyCloseFn 函数,从而发送 false 给 waitForBodyRead channel,使得 alive 为 false 退出循环,结束协程
OK,这样我们解决一个协程泄漏的问题了,接下来看第二个 writeLoop
writeLoop 阻塞根因
其实很简单,看下代码,会发现writeLoop的退出条件基本都满足,要么写错误退出,要么等待pconn连接关闭,所以大量的pconn就存在大量的协程泄漏
func (pc *persistConn) writeLoop() { defer close(pc.writeLoopDone) for { select { case wr := <-pc.writech: startBytesWritten := pc.nwrite err := wr.req.Request.write(pc.bw, pc.isProxy, wr.req.extra, pc.waitForContinue(wr.continueCh)) if bre, ok := err.(requestBodyReadError); ok { err = bre.error // Errors reading from the user's // Request.Body are high priority. // Set it here before sending on the // channels below or calling // pc.close() which tears down // connections and causes other // errors. wr.req.setError(err) } if err == nil { err = pc.bw.Flush() } if err != nil { if pc.nwrite == startBytesWritten { err = nothingWrittenError{err} } } pc.writeErrCh <- err // to the body reader, which might recycle us wr.ch <- err // to the roundTrip function if err != nil { pc.close(err) return } case <-pc.closech: return } }
}
连接为什么无法复用?
前面我们有提到过,为什么我们不读取 body,连接就无法复用?我们看下 io.copy 干了啥,通过debug 调用下去,看下调用链路;
// Copy copies from src to dst until either EOF is reached
// on src or an error occurs. It returns the number of bytes
// copied and the first error encountered while copying, if any.
_, _ = io.Copy(ioutil.Discard, resp.Body)
发现就是把 resp.body 读取到某个地方;过程汇总调用到了 net.http.body 这个结构体的 readLocked
// Must hold b.mu.
func (b *body) readLocked(p []byte) (n int, err error) {if b.sawEOF {return 0, io.EOF}n, err = b.src.Read(p)if err == io.EOF {// ... 省略,这个是正常读取到结束符的情况}// If we can return an EOF here along with the read data, do// so. This is optional per the io.Reader contract, but doing// so helps the HTTP transport code recycle its connection// earlier (since it will see this EOF itself), even if the// client doesn't do future reads or Close.if err == nil && n > 0 {if lr, ok := b.src.(*io.LimitedReader); ok && lr.N == 0 {// 最终会执行到这一步,提前结束err = io.EOFb.sawEOF = true}}if b.sawEOF && b.onHitEOF != nil {b.onHitEOF()}return n, err
}
根据这个注释,我们可以知道这个 sawEOF 就是提早返回 EOF 以及读取的数据的标志,并且返回了err为io.EOF;按照这个错误,回溯到 bodyEOSignal 的 Read函数,可以发现如果错误了,会调用 condfn,这个fn就是之前出现过的,readLoop中的resp.body的初始化的fn
func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {es.mu.Lock()closed, rerr := es.closed, es.rerres.mu.Unlock()if closed {return 0, errReadOnClosedResBody}if rerr != nil {return 0, rerr}n, err = es.body.Read(p)if err != nil {es.mu.Lock()defer es.mu.Unlock()if es.rerr == nil {es.rerr = err}err = es.condfn(err)}return
}// caller must hold es.mu.
func (es *bodyEOFSignal) condfn(err error) error {if es.fn == nil {return err}err = es.fn(err)es.fn = nilreturn err
}
这个回调函数,还是给 waitForBodyRead 赋值,但这次是 true,因为 err 等于 io.EOF
fn: func(err error) error {isEOF := err == io.EOFwaitForBodyRead <- isEOFif isEOF {<-eofc // see comment above eofc declaration} else if err != nil {if cerr := pc.canceled(); cerr != nil {return cerr}}return err
},
ok 又回到 readLoop 的select,可以发现通过 io.Copy 读取完,bodyEOF 的值为 true,这样alive 就会继续往下执行,直到调用 tryPutIdleConn
// readLopp 循环逻辑
select {case bodyEOF := <-waitForBodyRead:alive = alive &&bodyEOF &&!pc.sawEOF &&pc.wroteRequest() &&tryPutIdleConn(rc.treq)if bodyEOF {eofc <- struct{}{}}case <-rc.treq.ctx.Done():alive = falsepc.cancelRequest(context.Cause(rc.treq.ctx))case <-pc.closech:alive = false
}
tryPutIdleConn 就是尝试把连接 persistConn 放回 Idle 队列中,让下一个 http 请求过来能直接拿到现有的persistConn,具体是怎么做的,这里不细讲了;
总结:
- io.Copy 将 resp.body 读取出来,然后它发现能提前返回结果,则进行返回,或者读取完发现确实是EOF,结束了,也返回 err 为 io.EOF
- 对于返回的io.EOF,bodyEOFSignal 这个结构体识别到了则开始回调fn(正常结束的回调函数),而前面介绍过 earlyClosefn,则是提前结束的回调函数(通过resp.body.close调用触发的)
- io.EOF 的回调函数,则是通过 waitForBodyRead 这个channel 与 readLoop 交互,让其触发 tryPutIdleConn 操作,最终将 persistConn 放回 空闲连接队列中,达到下一个http请求能直接复用的效果
这就是为什么我们不调用 io.Copy 或者 io.ReadAll 将 resp.body 读取完时,连接无法被复用的根因,因为根因就没触发 tryPutIdleConn 呀~
随带提一下,resp.body.Close 也会调用 io.Copy 消费 body后,才去关闭连接
_, err = io.Copy(io.Discard, bodyLocked{b})
相关文章:
net-http-transport 引发的句柄数(协程)泄漏问题
Reference 关于 Golang 中 http.Response.Body 未读取导致连接复用问题的一点研究https://manishrjain.com/must-close-golang-http-responsehttps://www.reddit.com/r/golang/comments/13fphyz/til_go_response_body_must_be_closed_even_if_you/?rdt35002https://medium.co…...
高级软件工程-复习
高级软件工程复习 坐标国科大,下面是老师说的考试重点。 Ruby编程语言的一些特征需要了解要能读得懂Ruby程序Git的基本命令操作知道Rails的MVC工作机理需要清楚,Model, Controller, View各司什么职责明白BDD的User Story需要会写,SMART要求能…...
eslint.config.js和.eslintrc.js有什么区别
eslint.config.js 和 .eslintrc.js 的主要区别在于它们所对应的 ESLint 版本和配置方法: 1. .eslintrc.js: 这是 ESLint v8 及更早版本使用的配置文件格式。 它使用层级式的配置系统。 现在被称为"旧版"配置格式 。 2. eslint.config.js&am…...
如何使用MVC模式设计和实现校园自助点餐系统的微信小程序
随着智慧校园的普及,校园自助点餐系统在提高学生用餐效率、减轻食堂运营压力方面发挥了重要作用。 在开发这类系统时,MVC(Model-View-Controller)模式是一种非常适合的架构,它将系统的业务逻辑、用户界面和数据交互清晰…...
继续坚持与共勉
经过期末考试后,又要开始学习啦。 当时一直在刷算法题就很少写博客了,现在要继续坚持写博客,将每天对于题的感悟记录下来。 同时我将会在学习Linux操作系统,对于过去学习的内容进行回顾!! 在此ÿ…...
人机交互 | 期末复习(上)| 补档
文章目录 📚1-HCI Introduction🐇人机交互的定义,分别解释人-机-交互的概念🐇six ”mantras“ of UCD🐇Difference between User-Interface (UI) and User-Experience(UX)📚2-HCI history🐇WIMP🐇WYSIWYG📚3-Understanding User🐇Design Thinking Process的…...
Oracle 表分区简介
目录 一. 前置知识1.1 什么是表分区1.2 表分区的优势1.3 表分区的使用条件 二. 表分区的方法2.1 范围分区(Range Partitioning)2.2 列表分区(List Partitioning)2.3 哈希分区(Hash Partitioning)2.4 复合分…...
多并发发短信处理(头条项目-07)
1 pipeline操作 Redis数据库 Redis 的 C/S 架构: 基于客户端-服务端模型以及请求/响应协议的 TCP服务。客户端向服务端发送⼀个查询请求,并监听Socket返回。通常是以 阻塞模式,等待服务端响应。服务端处理命令,并将结果返回给客…...
网络编程的进程查看连接描述符信息等
一.查看当前进程的socket对应的fd信息 1. lsof lsof(List Open Files)命令可以列出系统中所有打开的文件的信息,包括 socket。 用法 要查看特定进程的 socket 信息,可以使用以下命令: lsof -p <pid> | grep…...
ChatGPT API快速搭建自己的第一个应用—文章摘要(单轮对话应用)
使用ChatGPT API快速搭建自己的第一个应用 1 安装库2 设置与导入3 文章摘要(单轮对话应用)3.1 任务简介:3.2 初始化3.3 点击发送3.4 保存3.5 检查并打印你的结果1 安装库 !pip install gradiogradio 是一个用于快速搭建交互式用户界面的 Python 库,特别适合展示机器学习模…...
【01】AE特效开发制作特技-Adobe After Effects-AE特效制作快速入门-制作飞机,子弹,爆炸特效以及导出png序列图-优雅草央千澈
【01】AE特效开发制作特技-Adobe After Effects-AE特效制作快速入门-制作飞机,子弹,爆炸特效以及导出png序列图-优雅草央千澈 开发背景 优雅草央千澈所有的合集,系列文章可能是不太适合完全初学者的,因为课程不会非常细致的系统…...
软件测试预备知识④—NTFS权限管理、磁盘配额与文件共享
在软件测试的实际环境搭建与管理过程中,了解和掌握NTFS权限管理、磁盘配额以及文件共享等知识至关重要。这些功能不仅影响系统的安全性和稳定性,还对测试数据的存储、访问以及多用户协作测试有着深远的影响。 一、NTFS权限管理 1.1 NTFS简介 NTFS&am…...
CI/CD 流水线
CI/CD 流水线 CI 与 CD 的边界CI 持续集成CD(持续交付/持续部署)自动化流程示例: Jenkins 引入到 CI/CD 流程在本地或服务器上安装 Jenkins。配置 Jenkins 环境流程设计CI 阶段:Jenkins 流水线实现CD 阶段:Jenkins 流水…...
【python3】 sqlite格式的db文件获得所有表和数据
【python3】 sqlite格式的db文件获得所有表和数据 1.背景2.代码3.解析1.背景 SQLite 格式的 .db 文件就是一个包含 SQLite 数据库的文件。 SQLite 格式的 .db 文件通常存储的是一个关系型数据库。 SQLite广泛用于应用程序、移动设备、浏览器等场景。它将整个数据库存储在一个文…...
【灵码助力安全3】——利用通义灵码辅助智能合约漏洞检测的尝试
前言 随着区块链技术的快速发展,智能合约作为去中心化应用(DApps)的核心组件,其重要性日益凸显。然而,智能合约的安全问题一直是制约区块链技术广泛应用的关键因素之一。由于智能合约代码一旦部署就难以更改…...
openEuler 22.04使用yum源最快速度部署k8s 1.20集群
本文目的 openEuler的官方源里有kubernetes 1.20,使用yum源安装是最快部署一个k8s集群的办法 硬件环境 主机名系统架构ipmasteropenEuler release 22.03 (LTS-SP2)arm192.168.3.11edgeopenEuler release 22.03 (LTS-SP2)arm192.168.3.12deviceopenEuler release 22.…...
Docker Compose 教程
Docker Compose 是一个 Docker 容器的依赖管理工具。 例如我们一个服务需要依赖到多个 Docker 容器,那么使用 Docker Compose 这个工具就能很方便的帮助我们管理。 Docker Compose 通过配置文件 .yml。 定义了所有容器的依赖关系。 然后我们只需把我们想要的 Docke…...
opencv的NLM去噪算法
NLM(Non-Local Means)去噪算法是一种基于图像块(patch)相似性的去噪方法。其基本原理是: 图像块相似性:算法首先定义了一个搜索窗口(search window),然后在该窗口内寻找…...
scala基础学习_方法函数
文章目录 方法与函数函数(又称函数值/匿名函数)定义方法注意 单参数函数多参数函数函数作为参数传递 方法将方法转换为函数方法的返回值总结 方法与函数 函数(又称函数值/匿名函数) 定义在任何地方:函数可以定义在类…...
Android车机DIY开发之软件篇(八)单独编译
Android车机DIY开发之软件篇(八)单独编译 1.CarLauncher单独编译 CarLauncher源码位于 packages/apps/Car/Launcher 用Eclipse ADT 谷歌定制版编译而成,.mk .bp编译 Android13目录如下: alientekalientek:~/packages/apps/Car$ ls Calendar …...
【Bug】报错信息:Required request body is missing(包含五种详细解决方案)
大家好,我是摇光~ 遇到“Required request body is missing”错误通常意味着服务器期望在HTTP请求中包含一个请求体(body),但是实际上并没有收到。 例如: 当你在使用网页或应用程序的后台(比如一个网站或手…...
Docker 专栏 —— Dockerfile 指令详解
文章目录 ADD 复制文件COPY 复制文件ARG 设置构建参数CMD 容器启动命令ENTRYPOINT ⼊⼝点ENV 设置环境变量EXPOSE 声明暴露的端⼝FROM 指定基础镜像LABEL 为镜像添加元数据MAINTAINER 指定维护者的信息RUN 执⾏命令USER 设置⽤户VOLUME 指定挂载点WORKDIR 指定⼯作⽬录 ADD 复制…...
Spring Boot 项目自定义加解密实现配置文件的加密
在Spring Boot项目中, 可以结合Jasypt 快速实现对配置文件中的部分属性进行加密。 完整的介绍参照: Spring Boot Jasypt 实现application.yml 属性加密的快速示例 但是作为一个技术强迫症,总是想着从底层开始实现属性的加解密,…...
在ubuntu下对NFS做性能测试
安装NFS 首先,安装服务 sudo apt update sudo apt install nfs-kernel-server然后创建共享文件夹 # 请自定义你自己的共享目录 sudo mkdir -p /exports/nfs4/homes sudo chmod -R 777 /exports/nfs4/homes# 这个可以根据no_root_squash标致选择设置。 # 如果不设…...
Spring-Cloud-Gateway-Samples,nacos为注册中心,负载均衡
背景:本想找个简单例子看下,无奈版本依赖太过复杂,花了点时间。记录下吧 使用Spring Cloud Gateway作为网关服务,Nacos作为注册中心,实现对子服务的负载均衡访问。简单例子。 一、gateway-main-nacos服务端ÿ…...
StarRocks Awards 2024 年度贡献人物
在过去一年,StarRocks 在 Lakehouse 与 AI 等关键领域取得了显著进步,其卓越的产品功能极大地简化和提升了数据分析的效率,使得"One Data,All Analytics" 的愿景变得更加触手可及。 虽然实现这一目标的道路充满挑战且漫…...
Autoencoder(李宏毅)机器学习 2023 Spring HW8 (Boss Baseline)
1. Autoencoder 简介 Autoencoder是一种用于学习数据高效压缩表示的人工神经网络。它由两个主要部分组成: Encoder 编码器将输入数据映射到一个更小的、低维空间中的压缩表示,这个空间通常称为latent space或bottleneck。 这一过程可以看作是数据压缩,去除冗余信息,仅保留…...
深入探索 ScottPlot.WPF:在 Windows 桌面应用中绘制精美图表的利器
一、ScottPlot.WPF 简介 ScottPlot.WPF 是基于 ScottPlot 绘图库专门为 Windows Presentation Foundation (WPF) 框架量身定制的强大绘图组件。它无缝集成到 WPF 应用程序中,为开发者提供了一种简洁、高效的方式来可视化数据,无论是科学研究中的实验数据展示、金融领域的行情…...
React中的useMemo 和 useEffect 哪个先执行?
在 React 组件的渲染过程中,useMemo 和 useEffect 的执行顺序是不同的。具体来说: useMemo 先执行:useMemo 是在 渲染阶段 执行的,它的作用是缓存计算结果,确保在渲染过程中可以直接使用缓存的值。 useEffect 后执行&…...
错误修改系列---基于RNN模型的心脏病预测(pytorch实现)
前言 前几天发布了pytorch实现,TensorFlow实现为:基于RNN模型的心脏病预测(tensorflow实现),但是一处繁琐地方 一处错误,这篇文章进行修改,修改效果还是好了不少;源文章为:基于RNN模型的心脏病…...
廊坊网站建设优化/培训学校机构
思路: 特殊的情况是k0k0k0的时候,此时直接输出即可。 其他情况,很明显最后构造成这样: 1xxxxx1xxx0 1xxxxx0xxx1 要求k<ab−2k<ab-2k<ab−2,并且a≥1,b≥2a≥1,b≥2a≥1,b≥2 #include <cstdio> #incl…...
网站前置审批专项/seo搜索优化是什么
字符串的转置,反转,逆序 String s1"1a2b3c"; String s2s1.reverse();//即s2"c3b2a1"如上,可以说s2是s1的转置,反转,逆序。 不严谨的可以认为三个词是同一个意思,在各大算法书中&#…...
北京专业网站建设网站推广/seo具体怎么优化
在线课堂:https://www.100ask.net/index(课程观看) 论 坛:http://bbs.100ask.net/(学术答疑) 开 发 板:https://100ask.taobao.com/ (淘宝) https://weid…...
烟台H5网站设计公司/东莞网站建设推广技巧
点击上方“Github爱好者社区”,选择星标回复“资料”,获取小编整理的一份资料作者 l Hollis来源 l Hollis在我的博客和公众号中,发表过很多篇关于并发编程的文章,之前的文章中我们介绍过了两个在Java并发编程中比较重要的两个关键…...
ps2017做网站/网站seo视频狼雨seo教程
路径:在一棵树中从一个结点往下到孩子或孙子结点之间的通路 结点的路径长度:从根节点到该节点的路径上分支的数目 树的路径长度:树中每个结点的路径长度之和 结点的权:给树中的结点赋予一个某种含义的值,则该值为该节点…...
类似于众人帮的做任务赚佣金网站/友情链接交换要注意哪些问题
要在 Java 中获取字符串的编码格式,您可以使用 Java 的 Charset 类。您可以通过以下方式获取字符串的编码: String str "your string"; Charset charset Charset.forName("UTF-8"); System.out.println(charset.displayName());其中…...