GaussDB外部连接报错汇总
1 问题背景
在使用客户端连接GaussDB时,可能会出现连接失败或者连接慢的问题,如果不能及时解决恢复很容易影响使用体验甚至业务运行。本文针对几种常见场景,结合原理快速分析定位,也可帮助客户进行合理的使用配置,从而在根源上减少问题的发生。文中涉及的参数、视图详情可参考产品文档。
2 场景一:too many clients already, active/non_active: xxxx/xxxx.
2.1 问题描述
客户端连接数据库、查询语句等,报错连接已满:too many clients already, active/non_active: xxxx/xxxx.
图1. dn上连接满
2.2 原理分析
max_connections决定了进程内的线程数量上限。
2.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情况。
2.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)。
2.3 定位方法
2.3.1 DN上连接满
1. clean connection to [node (dn_xxxx_xxxx/cn_xxxx)] [all] for database dbname;
CN上提供了pg_pooler_status视图可以看到当前CN与所有DN的连接情况,对于某个报错的DN,需要分别在所有CN上执行:
1. 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的连接也同样会进行清理,详见产品文档。
2. 如果开启了persistent_datanode_connections,释放掉CN上的idle连接或关闭此参数;
当无可执行的query时,CN和DN的连接将归还pool,即in_use由t变为f,但是若此参数开启,则会一直保留连接。关闭此参数或清理CN上idle状态的连接(详见2.3.2)是一种有效的手段。
3. GUC参数max_connections;
CN和DN上的max_connections设置是有依据的,理论上,DN上应设置为CN上的值 * CN的个数。这是由于不同的CN都会与同一个DN进行建连,DN的连接上限要满足大于所有CN连接上限之和。核查一下,说不定真的是DN上不够了!
4. 神级视图:pgxc_thread_wait_status;
1. 查询当前DN上所有线程数量:select count(*) from pgxc_thread_wait_status where thread_name like 'cn%' and node_name = 'dn_xxxx_xxxx';
2. 查询当前DN上所有Stream线程数量:select count(*) from pgxc_thread_wait_status where thread_name like 'cn%' and tlevel <> 0 and node_name = 'dn_xxxx_xxxx';
3. 查询所有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;
4. 查询所有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;
5. 查询所有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;
6. ...
使用-rm工程模式登录数据库,根据pgxc_thread_wait_status视图查看所需信息。然而,存在一些情况是场景丢失、连接数已恢复正常,此时可以部署脚本等待问题再次复现。
图3. general脚本用法
5. gstack打堆栈;
若前几种方案均无法排查出问题,可以使用gstack收集堆栈信息,逐一线程分析为何没有正常退出(线程数量可能很庞大,建议打印的堆栈重定向至文件中)。
6. kill dn_xxxx_xxxx;(应急)
1. 找DN目录:cm_ctl query -Cvd | grep dn_xxxx_xxxx
2. 找DN进程:ps ux | grep 上一步找到的目录
3. 统计连接数量:netstat -anpt | grep pid | wc -l
4. Kill DN进程:kill -9 pid
kill连接数满的DN进程是一种快速、有效的规避手段,然而此方法会对正在执行的业务有短暂影响,且不利于定位根因,使用时需慎重考虑。
7. cm_ctl switchover –n 备机序号 –D 备机目录;(应急)
对于一些特殊情况,例如仅在个别节点应急后连接数仍频繁涨满、其他节点均正常且暂时无法定位根因,使用主备切换方法可进行应急。
2.3.2 CN上连接满
1. 设置session_timeout;
当业务侧未配备连接池管控idle连接时,需要数据库进行管控,否则idle连接一直不释放,很容易造成连接满的问题。对此,设置session_timeout不为0,当达到此时间没有任何操作,则数据库会自动断开连接。值得注意的是,若业务侧有进行对idle的管控,数据库同时设置session_timeout,当超时后数据库自动断开连接,业务侧再获取idle状态的连接,此时连接已断开,会报错:An I/O error occurred while sending to the backend.
2. 排查业务侧哪些连接没有释放;
1. 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;
2. 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;
3. ...
根据pgxc_stat_activity视图可以看到报错CN上的所有连接情况,按照相应的客户端IP地址、用户名、应用名称等信息可定位到连接来源,从而分析根因。此视图可在任意CN上查询,根据需求选择字段查询,详见产品文档。
3. 神级视图:pgxc_thread_wait_status;
1. select * from pgxc_thread_wait_status where node_name = 'cn_xxxx';
同DN连接满,此视图可查每个连接对应的线程状态,进而分析根因。
4. gstack打堆栈;
若所有CN均出现此报错、无法正常登录数据库或前几种方案均无法排查出问题,可以使用gstack收集堆栈信息,逐一线程分析为何没有正常退出(线程数量可能很庞大,建议打印的堆栈重定向至文件中)。
5. kill连接线程;(应急)
1. select pid, state from pgxc_stat_activity;
2. select pg_terminate_backend(pid);
对于CN上的每一条连接,都有一个线程相对应,将对应线程kill则会将连接断开。然而此方法同样不利于分析根因,日后还是容易复发。不过尝试kill少量空闲连接对定位影响较小,且也有助于业务恢复。
6. kill cn_xxxx;(应急)
同样,kill CN进程应急快、效果好,但是难以分析根因,且执行中的业务会受到影响。
3 场景二:An I/O error occurred while sending to the backend.
3.1 问题描述
客户端使用与数据库建好的连接报错:An I/O error occurred while sending to the backend.
图4. 场景二报错截图
3.2 原理分析
正如2.3.2中所说,当数据库侧由于某种原因将连接断开,应用侧再次使用此连接就会产生这个报错。而数据库侧连接异常断开主要有四种原因:1. CN进程异常重启;2. session超时;3. 人为kill session;4. LVS中virtual router id冲突。
3.3 定位方法
1. 查看CN进程是否异常重启
1. ps -eo pid,lstart,etime,cmd | grep coo
如果连接在CN进程启动之前就存在,那么CN进程重启之后,连接就会断开,业务侧继续使用则会报错。
2. 未达到session_timeout时间就报错
如果不存在CN重启,且时间未到设置的session_timeout,或者业务正在运行中就产生此报错,可排查LVS中的virtual router id是否冲突。
查看OS日志,搜索Keepalive字段,若存在如下图字样,即为冲突,可修改主备LVS配置文件后,重启LVS即可:.
/etc/keepalived/keepalived.conf:文件中virtual_router_id字段,同一子网下不可冲突
/etc/init.d/gs_keepalived restart:重启LVS
3. 排查CN日志是否含有due to
非常清晰的两种情况,根据日志中相应时间分析;
调用select pg_terminate_backend(pid); 终止了会话,多数是用户操作产生,个别情况是由于cm_agent在检测磁盘超过阈值(默认90%)场景下,会调用此方法终止当前CN上所有非omm用户的SQL,同时会打印调用日志“cancel session ok”,可排查cm_agent日志。
会话设置了session_timeout时间,当超过此时间,数据库会自动清理连接。对于需要保持长连接的场景。可通过客户端对此session设置session_timeout为0或预期时长。
4 场景三:客户端连接数据库很慢.
4.1 问题描述
客户端可以连接数据库,但是耗时长。(可能连接个别CN出现此现象)
4.2 原理分析
已知的问题是GaussDB提供GUC参数log_hostname,此参数默认开启。当客户端来连接数据库时,会根据log_hostname来选择调用操作系统函数getnameinfo()时是否进行DNS解析域名,即根据客户端ip获取主机名。由于DNS的配置问题,解析可能耗时较长甚至失败,因此对于客户端来说,连接数据库很慢。
4.3 定位方法
1. show log_hostname;
登录连接慢的CN,查看log_hostname的值,若为on,可根据线程号打印堆栈;
1. select application_name, lwtid from pg_stat_activity a, pg_thread_wait_status b where a.pid = b.tid and client_addr = 'xxxx' and datname = 'xxxx' and usename = 'xxxx' and backend_start between 'xxxx' and 'xxxx';
2. gstack lwtid
图5. getnameinfo()堆栈信息
若堆栈中存在明显的getnameinfo(),则关闭此参数即可;没有打印堆栈的工具也可尝试关闭。
2. log_hostname = on?
若log_hostname为on,可使用ping命令排查网络时延等问题;如果网络正常,那么还是根据线程号打堆栈进一步分析。
- 点赞
- 收藏
- 关注作者
评论(0)