django项目优化实战

最近一段时间练习了一个django的小项目,想做一些优化看看能抗住多个的访问量:
Python:3.6 Djanog:2.1 MySQL:5.7 缓存后端:reids
这个项目是个电商网站,我将商品的数量搞到了100W的数据量,这些商品数据基本平均分布在六个商品大类中。在没有设置缓存的情况下,打开首页都要几十秒。。。
首先优化首页,下面是首页页面:
django项目优化实战_第1张图片
轮播图和旁边的广告,只有几行数据,可以不用设置缓存。下面是六个大类,每个大类展示当前分类下销量最高的商品,要将这个设置缓存:

        context = cache.get('index_context')
        if context is None:
            types = GoodType.objects.all()
            for good_type in types:
                good_type.top = Good.objects.filter(type=good_type).order_by('-sales')[:4]
            context = {
                'types': types,
            }
            cache.set('index_context', context, 3600)

加上缓存后,用ab测试,在保证响应时间200ms以内的情况下,可以达到20并发,QPS 110 左右。
django项目优化实战_第2张图片
接着测试商品列表页,这里是按照销量高低进行排序,每页显示20个商品,左边显示的当前分类下的最新上架的2个产品,如图
django项目优化实战_第3张图片
由于这里涉及到分页,耗时大,所以可以将每个分类的前几页进行缓存。这时候如果直接将分页后的对象使用django自带的缓存框架缓存,虽然响应时间会减少,但是还是3~4秒,达不到我们的要求。这是由于django自带的缓存框架会将对象使用pickle序列化后再存入redis中,可能由于我们分页后的对象比较大,从缓存中读取时,绝大多数时间都耗在反序列化的过程中,这个过程大致等于响应时间。于是我换个思路 ,将每页的商品的id直接存到redis中(不适用django的cache),这样有个问题,就是分页后的对象是含有上下页的信息的,而我这样缓存的数据是不包含这些信息的,所有还需要将上面的信息也同样直接存到redis中。这样当访问这个页面的时候,直接从redis中获取对应的id,再将其从数据库中查找出来,(由于查找使用的是主键,速度很快,经我测试,每个查询大约1ms,这样一页20个商品,只需20ms)下面是部分代码:

    conn = get_redis_connection()
    list_id_num = 'list_%s_%s' % (id, num)
    h_list_id_num = 'h_list_%s_%s' % (id, num)
    skus = conn.lrange(list_id_num, 0, 20)
    if not skus:
        goods = Good.objects.filter(type=good_type).order_by('-sales').all()
        paginator = Paginator(goods, 20)
        skus = paginator.page(num)
        for sku in skus:
            conn.rpush(list_id_num, sku.id)
        if skus.has_next():
            number = skus.next_page_number()
            conn.hset(h_list_id_num, 'next', str(number))
        if skus.has_previous():
            number = skus.previous_page_number()
            conn.hset(h_list_id_num, 'pre', str(number))
        skus = conn.lrange(list_id_num, 0, 20)
    goods = []
    pages = {
        'next': False,
        'pre': False,
        'next_number': None,
        'pre_number': None,
    }
    for sku in skus:
        good = Good.objects.get(id=int(sku.decode()))
        goods.append(good)
    next_number = conn.hget(h_list_id_num, 'next')
    if next_number:
        pages['next'] = True
        pages['next_number'] = int(next_number)
    pre_number = conn.hget(h_list_id_num, 'pre')
    if pre_number:
        pages['pre'] = True
        pages['pre_number'] = int(pre_number)

用ab测试,在保证响应时间200ms的情况下,可以做到10个并发,QPS 50左右
django项目优化实战_第4张图片
接下来是商品详情页面,如下:
django项目优化实战_第5张图片
这个页面中比较耗时的是新品推荐的查找,于是和上面一样将id直接存储到redis中(这里也可以直接将这个变量存到django自带的缓存框架中,因为数据量比较小,但效率应该还是比不上前面的方法),部分代码如下:

    new_goods_type = 'new_goods_%s' % good.type.id
    new_goods_list = conn.lrange(new_goods_type, 0, 2)
    if not new_goods_list:
        new_goods = Good.objects.filter(type=good.type).order_by('-create_time')[:2]
        new_goods = list(new_goods)
        for good in new_goods:
            conn.rpush(new_goods_type, good.id)
    else:
        new_goods = []
        for good_id in new_goods_list:
            good = Good.objects.get(id=int(good_id))
            new_goods.append(good)

ab测试结果如下,在保证响应时间200ms的情况下,可以达到25并发,130QPS左右
uwsgi配置:

http-socket=127.0.0.1:8000
socket = 127.0.0.1:6000
chdir = /home/lya/PycharmProjects/jd
wsgi-file =jd/wsgi.py
processes = 4
threads = 2
stats = 127.0.0.1:9191
enable-threads= true
virtualenv= /home/lya/PycharmProjects/jd/venv
vacuum=true
listen=2000
memory-report=true
pidfile=%(chdir)/uwsgi/uwsgi.pid
daemonize=%(chdir)/uwsgi/uwsgi.log

在设置了缓存的情况下,单个请求的响应时间已经很小了。后期还可以优化的地方有:1 重新梳理代码逻辑,看是否有不必要的代码 2 将数据库查询优化,只查找我们需要的字段,不要将一行数据全部取出 3 看下html渲染是否有优化的地方,有些视图函数中,逻辑处理(包括数据查找)只占了一半的时间。
源码链接

你可能感兴趣的:(django项目优化实战)