本次大赛代码Java版本: https://github.com/WhiteBlue/eleme-hackathon

回想起自己的黑历史时代,其实关于”性能优化有很多误解,很多都是我们自以为的”性能提升了”:

“去掉了几个循环”,又或者是”我把XX框架换成了XX框架,比之前快多了”。(感觉说起来好羞耻233)。

题目

好歹也是小结,还是提一提参加的这次比赛。简单来说就是:”对方提供服务器,实现一个符合要求的api,要求并发量尽量高”

可以参考这里的 文档

简要分析

提供的是三台server部署应用+单台redis+单台Mysql,初始数据在Mysql。这样看来,首先要保证三台的一致性,其次就是用好redis了。

先贴一些简要的数据:

  • 针对这次的情况而言,Mysql最大连接数设定为800(三台共享)。
  • Redis连接数不作限制,那么按照理论就连接数可以达到redis服务器内存的极限。
  • Nosql相比Mysql的优势不用多说,Redis又是其中的佼佼者,用好redis是本次的关键。

优化点:

  • Connection pool: 连接池化,预先准备空闲连接,缩短wait
  • Redis lua脚本: redis中能保证多条命令原子性执行的方法。使用load+sha方式执行效率颇高。
  • Redis Pipeline: 多条Redis操作一次性发出,减少io量
  • Golang [go]: Golang的异步执行方法,充分利用可在业务逻辑跑完之前返回请求

黑科技

事后和其他选手交流得,也是我当时没有想到的部分….

  • 333大法,每种食物1000库存,除以三台服务器。那么就代表每台服务器的前333个订单都是肯定成功的。
  • 本地登录大法,token按照一定规则生成,不存进redis,每次只校验合法性
  • 阻塞大法,奇数秒阻塞请求,偶数秒释放,有效提高请求峰值(针对比赛规定的hack)

个人思路

第一阶段[Java]

其实一开始的解决方案是十分传统的,拒绝使用各类黑科技。

流程:

  • [登陆],用md5(uuid)生成token扔进redis并设置expire_time。

  • [创建购物篮],同样方法生成bracket_id后扔进redis中的bracket_buffer。

  • [添加食物],向名为[bracket_id]的hashmap中添加对应的食物id和数量(redis视为不存在自动新建)。

  • [下单],开启mysql事务,取出redis中bracket_id对应hashmap,循环数量减去,遇到数量不足会滚,数量无误提交,delete掉bracket_buffer中的bracket_id,在Order表中新建订单。

结果:
因为解决方式传统,一天内完成,成功过测试1000分左右(同比最高1300)。

第二阶段[Java]

官方调整比赛制度,提高并发量。各学校各路队伍提交代码,成功被各路名校组合踢出前十,压力山大。所以把订单逻辑完全迁移到redis,完全脱离Mysql。

流程(只写出有变化的):

  • [下单],使用lua脚本对food_stock进行原子操作,确保不会超售。delete掉bracket_buffer中的bracket_id,在order_buffer中添加bracket_id。(下单操作其实只是把bracket_id移动到了order_buffer)

结果:
使用lua操作成功使效率上升一个数量级。结果3500分(同比最高4000)

第三阶段[Go]

各路大佬们的提交让分数又上升了一个数量级(5000),当时认为Jetty内置的性能封顶,不能进一步上升。故使用Golang重构代码。

流程和结构无明显变化,使用gin作为router。

结果:
时间不足,仓促完成,惨败。最终分数4400~4600分,(最高4900)

总结

虽说到最后还是太矜持,没有进一步使用黑科技,不过这点还是没有什么遗憾的。个人的不足而言,时间仓促有很多方案没有来得及去尝试(还是自己作死临时换语言所致)…还是略微遗憾的

(顺带吐槽饿了么你给的会员卡在学校用不了orz)