python3 多表入库再优化

踩坑日记 专栏收录该内容
8 篇文章 0 订阅
>>>>场景1:多线程多表入库优化--每个线程对应一张表
https://jia666666.blog.csdn.net/article/details/115373369
>>>>场景2:基于场景1进行再细分优化
1.已知多表入库
2.已知每个表的入库方式不同,存在逐条入库与批量入库两大类
3.逐条入库分为两种:
	3.1 直接入库,触发唯一索引异常,直接跳过,不入库
	3.2 入库前检测,数据是否存在,存在则更新状态码+1,新数据状态码为0入库
4.批量入库也分为两种:
	与3相同
5.本次优化内容为:提高数据入库效率,仅对数据入库部分作出优化
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author  : jia666
# Time    : 2021/4/21 9:17

import time
import queue
import random
import pandas as pd
from threading import Thread
import multiprocessing
import concurrent.futures

'''优化思路
-------------------------------------------------------------------------
多线程生产者--随机两个情景
情景1.-->生产逐条入库数据-->数据暂存主队列1
情景2.-->生产批量入库数据-->数据暂存主队列2

主队列1----->多表,逐条数据类型--->采用多表多线程入库--->单个线程对应单个表
详细参见:Python多线程多表入库优化--https://jia666666.blog.csdn.net/article/details/115373369

主队列2----->多表,批量数据类型--->采用多进程池+多线程池入库--->
----->多进程下开多线程,对批量数据尝试批量入库,入库失败,进行逐条入库,每个线程负责一条数据入库
--------------------------------------------------------------------------------
'''
class product_data(Thread):
    '模拟生产者--随机生产单条数据与批量数据'
    def __init__(self):
        super(product_data, self).__init__()

    def getRandomSet(self,main_que,num):
        '随机生产指定的数据条数'
        num_set = [chr(i) for i in range(48, 58)]  # 数字【0-9】
        char_set = [chr(i) for i in range(97, 123)]  # 字符【a-z】
        total_set = num_set + char_set  # 整合到一个列表

        table_name_rand = 'aqc_' + ''.join(random.sample(char_set, 1))  # 随机表名

        tmp_que_list = []  # 临时暂存队列

        for i in range(num):  # 生产数据
            data_random = "".join(random.sample(total_set, 8))  # 随机8位字符串数据
            id_random = ''.join(random.sample(num_set, 5))  # 随机5位数ID
            tmp_que_dict = {'table_name': table_name_rand, 'data_id': id_random, 'data': data_random}
            tmp_que_list.append(tmp_que_dict)
        df=pd.DataFrame(tmp_que_list) #格式处理
        main_que.put((table_name_rand,df)) #数据入队列
    def run(self):
        '主函数随机生产数据'
        num = random.randint(1, 2)  # 指定范围内随机整数

        if num == 1: #生产单条数据
            self.getRandomSet(task_que, num)
        else: #生产多条数据
            self.getRandomSet(process_que, num)


        self.data_to_deal()
    def data_to_deal(self):
        '模拟数据处理入库'
        '数据入库'
        try:
            example.check_thd_alive()
        except Exception as e:
            print(e)

        try:
            global Thd_Sql
            if Thd_Sql.is_alive():
                pass
            else:
                Thd_Sql = process_funciton()
                Thd_Sql.start()
        except Exception as e:
            print(e)

class MysqlA(Thread):
    '模拟多线程数据入库--单条数据'
    def __init__(self,task_que):
        super(MysqlA, self).__init__()
        self.task_queue=task_que #任务队列
    def run(self):
        while not self.task_queue.empty():
            table,df=self.task_queue.get(timeout=1) #获取一个数据
            a = random.randint(1, 3)  # 随机时间
            self.table_deal(table) #模拟对应的表处理
            self.print_df(df)
            #time.sleep(a)  # 模拟入库需要的时间
            print('----A已处理一个单条数据------')
    def table_deal(self,table):
        '模拟对应的表处理,这里不是重点,不大量描述'
        '''
        1.如果表不存在则新建表
        2.检测df是否有新增字段,有则在该表新增字段
        3.df入库前的逻辑,增删改查等等
        4.数据入库(这里是重点处理的)
        '''
        pass

    def print_df(self,df):
        '模拟数据入库--打印df'
        #print(df) #注释掉打印
        pass


class MysqlB():
    '模拟多进程数据入库--批量数据'
    def __init__(self,task_queue):
        self.task_queue=task_queue #任务队列
        self.run()
    def run(self):
        while not self.task_queue.empty():
            table,data_list=self.task_queue.get(timeout=1) #获取一个数据
            a = random.randint(1, 3)  # 随机时间
            self.table_deal(table)  # 模拟对应的表处理
            sub_df = [data_list[i:i + 1] for i in range(len(data_list))]
            with concurrent.futures.ThreadPoolExecutor(max_workers=10) as p:
                ex=p.map(self.print_df,sub_df)

            #time.sleep(a) #模拟入库需要的时间
            print('----B已处理一个批量数据------')
    def table_deal(self,table):
        '模拟对应的表处理,这里不是重点,不大量描述'
        '''
        1.如果表不存在则新建表
        2.检测df是否有新增字段,有则在该表新增字段
        3.df入库前的逻辑,增删改查等等
        4.数据入库(这里是重点处理的)
        '''
        pass

    def print_df(self, df):
        '模拟数据入库--打印df'
        '''实际生产环境中入库如下,这里只是模拟入库-采用打印
        df.to_sql(tablename, con=mysqlengine, if_exists='append', index=False)'''
        #print(df) #注释掉打印
        pass




class auxiliary_function(object):
    '辅助对象'
    def __init__(self):
        pass

    def names_var(self):
        '生成动态变量'
        for i in range(Mysql_thd): #生成动态变量队列
            names['queue_' + str(i)] = queue.Queue()
            register_dict.update({'queue_' + str(i):-1})
    def main_split_queue(self):
        '主队列数据分配到子队列'
        print('主入库队列堆积数据:' + str(task_que.qsize()))
        while not task_que.empty(): #队列不为空,一直循环
            table,df=task_que.get(timeout=1)
            tmp_queue=False
            for key,value in register_dict.items():
                if value==-1: #说明此为空队列
                    tmp_queue=names.get(key)# 临时队列指针-指向动态新增变量队列
                    register_dict.update({key:table}) #更新变量队列专属对应的表名
                    tmp_queue.put((table,df))
                    break
                elif value==table: #该表名称已有专属对应队列
                    tmp_queue=names.get(key)# 临时队列指针-指向动态新增变量队列
                    tmp_queue.put((table,df))
                    break
                else: #其他不处理
                    pass
            if not tmp_queue: #已经没有专属队列可以分配,退出循环,将取出的是数据放回
                task_que.put((table,df))
                break
    def count_size(self):
        '统计每个子队列的含有的数据量'
        for key,value in register_dict.items():
            tmp_queue=names.get(key)# 临时队列指针-指向动态新增变量队列
            print(key,tmp_queue.qsize())
    def thd_to_mysql(self):
        '初始化多线程入库'
        for que_id,table_name in register_dict.items():
            tmp_queue = names.get(que_id)  # 临时队列指针-指向动态新增变量队列
            sa=MysqlA(task_que=tmp_queue)
            sa.start()
            thd_dict.update({que_id:sa})

    def check_thd_alive(self):
        '检测线程是否存活-触发二次分配'

        spilt_sign=False    #触发子队列分配标识符
        for que_id,thd_id in thd_dict.items():
            if not thd_id.is_alive():#如果入库线程不存活-
                register_dict.update({que_id:-1})
                spilt_sign=True
        if spilt_sign:
            self.main_split_queue() #触发下一次的子队列分配
            self.data_to_sql() #触发多线程入库


    def data_to_sql(self):
        '持续多线程入库'
        for que_id,table_name in register_dict.items():
            tmp_queue = names.get(que_id)  # 临时队列指针-指向动态新增变量队列
            thd_id=thd_dict.get(que_id)
            if not thd_id.is_alive():  # 如果入库线程不存活-重建线程启动
                sa=MysqlA(task_que=tmp_queue)
                sa.start()
                thd_dict.update({que_id:sa})


    def run(self):
        '运行主函数'
        self.names_var()  # 创建子队列变量
        self.main_split_queue()  # 主队分割多个队列
        self.thd_to_mysql()  # 初始第一次启动
    def thd_end(self):
        '线程结束'
        while not task_que.empty():
            self.check_thd_alive()
        for que_id,thd_id in thd_dict.items():
            try:
                thd_id.join()
            except:
                pass


class process_funciton(Thread):
    def __init__(self):
        super(process_funciton, self).__init__()
        pass
    def run(self):
        print('主队列2剩余%s数据'%process_que.qsize())

        xlist = [process_que for i in range(Process_num)]
        try:
            with concurrent.futures.ProcessPoolExecutor(max_workers=Process_num) as p:  # 类似打开文件,可省去.shutdown(),不指定进程数,默认CPU核数
                ext = p.map(MysqlB, xlist)
        except Exception as e:
            print(e)

if __name__ == '__main__':
    task_que=queue.Queue()  #模拟多线程主任务队列
    process_que=multiprocessing.Manager().Queue() #模拟多进程处理主任务队列

    names = locals() # 局部变量
    thread_list = []  # 生产者多线程列表
    register_dict = {}  # 多线程入库登记字典

    Thd_Num = 5  # 模拟生产者线程数
    Mysql_thd = 5  # 模拟入库线程数
    Process_num = 2  # 模拟多进程数

    thd_dict = {}  # 多线程字典(队列id:线程id)

    example = auxiliary_function()  # 实例化对象
    example.run()  # 主函数

    Thd_Sql = process_funciton() # 单独建立一个线程负责维护进程池
    Thd_Sql.start()

    while True:
        while True:
            thd_sign = False  # 新线程创建与否标志
            if len(thread_list) == Thd_Num:  # 线程开启是否完全
                thread_list = [t for t in thread_list if t.is_alive()]  # 记录存活的线程
                if len(thread_list) == Thd_Num:  # 存活线程已满负荷,等待一段时间后再次检测
                    pass
                    # print('睡眠3秒')
                    #time.sleep(3)
                else:
                    thd_sign = True
            else:
                thd_sign = True
            if thd_sign:  # 创建新线程
                for t in range(Thd_Num - len(thread_list)):  # 没有完全开启,继续开启新线程
                    t = product_data()
                    t.start()
                    thread_list.append(t)
                break

  • 1
    点赞
  • 2
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值