Erlang pool management -- Emysql pool optimize

在上一篇关于Emysql pool (http://www.cnblogs.com/--00/p/4281938.html)的分析的最后提到

现在的emysql_conn_mgr gen_server 进程属于单点,也就是所有的pool 的管理调度都是由一个进程来完成.

如果在同一个Erlang node 中管理为数众多的pool,就会存在瓶颈. 对于热点进程而言,提高其process priority 是一个optimize 的方向,但是并不能彻底解决因单点带来的问题. 因此, 应该尝试将单个emysql_conn_mgr gen_server 进程拆分为多个, 而拆分的依据, 就是将pool 的管理optimize 为每个pool 交由一个emysql_conn_mgr 进程管理, 而不是现在的所有的pool 都是由同一个emysql_conn_mgr 管理.

pool 添加操作

对于pool 的数据结构,保持之前的结构不变.使用一个emysql_pool_mgr gen_server 进程,并在其中维护一个ets table(用以保存poolid 和 emysql_conn_mgr 进程ID).在每一次添加 pool 操作时start_child 一个emysql_conn_mgr 匿名进程,并与pool 的ID进行关联, 写入emysql_pool_mgr 进程维护的ets table 中.

 1 add_pool(#pool{pool_id=PoolId,size=Size,user=User,password=Password,host=Host,port=Port,
 2                database=Database,encoding=Encoding,start_cmds=StartCmds,
 3                connect_timeout=ConnectTimeout,warnings=Warnings}=PoolSettings)->
 4     config_ok(PoolSettings),
 5     case emysql_pool_mgr:has_pool(PoolId) of
 6         true ->
 7             {error,pool_already_exists};
 8         false ->
 9             Pool = #pool{
10                     pool_id = PoolId,
11                     size = Size,
12                     user = User,
13                     password = Password,
14                     host = Host,
15                     port = Port,
16                     database = Database,
17                     encoding = Encoding,
18                     start_cmds = StartCmds,
19                     connect_timeout = ConnectTimeout,
20                     warnings = Warnings
21                     },
22             Pool2 = case emysql_conn:open_connections(Pool) of
23                 {ok, Pool1} -> Pool1;
24                 {error, Reason} -> throw(Reason)
25             end,
26             {ok, PoolServer} = emysql_pool_mgr:add_pool(PoolId, Pool2),
27             [gen_tcp:controlling_process(Conn#emysql_connection.socket, PoolServer)
28              || Conn <- queue:to_list(Pool2#pool.available)],
29             ok
30     end.

start_child emysql_conn_mgr 匿名进程, 并将poolid 与 emysql_conn_mgr 进程ID关联的操作都在emysql_pool_mgr:add_pool/2函数中实现(L26).

emysql_pool_mgr module

emysql_pool_mgr module 是一个gen_server 进程, 其中维护了一个ets table, 用于存放{PoolID, EmysqlConnMgrProcessID} 信息. 因为每次execute SQL语句时, 都需要对pool 操作,也就是需要获取与之对应的EmysqlConnMgrProcessID, 如果将关联信息保存在emysql_pool_mgr 进程中, emysql_pool_mgr 进程同样会成为单点, 因此使用ets table 来分担emysql_pool_mgr 进程的压力负担.

emysql_pool_mgr module 提供了一下几个API:

1, has_pool/1

用于判断当前系统中是否存在该pool,输入参数为PoolID

2, add_pool/2

用以start_child emysql_conn_mgr 进程, 并写入{PoolID, EmysqlConnMgrProcessID}信息到ets table,输入参数为PoolID 和 Pool结构

3, remove_pool/1

删除Pool, 并stop emysql_conn_mgr 进程, 输入参数为PoolID

4, get_pool_server/1

根据PoolID 获取对应的EmysqlConnMgrProcessID, 输入参数为PoolID

5, pools/0

当前系统中所有的PoolID以及其对应的EmysqlConnMgrProcessID

6, conns_for_pool/1

根据PoolID 获取对应的pool 中的所有链接, 输入参数为PoolID

add_pool/2

add_pool/2 函数主要调用supervisor:start_child/2 函数在emysql_conn_pool_sup 监控树下添加emysql_conn_mgr gen_server 进程.

emysql_conn_pool_sup module 的代码片段:

1 start_link(Name, Module) ->
2     supervisor:start_link({local, Name}, ?MODULE, Module).
3
4 init(Module) ->
5     {ok,
6      { {simple_one_for_one, 10, 1},
7       [{undefined, {Module, start_link, []}, temporary,
8         brutal_kill, worker, [Module]}]} }.

相当常见的使用方式.

emyslq_conn_mgr module

optimize 之后,之前的emysql_conn_mgr module 的代码就必须做一些简单调整, 在调用start_link 函数 init初始化时, 就需要将pool 信息添加到进程 state 信息中.

1 init([Pool]) ->
2     erlang:process_flag(priority, high),
3     {ok, #state{pools = [Pool]}}.

此处提升了emysql_conn_mgr 进程的 priority (L2).

而对于其他函数的调用, 需要添加一个EmysqlConnMgrProcessID 参数,使其在gen_server:call/2 时, 将 "?MODULE" 改为 EmysqlConnMgrProcessID.

pool 使用

在调用execute 函数 执行某SQL 语句时, 代码同样需要做一些调整:

1 execute(PoolId, Query, Args, Timeout) when (is_list(Query) orelse is_binary(Query)) andalso is_list(Args) andalso (is_integer(Timeout) orelse Timeout == infinity) ->
2     PoolServer = emysql_pool_mgr:get_pool_server(PoolId),
3     Connection = emysql_conn_mgr:wait_for_connection(PoolServer, PoolId),
4     monitor_work(PoolServer, Connection, Timeout, [Connection, Query, Args]);

也就是在调用emysql_conn_mgr module 的函数之前,需要先根据PoolID 获取EmysqlConnMgrProcessID(L2), 然后将EmysqlConnMgrProcessID 作为emysql_conn_mgr module 函数的第一个参数进行调用(L3).

initialize_pools

在现今的Emysql 项目中, 有一个初始化pools 的功能. 也就是可以在app start 的时候, 自动加载config 文件中配置好的pool .现在的做法是在emysql_conn_mgr 进程 init 的时候, 调用相关函数, 将结果添加到 进程 state 中.

optimize 之后的结构, emysql_conn_mgr 只有在手动添加一个pool 的时候才会被start_link, 也就不能执行相关函数执行initialize_pools 的操作.

现在调整为在emysql_pool_mgr init 之后, emysql_pool_mgr 发送{initialize_pools} message 给self, 在handle_info callback 函数中进行处理:

 1 handle_info({initialize_pools}, State) ->
 2     %% if the emysql application values are not present in the config
 3     %% file we will initialize and empty set of pools. Otherwise, the
 4     %% values defined in the config are used to initialize the state.
 5     InitializesPools =
 6         [
 7             {PoolId, #pool{
 8                 pool_id = PoolId,
 9                 size = proplists:get_value(size, Props, 1),
10                 user = proplists:get_value(user, Props),
11                 password = proplists:get_value(password, Props),
12                 host = proplists:get_value(host, Props),
13                 port = proplists:get_value(port, Props),
14                 database = proplists:get_value(database, Props),
15                 encoding = proplists:get_value(encoding, Props),
16                 start_cmds = proplists:get_value(start_cmds, Props, [])
17             }} || {PoolId, Props} <- emysql_app:pools()
18         ],
19     [begin
20         case emysql_conn:open_connections(Pool) of
21             {ok, Pool1} ->
22                 emysql_pool_mgr:add_pool(PoolId, Pool1);
23             {error, Reason} ->
24                 erlang:throw(Reason)
25         end
26     end || {PoolId, Pool} <- InitializesPools],
27     {noreply, State, ?HIBERNATE_TIMEOUT};

总不能optimize 之后, 直接remove 掉一些函数功能啊. :)

总结

整体上,对每一个pool 使用与之对应的一个emysql_conn_mgr 进程作为管理.能够尽可能的避免多pool 的管理工作给emysql_conn_mgr 进程带来的压力.

而新引入的emysql_pool_mgr gen_server 进程, 用来维护PoolID 和 EmysqlConnMgrProcessID 关联信息. 同时为了避免emysql_pool_mgr 的单点问题, 使用ets table 来分担emysql_pool_mgr gen_server 进程的负担.

optimize 的branch 为add_pool_mgr, 还未PR(等待小伙伴的测试反馈,欢迎 code review).

时间: 2024-12-15 01:57:50

Erlang pool management -- Emysql pool optimize的相关文章

Erlang pool management -- Emysql pool

从这篇开始,这一系列主要分析在开源社区中,Erlang 相关pool 的管理和使用. 在开源社区,Emysql 是Erlang 较为受欢迎的一个MySQL 驱动. Emysql 对pool 的管理和使用是非常典型的,pool 的管理角色中,主要有available(记录当前pool 中可供使用的成员),locked(记录当前pool 中正在被使用的成员),waiting(记录当前正在处理等待该pool 的用户).用户进程在使用pool 过程中, pool 中的成员在这三个角色中来回迁移. poo

Erlang pool management -- RabbitMQ worker_pool

在RabbitMQ中,pool 是以worker_pool 的形式存在的, 其主要用途之一是对Mnesia transaction 的操作. 而在RabbitMQ 中, pool 中的worker 数量是固定不变的, 是和虚拟机的schedulers 相关.这次会首先分别分析设计worker_pool 功能的三个module, 然后分析worker_pool 和 worker_pool_worker module 之间的调度关系. worker_pool_sup module worker_po

shared pool 和buffer pool 详解(之二, Cache Buffers LRU Chain、Cache Buffers LRU Chain闩锁竞争与解决)

[深入解析--eygle]学习笔记 1.1.2  Cache BuffersLRU Chain闩锁竞争与解决 当用户进程需要读数据到Buffer Cache时或Cache Buffer根据LRU算法进行管理等,就不可避免的要扫描LRU  List获取可用Buffer或更改Buffer状态,我们知道,Oracle的Buffer Cache是共享内存,可以为众多并发进程并发访问,所以在搜索的过程中必须获取Latch(Latch是Oracle的一种串行锁机制,用于保护共享内存结构),锁定内存结构,防止

buffer pool和shared pool详解(之四,重要视图、以及转储)

1.2.5  X$KSMSP视图 Shared  Pool 的空间分配和使用情况,可以通过一个内部视图来观察,这个视图就是X$KSMSP. X$KSMSP的名称含义为: [K]ernal [S]torage [M]emory Management [S]GA Hea[P]其中每一行都代表着Shared Pool中的一个Chunk.以下是x$ksmsp的结构: 12:03:45 [email protected] SQL>desc x$ksmsp Name                     

buffer pool 和shared pool 详解(一)

[深入解析--eygle]学习笔记 1.1 buffer pool原理 Buffer Cache是Oracle SGA中一个重要部分,通常的数据访问和修改都需要通过BufferCache来完成.当一个进程需要访问数据时,首先需要确定数据在内存中是否存在,如果数据在Buffer中存在,则需要根据数据的状态来判断是否可以直接访问还是需要构造一致性读取:如果数据在Buffer中不存在,则需要在Buffer Cache中寻找足够的空间以装载需要的数据,如果Buffer  Cache中找不到足够的内存空间

Erlang pool management -- RabbitMQ worker_pool 2

上一篇已经分析了rpool 的三个module , 以及简单的物理关系. 这次主要分析用户进程和 worker_pool 进程还有worker_pool_worker 进程之间的调用关系. 在开始之前, 必须先明确一点, 就是一个worker_pool_worker 进程只有在处理完一个用户进程的任务之后才能开始处理另一用户进程的任务. worker_pool 状态管理 在上一篇已经说明, worker_pool 管理了rpool 工作进程的ready idle busy 状态,从worker_

Apache Commons pool 简介和pool连接池代码

在实际中工作,我们经常遇到需要连接池的地方,特别是数据库连接池. 我们为什么需要池呢?因为这些资源的创建,都很消耗资源.因此,我们使用一个对象池,里面预先创建了一些资源对象.当我们需要时,从池中取出对象,而不需要时,把对象返回池中.这样就可以提高代码运行的效率. Apache Commons Pool(http://commons.apache.org/pool/)为我们提供了很方便的接口来实现对象池.我们唯一需要实现的就是如何产生对象,而不用去考虑一堆多线程问题. 2013年,Apache C

The Run-Time Constant Pool The Constant Pool

http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4 http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.5.5

ceph cache pool配置

0.引入 本文介绍如何配置cache pool tiering. cache pool的作用是提供可扩展的cache,用来缓存ceph的热点数据或者直接用来作为高速pool.如何建立一个cache pool:首先利用ssd盘做一个虚拟的bucket tree, 然后创建一个cache pool,设置其crush映射rule和相关配置,最后关联需要用到的pool到cache pool. 1.建立ssd bucket tree 这是新增ssd bucket(vrack)后的osd tree.其中os