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). 

  • 相关阅读:
    Atitit (Sketch Filter)素描滤镜的实现  图像处理  attilax总结v2
    JS设置cookie、读取cookie、删除cookie
    Atitit 图像处理30大经典算法attilax总结
    Atitit数据库层次架构表与知识点 attilax 总结
    Atitit 游戏的通常流程 attilax 总结 基于cocos2d api
    Atitti css transition Animation differ区别
    Atitit 图像清晰度 模糊度 检测 识别 评价算法 源码实现attilax总结
    Atitit 全屏模式的cs桌面客户端软件gui h5解决方案 Kiosk模式
    Atitit 混合叠加俩张图片的处理 图像处理解决方案 javafx blend
    Atitit  rgb yuv  hsv HSL 模式和 HSV(HSB) 图像色彩空间的区别
  • 原文地址:https://www.cnblogs.com/--00/p/4284912.html
Copyright © 2011-2022 走看看