go之道--搭建高性能服务器

简介: 搭建一个后台服务器, 有很种方法与模式,c/c++使用epoll, nodejs的V8等都能实现。 今天我们来看看go server的模式。 同以往其他语言底层采用epoll模式略有不同,golang采用了goroutine(协程)+channel的模式。 ![image.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/

搭建一个后台服务器, 有很种方法与模式,c/c++使用epoll, nodejs的V8等都能实现。

今天我们来看看go server的模式。

同以往其他语言底层采用epoll模式略有不同,golang采用了goroutine(协程)+channel的模式。
image.png

routine: 在线程之上封装的协程,每个都有自己的堆栈。

channel:协程间通信使用的。

创建routine的命令是

go func()

我们来举个例子。

比如创建一个http服务:

func Index(w http.ResponseWriter, r *http.Request){
    log.Println("static:index_request,  ip:", r.RemoteAddr)
    io.WriteString(w, r.URL.Path)
     
}
func GetData(w http.ResponseWriter, r *http.Request){
    log.Println("static:index_request,  ip:", r.RemoteAddr)
    vars := mux.Vars(r)
    id := vars["id"]
    log.Println("get data, id:", id)
    io.WriteString(w, r.URL.Path)
}
 
func createServer() error{
    router := mux.NewRouter().StrictSlash(true)
    router.HandleFunc("/getData/{id}", GetData)
    router.HandleFunc("/", Index)
    http.ListenAndServe(":12345", router)
    return nil
}

router专门负责请求路由。

/请求只是负责打印请求链接。

/getData/{id}是一条查询数据请求。

看看内部对每条新请求的处理逻辑:

func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    if fn := testHookServerServe; fn != nil {
        fn(srv, l)
    }
    var tempDelay time.Duration // how long to sleep on accept failure
    if err := srv.setupHTTP2_Serve(); err != nil {
        return err
    }
    // TODO: allow changing base context? can't imagine concrete
    // use cases yet.
    baseCtx := context.Background()
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
    for {
        rw, e := l.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

可以看到最后一条语句为 go c.serve(ctx) ,意味着每条新链接都会被放进一条新routine里去执行,这样避免了各请求间的阻塞关系。

假设同一条请求有多个阻塞子请求,比如

getData调用2个请求以获得数据:

func GetDataA() (*A, error){
    time.Sleep(time.Second * 1)
    return &A{string("a")}, nil
}
func GetDataB() (*B, error){
    time.Sleep(time.Second * 0.5)
    return &B{string("b")}, nil
}
 
 
 
func GetData(w http.ResponseWriter, r *http.Request){
    log.Println("static:index_request,  ip:", r.RemoteAddr)
    vars := mux.Vars(r)
    id := vars["id"]
    log.Println("get data, id:", id)
    io.WriteString(w, r.URL.Path)
    a, _ := GetDataA()
    b, _ := GetDataB()
    log.Println("a:", a.a, ",b:", b.b)
}

一般情况下,会将GetDataA/GetDataB顺序执行,假设A函数占用1s, B函数占用0.5秒,则GetData调用时间将超过1.5秒。

在go routine下可以很好的解决问题。

func GetDataA(ca *chan A) error{
    time.Sleep(time.Second * 1)
    ca <- A{string("a")}
    return nil
}
func GetDataB(cb *chan B) error{
    time.Sleep(time.Second * 0.5)
    cb <- &B{string("b")}
    return nil
}
func GetData(w http.ResponseWriter, r *http.Request){
    log.Println("static:index_request,  ip:", r.RemoteAddr)
    vars := mux.Vars(r)
    id := vars["id"]
    log.Println("get data, id:", id)
    io.WriteString(w, r.URL.Path)
    var ca chan A
    var cb chan B
    go GetDataA(&ca)
    go GetDataB(&cb)
    a := <- ca
    b := <- cb
    log.Println("a:", a.a, ",b:", b.b)
}

这样GetDataA/GetDataB即可以并行执行,总共时间约1秒即可完成。

上面的 a := <- ca即表示a是接受channel的请求。

整条链路的流程:
image.png

现实工作中可能会有很多类似串行转并行的过程。

比如前端界现在流行的服务端渲染, 对于多个model数据的请求,采用routine获取,可以将时间长度最大缩短。

备注:

c++与golang高并发模式不同,之前我写过c/c++的高性能服务器文章:http://blog.csdn.net/xiaofei_hah0000/article/details/8743627

目录
相关文章
|
8月前
|
移动开发 JavaScript 前端开发
精通服务器推送事件(SSE)与 Python 和 Go 实现实时数据流 🚀
服务器推送事件(SSE)是HTML5规范的一部分,允许服务器通过HTTP向客户端实时推送更新。相比WebSocket,SSE更轻量、简单,适合单向通信场景,如实时股票更新或聊天消息。它基于HTTP协议,使用`EventSource` API实现客户端监听,支持自动重连和事件追踪。虽然存在单向通信与连接数限制,但其高效性使其成为许多轻量级实时应用的理想选择。文中提供了Python和Go语言的服务器实现示例,以及HTML/JavaScript的客户端代码,帮助开发者快速集成SSE功能,提升用户体验。
|
4月前
|
安全
基于Reactor模式的高性能服务器之Acceptor组件(处理连接)
本节介绍了对底层 Socket 进行封装的设计与实现,通过 `Socket` 类隐藏系统调用细节,提供简洁、安全、可读性强的接口。重点包括 `Socket` 类的核心作用(管理 `sockfd_`)、成员函数的功能(如绑定地址、监听、接受连接等),以及 `Acceptor` 组件的职责:监听连接、接收新客户端连接并分发给上层处理。同时说明了 `Acceptor` 与 `EventLoop` 和 `TcpServer` 的协作关系,并展示了其成员变量和关键函数的工作机制。
108 2
|
4月前
|
人工智能 负载均衡 监控
使用 Go 和 Gin 实现高可用负载均衡代理服务器
本文基于Go语言和Gin框架,实现了一个企业级负载均衡代理服务器,支持动态路由、健康检查、会话保持等功能。具备高可用性与高性能,单节点支持100k+ QPS,延迟达亚毫秒级,并提供完整的压力测试方案与优化建议。
147 7
|
4月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
4月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
370 0
|
7月前
|
人工智能 搜索推荐 程序员
用 Go 语言轻松构建 MCP 客户端与服务器
本文介绍了如何使用 mcp-go 构建一个完整的 MCP 应用,包括服务端和客户端两部分。 - 服务端支持注册工具(Tool)、资源(Resource)和提示词(Prompt),并可通过 stdio 或 sse 模式对外提供服务; - 客户端通过 stdio 连接服务器,支持初始化、列出服务内容、调用远程工具等操作。
1748 4
|
7月前
|
Go API 定位技术
MCP 实战:用 Go 语言开发一个查询 IP 信息的 MCP 服务器
随着 MCP 的快速普及和广泛应用,MCP 服务器也层出不穷。大多数开发者使用的 MCP 服务器开发库是官方提供的 typescript-sdk,而作为 Go 开发者,我们也可以借助优秀的第三方库去开发 MCP 服务器,例如 ThinkInAIXYZ/go-mcp。 本文将详细介绍如何在 Go 语言中使用 go-mcp 库来开发一个查询 IP 信息的 MCP 服务器。
434 0
|
8月前
|
人工智能 安全 大数据
【限时特惠】阿里云服务器7折抢购!高性能+高性价比,开发者与企业必备攻略
阿里云服务器限时7折特惠,高性能、高性价比,为开发者和企业量身打造!新老用户均可参与,灵活配置满足多种需求,全球节点低延迟覆盖。自研神龙架构保障稳定性,安全防护全面,操作便捷,生态丰富。适用于个人开发、企业部署、跨境业务及AI计算等场景。点击专属链接立即抢购,活动名额有限,速来享受云端算力带来的高效体验!
212 0
|
9月前
|
存储 人工智能 弹性计算
2025年阿里云企业高性能云服务器租用价格与选型详解
随着企业数字化转型,阿里云于2025年推出多款高性能云服务器实例,涵盖计算、通用和内存密集型场景。文章分析了企业选择云服务器的核心要点,包括明确业务需求(如计算密集型任务推荐计算型实例)、性能与架构升级(如第八代实例性能提升20%),以及第九代实例支持AI等高算力需求。同时提供了配置价格参考和成本优化策略,助力企业实现效率与成本的最优平衡。
|
安全 Go 调度
Go语言中的并发编程:解锁高性能程序设计之门####
探索Go语言如何以简洁高效的并发模型,重新定义现代软件开发的边界。本文将深入剖析Goroutines与Channels的工作原理,揭秘它们为何成为实现高并发、高性能应用的关键。跟随我们的旅程,从基础概念到实战技巧,一步步揭开Go并发编程的神秘面纱,让您的代码在多核时代翩翩起舞。 ####

热门文章

最新文章