ftp pool 功能分析及 golang 实现

news2024/11/13 16:34:47

本文探究一种轻量级的 pool 实现 ftp 连接。

一、背景

简要介绍:业务中一般使用较多的是各种开源组件,设计有点重,因此本文探究一种轻量级的 pool 池的思想实现。

期望:设置连接池最大连接数为 N 时,批量执行 M 个 FTP 请求,所有请求都可以成功。

关键点: 使用池的思想存储 FTP 链接,同时控制 FTP 连接的数量。

二、池思想及 sync.Pool 重点分析

池思想设计模式

Golang 是有自动垃圾回收机制的编程语言,使用三色并发标记算法标记对象并回收。但是如果想开发一个高性能的应用程序,就必须考虑垃圾回收给性能带来的影响。因为 Go 的垃圾回收机制有一个 STW 时间,而且在堆上大量创建对象,也会影响垃圾回收标记时间。

通常采用对象池的方式,将不用的对象回收起来,避免垃圾回收。另外,像数据库连接、TCP 长连接等,连接的创建是非常耗时操作,如果每次使用都创建新链接,则整个业务很多时间都花在了创建链接上了。因此,若将这些链接保存起来,避免每次使用时都重新创建,则能大大降低业务耗时,提升系统整体性能。

sync.Pool 要点

在这里插入图片描述

golang 中提供了 sync.Pool 数据结构,可以使用它来创建池化对象。不过使用时有几个重点是要关注的,避免踩坑:

  1. sync.Pool 本身是线程安全的,多个 goroutine 可以并发地调用存取对象。
  2. sync.Pool 不可用在使用之后复制使用。关于这一点 context 包里面有大量使用,不再赘述。
  3. sync.Pool 用来保存的是一组可独立访问的“临时”对象。注意这里的“临时”,这表明池中的对象可能在未来某个时间被毫无预兆的移除(因为长久不使用被 GC 回收了)。

关于第 3 点非常重要,下面我们实现一个 demo 来详细说明:

package main

import (
	"fmt"
	"net/http"
	"sync"
	"time"
)

func main() {
	var p sync.Pool // 创建一个对象池
	p.New = func() interface{} {
		return &http.Client{
			Timeout: 5 * time.Second,
		}
	}
	var wg sync.WaitGroup
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			client := p.Get().(*http.Client)
			defer p.Put(client)
			//获取http请求并打印返回码
			resp, err := client.Get("https://www.baidu.com")
			if err != nil {
				fmt.Println("http get error", err)
				return
			}
			resp.Body.Close()
			fmt.Println("success", resp.Status)
		}()
	}
	//等待所有请求结束
	wg.Wait()
}

这里我们使用 New 定义了创建 http.Client 的方法,然后启动 10 个 goroutine 并发访问网址,使用的 http.Client 对象都是从池中获取的,使用完毕后再放回到池子。

实际上,这个池中可能创建了 10 个 http.Client ,也可能创建了 8 个,还有可能创建了 3 个。取决于每个请求执行时池中是否有空闲的 http.Client ,以及其它的 goroutine 是否及时的放回去。

另外这里要注意的是,我们设置了 New 字段,当没有空闲请求时,Get 方法会调用 New 重新生成一个新的 http.Client。这种方式实现的好处是不必担心没有 http.Client 可用,缺点是数量不可控。你可能会想,不设置 New 字段是否可以?也是可以的,实现如下:

package main

import (
	"fmt"
	"net/http"
	"sync"
	"time"
)

func main() {
	var p sync.Pool // 创建一个对象池
	for i := 0; i < 5; i++ {
		p.Put(&http.Client{Timeout: 5 * time.Second}) // 不设置 New 字段,初始化时就放入5个可重用对象
	}
	var wg sync.WaitGroup
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func() {
			defer wg.Done()
			client, ok := p.Get().(*http.Client)
			if !ok {
				fmt.Println("get client is nil")
				return
			}
			defer p.Put(client)
			resp, err := client.Get("https://www.baidu.com")
			if err != nil {
				fmt.Println("http get error", err)
				return
			}
			resp.Body.Close()
			fmt.Println("success", resp.Status)
		}()
	}
	//等待所有请求结束
	wg.Wait()
}

在这里插入图片描述

在初始化时直接放入一定数量的可重用对象,从而达到了控制数量的目的。但是不设置 New 字段的风险很大,因为池化的对象如果长时间没有被调用,可能会被回收,而我们是无法预知什么时候池化的对象是会被回收的。因此一般不会使用这种方式,而是通过其它的方式来实现并发数量控制。

至此,也清楚了我们想实现的诉求:既要通过池满足连接复用,也要控制连接数量。(我们已经知道,仅仅依靠 sync.Pool 是实现不了的)

三、FTP 连接池的实现

  1. 创建 ftp docker 容器
 docker run -d --name ftp_server \
-p 2100:21 \
-p 30010-30019:30010-30019 \
-e "FTP_PASSIVE_PORTS=30010:30019" \
-e FTP_USER_HOME=/home/test \
-e FTP_USER_NAME=test \
-e FTP_USER_PASS=123456 \
-e FTP_USER_LIMIT=30 \  
-e "PUBLICHOST=localhost" \
stilliard/pure-ftpd
  1. 使用 golang ftp client 库进行代码开发
package main

import (
	"bytes"
	"fmt"
	"time"

	"github.com/jlaffaye/ftp"
)

type FTPConnectionPool struct {
	conns    chan *ftp.ServerConn
	maxConns int
}

func NewFTPConnectionPool(server, username, password string, maxConns int) (*FTPConnectionPool, error) {
	pool := &FTPConnectionPool{
		conns:    make(chan *ftp.ServerConn, maxConns),
		maxConns: maxConns,
	}

	for i := 0; i < maxConns; i++ {
		conn, err := ftp.Dial(server, ftp.DialWithTimeout(5*time.Second))
		if err != nil {
			return nil, err
		}
		err = conn.Login(username, password)
		if err != nil {
			return nil, err
		}
		pool.conns <- conn
	}

	return pool, nil
}

func (p *FTPConnectionPool) GetConnection() (*ftp.ServerConn, error) {
	return <-p.conns, nil
}

func (p *FTPConnectionPool) ReleaseConnection(conn *ftp.ServerConn) {
	p.conns <- conn
}

func (p *FTPConnectionPool) Close() {
	close(p.conns)
	for conn := range p.conns {
		_ = conn.Quit()
	}
}

func (p *FTPConnectionPool) StoreFileWithPool(remotePath string, buffer []byte) error {
	conn, err := p.GetConnection()
	if err != nil {
		return err
	}
	defer p.ReleaseConnection(conn)

	data := bytes.NewBuffer(buffer)
	err = conn.Stor(remotePath, data)
	if err != nil {
		return fmt.Errorf("failed to upload file: %w", err)
	}
	return nil
}

func main() {
	fmt.Println("hello world")
}

  1. 性能测试
package main

import (
	"fmt"
	"log"
	"sync"
	"testing"
)

func BenchmarkFTPClient_StoreFileWithMaxConnections(b *testing.B) {
	// Assume NewFTPConnectionPool has been called elsewhere to initialize the pool
	// with a maxConns value of 4. For example:
	pool, err := NewFTPConnectionPool("localhost:2100", "jovy", "123456", 5)
	if err != nil {
		log.Fatalf("Failed to initialize FTP connection pool: %v", err)
	}
	defer pool.Close()

	var wg sync.WaitGroup
	buffer := []byte("test data for benchmarking")

	b.ResetTimer()

	for i := 0; i < 50; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			// Use the connection pool to store the file
			err := pool.StoreFileWithPool(fmt.Sprintf("file_%d.txt", i), buffer)
			if err != nil {
				b.Errorf("Failed to store file: %v", err)
			}
		}(i)
	}

	wg.Wait()
}

可以看到,池化后性能这块已经达到了极致。
至此,整个功能也实现差不多了,后续的错误处理及代码抽象可以在此基础上继续优化,感兴趣的同学可以测试看看。

四、参考

  • 《深入理解 Go 并发编程》 鸟窝
  • 在容器中搭建运行 FTP 服务器 https://www.niwoxuexi.com/blog/hangge/article/903.html
  • linux开启ftp服务和golang实现ftp_server_client https://www.liuvv.com/p/d43abcbd.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1925557.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Transformer模型:Decoder的self-attention mask实现

前言 这是对Transformer模型Word Embedding、Postion Embedding、Encoder self-attention mask、intra-attention mask内容的续篇。 视频链接&#xff1a;20、Transformer模型Decoder原理精讲及其PyTorch逐行实现_哔哩哔哩_bilibili 文章链接&#xff1a;Transformer模型&…

【JVM实战篇】内存调优:内存泄露危害+内存监控工具介绍+内存泄露原因介绍

文章目录 内存调优内存溢出和内存泄漏内存泄露带来什么问题内存泄露案例演示内存泄漏的常见场景场景一场景二 解决内存溢出的方法常用内存监控工具Top命令优缺点 VisualVM软件、插件优缺点监控本地Java进程监控服务器的Java进程&#xff08;生产环境不推荐使用&#xff09; Art…

JavaWeb(三:JDBC 与 MVC)

JavaWeb&#xff08;一&#xff1a;基础知识和环境搭建&#xff09;https://blog.csdn.net/xpy2428507302/article/details/140365130?spm1001.2014.3001.5501JavaWeb&#xff08;二&#xff1a;Servlet与Jsp&#xff0c;监听器与过滤器&#xff09;https://blog.csdn.net/xpy…

Python蜂窝通信Wi-Fi和GPU变分推理及暴力哈希加密协议图消息算法

&#x1f3af;要点 &#x1f3af;图模型和消息传递推理算法 | &#x1f3af;消息传递推理和循环消息传递推理算法 | &#x1f3af;空间人工智能算法多维姿势估计 | &#x1f3af;超图结构解码算法量子计算 | &#x1f3af;GPU处理变分推理消息传递贝叶斯网络算法 | &#x1f3…

5G-A通感融合赋能低空经济-RedCap芯片在无人机中的应用

1. 引言 随着低空经济的迅速崛起&#xff0c;无人机在物流、巡检、农业等多个领域的应用日益广泛。低空飞行器的高效、安全通信成为制约低空经济发展的关键技术瓶颈。5G-A通感一体化技术通过整合通信与感知功能&#xff0c;为低空网络提供了强大的技术支持。本文探讨了5G-A通感…

【中国近代史】林则徐虎门销烟(1839年)

中国古代朝代历史经过两周时间&#xff08;7.03-7.13&#xff09;的分享已经正式结束&#xff0c;首先感谢大家通过那个专栏点赞收藏关注我&#xff0c;这是我继续创作的动力。 接下来新的专栏就是中国近代史。让我们再次走入近代史的潮流中&#xff0c;去学习去感受先辈们的拼…

计组_多处理器的基本概念

2024.06.26&#xff1a;计算机组成原理多处理器的基本概念学习笔记 第21节 多处理器的基本概念 1. 计算机体系结构1.1 SISD单指令流单数据流&#xff08;前面几章一直在学习的内容&#xff09;1.2 SIMD单指令流多数据流1.2.1 改进&#xff1a;向量处理器 1.3 MISD多指令流单数据…

应用帕累托原则学习新的编程语言

在本文中&#xff0c;我将讨论如何应用帕累托原则快速学习一门新的编程语言&#xff0c;并在加深对编程语言的理解的同时开始解决实际问题。 什么是帕累托原则&#xff1f; 帕累托原则&#xff0c;又称 80/20 法则&#xff0c;指出对于许多结果而言&#xff0c;大约 80% 的后…

数据湖仓一体(一) 编译hudi

目录 一、大数据组件版本信息 二、数据湖仓架构 三、数据湖仓组件部署规划 四、编译hudi 一、大数据组件版本信息 hudi-0.14.1zookeeper-3.5.7seatunnel-2.3.4kafka_2.12-3.5.2hadoop-3.3.5mysql-5.7.28apache-hive-3.1.3spark-3.3.1flink-1.17.2apache-dolphinscheduler-3.1.9…

我的智能辅助大师-办公小浣熊

一、基本介绍 随着2022年ChatGPT为代表的AI工具对互联网领域进行第一次冲击后&#xff0c;作为一名对编程领域涉足不算特别深的一名程序员&#xff0c;对AI大模型的接触也真的不能算少了&#xff0c;这是时代的必然趋势。在此之前也曾接触过很多的AI工具&#xff0c;他们都能在…

跨境电商系统如何进行搭建

目前越来越多的商家&#xff0c;他们都在进行跨境电商建站&#xff0c;便于自己在网络上进行营销推广&#xff0c;跨境电商系统的搭建是至关重要的&#xff0c;商家应该先了解跨境电商的模式有哪些&#xff0c;这样才能对跨境电商系统有更好的搭建结果。 跨境电商模式 目前来…

1589. 【中山市第十二届义务教育段学生信息学邀请赛】象战(bishop)(Standard IO)

题目描述 国际象棋的棋盘可以表示为一个 8 行 8 列的格子图&#xff0c;其中每个格子都可以放一枚棋子。我们将第 1 行第 2 列的格子用 (1,2) 来表示&#xff0c;以此类推。 为了帮助妹妹认识国际象棋中的“象”这种棋子&#xff0c;Jimmy 可谓是煞费苦心——他首先教会妹妹&…

【Java】数据类型及类型转换

数据类型 Java语言的数据类型分为两大类&#xff1a; 基础数据类型引用数据类型 基础数据类型 基础数据类型包括以下8种&#xff1a; 类型名称关键字占用内存取值范围区间描述字节型byte1 字节-128~127-27~27-1短整型short2 字节-32768~32767-215~215-1整型int4 字节-2147…

算法学习笔记(8.3)-(0-1背包问题)

目录 最常见的0-1背包问题&#xff1a; 第一步&#xff1a;思考每轮的决策&#xff0c;定义状态&#xff0c;从而得到dp表 第二步&#xff1a;找出最优子结构&#xff0c;进而推导出状态转移方程 第三步&#xff1a;确定边界条件和状态转移顺序 方法一&#xff1a;暴力搜素…

经典元启发式算法的适用范围及基本思想

元启发式算法是针对优化问题设计的一类高级算法&#xff0c;它们具有广泛的适用性&#xff0c;可以解决不同类型的问题。不同的元启发式算法由于其特定的搜索机制和策略&#xff0c;适用的优化问题类型也有所不同。以下是一些常见元启发式算法及其适用范围&#xff1a; 1. 遗传…

OrangePi AIpro 浅上手及搭建卡通图像生成多元化AI服务

前言 很高兴&#xff0c;收到了一份新款 OrangePi AIpro 开发板&#xff0c;这是香橙派第一次与华为昇腾合作&#xff0c;使用昇腾系列 AI 处理器来设计这款高性价比的 AI 开发板。这块开发板不仅性能强大&#xff0c;还支持丰富的硬件接口&#xff0c;为AI开发者提供了一个理…

Nginx的访问限制与访问控制

访问限制 访问限制是一种防止恶意访问的常用手段&#xff0c;可以指定同一IP地址在固定时间内的访问次数&#xff0c;或者指定同一IP地址在固定时间内建立连接的次数&#xff0c;若超过网站指定的次数访问将不成功。 请求频率限制配置 请求频率限制是限制客户端固定时间内发…

代码随想录第十二天|226.翻转二叉树、 101.对称二叉树、104.二叉树的最大深度、111.二叉树的最小深度

文章目录 226.翻转二叉树思路解法一、前序遍历递归解法二、深度优先搜索--迭代 101.对称二叉树思路解法一、递归解法二、迭代 104.二叉树的最大深度解法一、深度优先搜索--递归解法二、广度优先搜索--迭代 111.二叉树的最小深度解法一、深度优先搜索--递归解法二、广度优先搜索…

制作显卡版docker并配置TensorTR环境

感谢阅读 相关概念docker准备下载一个自己电脑cuda匹配的docker镜像拉取以及启动镜像安装cudaTensorRT部署教程 相关概念 TensorRT是可以在NVIDIA各种GPU硬件平台下运行的一个模型推理框架&#xff0c;支持C和Python推理。即我们利用Pytorch&#xff0c;Tensorflow或者其它框架…

2、matlab打开显示保存点云文件(.ply/.pcd)以及经典点云模型数据

1、点云数据简介 点云数据是三维空间中由大量二维点坐标组成的数据集合。每个点代表空间中的一个坐标点&#xff0c;可以包含有关该点的颜色、法向量、强度值等额外信息。点云数据可以通过激光扫描、结构光扫描、摄像机捕捉等方式获取&#xff0c;广泛应用于计算机视觉、机器人…