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
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号