借天池中间件复赛谈NIO

比赛的简单总结

参加天池是被裴师兄安利的,这个很重要.在我眼中,裴博代码一直是写得飞起的那种,就是一边用B站放着billboard榜单,一边在那啪啪啪敲着代码,这看起来绝对很酷.所以我内心是崇拜他的,至少在技术上,他很刻苦,表现在很多方面.在此不一一赘述,因为我并不想在这开表彰会.他告诉我参加这个比赛会收获很多,我当然无条件相信,他当时参加的是机器学习相关的比赛.上半年正好有一场预测音乐播放量的比赛,我便屁颠颠地参加,对机器学习一窍不通,于是便慢慢学,特征,建立模型,训练.耗时一个半月,纯模型效果并不好,于是加了一些规则,后来还用了股票里经常遇到的时序模型,嗯,挺高大上的.这个比赛的确学习了很多,但总感觉浮于表面,略显浮夸,写代码也不是很多,更多的参赛选手都是搞机器学习和数学统计的,我并不在行.

这时候一个新比赛来华科路演,跑过去听了一听,意料之中,没什么太大收获,但是这个比赛引起了我极大的兴趣.首先它涉及系统设计,代码量跟机器学习类的比赛肯定不是一个级别,初赛和复赛考察内容很不一样.我觉得这种比赛还是很有趣的,但可能比较累,因为代码一多,因此而诞生的bug也会多起来,这点不太像机器学习的比赛,大部分时间是在等待和思考策略,动手的时间并不多.

中间件比赛分为初赛和复赛.初赛是实现一个实时统计双十一交易数据的系统,用到了三个组件jstorm,rocketmq和tair.都是阿里自家的产品,但都可以在原先的开源平台中找到原型,分别是storm,rabbitmq和redis.用法和api也大同小异,这个题目考察的主要还是熟悉这三个系统的使用,基本程序能正常运行跑出结果就能进前100了,如果想要跑到更好的成绩就需要想到更好的缓存策略.但你又不能把所有数据常驻内存,这样程序就会挂掉,而且这也不符合流计算的思想,流计算就应该是将数据想象成一段水流(不免让人想起破坏之王里的断水流大师兄),只不过水流会途径一些地方经过加工,如果水流囤积,那必然引起溃堤等风险.我的基本策略是jstorm的bolt主线程处理数据并统计,再开一个独立的线程用于数据的同步然后发送到下一个bolt.对我来说jstorm和storm最不同的一点就是:jstorm的Spout nextTuple和ack/fail运行在不同线程,这样可以防止CPU空转.storm的bolt和spout组件构成一个topology,一个设计优雅的拓扑图也是可以大大提高程序性能的.

关于复赛,设计一个订单查询系统,不在现有的开源平台上写应用,而是纯用java标准库,这个代码量可想而知.但后来赛方说可以用一些简单的轮子,比如基于磁盘的map,或者B+树什么的,纯用别人的开源数据库肯定是不行的.但比赛核心并不是这个,如果是这样功能大家都可以做出来,最重要的还是策略和优化.起先在github上找基于磁盘的B+树轮子,找了半天找到一个外国小伙写得轮子,很简洁.拿来一用,发现一个bug还有些许缺陷,后来还提交了commit给他,他也很客气merge了,不小的成就感,不得不说.

简单说一下程序策略:

  • 构造:采用多线程分别对各个订单文件建立索引,索引内容为记录在文中的偏移量和该记录长度的结合体,起初先合并所有的订单文件再建立索引,后来发现合并与否对查询速度没有太大影响,由于没有进行多次测试,这个结论可能不成立。建立索引的方式起初是一部分B+树,一部分采用基于磁盘的map,后期由于B+树建立索引较慢,经常一小时内不能建完,后期全部改为基于disk的map。比赛后期性能改进包括将卖家和商品信息的索引全部放置在内存中,因为这两部分信息的索引大小之和并不大;将卖家与商品信息出现的字段缓存起来,若后期查询中字段不在其中,则省略了部分查询的步骤。
  • 查询:针对每个索引上层封装为一个DB,其中包含多个table对象,对应各个小索引文件。在构造索引期间对每个原记录文件和索引文件分别映射为一个MappedByteBuffer对象,有的记录长度大于int最大值,对这种文件进行分段映射多个mappedbytebuffer对象,具体查询请求时,在bytebuffer中读取信息。查询支持完全并发,但是由于每个索引文件都对应了一个table所以查询时需要遍历所有table最后返回结果,这种策略较为愚蠢。

关于NIO

这次比赛算是很好的一次理解和使用NIO的机会,借这次简单一下介绍下这方面.
//未完待续