zoukankan      html  css  js  c++  java
  • 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). 

  • 相关阅读:
    MVC入门学习笔记(五)
    IIS搭配Serveru构建企业空间服务(一)
    HTMLTextBox基于WebBrowser的HTML编辑控件
    MVC入门学习笔记(一)
    MVC入门学习笔记(七)
    MVC入门学习笔记(十)
    关注以下.NET技术
    Notification状态栏通知
    Activity设置横屏显示
    通过xml文件与代码去除通知栏和标题全屏显示
  • 原文地址:https://www.cnblogs.com/--00/p/4284912.html
Copyright © 2011-2022 走看看