分享|七牛云Go+实习体验

分享|七牛云Go+实习体验

讲一下我在七牛云实习的前三个月的经历

由于我的资历尚浅,一开始其实我是很怕跟不上大家的,并且是自己完全不熟悉的方向(Java -> Go -> Go+/LLGo),但是很幸运,七牛提供的实训营,只要你在努力,有进步,导师们都看在眼里。导师们都是公司技术top并且很负责,我与队友也相处的很融洽,最终我也慢慢的重拾自信。

目标

一开始是产品调研和目标确定,我们的想法不断的被推倒或者认同,最后和队友以及导师们确定了最终的一些目标:

核心点就是将 Rust 生态的能力带到 LLGo 中来

首先是学会怎么将 Rust 库迁移到 LLGo

迁移一些 Rust 生态到 LLGo 中验证可行性

使用 Libuv 为 LLGo 实现异步 I/O 能力

使用 Hyper 来实现 LLGo 的 net/http 库

使用 tokio/io-uring 为 LLGo 加速异步 I/O 能力

Rust 到 LLGo 这条路之前 LLGo 的开发者并没有尝试过,所以一切都得我们自己探索,充满了未知性。

我们最终都在围绕着使用 Rust 生态的 Hyper 来实现 net/http 库,但是考虑到 LLGo 中的 goroutine 使用的是 pthread,所以我们才引入了 Libuv,主要依靠它的事件循环机制来提高我们处理请求的速度。

前置准备

探索怎么将 Rust 迁移到 LLGo

这里感兴趣的同学可以看一下我们写的文档:How to support a Rust Library

这里真的就是要感谢我们的 之阳同学,因为他有过迁移 C 到 LLGo 的经验,所以给予了我们很大的帮助,让我们能够少走很多的弯路。(第一次感叹实习的同学们和导师们真的特别友好!!!)

迁移Hyper & Libuv

迁移的过程是枯燥的,我们只需要根据文档,就能够迁移大部分内容,如果遇到难点,是可以马上找到导师进行探讨的(第二次感叹实习的同学们和导师们真的特别友好!!!)

我们可以看一下 Libuv 在迁移前(C),和迁移后(Go)代码的调用模样(伪代码):

C:

loop = uv_default_loop();

uv_tcp_t server;

uv_tcp_init(loop, &server);

struct sockaddr_in addr;

uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);

uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);

int r = uv_listen((uv_stream_t*) &server, DEFAULT_BACKLOG, on_new_connection);

if (r) {

fprintf(stderr, "Listen error %s\n", uv_strerror(r));

return 1;

}

uv_run(loop, UV_RUN_DEFAULT);

Go:

loop := libuv.DefaultLoop()

var server libuv.Tcp

libuv.InitTcp(loop, &server)

var addr cnet.SockaddrIn

libuv.Ip4Addr(c.Str("0.0.0.0"), DEFAULT_PORT, &addr)

server.Bind((*cnet.Sockaddr)(unsafe.Pointer(&addr)), 0)

r := (*libuv.Stream)(unsafe.Pointer(&server)).Listen(DEFAULT_BACKLOG, onNewConnection)

if r != 0 {

fmt.Fprintf(os.Stderr, "Listen error %s\n", c.GoString(libuv.Strerror(libuv.Errno(r))))

return 1

}

loop.Run(libuv.RUN_DEFAULT)

官方Go编译器当然是不能运行的,得使用我们的 LLGo 编译器(崇拜🤩

实现 net/http 标准库

我负责实现的是 client 相关的功能,这里借用一下我队友的图来说明一下设计架构:

https://blog.cdn.hackerchai.com/images/2024/10/llgo-net-http-server.png

server 部分和 client 部分的设计基本一致,当用户发送一个请求,就将该请求分配给一个 LibuvLoop,利用 Libuv 事件循环促使 Hyper Executor 进行 Poll Task 操作完成对于 Request 的处理,然后我们得到 Response 对象并返回给用户。

为了加速性能和利用 CPU 多核特性,我们采用了 thread-per-core 的架构设计,一个 CPU 核心对应一个 Libuv 的 EventLoop 并且对应一个 Hyper 的 Executor,然后采用一定的 load balance 策略去分配进来的请求。

可以看一下我们的一个实现成果,我们正常调用 LLGo 的 net/http 库,就和使用标准库一样,但是底层实现逻辑已经被我们替换掉:

package main

import (

"fmt"

"io"

"github.com/goplus/llgoexamples/x/net/http" // 导入的包被替换

)

func main() {

resp, err := http.Get("https://httpbin.org")

if err != nil {

fmt.Println(err)

return

}

defer resp.Body.Close()

fmt.Println(resp.Status, "read bytes: ", resp.ContentLength)

for key, values := range resp.Header {

for _, value := range values {

fmt.Printf("%s: %s\n", key, value)

}

}

body, err := io.ReadAll(resp.Body)

if err != nil {

fmt.Println(err)

return

}

fmt.Println(string(body))

}

下面是我们分配 libuv eventLoop 的逻辑,主要通过 hash 进行负载均衡,好处是复用 连接池 中的 空闲连接 时,使用的是同一个 eventLoop:

func (t *Transport) getClientEventLoop(req *Request) *clientEventLoop {

t.loopsMu.Lock()

defer t.loopsMu.Unlock()

if t.loops == nil {

t.loops = make([]*clientEventLoop, cpuCount)

}

key := t.getLoopKey(req)

h := fnv.New32a()

h.Write([]byte(key))

hashcode := h.Sum32()

return t.getOrInitClientEventLoop(hashcode % uint32(cpuCount))

}

在下面 Libuv 的 uv_check 的回调中,去执行 exec.Poll 得到 task,然后去执行我们的 task :

func readWriteLoop(checker *libuv.Check) {

eventLoop := (*clientEventLoop)((*libuv.Handle)(c.Pointer(checker)).GetData())

// The polling state machine! Poll all ready tasks and act on them...

task := eventLoop.exec.Poll()

for task != nil {

eventLoop.handleTask(task)

task = eventLoop.exec.Poll()

}

}

感谢

对我来说,这段实习实习真的很棒。我们小队从产品设计,到技术选型,再到内容实现,每一步都有着许多的困难,接下来做什么,怎么做都是一个问题,队员间也充斥着不同的声音。之前的我们更多的是参与到内容实现的部分,也更多的是一个人完成工作。而这样一次机会,让我能够看到整个产品的生命周期,也能够和队友一起去思考,去讨论,去实现。导师们也很友善,遇到技术难点,导师也会引导我们去确定方案。实训结束后,能够明显感觉到自己解决问题的能力提升了很多,知道如何去定位问题,知道如何去思考解决问题的方式,当然提升最大的我觉得还是团队的协作能力。在这里你就是主角,你可以充分的展示自己,同时也能够从导师和队友身上学到很多。

也很感谢我的导师们 傲飞老师 、 七叶老师 、 长军老师 还有 老许 的倾情指导,让我对于系统底层和 Golang 底层有了更深刻的理解。也要感谢以下我的几位给力队友:轶晟、之阳,如果没有他们我可能很难独自完成这么复杂的工作。(第三次感叹实习的同学们和导师们真的特别友好!!!)

链接

幻灯片: qiniu-campus-slide

LLGo:https://github.com/goplus/llgo

适配 Libuv 的 Hyper FFI 分支: feature/server-ffi-libuv-demo

相关文章