多线程共享全局变量引发线上事故分析
  Pv8ga8lmRvBb 2023年11月02日 69 0



目录

  • 背景
  • 伪代码分析
  • 并发测试
  • 解决方案


背景

  • 数据背景:我们有22个进件分表, 大概10亿的数据,为了方便查询进件, 我们原来封装了一个查询组件, 用来循环遍历这22张表的全部数据。
  • 优化背景: 我们在分析变量计算系统的时候,发现很多sql只需要查询关联的一个数据, 或者只需要一个最热的数据。 这种情况下, 没必要把所有的表都遍历一遍。 因此主要有两点可优化点。
  • 进件表查询必须调整成从热到冷检索。
  • 新增一个stop参数, 可以控制查询到一个数据后就不再扫描后续的表。

伪代码分析

  • 原始代码
from DBUtils.PooledDB import PooledDB


class MysqlOps(object):

    def __init__(self):
        self.table_list = list()  # 进件分表信息

    def _get_table_list(self):
        """获取进件分表信息"""
        sql = "SELECT COUNT(id) FROM data_split_info; "
        table_num = list(self.select(sql))[0][0]
        self.table_list = [num for num in range(0, table_num)]

    def mulselect(self, sql, apply_id, values=[], stop=False):
        """进件分表查询"""
        self._get_table_list()

        for num in self.table_list:
            print("后续是遍历分表信息")
            pass
  • 新代码
from DBUtils.PooledDB import PooledDB


class MysqlOps(object):

    def __init__(self):
        self.table_list = list()  # 进件分表信息

    # 新逻辑:按照热表优先扫描。
    def _get_table_list(self):
        if len(self.table_list) == 0:
            sql = "SELECT table_name FROM data_split_info order by id desc; "
            rows = self.select(sql)
            for row in rows:
                self.table_list.append(row[0])

    def mulselect(self, sql, apply_id, values=[], stop=False):
        """进件分表查询"""
        self._get_table_list()

        for num in self.table_list:
            print("后续是遍历分表信息")
            pass
  • 新代码调整分析
  • 新代码主要是将表顺序按照从热到冷的顺序输出了一下。
  • 问题:主要的问题在于我用append方法给共享变量table_list由原来的赋值调整成了append追加值。
  • 并发分析: 当两个请求同时判断self.table_list 长度为0的时候, 原来的逻辑后先后给这个变量赋值一个相同的值。因此虽然存在并发问题, 但self.table_list这个变量值不会发生任何变化。 但是调整成append后, 当两个请求同时判断self.table_list 长度为0的时候, 会导致self.table_list数据加倍。

并发测试

  • 测试案例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time   : 2023/8/15 11:09
# @Author : shanwen.ren
import time
from concurrent.futures import ThreadPoolExecutor


class Test(object):

    def __init__(self):
        self.table_list = list()

    def _get_table_list(self):
        if len(self.table_list) == 0:
            for i in range(30):
                time.sleep(0.01)
                self.table_list.append(i)

    def run(self):
        self._get_table_list()


# 进程启动时初始化对象
this_test = Test()


def test():
    """进行测试"""
    futures = []
    try:
        with ThreadPoolExecutor(max_workers=80) as executor:
            for i in range(10):
                futures.append(executor.submit(this_test.run))
        for future in futures:
            future.result()
    except Exception as e:
        print("error is {}".format(e))
    # 输出最终的结果
    print(this_test.table_list)


if __name__ == '__main__':
    test()
  • 输出结果
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 18, 19, 19, 19, 19, 19, 19, 19, 19, 20, 19, 20, 20, 20, 20, 20, 20, 20, 21, 20, 20, 21, 21, 21, 21, 21, 21, 21, 22, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 23, 22, 23, 23, 23, 23, 23, 23, 24, 23, 23, 23, 24, 24, 24, 24, 24, 25, 24, 24, 24, 24, 25, 25, 25, 25, 25, 26, 25, 25, 25, 25, 26, 26, 26, 26, 26, 27, 26, 26, 26, 27, 27, 26, 27, 27, 27, 28, 27, 27, 27, 28, 28, 28, 27, 28, 28, 29, 28, 29, 28, 28, 29, 28, 29, 29, 29, 29, 29, 29, 29]

解决方案

  • 方法1、给共享变量赋值前加锁,同一时间只能一个线程对共享资源进行赋值操作。
  • 方法2、将共享资源self.table_list放到类的初始化中进行获取赋值(保证进程启动后,请求进入前初始化)。
  • 方法3、与之前一样, 用等号赋值的方式, 这样虽然有并发问题, 但是数据不会加倍。


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
Pv8ga8lmRvBb