mysql too-many-connections問題排查經歷

 最近監控告警發現,每天一個線上數據庫都會報too-many-connections問題,每次大約會有幾千封的告警郵件,在某個週六甚至有了幾w封郵件,聯繫dba調整了幾次參數問題,沒有明顯好轉,於是覺得好好排查這個問題.

  1.  先分析數據庫的連接數配置問題.

首先查看數據庫連接數 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問題已經修復,每天不用在被告警郵件轟炸了.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章