golang 实现四层负载均衡
大家好,我是蓝胖子,做开发的同学应该经常听到过负载均衡的概念,今天我们就来实现一个乞丐版的四层负载均衡,并用它对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 实现四层负载均衡的更多相关文章
- nginx四层负载均衡配置
nginx四层负载均衡配置代理Mysql集群 环境如下: ip 192.168.6.203 Nginx ip 192.168.6.*(多台) Mysql 步骤一 查看Nginx是否安装stream模块 ...
- Nginx基于TCP/UDP端口的四层负载均衡(stream模块)配置梳理
通过我们会用Nginx的upstream做基于http/https端口的7层负载均衡,由于Nginx老版本不支持tcp协议,所以基于tcp/udp端口的四层负载均衡一般用LVS或Haproxy来做.至 ...
- LVS 原理(调度算法、四种模式、四层负载均衡和七层 的区别)
参考文档:http://blog.csdn.net/ioy84737634/article/details/44916241 目录 lvs的调度算法 lvs的四种模式 四层均衡负载和七层的区别 1.l ...
- MGW——美团点评高性能四层负载均衡
转自美团点评技术博客:https://tech.meituan.com/MGW.html 前言 在高速发展的移动互联网时代,负载均衡有着举足轻重的地位,它是应用流量的入口,对应用的可靠性和性能起着决定 ...
- 安装Nginx四层负载均衡
Nginx1.9开始支持tcp层的转发,通过stream实现的,而socket也是基于tcp通信. stream模块默认不安装的,需要手动添加参数:–with-stream,官方下载地址:downlo ...
- 【Nginx】四层负载均衡配置
一.概述 二.配置 2.1 环境准备 2.2 安装及配置 1).下载Nginx 2).下载nginx_tcp_proxy_module 插件 3).编译Nginx 4).修改Nginx.conf配置文 ...
- 14.Nginx四层负载均衡
1.七层负载均衡: 根据url 调度不同的集群 url.cheng.com 10.0.0.5 10.0.0.7 /pass 10.0.0.8 /user 1.web01和web02配置 (只不过代码不 ...
- Nginx四层负载均衡概述
目录 Nginx四层负载均衡概述 什么是负载均衡 负载均衡应用场景 四层,七层集群架构 四层负载均衡总结 Nginx如何配置四层负载均衡 nginx四层负载均衡端口转发 Nginx四层负载均衡概述 什 ...
- 14、Nginx四层负载均衡
1.Nginx四层负载均衡基本概述 1.1.什么是四层负载均衡 四层负载均衡基于传输层协议包来封装的(如:TCP/IP),那我们前面使用到的七层是指的应用层,它的组装在四层基础之上,无论四层还是七层都 ...
- Nginx四层负载均衡
目录 Nginx四层负载均衡概述 Nginx如何配置四层负载均衡 使用nginx四层负载均衡实现tcp的转发 Nginx四层负载均衡概述 什么是四层负载均衡 四层负载均衡是基于传输层协议包来封装的(如 ...
随机推荐
- MYSQL5.7.30安装
1.下载MySQL 我用的是5.7.30(安装版) 我选择的下载链接:https://dev.mysql.com/downloads/windows/installer/5.7.html 官网链接:h ...
- Laravel 代码开发最佳实践(持续更新)
我们这里要讨论的并不是 Laravel 版的 SOLID 原则(想要了解更多 SOLID 原则细节查看这篇文章)亦或是设计模式,而是 Laravel 实际开发中容易被忽略的最佳实践. 内容概览 单一职 ...
- 这是一篇记录——django-xadmin重新开发记录
利用下面的代码把django的版本换成和xadmin2适配的版本,注意xadmin最新版本出了3.0但是就是一个纯前端的框架,和之前的版本差异较大. 因为此时距离ddl不到24小时,所以使用旧的版本. ...
- Python_14 接口测试报告
一.查缺补漏 1. 测试用例要复制到pycharm执行的项目中,才能显示 2. 函数用下划线,类用大驼峰 3. pycharm一行显示(不换行): File -> settings-> E ...
- springboot升级过程中踩坑定位分析记录 | 京东云技术团队
作者:京东零售 李文龙 1.背景 " 俗话说:为了修复一个小bug而引入了一个更大bug " 因所负责的系统使用的spring框架版本5.1.5.RELEASE在线上出过一个偶发的 ...
- MQTT.fx的安装和使用
一.下载和安装 MQTT.fx支持Windows/Linux/Mac,附下载地址:http://www.jensd.de/apps/mqttfx/,下载完成之后双击进行安装. 二.配置使用 打开软件, ...
- ATL创建的ActiveX实现JS回调
最近公司的产品需要使用ActiveX文件上传.讨论了基本所有的技术,最后还是决定C++搞个ActiveX...但上传的回调费了半天劲,才搞定.代码如下 STDMETHODIMP CFileSelect ...
- define定义常量和宏
define:预处理指令 使用方法有两种 1.define定义符号 denfine定义常量 2.define定义宏 宏是有参数的,它的参数是替换 常规来说这样写define定义宏没啥问题 但是这样写 ...
- 2022-06-23:给定一个非负数组,任意选择数字,使累加和最大且为7的倍数,返回最大累加和。 n比较大,10的5次方。 来自美团。3.26笔试。
2022-06-23:给定一个非负数组,任意选择数字,使累加和最大且为7的倍数,返回最大累加和. n比较大,10的5次方. 来自美团.3.26笔试. 答案2022-06-23: 要i还是不要i,递归. ...
- ABP - 初识 ABP
ABP框架 ABP是用于创建现代化Web应用程序的完整体系结构和强大的基础架构,以模块化的方式进行开发,所有模块以nuget包的方式提供,开箱即用,遵循最佳实践和约定,提供SOLID开发经验. 缩写 ...