首页 > 代码库 > 不是所有的大作业都叫微信抢票大作业

不是所有的大作业都叫微信抢票大作业

  为时四周的微信抢票大作业终于接近尾声,回首这段时间,真是感慨万千。不是所有的大作业都是微信抢票大作业,能够让人同时体验产品经理、开发工程师、测试工程师、运维工程师四个角色。经过了微信抢票大作业的洗礼,才知道之前对老师上课讲的内容只是一知半解,只有实践才能出真知。

一、搞开发

  讲道理,这次大作业的开发工作其实不是很多。因为框架设计的很好,接口也介绍的很详细,只需要按部就班填坑就可以达到基本要求了。

  但是既然助教上课都提到了几个优化方案,比如内存型数据库,异步队列等,好奇如我怎能不试呢。于是就开始给自己挖坑,然后再吭哧吭哧填坑。。。缓存机制和异步队列的设计实现文档里都有提及,不再赘述了(毕竟说起来真是好麻烦,一把辛酸泪。。。)

  就讲一个很有趣的debug经历吧:

 1         with transaction.atomic():
 2             try:
 3                 if is_key:
 4                     act = Activity.objects.select_for_update().get(key=key_or_id)
 5                 else:
 6                     act = Activity.objects.select_for_update().get(id=key_or_id)
 7                 ticket = Ticket.objects.select_for_update().get(student_id=self.user.student_id, activity_id=act.id)
 8                 reply_text = {
 9                     Title: self.get_message(ticket_title, act_name=act.name),
10                     Description: self.get_message(ticket_description, act_description=act.description),
11                     Url: self.url_ticket(ticket.unique_id)
12                 }
13                 is_reply_text = False
14             except Activity.DoesNotExist:
15                 reply_text = "活动不存在"
16             except Ticket.DoesNotExist:
17                 if act.book_start > timezone.now():
18                     reply_text = "抢票尚未开始"
19                 elif act.book_end < timezone.now():
20                     reply_text = "抢票已经结束"
21                 elif act.remain_tickets <= 0:
22                     reply_text = "对不起,票已经被抢光啦"
23                 else:
24                     act.remain_tickets -= 1
25                     act.save()
26                     t = Ticket(student_id=self.user.student_id,
27                                activity_id=act.id,
28                                unique_id=gen_unique_id(self.user.student_id, act.id),
29                                status=Ticket.STATUS_VALID)
30                     t.save()
31                     reply_text = "抢票成功,请稍后查看抢票结果"
32                     is_success = True
33         if is_success:
34             task_update_activity_in_cache.delay(act.id)

  这是我抢票部分的代码,大概就是在锁内读取数据库,更新数据库,在锁外以异步队列更新缓存。

 1 @task
 2 def task_update_activity_in_cache(id):
 4     acts = Activity.objects.filter(id=id)
 5     print(acts[0].remain_tickets)
 6     print(timezone.now())
 7     if not acts:
 8         return
 9     if acts.count() > 1:
10         raise DbError("duplicated id")
11     activity = acts[0]
12     cache.set(querykey_of_act(id, activity.id), activity)
13     cache.set(querykey_of_act(key, activity.key), activity)

  这是我的task_update_activity_in_cache。设想中如果抢到票,会先更新数据库该活动的余票数,然后我异步更新缓存的时候,从数据库中读取这个活动,更新缓存中该活动的余票数。然而实际上我抢了票之后,再查看活动详情,余票数并没有-1。一开始我以为是celery没配好,没执行这个任务,但是后来我加了第五行后,控制台打印了信息,而且就是原来的余票数,并没有减一。查看数据库,余票数又是更新了的,so interesting...

  在我一筹莫展的时候,突然有一次,异步任务成功更新了缓存,这就更加玄学了。。。突然我灵机一动,决定分别在两个函数里打印save()操作和filter()操作的时间,发现当两个时间只相差0.0001秒的时候,filter()从数据库读取的还是原来的值,当时间差大一点之后,filter()从数据库读取的就是新值。所以。。。续一秒,解决问题:)

  唯二的遗憾就是没能用异步队列实现定时更新菜单的操作,以及部署时没能以deamon的方式运行celery,原因都是实在配不起来,查遍文档也配不起来,搜遍谷歌也配不起来,只好选择放弃T T

二、配环境

  我对配环境这件事的唯一印象就是:只要有大作业就要配环境,只要配环境就会头疼,配着配着就不知道自己配到哪里了,查遍谷歌度娘结果全是骗人的。但是这次大作业不仅要配环境,而且还要配很多很多的环境,而且不仅要配开发环境,还要配生产环境。也许是做好了长期抗战的心理准备,这次反而很淡定,也很顺利。现在我觉得,配环境这件事也不是那么枯燥乏味的。心得体会就是:配环境最重要的是要动脑子,要明白基本的工作原理,一定一定要查文档,一定一定要好好学习英语,一定一定不要迷信谷歌度娘,一定一定要看比较新的answer。。。

三、做测试

  部署完成之后,就可以开始做性能测试。性能测试我使用了jmeter进行压测。性能测试必须是在功能正确的基础上进行测试。所以从我准备开始性能测试到我真正开始性能测试,花了整整一天时间debug。。。因为是学生党,经费有限,所以选了一个配置最低的服务器,刚开始测的时候在并发数为200的时候就有超过10%的错误率,平均响应时间为几万毫秒。感觉这种错误率实在是太可怕了,所以又对抢票和缓存的代码进行重构,效果也只是将平均响应时间缩短了几百毫秒,错误率并没有下降。

  一筹莫展的时候,我突然想到可以用jmeter的结果分析树看看都是哪里出了问题,然后我发现错误都是no http response,也就是请求超时。说明错误率高并不是程序的问题,于是花了20块钱将服务器的带宽从1mbps调为2mbps,能承受的最大并发数一下子提高到500,零错误率,平均响应时间几百毫秒。一颗赛艇!!!

  当并发数达到600时,又开始有错误。立刻查看响应,是nginx发了502 BAD GATEWAY。说明nginx在转发请求之后迟迟得不到响应,也就是uwsgi的问题,为了进一步提高性能,我又提高了uwsgi的listen参数,但是由于系统net.core.somaxconn为128,所以这个参数只能到128。重新测试,能承受的并发量提高到700。因为服务器配置确实比较低,所以基本上达到瓶颈了。

 

总结下来,虽然最后仍有一些遗憾,但是成功完成了如此challenging的大作业,还是很开心的。

不是所有的大作业都叫微信抢票大作业