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 mainimport ("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.WaitGroupwg.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 mainimport ("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.WaitGroupwg.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 mainimport ("bytes""fmt""time""github.com/jlaffaye/ftp"
)type FTPConnectionPool struct {conns    chan *ftp.ServerConnmaxConns 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 mainimport ("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.WaitGroupbuffer := []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 fileerr := 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.mzph.cn/bicheng/46048.shtml

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

相关文章

vs2017/2019串口Qt Serial Port/modbus使用报错

vs2017/2019 Qt Serial Port/modbus配置 /* * 严重性 代码 说明 项目 文件 行 禁止显示状态 错误 LNK2019 无法解析的外部符号 "__declspec(dllimport) public: __cdecl QModbusTcpClient::QModbusTcpClient(class QObject *)" (__imp_??…

基于javaScript的冒泡排序

目录 一.前言 二.设计思路和原理 三.源代码展示 四. 案例运行结果 一.前言 冒泡排序简而言之&#xff0c;就是一种算法&#xff0c;能够把一系列的数据按照一定的顺序进行排列显示&#xff08;从小到大或从大到小&#xff09;。例如能够将数组[5,4,3,2,1]中的元素按照从小到…

读书笔记 《软技能:代码之外的生存指南(第2版)》

关于时间 每天记录并追踪自己的时间&#xff0c;以便我能了解自己的时间都去哪儿了 每天高效工作的时间有多少 这点皮皮哥深有同感,我发现我其实真正效率高的是早上,我一般周末周日的早晨回起的很早,泡一杯挂耳☕️,就在我的mac本上开始了本周的创作,我喜欢这种感觉 关于营…

ES6 Class 的继承(十一)

Class 可以通过extends关键字实现继承&#xff0c;这比 ES5 的通过修改原型链实现继承&#xff0c;要清晰和方便很多。 class Point { } class ColorPoint extends Point { }继承的特性&#xff1a; extends 关键字&#xff1a;使用 extends 来表示一个类继承自另一个类。supe…

了解Maven

一.环境搭建 如果使用的是社区版 版本要求为&#xff1a;2021.1-2022.1.4 如果使用的是idea专业版就无需版本要求,专业版下载私信我&#xff0c;免费教你下载 二&#xff0c;Maven 什么是Maven&#xff0c;也就是一个项目管理工具&#xff0c;用来基于pom的概念&#xff0c…

使用druid对sql进行血缘解析

实体类&#xff1a; Data public class SqlFlowEntity {/*表名称*/private String tableName;/*表操作类型*/private String type;/*涉及字段*/private List<String> columnList; } 核心代码&#xff1a; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.d…

Ghidra comment add script

init # -*- coding: utf-8 -*- import re from ghidra.program.model.listing import CodeUnit# 获取当前程序和指令迭代器 program getCurrentProgram() listing program.getListing() instructionIterator listing.getInstructions(True)# 用于存储唯一的指令类型和操作数…

PostgreSQL修改最大连接数

在使用PostgreSQL 的时候&#xff0c;经常会遇到这样的错误提示&#xff0c; sorry, too many clients already&#xff0c;这是因为默认PostgreSQL最大连接数是 100, 一般情况下&#xff0c;个人使用时足够的&#xff0c;但是在生产环境&#xff0c;这个连接数是远远不够的&am…

Linux笔记之显示当前路径下文件的数量

Linux笔记之显示当前路径下文件的数量 在Linux中&#xff0c;ls -l 和 wc -l 是两个常用命令&#xff0c;分别用于列出目录内容的详细信息和计算行数。将这两个命令结合使用&#xff0c;可以快速统计目录中包含的文件和子目录的数量。 ls -l ls -l 命令用于列出目录中的内容…

python机器学习5 数据容器

Python中有几个数据容器如下&#xff1a; List&#xff0c;数组&#xff0c;如同Array数组。 Dictionarie&#xff0c;字典&#xff0c;可以通过文字来访问数据。 Sets&#xff0c;序列集&#xff0c;做数学交集、并集等计算时使用。 Tuple&#xff0c;序列&#xff0c…

Elasticsearch-多边形范围查询(8.x)

目录 一、字段设计 二、数据录入 三、查询语句 四、Java代码实现 开发版本详见&#xff1a;Elasticsearch-经纬度查询(8.x-半径查询)_es经纬度范围查询-CSDN博客 一、字段设计 PUT /aoi_points {"mappings": {"properties": {"location": {…

redis 夺命21问

1.什么是redis? Redis 是一个基于内存的高性能key-value数据库。 2.Reids的特点 Redis本质上是一个Key-Value类型的内存数据库&#xff0c;很像memcached&#xff0c;整个数据库统统加载在内存当中进行操作&#xff0c;定期通过异步操作把数据库数据flush到硬盘上进行保存。…

回归求助 教程分享

大侠幸会&#xff0c;在下全网同名「算法金」 0 基础转 AI 上岸&#xff0c;多个算法赛 Top 「日更万日&#xff0c;让更多人享受智能乐趣」 今日 217/10000 抱个拳&#xff0c;送个礼 更多内容&#xff0c;见微*公号往期文章&#xff1a;通透&#xff01;&#xff01;十大回…

NFT如何解决音乐版权的问题

音乐版权问题一直困扰着音乐产业。传统的音乐版权管理模式存在以下问题。需要注意的是&#xff0c;NFT在音乐版权领域仍处于早期发展阶段&#xff0c;存在一些需要解决的问题&#xff0c;例如技术标准不统一、应用场景有限、法律法规不明朗等。但随着技术的进步和市场的完善&am…

小程序自学教程

从0开始搭建微信小程序前后台 0、准备 如何安装&#xff1f;去CSDN搜索“xxx安装教程”即可。 &#xff08;1&#xff09;工具 IntelliJ IDEA&#xff08;必选&#xff09;——Java开发集成环境&#xff0c;可以前后端同时使用 Web Storm——web开发集成环境&#xff0c;主要…

【Dison夏令营 Day 18】如何用 Python 中的 Pygame 制作国际象棋游戏

对于 Python 中级程序员来说&#xff0c;国际象棋游戏是一个很酷的项目创意。在熟练使用类的同时&#xff0c;它也是制作图形用户界面应用程序的良好练习。在本教程中&#xff0c;您将学习到 使用 pygame 的基础知识。 学习如何使用 Python 类编码一个国际象棋游戏。 安装和设…

Mybatis防止SQL注入

防止SQL注入的中心思想就是参数化查询&#xff0c;将输入当作参数传递&#xff0c;而不是直接拼接到 SQL 语句中。 常见的防止SQL注入的方式 1、使用#{}占位符 2、使用动态SQL 3、[配置 SQL 注入过滤器](#配置 SQL 注入过滤器) 使用#{}占位符 先来看一个错误的示范${} /…

PostgreSQL的pg_dirtyread工具

PostgreSQL的pg_dirtyread工具 pg_dirtyread 是一个第三方PostgreSQL扩展&#xff0c;它允许用户读取数据库文件中的“脏”数据&#xff0c;即那些被标记为删除或不再可见的数据。这个扩展对于数据恢复和调试非常有用&#xff0c;尤其是在需要恢复被删除或更新前的数据时。 以…

33.异步FIFO IP核的配置、调用与仿真

&#xff08;1&#xff09;异步FIFO的配置过程&#xff1a; ps&#xff1a;异步fifo相比较同步fifo少一个实际深度 &#xff08;2&#xff09;异步FIFO的调用: module dcfifo (input wr_clk ,input rd_clk ,input [7:0] …

2024-07-13 Unity AI状态机2 —— 项目介绍

文章目录 1 项目介绍2 模块介绍2.1 BaseState2.2 ...State2.2.1 PatrolState2.2.2 ChaseState / AttackState / BackState 2.3 StateMachine2.4 Monster 3 其他功能4 类图 项目借鉴 B 站唐老狮 2023年直播内容。 点击前往唐老狮 B 站主页。 1 项目介绍 ​ 本项目使用 Unity 2…