FastDFS并发上传任务时异常:Unable to borrow buffer from pool

10 篇文章 0 订阅
1 篇文章 0 订阅

  因需求,公司SpringBoot项目中需使用异步多线程进行FastDFS的文件上传,单个上传耗时大概1秒左右,当并发操作超过8个时总是出现Unable to borrow buffer from pool异常,小于8个时全部正常,因此怀疑是使用的组件的默认连接数限制导致的。上网翻查资料无果后决定分析一下所用的FastDFS组件的源码。

  • 使用的FastDFS组件:
<dependency>
	<groupId>com.github.tobato</groupId>
	<artifactId>fastdfs-client</artifactId>
	<version>1.26.2</version>
</dependency>

异常是在com.github.tobato.fastdfs.conn.ConnectionManager类中的getConnection方法执行到pool.borrowObject(address)时抛出的:

/**
 * 获取连接
 * 
 * @param address
 * @return
 */
protected Connection getConnection(InetSocketAddress address) {
	Connection conn = null;
	try {
		// 获取连接
		conn = pool.borrowObject(address);
	} catch (FdfsException e) {
		throw e;
	} catch (Exception e) {
		LOGGER.error("Unable to borrow buffer from pool", e);
		throw new RuntimeException("Unable to borrow buffer from pool", e);
	}
	return conn;
}

参数address即配置文件中配置的fdfstracker地址,查看borrowObject方法得知,每个tracker地址
都是作为key被存到ConcurrentHashMap中和该tracker地址的连接池数据进行关联的。

回到getConnection方法,这里的pool是在ConnectionManager类中使用@Autowire注解得到的:

@Component
public class ConnectionManager {

    /** 连接池 */
    @Autowired
    private FdfsConnectionPool pool;

进入FdfsConnectionPool类,发现该类没有默认的无参构造方法,此时注解获取该对象时默认使用的是如下构造方法:

@Autowired
public FdfsConnectionPool(KeyedPooledObjectFactory<InetSocketAddress, Connection> factory,
		GenericKeyedObjectPoolConfig config) {
	super(factory, config);
}

这两个参数是通过@Autowire注解获取的,找到第一个参数KeyedPooledObjectFactory接口的实现类com.github.tobato.fastdfs.conn.PooledConnectionFactory

@Component
@ConfigurationProperties(prefix = FdfsClientConstants.ROOT_CONFIG_PREFIX)
public class PooledConnectionFactory extends BaseKeyedPooledObjectFactory<InetSocketAddress, Connection> {

    /** 读取时间 */
    private int soTimeout;
    /** 连接超时时间 */
    private int connectTimeout;

这里可以看出这两个参数设置的是fdfs连接的读取时间连接超时时间

接着查看构造方法的另一个入参GenericKeyedObjectPoolConfig类:

public class GenericKeyedObjectPoolConfig extends BaseObjectPoolConfig {

    /**
     * The default value for the {@code maxTotalPerKey} configuration attribute.
     * @see GenericKeyedObjectPool#getMaxTotalPerKey()
     */
    public static final int DEFAULT_MAX_TOTAL_PER_KEY = 8;

    /**
     * The default value for the {@code maxTotal} configuration attribute.
     * @see GenericKeyedObjectPool#getMaxTotal()
     */
    public static final int DEFAULT_MAX_TOTAL = -1;

    /**
     * The default value for the {@code minIdlePerKey} configuration attribute.
     * @see GenericKeyedObjectPool#getMinIdlePerKey()
     */
    public static final int DEFAULT_MIN_IDLE_PER_KEY = 0;

    /**
     * The default value for the {@code maxIdlePerKey} configuration attribute.
     * @see GenericKeyedObjectPool#getMaxIdlePerKey()
     */
    public static final int DEFAULT_MAX_IDLE_PER_KEY = 8;

    private int minIdlePerKey = DEFAULT_MIN_IDLE_PER_KEY;

    private int maxIdlePerKey = DEFAULT_MAX_IDLE_PER_KEY;

    private int maxTotalPerKey = DEFAULT_MAX_TOTAL_PER_KEY;

    private int maxTotal = DEFAULT_MAX_TOTAL;

发现该类的maxTotalPerKey属性默认的值是8maxTotal默认值为-1,意为不限制总连接数。但是该类属于commons-pool2组件,并不能通过@Autowire注解获取实例,所以fastdfs组件中肯定有一个类继承了这个类。

最终从fdfs组件中找到com.github.tobato.fastdfs.conn.ConnectionPoolConfig类,所以上面FdfsConnectionPool
类的默认构造方法参数注入时实际上用的是这个类。从这个类的构造方法可以看出该连接池配置类使用了一些默认的
参数。其中有以下三个静态常量:

/** 从池中借出的对象的最大数目 */
public static final int FDFS_MAX_TOTAL = 50;

/**
 * 连接耗尽时是否阻塞(默认true)
 * false报异常,ture阻塞直到超时
 */
public static final boolean FDFS_BLOCK_WHEN_EXHAUSTED = true;

/**
 * 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted)
 * 如果超时就抛异常,小于零:阻塞不确定的时间,默认-1
 */
public static final long FDFS_MAX_WAIT_MILLIS = 100;

可以看出,fdfs组件并发最大连接数50,连接耗尽时默认会阻塞直到超时,但是阻塞时间仅为0.1秒,所以当连接耗尽且达到阻塞时间时就会抛出异常。该类最下面有默认构造方法:

public ConnectionPoolConfig() {
	// 从池中借出的对象的最大数目
	setMaxTotal(FDFS_MAX_TOTAL);
	// 在空闲时检查有效性
	setTestWhileIdle(FDFS_TEST_WHILE_IDLE);
	// 连接耗尽时是否阻塞(默认true)
	setBlockWhenExhausted(FDFS_BLOCK_WHEN_EXHAUSTED);
	// 获取连接时的最大等待毫秒数100
	setMaxWaitMillis(FDFS_MAX_WAIT_MILLIS);
	// 视休眠时间超过了180秒的对象为过期
	setMinEvictableIdleTimeMillis(FDFS_MIN_EVICTABLE_IDLETIME_MILLIS);
	// 每过60秒进行一次后台对象清理的行动
	setTimeBetweenEvictionRunsMillis(FDFS_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
	// 清理时候检查所有线程
	setNumTestsPerEvictionRun(FDFS_NUM_TESTS_PEREVICTION_RUN);
	// 配置jmx
	this.setJmxNameBase(FDFS_JMX_NAME_BASE);
	this.setJmxNamePrefix(FDFS_JMX_NAME_PREFIX);
}

即在依赖注入ConnectionPoolConfig类的对象时对commons-pool2对象池进行的一些初始设置。

该类上使用了@ConfigurationProperties(prefix = FdfsClientConstants.POOL_CONFIG_PREFIX)注解,意思是从SpringBoot的配置文件中读取配置信息通过对象属性的set方法赋值给对象。所以该类的父类org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig中的属性是可以通过配置文件中的配置指定初始化值的。

查看GenericKeyedObjectPoolConfig类和它的父类BaseObjectPoolConfig中的属性所拥有的公共set方法,可以针
fdfs组件的总连接数(maxTotal)每个tracker地址(fdfs可以配置多个tracker地址)的连接数(maxTotalPerKey)
阻塞时的最大等待时间(maxWaitMillis)这个三参数在配置文件中进行如下配置(具体数值结合项目实际情况配置):

## 连接池最大数量
fdfs.pool.max-total=200
## 每个tracker地址的最大连接数
fdfs.pool.max-total-per-key=50
## 连接耗尽时等待获取连接的最大毫秒数
fdfs.pool.max-wait-millis=5000

至此,标题中的异常再也没有出现。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
org.csource:fastdfs-client-java:1.29-是一个Java语言的FastDFS客户端,用于访问FastDFS分布式文件系统。FastDFS是一个开源的分布式文件系统,具有高性能、高可靠性、可扩展性和易于管理等特点。FastDFS将文件分成许多小块,然后存储在多台服务器上,提供了快速的文件上传和下载功能。 org.csource:fastdfs-client-java:1.29-是FastDFS的Java语言实现,通过该客户端,我们可以轻松地在Java项目中使用FastDFS进行文件的上传和下载。它提供了一组简单易用的API,允许我们通过指定文件路径或字节数组来上传文件,并通过文件的标识符来下载文件。同,我们还可以获取文件的元信息,例如文件大小、创建间等。 通过该客户端,我们还可以进行文件的删除、修改和查询等操作。它提供了丰富的接口方法,可以满足不同的业务需求。此外,该客户端还支持文件的断点续传功能,当网络中断或上传下载过程中出现异常,我们可以恢复中断的操作,避免重新上传或下载整个文件。 org.csource:fastdfs-client-java:1.29-是一个成熟稳定的Java组件,被广泛应用于各种基于Java的项目中。它的源代码是开放的,意味着我们可以根据自己的需求进行修改和定制。此外,它还具有良好的文档和社区支持,我们可以在遇到问题获得帮助和解决方案。 总之,org.csource:fastdfs-client-java:1.29-是一个功能强大、易用的Java客户端,提供了丰富的API和功能,帮助我们轻松地在Java项目中使用FastDFS分布式文件系统。它是一个值得信赖和推荐的工具,可以提高文件操作的效率和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值