大家好,我是蓝胖子,做开发的同学应该经常听到过负载均衡的概念,今天我们就来实现一个乞丐版的四层负载均衡,并用它对mysql进行负载均衡测试,通过本篇你可以了解到零拷贝的应用,四层负载均衡的本质以及实践。

本文代码已经上传到github

https://github.com/HobbyBear/codelearning/tree/master/layer4balance

为了知识的完整性,我们也科普下七层负载均衡的概念,我们先简单了解下四层负载均衡和7层负载均衡的区别。

四层负载均衡和七层负载均衡

七层负载均衡

首先,我们来看下七层负载均衡,它一般是针对应用层请求协议做请求转发,拿http请求举例,有A,B两台服务器,如果采用轮询的负载均衡策略,负载均衡器将第一个请求转发给了A服务器,那么第二个请求到达时,负载均衡器就会把请求转发到B服务器。

在转发时,能够在应用协议层对请求做一些变动,拿http请求来说,可以对http的请求头,http路径做相应的变动。

四层负载均衡

再来看看四层负载均衡,它一般是指针对连接做的负载均衡,举例说明下,有A,B两台服务器,同样采取轮询的策略,某个客户端发起一个新的连接,经过均衡器连接到了A服务器,现在又来一个客户端同样发起连接,经过均衡器后,此时就该和B服务器建立连接了。而在同一个连接里是能够发送多个请求的,这也是和七层负载均衡最本质的区别,它是针对连接做的负载均衡。

实现四层负载均衡器

实现四层负载均衡策略的方式有很多,比较著名的四层负载均衡软件就有lvs,它是通过修改数据包的ip地址或者mac地址实现四层负载均衡,性能较好,工作模式有好几种,具体的就不在本文展开了。

本文实现的四层负载均衡的原理和nginx四层负载类似 ,通过均衡器在客户端和服务端之前都维护一个连接来达到让 客户端在同一个连接里发送的请求都会被服务端同一个连接所接收的目的。如下图所示:

以后client1 通过连接A发的请求都会由连接B发往服务器,而client2通过连接C发送的请求,都将经过连接D发往另一台服务器。

实现逻辑

现在让我们来实现下这部分的逻辑,我将会以轮询的策略实现连接的负载均衡。

并且这里还要考虑下实现数据复制的逻辑,我们需要在均衡器分别建立对客户端和服务端的socket连接,并且将其中一个socket的数据转移到另一个socket,如果每次都将某一个socket数据读到用户层,再写到另一个socket就会导致一些没有必要的拷贝。伪代码如下:

var (
src net.Conn // 一个socket 连接
dst net.Conn // 一个socket连接
)
// ...
buf = make([]byte, size)
nr, er := src.Read(buf)
nw, ew := dst.Write(buf[0:nr])

有没有什么技术让内核自动将某个socket的数据转移到另一个socket,不用将数据拷贝到应用层来,这正是零拷贝相关的技术,关于零拷贝的技术原理我在之前这篇文章 有很详细的介绍,内核提供了一个splice的系统调用,专门用于socket连接间拷贝数据,只需要调用时传入对应socket连接的文件描述符即可让内核自动完成拷贝过程。

func Splice(rfd int, roff *int64, wfd int, woff *int64, len int, flags int) (n int64, err error) 

这个系统调用已经被golang更深层次的封装到了一个比较常用的方法io.Copy里,这个方法会自动判断reader和writer底层的类型,如果都是socket连接则会调用splice系统调用实现零拷贝。

func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}

接着我们看下均衡的代码逻辑,运行逻辑如下:

1, 监听到新连接,启动一个协程去处理连接。

2 , 在新协程里与通过轮询的策略,选择一个后端服务器并与之建立连接。

3, 启动两个协程分别进行io.Copy ,将客户端的socket写到服务端socket,将服务端socket返回的信息写到客户端socket。代码如下:

type Server struct {
Li net.Listener
Balance balancepolicy.Policy
} func (s *Server) Run() {
for {
c, err := s.Li.Accept()
if err != nil {
log.Fatal(err)
}
go func(c net.Conn) {
remoteAddr := c.RemoteAddr()
backendIp := s.Balance.PickNode(remoteAddr.String())
serverConn, err := net.Dial("tcp", backendIp)
if err != nil {
log.Fatal(err)
c.Close()
return
}
fmt.Println("获取到了新连接", remoteAddr, backendIp)
go func() {
_, err := io.Copy(serverConn, c)
if err != nil {
fmt.Println(err, 1)
}
c.Close()
serverConn.Close()
fmt.Println("结束1", err)
}()
go func() {
_, err := io.Copy(c, serverConn)
if err != nil {
fmt.Println(err, 2)
}
c.Close()
serverConn.Close()
fmt.Println("结束2", err)
}()
}(c)
} }

io.Copy 会不断的拷贝源socket的数据到目的socket,直到连接关闭。

更好的方案

可以看到上述方案中维护一个客户端的连接将会启动3个协程,当连接量上去后,均衡器很可能成为瓶颈,有没有办法减少下协程的数量,可以直接采用epoll的方式监听连接的读写,以及关闭事件(这样能在一个协程里处理多个连接),当连接可读时,直接使用splice系统调用对数据进行拷贝直到返回syscall.EAGAIN 就停止,因为返回syscall.EAGAIN 说明连接缓冲区内的数据暂时被读取完了,继续下一次epoll wait的监听循环。这样能极大的减少协程数量。不过实现我就不准备再继续展开了,后续有空再补充下这部分。对epoll的使用有兴趣的同学也可以看看我之前一篇用epoll实现类似redis的网络模型框架这篇文章

测试负载均衡代码

现在让我们来测试下负载均衡的代码,我会用docker-compose去启动两个mysql,然后本地启动我们负载均衡器的代码,之后用两个mysql客户端去连接负载均衡器,看下是不是mysql客户端连接到了不同的mysql服务器。

docker-compose的配置文件如下:

version: '3'
services:
mysql1:
restart: always
image: amd64/mysql:latest
container_name: mysql1
environment:
- "MYSQL_ROOT_PASSWORD=1234567"
- "MYSQL_DATABASE=test"
ports:
- "3306:3306" mysql2:
restart: always
image: amd64/mysql:latest
container_name: mysql2
environment:
- "MYSQL_ROOT_PASSWORD=1234567"
- "MYSQL_DATABASE=test2"
ports:
- "3307:3306"

为了能验证不同客户端的确连上了不同的mysql服务器,我在mysql1上创建了test数据库,在mysql2上创建了test2数据库。到时候连上不同服务器数据库是不一样的。

均衡服务器监听5555端口启动

s := &proxy.Server{}
li, err := net.Listen("tcp", ":5555")
if err != nil {
log.Fatal(err)
}
s.Li = li
s.Balance = balancepolicy.NewRoundRobin()
s.Balance.AddNode("127.0.0.1:3306", "mysql1")
s.Balance.AddNode("127.0.0.1:3307", "mysql2")
s.Run()

之后用mysql客户端去连接均衡服务器

## client1
mysql -h 127.0.0.1 -u root -P 5555 -D test -p1234567 ## client2
mysql -h 127.0.0.1 -u root -P 5555 -D test2 -p1234567

发现两个mysql客户端的确连接到了不同服务器,并且能正常执行命令,over。

golang 实现四层负载均衡的更多相关文章

  1. nginx四层负载均衡配置

    nginx四层负载均衡配置代理Mysql集群 环境如下: ip 192.168.6.203 Nginx ip 192.168.6.*(多台) Mysql 步骤一 查看Nginx是否安装stream模块 ...

  2. Nginx基于TCP/UDP端口的四层负载均衡(stream模块)配置梳理

    通过我们会用Nginx的upstream做基于http/https端口的7层负载均衡,由于Nginx老版本不支持tcp协议,所以基于tcp/udp端口的四层负载均衡一般用LVS或Haproxy来做.至 ...

  3. LVS 原理(调度算法、四种模式、四层负载均衡和七层 的区别)

    参考文档:http://blog.csdn.net/ioy84737634/article/details/44916241 目录 lvs的调度算法 lvs的四种模式 四层均衡负载和七层的区别 1.l ...

  4. MGW——美团点评高性能四层负载均衡

    转自美团点评技术博客:https://tech.meituan.com/MGW.html 前言 在高速发展的移动互联网时代,负载均衡有着举足轻重的地位,它是应用流量的入口,对应用的可靠性和性能起着决定 ...

  5. 安装Nginx四层负载均衡

    Nginx1.9开始支持tcp层的转发,通过stream实现的,而socket也是基于tcp通信. stream模块默认不安装的,需要手动添加参数:–with-stream,官方下载地址:downlo ...

  6. 【Nginx】四层负载均衡配置

    一.概述 二.配置 2.1 环境准备 2.2 安装及配置 1).下载Nginx 2).下载nginx_tcp_proxy_module 插件 3).编译Nginx 4).修改Nginx.conf配置文 ...

  7. 14.Nginx四层负载均衡

    1.七层负载均衡: 根据url 调度不同的集群 url.cheng.com 10.0.0.5 10.0.0.7 /pass 10.0.0.8 /user 1.web01和web02配置 (只不过代码不 ...

  8. Nginx四层负载均衡概述

    目录 Nginx四层负载均衡概述 什么是负载均衡 负载均衡应用场景 四层,七层集群架构 四层负载均衡总结 Nginx如何配置四层负载均衡 nginx四层负载均衡端口转发 Nginx四层负载均衡概述 什 ...

  9. 14、Nginx四层负载均衡

    1.Nginx四层负载均衡基本概述 1.1.什么是四层负载均衡 四层负载均衡基于传输层协议包来封装的(如:TCP/IP),那我们前面使用到的七层是指的应用层,它的组装在四层基础之上,无论四层还是七层都 ...

  10. Nginx四层负载均衡

    目录 Nginx四层负载均衡概述 Nginx如何配置四层负载均衡 使用nginx四层负载均衡实现tcp的转发 Nginx四层负载均衡概述 什么是四层负载均衡 四层负载均衡是基于传输层协议包来封装的(如 ...

随机推荐

  1. IBM Cloud Computing Practitioners 2019 (IBM云计算从业者2019)Exam答案

    Cloud Computing Practitioners 2019 IBM Cloud Computing Practitioners 2019 (IBM云计算从业者2019)Exam答案,加粗的为 ...

  2. 面向对象编程(python)和部分面向对象高级编程

    1.类和对象 在python中定义类 class 类名(首字母最好大写)Student (Object(父类)): def __init__(self): self.属性 1= 参数1 self.属性 ...

  3. 从内核源码看 slab 内存池的创建初始化流程

    在上篇文章 <细节拉满,80 张图带你一步一步推演 slab 内存池的设计与实现 >中,笔者从 slab cache 的总体架构演进角度以及 slab cache 的运行原理角度为大家勾勒 ...

  4. 弱语言返回的数值型变量有可能是int,也有可能是string,该如何赋值给结构体

    包地址 github.com/jefferyjob/go-easy-util... 介绍 在解析弱语言类型返回的 Json 数据时,我们可能会遇到一些麻烦,比如 Json 数据中的数值型变量既可能是 ...

  5. 极简组调度-CGroup如何限制cpu

    1. 说明 1> linux内核关于task调度这块是比较复杂的,流程也比较长,要从源码一一讲清楚很容易看晕,因此需要简化,抓住主要的一个点,抛开无关的部分才能讲清楚核心思想 2> 本篇文 ...

  6. ArcGIS Desktop发布地形高程服务(DEM/DSM)

    在做ArcGIS三维时,地形服务的发布与普通地图服务的发布不一样,需要发布成ImageServer,切片格式选择LERC. 本文示例使用软件: ArcGIS Desktop10.3.1 注:ArcGI ...

  7. 基于create-react-app构建静态博客

    前言: 用过hexo后,我被其强大的功能惊艳到了,于是便想自己也写一个静态博客生成器,并且可以发布到GitHub托管. 首先我们来看下效果图: 具体是怎么实现的呢? 我们通过create-react- ...

  8. \n 和 std::endl 的区别

    std::cout << std::endl; 等价于 std::cout << '\n' << std::flush; 除了写入换行符,std::endl 还会刷 ...

  9. C#使用词嵌入向量与向量数据库为大语言模型(LLM)赋能长期记忆实现私域问答机器人落地

    本文将探讨如何使用c#开发基于大语言模型的私域聊天机器人落地.大语言模型(Large Language Model,LLM 这里主要以chatgpt为代表的的文本生成式人工智能)是一种利用深度学习方法 ...

  10. js 获取 ajax返回数据及处理

    $.ajax({ url: "http://xiaocui.dgoods.cn/app/index.php?i=5&c=entry&do=check&m=stonef ...