在测试 HTTP 服务时,如果该进程我们忘记关闭,而重新尝试启动一个新的服务进程,那么将会遇到类似以下的错误信息:

$ go run main.go
listen tcp :8000: bind: address already in use

这是由于默认情况下,操作系统不允许我们打开具有相同源地址和端口的套接字 socket。但如果我们想开启多个服务进程去监听同一个端口,这可以吗?如果可以,这又能给我们带来什么?

socket 五元组

socket 编程是每位程序员都应该掌握的基础知识。因此,大家应该知道,socket 连接通过五元组唯一标识。任意两条连接,它的五元组不能完全相同。

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

protocol 指的是传输层 TCP/UDP 协议,它在 socket 被创建时就已经确定。src addr/portdest addr/port  分别标识着请求方与服务方的地址信息。

因此,只要请求方的 dest addr/port 信息不相同,那么服务方即使是同样的 src addr/port ,它仍然可以标识唯一的 socket 连接。

基于这个理论基础,那实际上,我们可以在同一个网络主机复用相同的 IP 地址和端口号。

Linux SO_REUSEPORT

为了满足复用端口的需求,Linux 3.9 内核引入了 SO_REUSEPORT选项(实际在此之前有一个类似的选项 SO_REUSEADDR,但它没有做到真正的端口复用,详细可见参考链接1)。

SO_REUSEPORT 支持多个进程或者线程绑定到同一端口,用于提高服务器程序的性能。它的特性包含以下几点:

  • 允许多个套接字 bind 同一个TCP/UDP 端口

    • 每一个线程拥有自己的服务器套接字

    • 在服务器套接字上没有了锁的竞争

  • 内核层面实现负载均衡

  • 安全层面,监听同一个端口的套接字只能位于同一个用户下(same effective UID)

有了 SO_RESUEPORT 后,每个进程可以 bind 相同的地址和端口,各自是独立平等的。

让多进程监听同一个端口,各个进程中 accept socket fd 不一样,有新连接建立时,内核只会调度一个进程来 accept,并且保证调度的均衡性。

其工作示意图如下

有了 SO_REUSEADDR 的支持,我们不仅可以创建多个具有相同 IP:PORT 的套接字能力,而且我们还得到了一种内核模式下的负载均衡能力。

Go 如何设置 SO_REUSEPORT

Linux 经典的设计哲学:一切皆文件。当然,socket 也不例外,它也是一种文件。

如果我们想在 Go 程序中,利用上 linux 的 SO_REUSEPORT 选项,那就需要有修改内核 socket 连接选项的接口,而这可以依赖于 golang.org/x/sys/unix 库来实现,具体就在以下这个方法。

import “"golang.org/x/sys/unix"”
...
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)

因此,一个持有 SO_REUSEPORT 特性的完整 Go 服务代码如下

package mainimport ("context""fmt""net""net/http""os""syscall""golang.org/x/sys/unix"
)var lc = net.ListenConfig{Control: func(network, address string, c syscall.RawConn) error {var opErr errorif err := c.Control(func(fd uintptr) {opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)}); err != nil {return err}return opErr},
}func main() {pid := os.Getpid()l, err := lc.Listen(context.Background(), "tcp", "127.0.0.1:8000")if err != nil {panic(err)}server := &http.Server{}http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.WriteHeader(http.StatusOK)fmt.Fprintf(w, "Client [%s] Received msg from Server PID: [%d] \n", r.RemoteAddr, pid)})fmt.Printf("Server with PID: [%d] is running \n", pid)_ = server.Serve(l)
}

我们将其编译为 linux 可执行文件 main

$ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go

在 linux 主机上开启三个同时监听 8000 端口的进程,我们可以看到三个服务进程的 PID 分别是 32687 、32691 和 32697。

~ $ ./main
Server with PID: [32687] is running
~ $ ./main
Server with PID: [32691] is running
~ $ ./main
Server with PID: [32697] is running

最后,通过 curl 命令,模拟多次 http 客户端请求

~ $ for i in {1..20}; do curl localhost:8000; done
Client [127.0.0.1:56876] Received msg from Server PID: [32697]
Client [127.0.0.1:56880] Received msg from Server PID: [32687]
Client [127.0.0.1:56884] Received msg from Server PID: [32687]
Client [127.0.0.1:56888] Received msg from Server PID: [32687]
Client [127.0.0.1:56892] Received msg from Server PID: [32691]
Client [127.0.0.1:56896] Received msg from Server PID: [32697]
Client [127.0.0.1:56900] Received msg from Server PID: [32691]
Client [127.0.0.1:56904] Received msg from Server PID: [32691]
Client [127.0.0.1:56908] Received msg from Server PID: [32697]
Client [127.0.0.1:56912] Received msg from Server PID: [32697]
Client [127.0.0.1:56916] Received msg from Server PID: [32687]
Client [127.0.0.1:56920] Received msg from Server PID: [32691]
Client [127.0.0.1:56924] Received msg from Server PID: [32697]
Client [127.0.0.1:56928] Received msg from Server PID: [32697]
Client [127.0.0.1:56932] Received msg from Server PID: [32691]
Client [127.0.0.1:56936] Received msg from Server PID: [32697]
Client [127.0.0.1:56940] Received msg from Server PID: [32687]
Client [127.0.0.1:56944] Received msg from Server PID: [32691]
Client [127.0.0.1:56948] Received msg from Server PID: [32687]
Client [127.0.0.1:56952] Received msg from Server PID: [32697]

可以看到,20 个客户端请求被均衡地打到了三个服务进程上。

总结

linux 内核自 3.9 提供的 SO_REUSEPORT 选项,可以让多进程监听同一个端口。

这种机制带来了什么:

  • 提高服务器程序的吞吐性能:我们可以运行多个应用程序实例,充分利用多核 CPU 资源,避免出现单核在处理数据包,其他核却闲着的问题。

  • 内核级负载均衡:我们不需要在多个实例前面添加一层服务代理,因为内核已经提供了简单的负载均衡。

  • 不停服更新:当我们需要更新服务时,可以启动新的服务实例来接受请求,再优雅地关闭掉旧服务实例。

如果你们的 Go 项目,一到高峰期就有请求堆积问题,这个时候就可以考虑采用 SO_REUSEPORT 选项。

参考链接:

【1. How do SO_REUSEADDR and SO_REUSEPORT differ?】https://stackoverflow.com/questions/14388706/how-do-so-reuseaddr-and-so-reuseport-differ

【2. SO_REUSEPORT 性能测试】 http://www.blogjava.net/yongboy/archive/2015/02/12/422893.html

【3. linux socket man-page】https://man7.org/linux/man-pages/man7/socket.7.html

感谢你的点赞在看哦~

Go 如何利用 Linux 内核的负载均衡能力?相关推荐

  1. Go 如何利用 Linux 内核的负载均衡能力

    在测试 HTTP 服务时,如果该进程我们忘记关闭,而重新尝试启动一个新的服务进程,那么将会遇到类似以下的错误信息: $ go run main.go listen tcp :8000: bind: a ...

  2. linux内核SMP负载均衡浅析

    需求       在<linux进程调度浅析>一文中提到,在SMP(对称多处理器)环境下,每个CPU对应一个run_queue(可执行队列).如果一个进程处于TASK_RUNNING状态( ...

  3. linux内核死锁检测机制 | oenhan,Linux内核CPU负载均衡机制 | OenHan

    还是神奇的进程调度问题引发的,参看Linux进程组调度机制分析,组调度机制是看清楚了,发现在重启过程中,很多内核调用栈阻塞在了double_rq_lock函数上,而double_rq_lock则是lo ...

  4. 使用LVS(Linux Virtual Server)在Linux上搭建负载均衡的集群服务

    使用LVS(Linux Virtual Server)在Linux上搭建负载均衡的集群服务 一.基于于NAT的LVS的安装与配置. 1. 硬件需求和网络拓扑                       ...

  5. 全面讲述linux集群负载均衡

    学习linux时,你可能会遇到linux集群的问题,这里将介绍linux集群负载均衡的方法,经过仔细整理,在这里拿出来和大家分享一下,希望本文能教会你更多东西. 集群原理 linux集群系统包括集群节 ...

  6. 华为路由器负载均衡_华为路由器配置利用NAT实现TCP负载均衡

    7.8 利用 NAT 实现 TCP 负载均衡 TCP 负载均衡是为了把一个外部的合法地址交替映射到多个内部地址上,这样可以使 多台服务器使用同一个外部地址进行访问. 一.实验目的 1. 掌握利用 NA ...

  7. 22岁精神小伙居然利用 Linux 内核漏洞实现 Docker 逃逸

    1 前言 Docker是时下使用范围最广的开源容器技术之一,具有高效易用等优点.由于设计的原因,Docker天生就带有强大的安全性,甚至比虚拟机都要更安全,但如此的Docker也会被人攻破,Docke ...

  8. Linux内核中段伪例,利用Linux内核里的Use-After-Free(UAF)漏洞提权

    *本文中涉及到的相关漏洞已报送厂商并得到修复,本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担. * 作者:nickchang 上个月爆出的CVE-2016-0728 (http: ...

  9. linux双网卡负载均衡,四个步骤完成双网卡负载均衡

    在linux下实现负载均衡我们已经对它的配置说过很多了,现在我们介绍的是关于在这个系统下的双网卡负载均衡的设定过程,总的可以分为四个步骤,首先我们要对虚拟网络接口文件进行改动,然后是对网卡的信息文件的 ...

最新文章

  1. sqlconnection,sqlcommand,SqlDataAdapter ,ExecuteNonQuery,ExecuteScalar
  2. 数字签名时间戳服务器的原理
  3. ECMAScript 6:更好的 Unicode 支持
  4. 赫尔维茨矩阵与matlab,第3章时域分析法剖析.ppt
  5. GeoTools——JTS空间操作
  6. fat linux 链接,FAT格式磁盘镜像制作方法
  7. oracle分析函数over(Partition by...)及开窗函数详解
  8. Flash课堂计分板
  9. 论现场跟客户演示软件产品
  10. 美服lol服务器状态,LOL美服训练模式上线:炮塔可关闭 野区无限刷新
  11. bzoj4556(sam)
  12. CLOCs: Camera-LiDAR Object Candidates Fusion for 3D Object Detection(论文阅读笔记)
  13. 电脑清灰过后,CPU温度下降,显卡温度却上升了
  14. 目标检测简介和滑动窗口
  15. MacBook Pro (M1 Pro芯片)使用安卓USB共享上网
  16. 从“最后一公里”问题谈起
  17. 《计算机视觉与图像处理》最全总结之就业必备-小白易懂易上手
  18. 多张图片合并一张图片,在中间添加文字
  19. 肠道微生物群的老化及其对宿主免疫力的影响
  20. xfire ---java web服务器引擎

热门文章

  1. oracle11 不更新记录,oracle11g 使用first_value获取表中不连接的ID号及掉失记录数量...
  2. python关闭文件os_如何关闭使用os.startfile(),Python 3.6打开的文件
  3. MaxCompute与OSS非结构化数据读写互通(及图像处理实例)
  4. 13.4 MySQL用户管理;13.5 常用sql语句;13.6 MySQL数据库备份恢复
  5. Linux文件系统之swap
  6. js实现svg图形转存为图片下载[转]
  7. Oracle 创建主键自增表
  8. 实战 MDT 2012(六)---基于MAC地址的部署
  9. 免费好用的web应用托管平台
  10. 设计模式(创建型)之原型模式