最近一段时间练习了一个django的小项目,想做一些优化看看能抗住多个的访问量:
Python:3.6 Djanog:2.1 MySQL:5.7 缓存后端:reids
这个项目是个电商网站,我将商品的数量搞到了100W的数据量,这些商品数据基本平均分布在六个商品大类中。在没有设置缓存的情况下,打开首页都要几十秒。。。
首先优化首页,下面是首页页面:
轮播图和旁边的广告,只有几行数据,可以不用设置缓存。下面是六个大类,每个大类展示当前分类下销量最高的商品,要将这个设置缓存:
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 左右。
接着测试商品列表页,这里是按照销量高低进行排序,每页显示20个商品,左边显示的当前分类下的最新上架的2个产品,如图
由于这里涉及到分页,耗时大,所以可以将每个分类的前几页进行缓存。这时候如果直接将分页后的对象使用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左右
接下来是商品详情页面,如下:
这个页面中比较耗时的是新品推荐的查找,于是和上面一样将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渲染是否有优化的地方,有些视图函数中,逻辑处理(包括数据查找)只占了一半的时间。
源码链接