Archery 学习(二)---Mysql查询超时主动Kill查询进程

一 Review 代码所得

通过这块代码的review了解到以下知识点:

(1) 了解Archery 查询功能的主要功能实现方式;

(2) 了解Python如果获取MySQL 的thread_id;

(3) 学习通过schedule实现时间相关的守护进程;

(4) 学习FuncTimer()方法,实现记录某一功能的耗时;

(5)学习schedule模块的相关知识。

二 详细说明

1【SQL查询】中【查询】对应代码

其按钮对于的方法为def query(request),位于  .../sql/query.py 文件中。

@permission_required("sql.query_submit", raise_exception=True)
def query(request):
    """
    获取SQL查询结果
    :param request:
    :return:
    """
    instance_name = request.POST.get("instance_name")
    sql_content = request.POST.get("sql_content")
。。。。。。。。。。省    略。。。。。。。。。。。。。
。。。。。。。。。。省    略。。。。。。。。。。。。。
。。。。。。。。。。省    略。。。。。。。。。。。。。
。。。。。。。。。。省    略。。。。。。。。。。。。。
# 返回查询结果
    try:
        return HttpResponse(
            json.dumps(
                result,
                use_decimal=False,
                cls=ExtendJSONEncoderFTime,
                bigint_as_string=True,
            ),
            content_type="application/json",
        )
    # 虽然能正常返回,但是依然会乱码
    except UnicodeDecodeError:
        return HttpResponse(
            json.dumps(result, default=str, bigint_as_string=True, encoding="latin1"),
            content_type="application/json",
        )

2.kill超时连接 设计

核心代码其实不多,如下:

 1         query_engine = get_engine(instance=instance)
 2 
 3         # 先获取查询连接,用于后面查询复用连接以及终止会话
 4         query_engine.get_connection(db_name=db_name)
 5         thread_id = query_engine.thread_id
 6         max_execution_time = int(config.get("max_execution_time", 60))
 7         # 执行查询语句,并增加一个定时终止语句的schedule,timeout=max_execution_time
 8         if thread_id:
 9             schedule_name = f"query-{time.time()}"
10             run_date = datetime.datetime.now() + datetime.timedelta(
11                 seconds=max_execution_time
12             )
13             add_kill_conn_schedule(schedule_name, run_date, instance.id, thread_id)
14         with FuncTimer() as t:
15             # 获取主从延迟信息
16             seconds_behind_master = query_engine.seconds_behind_master
17             query_result = query_engine.query(
18                 db_name,
19                 sql_content,
20                 limit_num,
21                 schema_name=schema_name,
22                 tb_name=tb_name,
23                 max_execution_time=max_execution_time * 1000,
24             )
25         query_result.query_time = t.cost
26         # 返回查询结果后删除schedule
27         if thread_id:
28             del_schedule(schedule_name)

 简单来讲,就是开启了一个守护进程(或者说是schedule),如果定义的超时阈值的时间到了,就会触发这个kill thread_id的schedule。如果在返回查询结果前,都没有超时,我们就把这个schedule 删除掉。

3.获取 MySQL thread_id

方法来源于 .../sql/engines/mysql.py文件

    def get_connection(self, db_name=None):
        # https://stackoverflow.com/questions/19256155/python-mysqldb-returning-x01-for-bit-values
        conversions = MySQLdb.converters.conversions
        conversions[FIELD_TYPE.BIT] = lambda data: data == b"\x01"
        if self.conn:
            self.thread_id = self.conn.thread_id()
            return self.conn
        if db_name:
            self.conn = MySQLdb.connect(
                host=self.host,
                port=self.port,
                user=self.user,
                passwd=self.password,
                db=db_name,
                charset=self.instance.charset or "utf8mb4",
                conv=conversions,
                connect_timeout=10,
            )
        else:
            self.conn = MySQLdb.connect(
                host=self.host,
                port=self.port,
                user=self.user,
                passwd=self.password,
                charset=self.instance.charset or "utf8mb4",
                conv=conversions,
                connect_timeout=10,
            )
        self.thread_id = self.conn.thread_id()
        return self.conn

 4.kill 连接的方法--kill_query_conn

这个方法为 守护进程 --add_kill_conn_schedule,所调用。

kill_query_conn的定义也很简单,方法位于文件 .../sql/query.py中。

def kill_query_conn(instance_id, thread_id):
    """终止查询会话,用于schedule调用"""
    instance = Instance.objects.get(pk=instance_id)
    query_engine = get_engine(instance)
    query_engine.kill_connection(thread_id)

kill_connection的具体定义,在文件.../sql/engines/mysql.py中。

    def kill_connection(self, thread_id):
        """终止数据库连接"""
        self.query(sql=f"kill {thread_id}")

5.获取代码执行时间 --FuncTimer()

这个方法定义在 ..../common/utils/timer.py文件中。主要代码如下:

# -*- coding: UTF-8 -*-
""" 
@author: hhyo
@license: Apache Licence
@file: timer.py
@time: 2019/05/15
"""
import datetime

__author__ = "hhyo"


class FuncTimer(object):
    """
    获取执行时间的上下文管理器
    """

    def __init__(self):
        self.start = None
        self.end = None
        self.cost = 0

    def __enter__(self):
        self.start = datetime.datetime.now()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.end = datetime.datetime.now()
        self.cost = (self.end - self.start).total_seconds()

6.schedule相关的知识

 其代码定义位于.../sql/utils/tasks.py文件中的。

主要引用的模块如下:

from django_q.tasks import schedule
from django_q.models import Schedule

 例如 kill conn 的schedule的方法定义:

def add_kill_conn_schedule(name, run_date, instance_id, thread_id):
    """添加/修改终止数据库连接的定时任务"""
    del_schedule(name)
    schedule(
        "sql.query.kill_query_conn",
        instance_id,
        thread_id,
        name=name,
        schedule_type="O",
        next_run=run_date,
        repeats=1,
        timeout=-1,
    )

其中调用的del_schedule(name)定义是

def del_schedule(name):
    """删除schedule"""
    try:
        sql_schedule = Schedule.objects.get(name=name)
        Schedule.delete(sql_schedule)
        logger.debug(f"删除schedule:{name}")
    except Schedule.DoesNotExist:
        pass

三Archery 完整 代码

https://gitee.com/rtttte/Archery

 

posted @ 2022-07-30 22:39  东山絮柳仔  阅读(553)  评论(0)    收藏  举报