zoukankan      html  css  js  c++  java
  • django -- 一次性能问题的排查

    在最近的工作中,由于服务中数据量较大,在前端请求的时候,延迟比较严重,(我们服务中web界面的数据都是从数据库中取出通过一定计算得到的,则就会有大量的数据库操作),在前期的时候,我们都是无脑的将数据返回较慢的接口给加了一层缓存,而我们的缓存的更新方式也很简单粗暴,就是间隔一定时间后,后端自己去更新所有的缓存,随着功能的扩展,添加缓存的接口也越来越多,终于,我们服务占用的cpu大部分时间都在100%以上,得不偿失,而且缓存更新的间隔时间也被拉长。

    1. 由于我们服务中有很多的线程,而我们也不能直接断定是哪一个线程占用cpu较高,那么这种我们该怎么去知道是哪一个线程占用cpu较高呢??

    答: 当时我是这样做的,在我们服务中预测占用cpu较高的线程中加入了一个方法,可以记录这个线程在linux中的原生线程号,这样我们就可以知道了每一个线程对应的linux中原生线程ID,python中获取线程原生线程ID是有方法的:

    import cytypes
    linux_thread_id = ctypes.CDLL('libc.so.6').syscall(186)

           而后我们使用top -H -p pid 就可以知道服务进程中线程占用cpu的情况,从而找到我们占用cpu较高的线程。

    2. 这个时候我们只是找到了那个线程占用cpu较高,但是也并不能明确到底是哪儿慢了,当然我们可以猜测是数据库相关的操作慢了,但是并不能明确,那么我们怎么知道是执行那一句的时候慢了呢?

    答: 为了知道是那些语句执行较慢,我们就找到了python自带的line_profiler进行代码的分析,这个模块可以打印出每一句语句执行所耗费的时间,次数,以及占比等信息。

    from line_profiler import LineProfiler
    def test():
        print('test')
    
    def __name__ == "__main__":
        profile = LineProfiler(test)  # 把函数传递到性能分析器
        profile.enable()  # 开始分析
        test()
        profile.disable()  # 停止分析
        profile.dump_stats('test.stats'.format(more_msg, fn_name))
     

          这样就可以把函数中每一句执行的情况给打印出来,也可以放到一个文件中,而后进行查看分析。

    3. 这个时候我们就知道了到底是那一句太慢了,果不其然,是数据库相关的操作导致,尤其在将数据库中的数据反序列化相关操作较慢,因此当时我们就是不需要反序列化就尽量不要反序列化,当然,还有一个特别大的原因是django中orm惰性执行导致的?

         1. 在创建查询集的时候,其实是不会去访问数据库的·,知道调用数据时,才会真正的访问数据库,而调用数据库的情况包括(迭代,序列化,同if语句使用等)

         2. 当存在外键和多对多的关系时,也是不会去检索关联对象的数据,只有在调用关联对象时才会再次去查询数据库,这也就导致了多次的数据库连接,而连接数据库又是一个特别耗时的过程!!

    4. 那么我们该怎么解决这种问题呢?

         1. 这个时候我们就想到了django中orm的预加载。

         2. 预加载单个关联对象--》可以使用select_related

         3. 预加载多个关联对象--》可以使用prefetch_related

         4. 一个Queryset中可以同时使用select_related和prefetch_related,这样可以减少数据库连接次数。

         5. 需要注意的是只有在prefetch_related之前的select_related才是有效的,之后的都是无效的。

    hosts=hosts.filter(type=xdata.PROXY_AGENT).exclude(ext_info__contains=r'"is_deleted":true').select_related('user__first_name','user__username')
    .prefetch_related('backup_task_schedules', 'cluster_backup_schedules','remote_backup_schedules')
    
    # 数据库表
    
    class Host(models.Model):
        # 主机唯一编码
        ident = models.CharField(max_length=32, unique=True)
        # 用于显示的别名,用户可自定义
        display_name = models.CharField(max_length=256, blank=True)
        # 主机最近的连接时间
        login_datetime = models.DateTimeField(blank=True, null=True)
        # 从属用户
        user = models.ForeignKey(User, related_name='hosts', blank=True, null=True)
    
    class User(AbstractUser):
        """
        Users within the Django authentication system are represented by this
        model.
    
        Username and password are required. Other fields are optional.
        """
        class Meta(AbstractUser.Meta):
            swappable = 'AUTH_USER_MODEL'
    
    class ClusterBackupSchedule(models.Model):
        # 使能/禁用
        enabled = models.BooleanField(blank=True, default=True)
        # 删除(不会在界面显示)
        deleted = models.BooleanField(blank=True, default=False)
        # 计划名称,用户可自定义
        name = models.CharField(max_length=256)
        # 计划周期类型
        cycle_type = models.IntegerField(choices=xdata.CYCLE_CHOICES)
        # 计划创建时间
        created = models.DateTimeField(auto_now_add=True)
        # 计划开始时间
        plan_start_date = models.DateTimeField(blank=True, null=True, default=None)
        # 关联的主机
        hosts = models.ManyToManyField(Host, related_name='cluster_backup_schedules')

    通过预加载,可以较高的提升速度。

    5. 由于项目紧急,并没有所有的线程这样做,而后只是把更新缓存的线程单独以进程外挂在服务主进程中,也就是踢到了另一个进程中来更新我们的缓存,这样就可以给我们服务的进程减压,cpu占用减低。

    def _start_update_workload_cache():
        t = Process(target=update_workload_cache, args=())
        t.start()
    
    def update_workload_cache():
        cmd = r'python /sbin/aio/box_dashboard/update_workload_cache.py'
        os.system(cmd)
    
    _start_update_workload_cache()
    

     以这样的方式外挂在服务主进程上,可以较好的解决cpu占用较高的问题

    坚持不一定成功,但放弃必定失败。
  • 相关阅读:
    python note 30 断点续传
    python note 29 线程创建
    python note 28 socketserver
    python note 27 粘包
    python note 26 socket
    python note 25 约束
    Sed 用法
    python note 24 反射
    python note 23 组合
    python note 22 面向对象成员
  • 原文地址:https://www.cnblogs.com/bao9687426/p/13159571.html
Copyright © 2011-2022 走看看