django -- 一次性能问题的排查
Posted bao9687426
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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占用较高的问题
以上是关于django -- 一次性能问题的排查的主要内容,如果未能解决你的问题,请参考以下文章