You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python简易DNS解析器出现内存泄漏问题求助

DNS解析器内存泄漏问题排查与修复

内存泄漏根源分析

你的代码存在几个明确的内存泄漏触发点:

  1. 数据库连接未可靠关闭:仅在正常执行分支调用db.close(),如果MySQL.connect()之后、db.close()之前抛出异常(比如查询语法错误、数据库临时中断),连接资源会被遗留,无法被回收。
  2. 查询结果集未释放:调用db.store_result()获取的结果集没有调用free_result()释放,底层C库资源无法被Python垃圾回收器处理,长期积累会导致内存占用持续上升。
  3. 频繁创建销毁连接:每次DNS请求都新建数据库连接,即使调用了close(),底层也可能残留未彻底回收的资源,高并发场景下会加速内存泄漏。

修复方案

1. 强制保证连接与结果集的释放

使用try-finally块包裹数据库操作逻辑,确保无论是否发生异常,连接和结果集都能被正确释放。

2. (可选)使用数据库连接池

复用数据库连接可以避免频繁创建销毁连接带来的资源开销,进一步降低泄漏风险,同时提升DNS请求处理效率。

修复后的代码

#!/usr/bin/python3

from MySQLdb import _mysql as MySQL
from dnslib import RR, QTYPE, RCODE, A
from dnslib.label import DNSLabel
from dnslib.server import DNSServer, BaseResolver
from time import sleep, time


class MariaResolver(BaseResolver):
    DELTA = 300

    def __init__(self):
        self.password = "********************"

    def resolve(self, request, handler):
        reply = request.reply()
        qname = request.q.qname
        fqdn = str(request.q.qname)
        db = None
        result = None
        try:
            if fqdn.find("iut-") == -1:
                reply.header.rcode = RCODE.REFUSED
                return reply
                
            hostname = fqdn.split(".")[0]
            timestamp = int(time()) - self.DELTA
            query = "SELECT ip FROM dns WHERE record='{}' AND timestamp>{}"
            db = MySQL.connect("localhost", "dns", self.password, "salles")
            db.query(query.format(hostname, timestamp))
            result = db.store_result()
            row = result.fetch_row(how=1)
            if row:
                ip = row[0]["ip"].decode("utf-8")
                reply.add_answer(RR(qname, QTYPE.A, ttl=0,
                                    rdata=A(ip)))
            else:
                reply.header.rcode = RCODE.REFUSED
        except Exception as e:
            print(e)
            reply.header.rcode = RCODE.REFUSED
        finally:
            # 强制释放查询结果集
            if result:
                result.free_result()
            # 强制关闭数据库连接
            if db:
                db.close()
        return reply

if __name__ == '__main__':
    resolver = MariaResolver()
    udp_server = DNSServer(resolver, port=53)
    udp_server.start_thread()
    while udp_server.isAlive():
        sleep(0.1)

进阶优化:连接池版本

如果DNS请求量较大,推荐使用连接池复用连接,示例如下:

# 先安装依赖:pip install DBUtils
from DBUtils.PooledDB import PooledDB
from MySQLdb import _mysql as MySQL
from dnslib import RR, QTYPE, RCODE, A
from dnslib.label import DNSLabel
from dnslib.server import DNSServer, BaseResolver
from time import sleep, time


class MariaResolver(BaseResolver):
    DELTA = 300

    def __init__(self):
        self.password = "********************"
        # 初始化连接池,复用连接避免频繁创建销毁
        self.pool = PooledDB(
            creator=MySQL,
            host="localhost",
            user="dns",
            passwd=self.password,
            db="salles",
            maxconnections=5,  # 按需调整最大连接数
            blocking=True
        )

    def resolve(self, request, handler):
        reply = request.reply()
        qname = request.q.qname
        fqdn = str(request.q.qname)
        db = None
        result = None
        try:
            if fqdn.find("iut-") == -1:
                reply.header.rcode = RCODE.REFUSED
                return reply
                
            hostname = fqdn.split(".")[0]
            timestamp = int(time()) - self.DELTA
            query = "SELECT ip FROM dns WHERE record='{}' AND timestamp>{}"
            # 从连接池获取连接
            db = self.pool.connection()
            db.query(query.format(hostname, timestamp))
            result = db.store_result()
            row = result.fetch_row(how=1)
            if row:
                ip = row[0]["ip"].decode("utf-8")
                reply.add_answer(RR(qname, QTYPE.A, ttl=0,
                                    rdata=A(ip)))
            else:
                reply.header.rcode = RCODE.REFUSED
        except Exception as e:
            print(e)
            reply.header.rcode = RCODE.REFUSED
        finally:
            if result:
                result.free_result()
            if db:
                db.close()  # 此处是将连接放回连接池,而非真正关闭
        return reply

if __name__ == '__main__':
    resolver = MariaResolver()
    udp_server = DNSServer(resolver, port=53)
    udp_server.start_thread()
    while udp_server.isAlive():
        sleep(0.1)

内容的提问来源于stack exchange,提问作者Cédric Bellegarde

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 06:50:43