网站一直比美丽说蘑菇街等同类型的网站慢一些,但到底有多慢,下面是一个对比:
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()的代码:
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个查询:
其中有一些方法走了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区。