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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值