GO服务端Syscall、epollwait CPU开销过高

wudi2022 · 2022-11-18 22:57:09 · 3415 次点击 · 大约8小时之前 开始浏览    置顶
这是一个创建于 2022-11-18 22:57:09 的主题,其中的信息可能已经有所发展或是发生改变。

用go实现了一个数据库访问的代理,即客户端发数据到proxy proxy转发至后端数据库并将数据返回给客户端,项目使用的go net包; pprof分析cpu耗时,发现syscall (28%)和epollwait(50%)占比相当高,syscall主要是net的read和write耗时,已经优化合并了部分读写操作,epollwait高的吓人,请大神帮助分析下,感激不尽

4069.png


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3415 次点击  
加入收藏 微博
8 回复  |  直到 2022-11-29 13:34:01
wudi2022
wudi2022 · #1 · 2年之前

image.png

zoloadang007
zoloadang007 · #2 · 2年之前

mark :smiley:

wudi2022
wudi2022 · #3 · 2年之前

程序是实现了一个数据库代理服务:

客户端 <---> 代理服务 <--> 数据库

代码逻辑大概如下: // 建立监听 s.listener, err = net.Listen(netProto, s.addr)

//循环accept for s.running { conn, err := s.listener.Accept() if err != nil { ... continue }

            // 协程处理connection
    go s.onConn(conn, dbType)
}

// func (s *Server) onConn(c net.Conn, dbType string) { ... // 循环监听读connection消息 for { // 使用 io.ReadFull读消息;RB是 bufio.Reader; 若没消息程序会停在这里,我理解这就是epoll的挂起 并不消耗cpu header := make([]byte, 5) if _, err := io.ReadFull(cc.pkg.Rb, header); err != nil { return nil, nil, err }

    ....
    //收到消息后 会通过后端数据库链接将消息写入数据库连接中;Wb是 io.Writer
    // 其中后端链接通过连接池维护,轮询选取,选取时有一个锁控制选取哪个节点;此方法cpu耗时很少
   err = cc.backendConn.Conn.pkg.Wb.Write(data)

   ...
   //for循环监听数据库端返回消息
    for {
    header := make([]byte, 5)
    data, err = io.ReadFull(cc.backendConn.Conn.pkg.Rb, header )
           // 处理消息
            .....
           //写消息到客户端
           c.pkg.Wb.Write(data)
         }
    // 回收数据库连接到资源池
    c.closeConn(c.backendConn, false)
 }

... }

wudi2022
wudi2022 · #4 · 2年之前

程序是实现了一个数据库代理服务:

客户端 <---> 代理服务 <--> 数据库

代码逻辑大概如下:

// 建立监听
s.listener, err = net.Listen(netProto, s.addr)
//循环accept
for s.running {
        conn, err := s.listener.Accept()
        if err != nil {
            ...
            continue
        }

                // 协程处理connection
        go s.onConn(conn, dbType)
    }

//
func (s *Server) onConn(c net.Conn, dbType string) {
...
// 循环监听读connection消息
for {
       // 使用 io.ReadFull读消息;RB是 bufio.Reader; 若没消息程序会停在这里,我理解这就是epoll的挂起 并不消耗cpu
    header := make([]byte, 5)
    if _, err := io.ReadFull(cc.pkg.Rb, header); err != nil {
        return nil, nil, err
    }

        ....
        //收到消息后 会通过后端数据库链接将消息写入数据库连接中;Wb是 io.Writer
        // 其中后端链接通过连接池维护,轮询选取,选取时有一个锁控制选取哪个节点;此方法cpu耗时很少
       err = cc.backendConn.Conn.pkg.Wb.Write(data)

       ...
       //for循环监听数据库端返回消息
        for {
        header := make([]byte, 5)
        data, err = io.ReadFull(cc.backendConn.Conn.pkg.Rb, header )
               // 处理消息
                .....
               //写消息到客户端
               c.pkg.Wb.Write(data)
             }
        // 回收数据库连接到资源池
        c.closeConn(c.backendConn, false)
     }
  ...
}
go-bai
go-bai · #5 · 2年之前

被你写成了对讲机式的半双工通信😂

wudi2022
wudi2022 · #6 · 2年之前
go-baigo-bai #5 回复

被你写成了对讲机式的半双工通信😂

请明示啊

wudi2022
wudi2022 · #7 · 2年之前

又做了测试, 数据链路 client --> proxy,然后proxy mock数据返回client,QPS能达到近110W; 然后增加一个 proxy到数据库的写(读还没增加),其他不变,QPS下降近一半 只有50多W;

nginx转发效率就很高,按道理数据链路应该差不多,怎么做到损耗这么小的?

wujincheng2333
wujincheng2333 · #8 · 2年之前

//for循环监听数据库端返回消息 for { header := make([]byte, 5) data, err = io.ReadFull(cc.backendConn.Conn.pkg.Rb, header ) // 处理消息 ..... //写消息到客户端 c.pkg.Wb.Write(data) } // 回收数据库连接到资源池 c.closeConn(c.backendConn, false) }

这个代码需要起一个协程。。。

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传