最近監控告警發現,每天一個線上數據庫都會報too-many-connections問題,每次大約會有幾千封的告警郵件,在某個週六甚至有了幾w封郵件,聯繫dba調整了幾次參數問題,沒有明顯好轉,於是覺得好好排查這個問題.
- 先分析數據庫的連接數配置問題.
首先查看數據庫連接數 show processlist;
查看發現只有1400多個連接,還是比較少的.
查看當前配置可以允許的連接數 show variables like '%max_connections%';
當前的連接應該遠遠沒有達到可允許的上線,那麼到底爲什麼會報too many connections呢?
2.查看系統連接情況
在數據庫沒有排查到可用信息,肯定有很多連接是被佔用的了,因此開始排查機器上的連接情況.
netstat -anp |grep 192.168.7.24:3306
發現有很多的TIME_WAIT的的連接,由於服務都是python開發的,每個進程都會維護一個到數據庫的長連接,理論上應該不會有這麼處於TIME_WAIT的連接?
3.TIME_WAIT問題排查
對比多臺服務器,發現大部分的隊列業務服務器都存在很多的TIME_WAIT狀態的連接,熟悉服務端開發的同學都知道,TIME_WAIT是tcp斷開連接所處於的一箇中間狀態,這說明有很多的額數據庫連接在client或者server端被主動斷開了連接,對於mysql來說,一般不會主動斷開連接,也就基本可以確定肯定是client斷開了連接了.
這中間還抓包排查了一下,確實一個連接在執行幾段sql之後,開始斷開連接.
在最後client會發送一個斷開連接quit(可以查看mysql的協議命令)的命令,
4:mysql Request Quit排查原因排查. 什麼情況下會斷開連接呢?
1 主動調用關閉連接的時候 2 服務重啓的時候 3 連接對象被銷燬的時候
排除原因2,開發是排查1,3.
a: 首先在關閉數據庫連接的close()方法打印日誌,發現並沒有輸出日誌,應該不是主動關閉的連接?
b: 連接對象被銷燬.這邊使用的數據庫包爲mysqlclient-python.這個包是給予Mysqldb-python修改後支持python3版本的mysql鏈接庫,相對於pymysql來說由於是使用c語言編寫,相對性能更好一些.分析底層代理,發現調用QUTI命令的有兩個地方,分別爲
// close 方法
static PyObject *
_mysql_ConnectionObject_close(
_mysql_ConnectionObject *self,
PyObject *noargs)
{
check_connection(self);
Py_BEGIN_ALLOW_THREADS
mysql_close(&(self->connection));
Py_END_ALLOW_THREADS
self->open = 0;
_mysql_ConnectionObject_clear(self);
Py_RETURN_NONE;
}
//python 調用的__del__方法
static void
_mysql_ConnectionObject_dealloc(
_mysql_ConnectionObject *self)
{
PyObject_GC_UnTrack(self);
if (self->open) {
mysql_close(&(self->connection));
self->open = 0;
}
Py_CLEAR(self->converter);
MyFree(self);
}
close方法已經排查過,那麼只剩下__del__方法有可能被影響,因此在__del___打印日誌,由於是c語言寫的,不好打印日誌,因此找到對應的python連接對象處打印堆棧信息.在該包的connections.py的類Connection中重寫__del__方法,並打印該函數的調用堆棧信息.發現有輸出了
class Connection(_mysql.connection):
"""MySQL Database Connection Object"""
# 省略了大部分代碼
def __del__(self):
import traceback
traceback.print_stack()
[p-1154:139826847079008:DummyThread-4 2019-07-10 15:58:34 log.py:282 WARNING] File "/usr/local/lib/python3.6/site-packages/gevent/hub.py", line 688, in run
loop.run()
[p-1154:139826847079008:DummyThread-4 2019-07-10 15:58:34 log.py:282 WARNING] File "/usr/local/lib/python3.6/site-packages/gevent/greenlet.py", line 608, in _notify_links
link(self)
[p-1154:139826847079008:DummyThread-4 2019-07-10 15:58:34 log.py:282 WARNING] File "/usr/local/lib/python3.6/site-packages/gevent/local.py", line 207, in clear
哈,終於找到了,gevent導致的,由於這個服務有大量的網絡請求,因此爲了提高性能,引入了gevent和celery,其他沒有沒有發現這個問題.
5:爲什麼會有這個問題呢?
嘗試不引入gevent?,但是在python3中,gevent 發起https會報錯,ssl在gevent的無限遞歸問題,如果單純解決這個問題,
查看gevent的源碼發現,在monkey.py中提供了多個patch方法
# patch_all會覆蓋很多系統庫,因此可能會帶來很多不可預知的影響,但是又需要gevent的性能,最好的解決辦法就是隻patch必要的部分
def patch_all(socket=True, dns=True, time=True, select=True, thread=True, os=True, ssl=True, httplib=False,
subprocess=True, sys=False, aggressive=True, Event=False,
builtins=True, signal=True):
"""
Do all of the default monkey patching (calls every other applicable
function in this module).
.. versionchanged:: 1.1
Issue a :mod:`warning <warnings>` if this function is called multiple times
with different arguments. The second and subsequent calls will only add more
patches, they can never remove existing patches by setting an argument to ``False``.
.. versionchanged:: 1.1
Issue a :mod:`warning <warnings>` if this function is called with ``os=False``
and ``signal=True``. This will cause SIGCHLD handlers to not be called. This may
be an error in the future.
"""
# pylint:disable=too-many-locals,too-many-branches
# Check to see if they're changing the patched list
_warnings, first_time = _check_repatching(**locals())
if not _warnings and not first_time:
# Nothing to do, identical args to what we just
# did
return
# order is important
if os:
patch_os()
if time:
patch_time()
if thread:
patch_thread(Event=Event, _warnings=_warnings)
# sys must be patched after thread. in other cases threading._shutdown will be
# initiated to _MainThread with real thread ident
if sys:
patch_sys()
if socket:
patch_socket(dns=dns, aggressive=aggressive)
if select:
patch_select(aggressive=aggressive)
if ssl:
patch_ssl()
if httplib:
raise ValueError('gevent.httplib is no longer provided, httplib must be False')
if subprocess:
patch_subprocess()
if builtins:
patch_builtins()
if signal:
if not os:
_queue_warning('Patching signal but not os will result in SIGCHLD handlers'
' installed after this not being called and os.waitpid may not'
' function correctly if gevent.subprocess is used. This may raise an'
' error in the future.',
_warnings)
patch_signal()
_process_warnings(_warnings)
在我們這裏只使用patch_ssl()即可
6:問題定位
沒有詳細的繼續排查gevent的代碼,初步懷疑應該更gevent的文件對象管理有關,在清除一些攜程的過程中,將該攜程的維護的fd全部刪除掉的過程,誤刪了數據庫的fd,導致的一系列的問題.
至此,線上數據庫的too many connections問題已經修復,每天不用在被告警郵件轟炸了.