网站有多慢
网站一直比美丽说蘑菇街等同类型的网站慢一些,但到底有多慢,下面是一个对比:
http://www.duitang.com/topics/ 42297 bytes
Requests per second: 0.49 [#/sec] (mean)
http://www.mogujie.com/shopping/ 2390 bytes
Requests per second: 2.87 [#/sec] (mean)
http://guang.com/xihuan 945170 bytes
Requests per second: 1.86 [#/sec] (mean)
可以看到逛的qps是我们的3倍多,所以这次优化的目标主要是提升搜索页面的QPS.
ps:为什么是从topics下手,因为topics是除了首页之外访问量最大的页面。
瓶颈在哪里
用户访问topics,网站通过ajax请求url:http://7599.t.duitang.com/hot/masn/?page=5&page_size=24&_type=&begin_time=1342876671
, 会做以下几件事情:
1. 从搜索引擎获(solr)取查询结果。(平均5ms,偶尔有20ms+的查询)
2. 搜索引擎返回的是id,通过id查询相应的message,并cache。
3. 执行cf.jsonResultMblog(),次方法主要是循环每个message,组装相应信息返回给页面。
经过测试发现 cf.jsonResultMblog()需要耗时900ms,这对一个方法来说太慢了。
cf.jsonResultMblog()做了什么
下面是cf.jsonResultMblog()的代码:
for
item in blogs:
if
item:
if
get_latest:
item = item.get_latest_forward()
if
not item:
continue
root = item.get_root()
common = root and root.is_buyable_common() or False
good = root and root.is_buyable_good() or False
buylnk = root and root.is_buyable() and root.get_source()
buylnk = buylnk and buylnk.link or ""
usr = item.sender
album = item.get_album()
blog = {
"id"
:item.id,
"common"
:common,
"good"
:good,
"msg"
:mbtrimlinks(item.msg),
"isrc"
:item.middle_photo_path2(),
"iht"
:item.photo and item.photo.middle_height() or 0,
"buylnk"
: buylnk,
"uid"
:usr.id,
"unm"
:usr.username,
"ava"
:usr.get_profile().tinyAvatar(),
"albid"
:album and album.id or '',
"albnm"
:album and album.name or u'默认专辑',
"favc"
:root and root.favorite_count or 0,
"repc"
:item.reply_count,
"zanc"
:item.like_count,
"sta"
:item.status,
"cmts"
:[]
}
if
root and root.sender:
rusr = root.sender
blog["rid"
] = root.id,
blog["ruid"
] = rusr.id
blog["runm"
] = rusr.username
blog["rava"
] = rusr.get_profile().tinyAvatar()
comms = item.get_top_comments()
for
comm in comms:
if
comm:
blog["cmts"
].append({
"id"
:comm.sender.id,
"ava"
:comm.sender.get_profile().tinyAvatar(),
"name"
:comm.sender.username,
"cont"
:comm.content,
})
ajblogs.append(blog)
我整理了一下,for循环有15个查询:
- item.get_latest_forward()
- item.get_root()
- root.get_source()
- item.get_album()
- item.photo.middle_height()
- usr.get_profile().tinyAvatar() (获取用户头像,此方法走2个查询,每次5ms)-
- root.sender
- rusr.get_profile().tinyAvatar()
- item.get_top_comments()
- comm.sender
- comm.sender.get_profile().tinyAvatar()
- comm.sender.username
其中有一些方法走了cache, 但还是有一些方法走的是数据库查询,数据库查询每次耗时都在几毫秒,循环24次,总耗时就是上百毫秒。
我这次优化主要就是把数据库查询改成cache,如果不能走cache,就通过select in子查询先一次性查询出结果再处理。
一些总结
1. item.sender.id 比 item.sender_id 慢一个数量级,前者会触发一次查询。
2. django的对象关联非常方便,获取一个物品发布人的信息只需要 item.sender.profile,但这样写性能很差,这些查询都是通过数据库查询,而没有走cache。建议不要直接用django的对象关联,通过提供方法来做:
def get_sender(self):
key = cf.generate_cache_key(self.sender_id, User)
model = key and cache.get(key)
if
not model:
model = self.sender
cache.set(key, model, 60*60*24*3)
return
model
3.不要在for循环做耗时的数据库查询,累加效应之后性能非常差。
4.什么对象应该走cache? 我总结的就是被依赖的对象越多越应该cache起来,比如Auth_User,UserProfile,UploadFile应该被cache,而 Message对象没有被任何对象依赖,生命周期比较段,被cache起来命中率也不高。这点比较像jvm的GC里面的old区。