ftp pool 功能分析及 golang 实现

本文探究一种轻量级的 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.mfbz.cn/a/797812.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

超时导致SparkContext构造失败的问题探究

文章目录 1.前言2. 基于事故现场对问题进行分析2.1 日志分析2.2 单独测试Topology代码试图重现问题 3. 源码解析3.1 Client模式和Cluster模式下客户端的提交和启动过程客户端提交时在两种模式下的处理逻辑ApplicationMaster启动时在两种模式下的处理逻辑 3.2 两种模式下的下层角…

OSPF.综合实验

1、首先将各个网段基于172.16.0.0 16 进行划分 1.1、划分为4个大区域 172.16.0.0 18 172.16.64.0 18 172.16.128.0 18 172.16.192.0 18 四个网段 划分R4 划分area2 划分area3 划分area1 2、进行IP配置 如图使用配置指令进行配置 ip address x.x.x.x /x 并且将缺省路由…

uniapp编译成h5后接口请求参数变成[object object]

问题&#xff1a;uniapp编译成h5后接口请求参数变成[object object] 但是运行在开发者工具上没有一点问题 排查&#xff1a; 1&#xff1a;请求参数&#xff1a;看是否是在请求前就已经变成了[object object]了 结果&#xff1a; 一切正常 2&#xff1a;请求头&#xff1a;看…

2024辽宁省数学建模C题【改性生物碳对水中洛克沙胂和砷离子的吸附】原创论文分享

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024 年辽宁省大学数学建模竞赛C题改性生物碳对水中洛克沙胂和砷离子的吸附完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃…

OpenGL笔记九之彩色三角形与重心插值算法

OpenGL笔记九之彩色三角形与重心插值算法 —— 2024-07-07 晚上 bilibili赵新政老师的教程看后笔记 code review! 文章目录 OpenGL笔记九之彩色三角形与重心插值算法1.运行3.main.cpp 1.运行 3.main.cpp 代码 #include <iostream>#define DEBUG//注意&#xff1a;glad…

虚拟机centos连接xshell

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 ☁️运维工程师的职责&#xff1a;监…

VsCode远程ssh连接失败:Could not establish connection to XXX

一、问题描述 在VsCode中按下"F1"&#xff0c;选择Remote-SSH:Connect to Host 选择一个已经配置好的SSH主机&#xff0c;比如我选择的是192.168.0.104&#xff1a; 结果提示&#xff1a;Could not establish connection to XXX 二、解决方法 观察VsCode的输出信息…

几何建模-Parasolid中GO功能使用

1.背景介绍 1.1 Parasolid和它的接口间关系 1.2 什么是GO GO全称是Graphical Output.你的程序需要在屏幕或者打印设备上显示模型数据时。在需要使用PK中的某个渲染函数时创建图形显示数据时&#xff0c;Parasolid会调用GO相关的函数。GO函数会输出绘图指令给你的应用程序提供…

《昇思25天学习打卡营第20天|onereal》

应用实践/LLM原理和实践/基于MindSpore的GPT2文本摘要 基于MindSpore的GPT2文本摘要 数据集加载与处理 数据集加载 本次实验使用的是nlpcc2017摘要数据&#xff0c;内容为新闻正文及其摘要&#xff0c;总计50000个样本。 数据预处理 原始数据格式&#xff1a; article: [CLS…

MySQL-基础点

目录 MySQL概念 数据库三大范式是什么&#xff1f; blob 和 text 有什么区别&#xff1f; DATETIME 和 TIMESTAMP 的异同&#xff1f; MySQL 中 in 和 exists 的区别&#xff1f; MySQL 里记录货币用什么字段类型比较好&#xff1f; MySQL 怎么存储 emoji? 用过哪些 M…

MongoDB7出现:Windows下使用mongo命令提示不是内部或外部命令

确保环境变量添加正确的情况&#xff0c;仍然出现这种问题。如果安装的是新版本&#xff0c;则大概率是新版本mongodb的bin里面没有mongo命令 解决方案&#xff1a; 下载mongodb shell 下载链接 把shell的命令放进来 启用命令&#xff1a;mongosh

浅谈数学模型在UGC/AIGC游戏数值调参中的应用(AI智能体)

浅谈数学模型在UGC/AIGC游戏数值调参中的应用 ygluu 卢益贵 关键词&#xff1a;UGC、AIGC、AI智能体、大模型、数学模型、游戏数值调参、游戏策划 一、前言 在策划大大群提出《游戏工厂&#xff1a;AI&#xff08;AIGC/ChatGPT&#xff09;与流程式游戏开发》讨论之后就已完…

每日一练,java

目录 描述示例 总结 描述 题目来自牛客网 •输入一个字符串&#xff0c;请按长度为8拆分每个输入字符串并进行输出&#xff1b; •长度不是8整数倍的字符串请在后面补数字0&#xff0c;空字符串不处理。 输入描述&#xff1a; 连续输入字符串(每个字符串长度小于等于100) 输…

JDK14新特征最全详解

JDK 14一共发行了16个JEP(JDK Enhancement Proposals&#xff0c;JDK 增强提案)&#xff0c;筛选出JDK 14新特性。 - 343: 打包工具 (Incubator) - 345: G1的NUMA内存分配优化 - 349: JFR事件流 - 352: 非原子性的字节缓冲区映射 - 358: 友好的空指针异常 - 359: Records…

网络规划设计师教程(第二版) pdf

网络规划设计师教程在网上找了很多都是第一版&#xff0c;没有第二版。 所以去淘宝买了第二版的pdf&#xff0c;与其自己独享不如共享出来&#xff0c;让大家也能看到。 而且这个pdf我已经用WPS扫描件识别过了&#xff0c;可以直接CtrlF搜索关键词&#xff0c;方便查阅。 链接…

为何你的旁路电容 总是无法滤除噪声

你一定遇过这样的困境 产品出现了噪声干扰 也找出干扰源了 但摆放了旁路电容 却总是解不掉干扰 请问原因为何? 先说结论 接地不好放太少颗电容值没有微调 在这篇文章 如何焊铜管 量测射频前端模块 我们提到了 不足的接地 会增加损耗 我们进一步 以阻抗的…

jmeter-beanshell学习9-放弃beanshell

写这篇时候道心不稳了&#xff0c;前面写了好几篇benashell元件&#xff0c;突然发现应该放弃。想回去改前面的文章&#xff0c;看了看无从下手&#xff0c;反正已经这样了&#xff0c;我淋了雨&#xff0c;那就希望别人也没有伞吧&#xff0c;哈哈哈哈&#xff0c;放在第九篇送…

在Linux系统实现瑞芯微RK3588部署rknntoolkit2进行模型转换

一、首先要先安装一个虚拟的环境 安装Miniconda包 Miniconda的官网链接:Minidonda官网 下载好放在要操作的linux系统,我用的是远程服务器的linux系统,我放在whl这个文件夹里面,这个文件夹是我自己创建的 运行安装 安装的操作都是yes就可以了 检查是否安装成功,输入下面…

LeetCode 面试题02.04.分割链表

LeetCode 面试题02.04.分割链表 C写法 思路&#x1f914;&#xff1a; ​ 将x分为两段&#xff0c;一段放小于x的值&#xff0c;另一段放大于x的值。开辟四个指针lesshead、lesstail、greaterhead、greatertail&#xff0c;head为哨兵位&#xff0c;防止链表为空时情况过于复杂…

Linux限速工具:FlowMaster - IP级网络流量控制脚本

目录 前言&#xff1a; Github加速器&#xff1a; 简介 功能 安装方法 使用方法 数值单位 基本命令 选项 示例 启动 FlowMaster 重启 FlowMaster 停止 FlowMaster 查看状态 查看 Iptables 设置 许可证 贡献 联系我们 前言&#xff1a; 在市面上看了好多关于网…