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占用较高的问题

    坚持不一定成功,但放弃必定失败。
  • 相关阅读:
    matlab之simulink仿真入门
    20160205.CCPP体系具体解释(0015天)
    logistic回归具体解释(二):损失函数(cost function)具体解释
    Java 垃圾回收之垃圾回收算法
    synchronized
    如何中断线程
    yield函数
    Linux
    notify和notifyAll的区别
    Sleep和Wait的区别
  • 原文地址:https://www.cnblogs.com/bao9687426/p/13159571.html
Copyright © 2011-2022 走看看