首页 > 代码库 > 不是所有的大作业都叫微信抢票大作业
不是所有的大作业都叫微信抢票大作业
为时四周的微信抢票大作业终于接近尾声,回首这段时间,真是感慨万千。不是所有的大作业都是微信抢票大作业,能够让人同时体验产品经理、开发工程师、测试工程师、运维工程师四个角色。经过了微信抢票大作业的洗礼,才知道之前对老师上课讲的内容只是一知半解,只有实践才能出真知。
一、搞开发
讲道理,这次大作业的开发工作其实不是很多。因为框架设计的很好,接口也介绍的很详细,只需要按部就班填坑就可以达到基本要求了。
但是既然助教上课都提到了几个优化方案,比如内存型数据库,异步队列等,好奇如我怎能不试呢。于是就开始给自己挖坑,然后再吭哧吭哧填坑。。。缓存机制和异步队列的设计实现文档里都有提及,不再赘述了(毕竟说起来真是好麻烦,一把辛酸泪。。。)
就讲一个很有趣的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的大作业,还是很开心的。
不是所有的大作业都叫微信抢票大作业