V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  lesismal  ›  全部回复第 27 页 / 共 62 页
回复总数  1239
1 ... 23  24  25  26  27  28  29  30  31  32 ... 62  
2023-04-27 21:12:02 +08:00
回复了 Nazz 创建的主题 Go 编程语言 修改 go websocket server 启动方式, 内存占用立省 40% !
@Nazz #19 我又测了下 bufio.Writer ,跟 buffer append 性能差不多,但 bufio.Writer 是在线连接长期占用这个,buffer append 的方式是当前有写才会占用下、可以复用 pool ,而且并发写有 mutex 的、一个 conn 同时最多也就占一个,总数量<= conn num ,而且核心数没那么多、并发多数时候没有那么多协程达到并行,所以实际应该是小于 conn num ,所以 buffer append 可能更好点
2023-04-27 19:46:04 +08:00
回复了 Nazz 创建的主题 Go 编程语言 修改 go websocket server 启动方式, 内存占用立省 40% !
我又测了下 net.Buffers vs user buffer append:
https://gist.github.com/lesismal/a40c420511252aa79b054cbd2acc896e

我的机器上还是 user buffer append 性能略好,而且内存更优,你可以自己环境跑下看看不同环境是否有差异
2023-04-27 19:14:09 +08:00
回复了 Nazz 创建的主题 Go 编程语言 修改 go websocket server 启动方式, 内存占用立省 40% !
> 能用于浏览器的,你试试.

刚看了下代码,这。。。

我之前说不能用于浏览器是因为你这句 "支持从 tcp conn 直接解析 websocket 协议" ,以为你是和我发的那个类似、直接 tcp 上的数据解析 websocket ,但其实不是,你这个仍然是先解析 http request 然后 upgrade ,只是没用标准库的 http 、而是自己实现了解析 http request 这步。
所以准确说,是使用自实现的 http request 解析进行 ws upgrade 。
但这个解析不够完备、没有非法字符之类的协议规范的判断,我不确定会不会有一些风险。

> 实测 net.Buffer 稍快点,而且能省掉 bufio.Writer 的内存

可能你对比的是 bufio.Writer ,我之前对比的是 buffer append

> net.Buffer 能减少一次拷贝呢, 没想到底层还是会拷贝

这个可以看下代码,跟进去,TCPConn 这种就是调用的 syscall.Writev 了。
内核 c 语言实现的 writev 也看下就知道了

> demo 里面加 go 是因为我想让请求上下文被 gc 掉, 结果还是有副作用

单就 upgrade 这个 request 而言,可能上下文的代价比新 go 更大一点,但实际应该也差不了太多,runtime 复用协程也是挺给力而且不是高频行为,剩下的主要是逃逸
2023-04-27 18:27:39 +08:00
回复了 Nazz 创建的主题 Go 编程语言 修改 go websocket server 启动方式, 内存占用立省 40% !
@lesismal #7 我只是肉眼分析,实际差别应该不会特别大。可以实测对比下玩玩看,需要排除掉多次启动基础内存占用可能不同的一些差别、比如多跑几轮
2023-04-27 18:20:30 +08:00
回复了 Nazz 创建的主题 Go 编程语言 修改 go websocket server 启动方式, 内存占用立省 40% !
单就主帖内容,TCP 这个可能会误导一些人、以为可以替换了原来的方案了,所以特地来回复一下,顺便又 review 了几眼
2023-04-27 18:18:19 +08:00
回复了 Nazz 创建的主题 Go 编程语言 修改 go websocket server 启动方式, 内存占用立省 40% !
还有一点,在 handler 里其实可以不用新开协程的,socket.Listen() 就可以了、不要 go ,这样就复用了 http server 原来的那个协程、避免了一次不必要的协程重建和一些变量逃逸。比如前面说到的 http conn 挂载的读写 buffer ,因为是默认 4k ,1.18 还是哪个版本之后来着、协程栈好像默认是 8k ,所以一般而言,复用了原来的协程,则挂载的这个读 buffer 至少不太涉及跨协程的逃逸,所以应该是能使用原来这个协程的栈空间,除非你要设置得很大 size
2023-04-27 18:10:55 +08:00
回复了 Nazz 创建的主题 Go 编程语言 修改 go websocket server 启动方式, 内存占用立省 40% !
另外,upgrader.Accept 和 socket.Listen 这两个命名实在是有点难受,Accept 和 Listen 都与实际的行为意义不匹配,OP 啥时候改成和大家一致的比如也叫 upgrader.Upgrade ,另一个叫 ReadLoop()之类的,老接口可以保留,提供个新的命名就行。。。
2023-04-27 18:06:48 +08:00
回复了 Nazz 创建的主题 Go 编程语言 修改 go websocket server 启动方式, 内存占用立省 40% !
这种 over TCP 的不能用于浏览器相关的领域,对于绝大多数 Websocket 用户是与 Web 浏览器的前段交互,所以绝大多数用户都不能用这种直接 over TCP 的 Websocket ,所以这样对比其实本身就没什么意义。

之前也有人在我这问基于 TCP 的,开放了一些字段,现在随便哪里的数据传递给 Websocket 解析器就可以了,基于 TCP/Unix/QUIC/KCP 或者随便什么协议都可以:
https://github.com/lesismal/nbio/issues/240#issuecomment-1304804444

如果是游戏、App 之类的用 TCP 通常也都是自家封装的协议、更简单更定制化。

内存占用的问题,标准库 HTTP 这块确实是,conn 上面挂载的读、写 buffer ,Hijack 的时候又新建了个写 buffer 传给用户,我看 OP 代码里是把新建的这个写 buffer=nil 释放了,写的时候是用 net.Buffers ,但这样不一定是最佳:
1. 标准卡库 Hijack 时创建的写 Buffer 虽然一瞬间又销毁了、但毕竟不是 c free ,不能立即释放。但正常业务 Upgrade 并不是超级频繁的动作,所以影响也不算大
2. net.Buffers 调用 TCPConn 这种,最后是 syscall.Writev ,以前做测试用 net.Buffers 性能比应用层自己拼接 buffer 然后 Write 要稍微差一点点。syscall.Writev 的内核 c 实现也是创建大 buffer 拷贝上去然后在 write ,但是用 syscall.Writev 可能消耗更多的内核资源和时间、而内核是整个系统共用资源时间比较宝贵且竞争,所以可能反倒不如让应用层来做这种划算,所以或许直接复用 Hijack 传过来的那个 Writer 也是相当于应用层的 Writev ,性能也还好。但是我很久没再做这个 syscall.Writev 与应用层自己 Writev 的对比了,不知道现在新版本如何。我自己的库一些实现是避免使用 net.Buffers 的,但是不管怎么用,性能差别应该不大

标准库 HTTP 的解析有些地方也是比较浪费的,重复拷贝、循环之类的,之前想去 pr 一波来着,但是标准库的实现太复杂了、功能也多、并发流也多,有的地方是一个连接可能多个协程处理的。pr 流程也很麻烦,要想 pr 成功太耗费精力了,所以放弃了
OP 可以试下去把标准库 Hijack()这块的代码优化下,Hijack 之后就把 conn 自己身上挂载的读写 buffer=nil ,说不定还有一些其他可以清理的
2023-04-26 22:13:52 +08:00
回复了 yagamil 创建的主题 程序员 保护手腕的最好办法还是尽量使用键盘操作
被动防守不如主动出击,所以,主要还是要多运动、不要长时间电脑前。
2023-04-26 22:10:28 +08:00
回复了 lesismal 创建的主题 Go 编程语言 最近犯闲,想再写点啥项目,有推荐的吗?
@honkki 没有呢,我比较懒,都是搜别人的博客看😂。。
2023-04-23 14:23:40 +08:00
回复了 xitek 创建的主题 程序员 2023 年了,希望果粉们不要掉在苹果的茧房里出不来了
> 其次价格,苹果的 2T 固态能卖 3000 元

OP 净胡诌!你哪只眼睛看到苹果 2T 固态 3000 了?

明明是 1T 3000 !
@0o0O0o0O0o 我们的一些新项目,没有历史包袱,就没有部署 nginx 了。但很多团队里,nginx 这些似乎成了一种技术惯性,不管有没有必要、都部署,整个社区需要很长时间去摆脱这个惯性
#17 go 性能、占用、开发效率能达到非常好的平衡。自定制起来也比 nginx lua/resty 要更有潜力。
@0o0O0o0O0o
其实用 go 最大的好处是:可以省去 nginx 了。:joy:
@lesismal #14

> 或者 OP 也可以另一种方案,还是先自己封装下 Listener ,tls Accept 得到的 tls Conn 用 RemoteAddr string 存 map 里

ConnContext 就可以了,更简单
@0o0O0o0O0o SaveConnInContext 这个挺好,学到了

@dzdh 要不我把 nbio 的 tls 的 clientHello 以及 clientHello 的 marshal 暴露出来,然后就方便拿到了,我本地改了试了下是可以的了,暂时没有更新到 repo:
https://gist.github.com/lesismal/316b711a7f39cc539cebaad6c8e5b0fd?permalink_comment_id=4545513#gistcomment-4545513



但是 nbio 只支持 http1.x ,http2.0/3.0QUIC 功能太多、我短期内没精力去做了。

或者 OP 也可以另一种方案,还是先自己封装下 Listener ,tls Accept 得到的 tls Conn 用 RemoteAddr string 存 map 里,handler 里取出 tls Conn ,并且反射或者自己 type 一个相同 fields 的结构体但是 ClientHello 大写导出的方式+unsafe 指针强转类型后把 clientHello 这个字段取出来用,但是这样怕 go 不同版本该结构体字段发生变化,要自己额外做一些对应 go 不同版本编译的兼容
#8

修改了下,用标准库的没必要自己封装 Listener ,直接 ConnState 处理就可以了,代码更新了

```golang
package main

import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/signal"
"sync"
"time"

"github.com/lesismal/nbio/nbhttp"
)

var connections = sync.Map{}

func nbioOnEcho(w http.ResponseWriter, r *http.Request) {
res, ok := w.(*nbhttp.Response)
if ok {
conn := res.Parser.Processor.Conn()
yourData, ok := connections.Load(conn)
if ok {
fmt.Println("nbioServer onEcho:", yourData)
} else {
fmt.Println("nbioServer onEcho: not found connection")
}
}
w.Write([]byte(time.Now().Format("20060102 15:04:05")))
}

func nbioServer() {
mux := &http.ServeMux{}
mux.HandleFunc("/", nbioOnEcho)

engine := nbhttp.NewServer(nbhttp.Config{
Network: "tcp",
Addrs: []string{"localhost:8080"},
Handler: mux,
IOMod: nbhttp.IOModBlocking,
})
engine.OnOpen(func(conn net.Conn) {
fmt.Println("nbioServer onOpen:", conn.RemoteAddr())
yourData := "data: " + conn.RemoteAddr().String() // or other things
connections.Store(conn, yourData)
})
engine.OnClose(func(conn net.Conn, err error) {
connections.Delete(conn)
fmt.Println("nbioServer onClose:", conn.RemoteAddr(), err)
})

err := engine.Start()
if err != nil {
fmt.Printf("nbio.Start failed: %v\n", err)
return
}

interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
<-interrupt

ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
engine.Shutdown(ctx)
}

func netOnEcho(w http.ResponseWriter, r *http.Request) {
yourData, ok := connections.Load(r.RemoteAddr)
if ok {
fmt.Println("netServer onEcho:", yourData)
} else {
fmt.Println("netServer onEcho: not found connection " + r.RemoteAddr)
}
w.Write([]byte(time.Now().Format("20060102 15:04:05")))
}

func netServer() {
mux := &http.ServeMux{}
mux.HandleFunc("/", netOnEcho)

server := &http.Server{
Addr: "localhost:8080",
Handler: mux,
ConnState: func(conn net.Conn, state http.ConnState) {
switch state {

case http.StateNew:
fmt.Println("netServer onOpen:", conn.RemoteAddr())
remoteAddr := conn.RemoteAddr().String()
yourData := "data: " + remoteAddr // or other things
connections.Store(remoteAddr, yourData)
case http.StateActive:
case http.StateIdle:
case http.StateHijacked:
case http.StateClosed:
connections.Delete(conn)
fmt.Println("netServer onClose:", conn.RemoteAddr())
}
},
}
err := server.ListenAndServe()
fmt.Println("server exit:", err)
}

func main() {
netServer()
// nbioServer()
}
```
哦对了,基于标准库的代码部分,我忘记处理连接关闭时 Map.Delete 了,得看下 ConnState 之类的 Hook 能不能处理,如果不能处理,那基于标准库还不能这样简单搞、连接断开不清理就相当于泄露了导致 map 无限增长
1 ... 23  24  25  26  27  28  29  30  31  32 ... 62  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2218 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 27ms · UTC 16:02 · PVG 00:02 · LAX 08:02 · JFK 11:02
Developed with CodeLauncher
♥ Do have faith in what you're doing.