11 Nov 2013

半年小结

blog有些时间没有更新,最近工作比较有意思,把精力都投入到了工作,更新blog的事情给耽搁了些。

时间过得真快,掐指一算,距离5月14日加入美团,一晃快也有半年了。半年多前,由于某些原因,离开了服务了近3年的一个初创公司。到云南吸了10天好空气后,承蒙朋友介绍,收到6,7个互联网公司的offer,最后加入美团。半年已经过去,庆幸选择了美团,也非常感谢现在的领导秦亚飞当初说服我加入美团:这半年,有机会干了很多以前没有干过的事,体会了未曾有机会体会过的感受,感觉成长了很多。说来也是缘分,美团是唯一一个自己投简历得到的offer。

在上家公司,专注于web开发,在web技术积累较多:从一个form的生成,到浏览器通过HTTP协议发送请求,Ngnix接到请求,forward给后台程序,到用户验证,到查询数据库,调用服务,生成结果,展示给用户等都涉猎。来到美团,职位为后台服务开发工程师,改变还是蛮大的,从一个全端工程师变成一个专注于后台服务的工程师。

第一件摆在面前的任务:搞定deal排序服务的稳定性。

Deal排序服务的稳定性问题

首页排序服务是一个Thrift服务,由Python编写。负责对首页和筛选页面的deal进行排序。历时近3年,几易维护者。它是一个很重要的服务,因为在首页,很多人盯着。两个问题,期望被解决:

  1. (CPU)流量高峰服务失败率偏高, latency偏高
  2. (RAM)与日剧增的在线单导致它的内存占用变得不可控

对我也是不小的挑战:初来乍到,不熟悉情况;这个程序有点历史,有不少的业务逻辑(水深);Thrift不熟悉,仅仅听说而已;Python也不是很熟。

第一个月在融入新的团队,熟悉业务,学习Python,学习go语言,熟悉Thrift中度过;给Thrift官方提了2个patch,改进它的go代码。

水深浅摸得差不多后,提出通过实现一个透明的proxy,来cache Python的计算结果。于是用go在TCP上实现了一个Thrift的代理,cache结果。3天左右,程序ready。小流量测试,3周后全流量接入。于此同时,挑了2-3个点,协助原程序负责人改了几段代码,内存占用省掉一半。问题解决的同时,还使老code简化了一些。收获最佳新人奖。

比组织预期提前2个月搞定Deal排序服务的稳定性问题。我开始加入推荐team。这时的推荐分流量测试需要别的部门配合,数据训练是定时hive跑,预算结果存Redis,效果观察需要等第二天的报表。在初创公司干了几年的我,对这种节奏不是很习惯。想改变这个事情,用程序员的方式:code。遂提出

  1. 写一个流量分配程序(Router),使我们自己可以控制分发流量
  2. 每个Response,打一个算法tag,主站的同事记到日志里面,并且把日志实时推给我们,方便统计。

流量分发程序:Router

Go语言,在TCP层实现的根据Thrift请求内容,分发流量的服务。主要的功能为流量分配,和负载均衡。它也会”修改”请求,比如查redis,补上用户最近几十天的点击,浏览,购买,消费等行为。由于有些推荐算法不能cover所有请求,也干按配置,调用多个后台服务,揉合为一个结果的事情。配置通过HTTP页面修改,点一个按钮生效,HTTP页面dump运行状态,比如QPS,lantency,出错率等,方便监控。

由于切流量方便,有负载均衡,容错等功能,其它流量也陆续接入:几乎已经支撑了美团主站后台的全部流量:EDM,搜索,搜索suggest,推荐,Deal排序等

实时日志解析,CTR监控,debug工具

实时的CTR,流量,点击等数据,对理解复杂系统的运行状况很有帮助。

  1. 推动日志团队推送实时日志给我的code。他们用Flume,通过Thrift发给来。由于缺少文档,dump binary发现原来用的是Framed CompactProtocol
  2. 解析日志(原来有code,但我想单进程做到能实时解析,并且在未来一段时间不想担心它的性能问题,自己动手,Python也能每秒搞定100k行日志)
  3. 计数(展示,点击等)到Redis,通过HINCRBY。另外一个Python程序提供HTTP服务,读取并处理数据,Javascript可视化
  4. 预处理后,记下有用的字段,dump到硬盘,分时间保存为文件。做为离线计算<用户,行为>矩阵程序的输入
  5. 统计<用户,行为>,放到Redis。Router会读取这部分数据,补在请求里面,这样其它程序接到的请求里面,就有用户的实时行为数据了。

推荐:基于用户的collaborative filtering

这是我第一次做推荐,挑战很大,摸着石头过河,数据和工具也都需要自己去建设。我一边写一些基础的服务和工具,比如日志解析,监控工具,流量分配程序(AB测试),debug工具等,一边不断完善推荐程序。这部分耗消耗了我大部分精力。由于很有意思,下班回家,周末在家都在写这部分code,天天想着这事。大部分code都是我在家完成,在公司的时候主要是线上测试,观察。

我好像摇身一变,从后台服务开发,变成了数据挖掘。不过用的却是优化程序的路子:整体考虑,做实验,找瓶颈,抓大放小,拒绝主观猜测,数据驱动。

推荐程序分为两个部分:1. 离线用户-行为, 行为-用户矩阵生成(Python为主);2. 在线计算(C++)

离线部分由python,shell,C++完成,程序分5步:

  1. 按行解析日志,生成 (user-id, action)对,写到硬盘。user-id为对用户的重编号,1,2,3,4….,重编号可以把用户id映射到int上,并可以节约内存,加快计算速度 (Python)
  2. 按照user-id排序生成的文件。由于生成的文件巨大,需要用到外排,Linux的sort命令很好的搞定了这个问题
  3. 读入排序好的文件,group by user-id,把action编号为int,写入硬盘(binary),格式为(user-id, action-cnt, actions),生成用户到对应的行为的矩阵 (Python)
  4. 把用户行为矩阵倒转,生成行为=>用户ids的矩阵 (C++)。同样存为自定义的binary格式。
  5. Query DB,通过Thrift的BianryProtocol Dump Deal数据到文件。这样在线程序可以以非常快的速度加载他们到内存。

生成的数据规模:6M用户,3M行为,116M <用户,行为>对。耗时25分钟。

在线部分由C++完成

  1. load生成的两个文件:mmap到内存,在上面用自己实现的hashmap建Index。自己实现hashmap可以做到比std::unordered_map快2到5倍。耗时1.5s
  2. load Deal息,商圈等其它数据到内存,建好索引,耗时1.5s
  3. 3s后,开始在线服务。来一个请求,用户近期行为已经被Router补全,它就专心计算。通过(行为,用户)索引快速找出有和他有至少一个相同行为的用户们,计算和他之间的相似度,找出top n个最相似用户
  4. 计算出top n用户的行为,按照相似性加权排序。按照业务规则,去掉不靠谱的(如过期单),过一遍多样性规则,取出前n个,返回给用户,总耗时20ms左右

依靠C++强悍的性能和我的精心设计,完成了能对用户行为做出实时反应的基于用户的协同过滤。很多论文在比我数据量小不少的情况下,说UserCF计算量太大。还好我没有听他们的。应用到首页排序,CTR显示,用户是买账的。

这个程序,可能还会折腾我一段时间。

小结

这半年,过得很充实,以较高的效率,干了不少事情,学习了很多东西。

  1. 熟悉了Python,搞定一个Python程序的性能,还用它写了几个工具。哈哈,在此之前,主要以写Java,Clojure,Javascript为生的。
  2. 学习go,并写了几个线上服务,支撑了美团主站后台流量。嗯,组里另一个同事也开始用go做一些数据挖掘的模型训练,体验还不错。
  3. 由以前的全端工程师,转为后台服务开发工程师,后又尝试做个“外行”的数据挖掘。oh,yeah
  4. 用C++写线上服务,写起来是麻烦了点,跑起来那是杠杠的快
  5. 到了一个很有活力的公司,公司的交易额月月涨,而我做的事情,也会影响交易额,虽然基本上一个人单干,这必须感谢Leader吴瑜华的信任和支持。
  6. 熟悉了Thrift,一个非常好的工具;给官方提交了Patch
  7. 教老婆学习Web Development,她硬是把udacity的《Web Development》听了一遍,做了一遍
  8. 读了几本书,技术类的《推荐系统实践》,《数学之美》,还有其它非技术类的。
  9. 每天晚,和老婆一边吃饭一边听《财经郎眼》,或者《晓说》,《杨澜访谈录》,《罗辑思维》,etc
  10. 仰望大师,听了Peter Norvig的《Design of Computer Programs》
  11. 周末开始跑步;买了个空气净化器,24小时全开。
  12. 从Emacs转到Vim了。没办法,线上写code,还是Vim比较好
  13. http-kit,在github上有600多个星星了,多了300多个吧,merge了几个pull request,有17个Contributors了
blog comments powered by Disqus