资源池

文章目录
  1. 1. 简介
  2. 2. 设计
  3. 3. 操作
    1. 3.1. borrow
    2. 3.2. return
    3. 3.3. add
    4. 3.4. invalidate
    5. 3.5. 典型用例
  4. 4. 大小限制
    1. 4.1. max_active
    2. 4.2. max_idle
    3. 4.3. min_idle
  5. 5. 行为选项
    1. 5.1. 资源池耗尽时的borrow行为
    2. 5.2. 资源检查
    3. 5.3. 资源在Idle列表中的顺序
    4. 5.4. 计时
  6. 6. 资源池实例的维护
    1. 6.1. new
    2. 6.2. clear
    3. 6.3. close
    4. 6.4. 资源池统计
  7. 7. 资源工厂
  8. 8. 示例
    1. 8.1. MySQL Driver连接池
    2. 8.2. Rabbit MQ 连接通道池
  9. 9. 参考资料

资源池是解决并发问题的一种常见模式

Versions:

  • 2014-11-13 Version 0.1

简介

软件资源的创建需要消耗大量的时间和内存, 如果能重用, 将极大地改善应用程序的性能. 资源池是一个在不同的平台和语言中广泛的使用的方法.本文所述Erlang资源池的设计灵感来源于Apache的Common Pool库. API和主要的功能是从其借用的, 但内部实现完全不同, 并使用了Erlang OTP设计原则, 以及Erlang并发模型.

设计

资源池由两个列表组成: Active(活动列表)和Idle(空闲列表). Active列表包含活跃的资源. Idle列表包含不活跃的资源

图-1: 资源池为空的状态,活动列表和空闲列表都为空

+-Pool-----------{0,0}-+
|                      |
| Active--+  Idle----+ |
| |       |  |       | |
| |       |  |       | |
| |       |  |       | |
| +-------+  +-------+ |
+----------------------+

操作

borrow

要从资源池中获得一个资源,需要调用函数borrow

1
Resource = resource_pool:borrow(test_pool)

如果资源池的Idle列表为空(在没有资源可用的情况下),资源池会直接在Active列表中创建一个资源并授予调用进程.

图-2: 创建一个新的资源,并把该资源放到 Active 资源列表

+-Pool-----------{1,0}-+          +-Pool-----------{2,0}-+
|                      |          |                      |
| Active--+  Idle----+ |          | Active--+  Idle----+ |
| |       |  |       | |          | |       |  |       | |
| |       |  |       | |    =>    | | <R.2> |  |       | |
| | <R.1> |  |       | |          | | <R.1> |  |       | |
| +-------+  +-------+ |          | +-------+  +-------+ |
+----------------------+          +----------------------+

如果在资源池的Idle列表中存在可用的资源. 将从Idle列表中取出一个资源,并转移到Active列表,然后授予调用者进程.

图-3: 从空闲资源列表中得到一个资源,并把它转移到活动列表

+-Pool-----------{1,2}-+          +-Pool-----------{2,1}-+
|                      |          |                      |
| Active--+  Idle----+ |          | Active--+  Idle----+ |
| |       |  |       | |          | |       |  |       | |
| |       |  | <R.2> | |    =>    | | <R.2> |  |       | |
| | <R.1> |  | <R.3> | |          | | <R.1> |  | <R.3> | |
| +-------+  +-------+ |          | +-------+  +-------+ |
+----------------------+          +----------------------+

return

一旦进程完成了对资源的使用,它必须把资源返回到资源池中

1
resource_pool:return(test_pool,Resource)

换句话说,就是该资源从Active列表移动到Idle列表(图-4). 以让其他进程能够从资源池中获取可用的资源.

图-4 进程把资源返还给资源池

+-Pool-----------{2,1}-+          +-Pool-----------{1,2}-+
|                      |          |                      |
| Active--+  Idle----+ |          | Active--+  Idle----+ |
| |       |  |       | |          | |       |  |       | |
| | <R.2> |  |       | |    =>    | |       |  | <R.2> | |
| | <R.1> |  | <R.3> | |          | | <R.1> |  | <R.3> | |
| +-------+  +-------+ |          | +-------+  +-------+ |
+----------------------+          +----------------------+

add

有时候我们需要添加新的资源

1
resource_pool:add(test_pool)

函数add创建一个新的资源,并添加到Idle列表中

+-Pool-----------{2,1}-+          +-Pool-----------{2,2}-+
|                      |          |                      |
| Active--+  Idle----+ |          | Active--+  Idle----+ |
| |       |  |       | |          | |       |  |       | |
| | <R.2> |  |       | |    =>    | | <R.2> |  | <R.4> | |
| | <R.1> |  | <R.3> | |          | | <R.1> |  | <R.3> | |
| +-------+  +-------+ |          | +-------+  +-------+ |
+----------------------+          +----------------------+

invalidate

1
resource_pool:invalidate(test_pool,Resource)

invalidate 函数把一个失败的资源标记为不可用,资源池然后会销毁这个资源.

+-Pool-----------{2,1}-+          +-Pool-----------{1,1}-+
|                      |          |                      |
| Active--+  Idle----+ |          | Active--+  Idle----+ |
| |       |  |       | |          | |       |  |       | |
| | <R.2> |  |       | |    =>    | |       |  |       | |
| | <R.1> |  | <R.3> | |          | | <R.1> |  | <R.3> | |
| +-------+  +-------+ |          | +-------+  +-------+ |
+----------------------+          +----------------------+

典型用例

1
2
3
4
5
6
7
8
9
10
case resource_pool:borrow(test_pool) of
{error, E} -> io:format("Error while borrow from pool, reason: ~p", [E]);
Resource ->
try
resource:operation(Resource),
resource_pool:return(test_pool, Resource)
catch
_:_ -> resource_pool:invalidate(test_pool, Resource)
end,
end

大小限制

1
2
3
{ok,Pid} = resource_pool:new(
test_pool,resource_factory,resource_metadata,options
)
1
max_active,
max_idle,
min_idle
            +-Pool-----------{0,0}-+
            |                      |
            | Active--+  Idle----+ |
            | |       |  |_______|_|__ max_idle
max_active__|_|_______|  |       | |
            | |       |  |       | |
            | |       |  |_______|_|__ min_idle
            | |       |  |       | |
            | +-------+  +-------+ |
            +----------------------+

max_active

Active列表默认最大值为8. 如果达到限制borrow操作将会阻塞或失败. 值 -1 (或任何负值) 表示Active列表没有大小限制.

1
2
{ok,Pid} =
resource_pool:new(test_pool,resource_factory,[],[{max_active,20}])

max_idle

Idle列表的最大大小,默认和max_active相同. 如果达到限制,后续return的资源会被销毁,值 -1 (或任何负值) 表示Idle列表没有大小限制.

1
2
3
4
5
6
{ok,Pid} = resource_pool:new(
test_pool, %% 资源池实例标识
resource_factory, %% 资源工厂
[], %% 资源元数据
[{max_active,20},{max_idle,10}] %% 资源池选项
)

min_idle

Idle列表默认的最小大小为0.

If it reaches the limit then following borrow operation will successfully supplies a resource to invoker and then pool will additionally create new resource in Idle container to provide min_idle condition.

1
2
3
4
5
6
{ok,Pid} = resource_pool:new(
test_pool, %% 资源池标识
resource_factory, %% 资源工厂
[], %% 资源元数据
[{max_active,20},{max_idle,10},{min_idle,3}] %% 资源池选项
)

关于空闲资源的最大和最小值应该根据实际需求控制在一个合理的区间. 太小会导致频繁的创建资源,太大又会消耗过多的内存.

行为选项

资源池耗尽时的borrow行为

  • {when_exhausted_action,fail}
    在耗尽的资源池上调用borrow将返回{error,pool_exhausted}

  • {when_exhausted_action,block}
    在耗尽的资源池上调用borrow将阻塞,知道有空闲的资源可用,等待的最大时间可以通过选项max_wait控制

  • {when_exhausted_action,grow}
    在耗尽的资源池上调用borrow将创建新资源,并增大Active列表的大小,此时max_Idle选项被忽略.

1
2
3
4
5
6
{ok,Pid} = resource_pool:new(
test_pool, %% 资源池实例标识
resource_factory, %% 资源工厂
[], %% 资源元数据
[{max_active,20},{when_exhausted_action,fail}] %% 资源池选项
)

资源检查

Resource pool can check status of managed resources. Options test_on_borrow and test_on_return control how pool tests resources: before providing resource to invoker {test_on_borrow, true} and after a resource was returned to pool {test_on_return, true}. If pool finds that the resource is not alive during test then the resource will be destroyed.

资源在Idle列表中的顺序

Option fifo (first-input-first-output) controls order of extracting a resources from Idle list. Diagrams below illustrate this. Suppose we fill out Idle list in order: was first, is next, then . Resource is active in given moment. If {fifo, true} is set the borrow operation leads to situation below: resource was came first and it becames active now (first input).

+-Pool-----------{1,2}-+          +-Pool-----------{2,1}-+
|                      |          |                      |
| Active--+  Idle----+ |          | Active--+  Idle----+ |
| |       |  | <R.3> | |          | |       |  |       | |
| |       |  | <R.2> | |    =>    | | <R.1> |  | <R.3> | |
| | <R.4> |  | <R.1> | |          | | <R.4> |  | <R.2> | |
| +-------+  +-------+ |          | +-------+  +-------+ |
+----------------------+          +----------------------+

If {fifo, false} is set it means that order will be last-input-first-output. borrow operation makes active resource (last input).

+-Pool-----------{1,2}-+          +-Pool-----------{2,1}-+
|                      |          |                      |
| Active--+  Idle----+ |          | Active--+  Idle----+ |
| |       |  | <R.3> | |          | |       |  |       | |
| |       |  | <R.2> | |    =>    | | <R.3> |  | <R.2> | |
| | <R.4> |  | <R.1> | |          | | <R.4> |  | <R.1> | |
| +-------+  +-------+ |          | +-------+  +-------+ |
+----------------------+          +----------------------+

Default value for fifo is false.

计时

max_wait选项定义了一个时间, 当在资源池耗尽时调用borrow函数,并且 when_exhausted_action 设置为block时所等待的最大时间.

max_idle_time ,用来配置资源的最大空闲时间. 如果一个资源空闲的超过这个时间, 就会被销毁. 但至少要在资源池中保留min_idle个资源. 如果max_idle_time设置为infinity, 不会销毁任何空闲的资源.

资源池实例的维护

pool_name是一个原子, 多个进程可以使用该名字来访问资源池. resource_factory是一个负责创建和维护资源的模块名称. resource_metadata是一个包含资源初始化信息的对象. 该对象作为参数传递给resource_factory的每个函数来帮助维护一个资源.

new

创建资源池

1
2
3
4
5
{ok, Pid} = resource_pool:new(
pool_name, %% 资源池名称
resource_factory, %% 资源工厂
resource_metadata %% 资源元数据
)

clear

清空资源池

1
resource_pool:clear(pool_name)

close

关闭资源池, 该函数终止资源池进程, 并销毁池中的所有资源

1
ok = resource_pool:close(pool_name)

资源池统计

get_num_active,get_num_idle,get_number

资源工厂

示例

MySQL Driver连接池

http://sourceforge.net/projects/erlmysql/

Rabbit MQ 连接通道池

http://sourceforge.net/projects/erlpool/files/1.0.x/erl.resource.pool.example.zip/download

参考资料

  1. https://erlangcentral.org/wiki/index.php?title=Resource_Pool