Python简易DNS解析器出现内存泄漏问题求助
DNS解析器内存泄漏问题排查与修复
内存泄漏根源分析
你的代码存在几个明确的内存泄漏触发点:
- 数据库连接未可靠关闭:仅在正常执行分支调用
db.close(),如果MySQL.connect()之后、db.close()之前抛出异常(比如查询语法错误、数据库临时中断),连接资源会被遗留,无法被回收。 - 查询结果集未释放:调用
db.store_result()获取的结果集没有调用free_result()释放,底层C库资源无法被Python垃圾回收器处理,长期积累会导致内存占用持续上升。 - 频繁创建销毁连接:每次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
相关产品推荐
相关产品推荐

