1.1 问题描述
客户端连接数据库、查询语句等,报错连接已满:too many clients already, active/non_active: xxxx/xxxx.
图1. dn上连接满
1.2 原理分析
max_connections决定了进程内的线程数量上限。
1.2.1 DN上连接满
DN上连接满通常是由于CN与此DN的连接数(一条连接即在DN上开启一个线程)和此DN上启动的Stream线程数量之和达到max_connections所致,报错信息一般会指明具体DN,如图1。当客户端与CN建连后,CN会根据连接的database、username和options匹配到pooler中对应的pool,pool中会保留与其他DN的连接。当需要执行query时,pool会根据执行计划选择与对应DN的保留的连接,若没有,则进行建连。当query执行结束或客户端断开与CN连接后,CN与DN间连接直接归还给相应的pool,便于其他query直接使用,节省建连所需的时间、资源成本。
然而,如果user1访问database1执行了100条并发语句后全部退出,那么相应的pool1中就会保留与其他每个DN的100条连接,user2访问database1执行100条并发语句后,又会在pool2中保留100条连接,随着不同pool保留的累积,最后在DN上的连接数达到了max_connections,连接报错。
图2. pooler通信框架
为此,数据库设计了pooler复用技术,即客户端连接数据库只根据database进行匹配pool,不同的user可以访问同一个pool,从而减少pool的数量,进而减少了保留的连接。然而,随着database数量的增加,相应的pool数量同样也会增加,保留的连接数依然可能达到max_connections。8.0版本增加自动clean connection功能,当CN与DN建连时发现DN连接已满,会清理所有pool中四分之一的空闲连接,不足四个则清理一个,从而保证空闲连接不会产生影响。
影响DN上此参数的另一方面是Stream线程数量,其与相应业务的执行计划相关,在定位该问题时,优先排查CN与DN间的连接数量,若数量偏差较大,再进一步考虑业务Stream情况。
1.2.2 CN上连接满
CN上连接满与DN上有着较大差别,是指CN与CN之间(同DN,也是和pooler相关)和客户端与CN的连接数之和达到max_connections,通常此报错与业务侧相关。对于CN与CN之间的连接,如果更多的是active状态,可能是业务中包含很多DDL操作;如果更多的是idle状态,那么则为pooler中保留的连接,可使用clean connection清理。对于客户端的连接,active状态表示正有业务运行,如果大部分连接都是此状态,那么很有可能是业务并发太高(存在特殊情况),需要降低并发。如果更多的是idle状态的话,则很有可能是对于idle状态连接管控的问题(详见2.3.2)。
1.3 定位方法
1.3.1 DN上连接满
- clean connection to [node (dn_xxxx_xxxx/cn_xxxx)] [all] for database dbname;
CN上提供了pg_pooler_status视图可以看到当前CN与所有DN的连接情况,对于某个报错的DN,需要分别在所有CN上执行:
- select in_use, count(*) from pg_pooler_status where node_name = ‘dn_xxxx_xxxx’ group by 1;
可查看此DN的连接情况,其中in_use为f表示此连接没有被占用,即保留在pool中,为t即为正在使用。
使用clean connection操作可清理in_use为f的连接,指定node (dn_xxxx_xxxx)即清理指定DN的连接,指定node (cn_xxxx)即清理指定CN与所有DN的连接,指定all则清理集群所有的in_use为f的连接。当指定all的时候可添加关键字force,这样in_use为t的连接也同样会进行清理,详见产品文档。
- 如果开启了persistent_datanode_connections,释放掉CN上的idle连接或关闭此参数;
当无可执行的query时,CN和DN的连接将归还pool,即in_use由t变为f,但是若此参数开启,则会一直保留连接。关闭此参数或清理CN上idle状态的连接(详见2.3.2)是一种有效的手段。
- GUC参数max_connections;
CN和DN上的max_connections设置是有依据的,理论上,DN上应设置为CN上的值 * CN的个数。这是由于不同的CN都会与同一个DN进行建连,DN的连接上限要满足大于所有CN连接上限之和。核查一下,说不定真的是DN上不够了!
- 神级视图:pgxc_thread_wait_status;
- 查询当前DN上所有线程数量:select count(*) from pgxc_thread_wait_status where thread_name like ‘cn%’ and node_name = ‘dn_xxxx_xxxx’;
- 查询当前DN上所有Stream线程数量:select count(*) from pgxc_thread_wait_status where thread_name like ‘cn%’ and tlevel <> 0 and node_name = ‘dn_xxxx_xxxx’;
- 查询所有DN上的线程数量:select node_name, count(*) from pgxc_thread_wait_status where thread_name like ‘cn%’ and node_name like ‘dn%’ group by 1 order by 2 desc;
- 查询所有DN与CN的连接和Stream线程数量:select node_name, sum(level0) cnt0, sum(level1) cnt1, cnt0 + cnt1 sum from (select node_name, case tlevel when 0 then 1 else 0 end as level0, case tlevel when 0 then 0 else 1 end as level1 from pgxc_thread_wait_status where thread_name like ‘cn%’ and node_name like ‘dn%’) group by 1 order by 1, 2, 3 desc;
- 查询所有query在每个DN上的Stream线程数量:select query_id, node_name, count(*) from pgxc_thread_wait_status where query_id <> 0 and tlevel <> 0 and thread_name like ‘cn%’ and node_name like ‘dn%’ group by 1, 2 order by 1, 3 desc;
- …
使用-rm工程模式登录数据库,根据pgxc_thread_wait_status视图查看所需信息。然而,存在一些情况是场景丢失、连接数已恢复正常,此时可以部署脚本等待问题再次复现。
(脚本文件无法上传,附件中需改回.sh后缀)
图3. general脚本用法
- gstack打堆栈;
若前几种方案均无法排查出问题,可以使用gstack收集堆栈信息,逐一线程分析为何没有正常退出(线程数量可能很庞大,建议打印的堆栈重定向至文件中)。
- kill dn_xxxx_xxxx;(应急)
- 找DN目录:cm_ctl query -Cvd | grep dn_xxxx_xxxx
- 找DN进程:ps ux | grep 上一步找到的目录
- 统计连接数量:netstat -anpt | grep pid | wc -l
- Kill DN进程:kill -9 pid
kill连接数满的DN进程是一种快速、有效的规避手段,然而此方法会对正在执行的业务有短暂影响,且不利于定位根因,使用时需慎重考虑。
- cm_ctl switchover –n 备机序号 –D 备机目录;(应急)
对于一些特殊情况,例如仅在个别节点应急后连接数仍频繁涨满、其他节点均正常且暂时无法定位根因,使用主备切换方法可进行应急。
1.3.2 CN上连接满
- 设置session_timeout;
当业务侧未配备连接池管控idle连接时,需要数据库进行管控,否则idle连接一直不释放,很容易造成连接满的问题。对此,设置session_timeout不为0,当达到此时间没有任何操作,则数据库会自动断开连接。值得注意的是,若业务侧有进行对idle的管控,数据库同时设置session_timeout,当超时后数据库自动断开连接,业务侧再获取idle状态的连接,此时连接已断开,会报错:An I/O error occurred while sending to the backend.
- 排查业务侧哪些连接没有释放;
- select usename, application_name, client_addr, count(*) from pgxc_stat_activity where state = ‘idle’ and coorname = ‘cn_xxxx’ group by 1, 2, 3 order by 4 desc;
- select usename, application_name, query_start from pgxc_stat_activity where state = ‘idle’ and coorname = ‘cn_xxxx’ group by 1, 2, 3 order by 3 desc;
- …
根据pgxc_stat_activity视图可以看到报错CN上的所有连接情况,按照相应的客户端IP地址、用户名、应用名称等信息可定位到连接来源,从而分析根因。此视图可在任意CN上查询,根据需求选择字段查询,详见产品文档。
- 神级视图:pgxc_thread_wait_status;
- select * from pgxc_thread_wait_status where node_name = ‘cn_xxxx’;
同DN连接满,此视图可查每个连接对应的线程状态,进而分析根因。
- gstack打堆栈;
若所有CN均出现此报错、无法正常登录数据库或前几种方案均无法排查出问题,可以使用gstack收集堆栈信息,逐一线程分析为何没有正常退出(线程数量可能很庞大,建议打印的堆栈重定向至文件中)。
- kill连接线程;(应急)
- select pid, state from pgxc_stat_activity;
- select pg_terminate_backend(pid);
对于CN上的每一条连接,都有一个线程相对应,将对应线程kill则会将连接断开。然而此方法同样不利于分析根因,日后还是容易复发。不过尝试kill少量空闲连接对定位影响较小,且也有助于业务恢复。
- kill cn_xxxx;(应急)
同样,kill CN进程应急快、效果好,但是难以分析根因,且执行中的业务会受到影响。
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/bigdata/316470.html