姚东旭 美芽 CTO
美芽作为一个美妆视频社区,在 2015 年 2 月份上线之后,极受女性用户的欢迎。此次美芽 CTO 姚东旭在 UPYUN Open Talk No.4 厦门场,分享了美芽从零到一的整个产品生命周期,带给创业公司很多值得借鉴的地方。
美芽在萌芽过程中,同很多初创产品一样,有常见的三个特点:
- 较小的用户规模。虽然偶有例外,譬如“足迹”的爆发式增长,传言 DAU 有 300 万,而一般的产品不会有这种运气;
- 快速的需求变化。初创产品蕴含很多的不确定性。无法同时兼顾产品细节,也无法在较短时间内将功能极尽完善,因而通常情况下会尽快推向市场,以验证想法的正确性;
- 尚未完善的团队。互联网创业公司对于技术人员的强烈需求,与市场的供不应求,有着难以消解的矛盾。加上创业公司无法高价请人,通常情况下的应对之策只能是一人多用。
面对这样的境况,相比较代码的执行效率或线上接口的响应效率,更为注重开发效率是美芽的应对之策。而影响开发效率主要包括三方面:一是沟通成本,以客户端和后端同学的沟通为主;二是重复轮子,已经有成熟的开源实现了,却用自己的代码重复实现了;三是过度设计。
沟通成本:协议 / 流程
1)协议
在协议方面,很多项目在设计前后端交互协议时非常随意,比如 /Post/Show/xx ,实现起来也没有统一标准。
从请求无法明确看出该以哪种方式获取数据,客户端同学必须询问服务端,或者在文档里清晰的描述出来。特别是对于新加入的开发者,需要一个熟悉的过程。比如删除一个帖子,使用 DELETE 或 GET 方法,也并没有统一的标准,针对这个问题,美芽是选用 REST风格。
REST可以翻译为表现层状态转化,这里应该还有个主语,那就是 Resource(资源),特指网络上的具体信息,比如帖子和评论。美芽在设计客户端请求接口的时候都是针对一个资源做操作。REST定义了一个对资源操作的标准,使用 HTTP 的四个动词表示对资源的操作,GET,POST,DELETE,PUT。以用户帖子为例:
- GET /posts 获取帖子列表,
- GET /posts/id 获取编号为 id 帖子的内容,
- POST /posts 发布帖子,
- DELETE /posts/id 删除编号为 id 的帖子,
- PUT /posts/id 修改编号为 id 的帖子。
无论新加任何功能模块,包括评论或点赞等都执行这一标准。在开发客户端的过程中,就避免了添加新功能时对接口做其他的约定。
以模型 Post 举例,接口可以变成在获取帖子时,将 id 传过去,返回来就是需要的对象。帖子在后端或数据库中的字段,在客户端对应的就是 Post 模型的属性。
不再需要有文档去介绍它,只需要自动生成一份数据库字典就把所有东西解决了,少了很多中间成本。在客户端要创建帖子就变得极其简单,客户端用定义好的方法请求到后端,服务端收到请求后就往数据库里新建一个资源。美芽在客户端接口设计的时候都是针对资源操作。
总结来说,接口设计面向资源、而非功能。在客户端有各种功能和操作,在接口上面肯定不能所有的东西都按客户端的要求设计,这样会增加很大成本。我们在接口设计上只关注数据,至于怎么操作是客户端的事情,将数据和表现分离。
2)流程
开发流程,刚出道的时候由于流程不完善,会出现各种问题,比如刚上线的代码或刚修正了一个 Bug随意发布,发布上去后又发现有问题。
针对这点,美芽制定了本地环境 - 开发环境 - 预发环境 - 生产环境四个环节。每个人在本地环境完成后将代码提交,会自动发布到集成开发环境;预发环境的存在,是想在上线之前,在同样的生产环境下,做最终的确认,之后再更新到用户环境中。
代码管理的流程,从仓库来说,首先有 master 和 dev 两个长期分支,master 是主干分支,dev 是开发的集成环境 。当要修改 bug或者开发新特性时会新建一个新分支,问题修复后需要将这个分支 merge 到 dev 分支,需要上线时就合并到 master,这是一个特定的生命周期。
代码在推向预发环境前要经过其他成员的审核,审核之后才会合并。合并后使用 git hooks 工具,自动将代码更新到预发环境,在App客户端 内部会做一些开关控制它用哪个环境。
在整个过程中,对所有同学都是透明的,我们使用了 Slack 作为消息枢纽,Slack 订阅了 Github 通知事件。团队里面的每一位成员,只要关注项目都会看到所有的动态,包括特性上线、Bug修复等。还有一个工具 fabric, 这是一个 异常上报 组件,客户端集成 fabric sdk后就可以将异常上报到 fabric,fabric再通知到 Slack, 团队成员就能在 slack 中看到客户端运行动态了。
重复轮子:框架 / 类库 / 开源软件
先看框架。其一,为了保证开发效率,希望框架使用起来能够足够简单,新的团队成员也能够很好地适应。关于颇受争议的 ORM 需要观察它的适用情况,在用户规模尚小、数据量较小时,ORM 能够快速实现业务需求,而当量级渐长就该果断舍弃;其二,框架必须保证功能强大,以尽可能减少开发量。
基于以上的综合考虑,美芽选择选择使用了以下PHP框架和工具:
1、PHP 采用 Laravel 框架,框架在设计的时候支持 RESTful,在代码层面当新加一个路由资源的时候,新加一个 Controller 就可以解决了。
2、DB migration,这个是修改数据表结构的一个工具。刚刚提及的需求会较不稳定,经常会遇到字段的增减,而此类操作如果只是手动修改,并需要同步到其它环境,将极其不利于管理。DB migration 这个机制是用要代码表示数据库字段的增删。
譬如在增设字段时,新增一个类表示对数据表的操作,其中,up 方法就是执行修改,down 是回滚修改。等到上线时,就只需更新 PHP代码,这就统一了表结构的统一操作,所有的东西都用代码呈现,呈现又是文本,可以进行版本管理。人员对数据库的所有操作都有纪录。
3、Command。比如,开发一个命令行工具,让用户输入一个名字,根据名字调用信息,需要包含输入参数,和选项参数以及输出。Command 会将这些东西做包装,不需要开发除了产品业务逻辑之外的东西。
4、Queue,进行很多操作时不能同步等待,比如推送一条消息,发布一条评论让对方收到消息通知,肯定不能在请求里同步处理,而是要放到队列里异步执行,再推送到苹果的服务器最后推送到用户。这个例子是项目中用的,我们添加队列的时候只需在服务器上执行一条命令打开这个队列。
5、 Tinker,这也是命令行的工具,可以做到在执行这条命令的时候,把整个项目环境加载进去。而且是一个交互式的操作,比如要取一个对象就可以直接查询,把信息打出来。相比较调试代码时用url或者添加各种参数,这种处理方式会非常方便。
6、还有一个是对 Log 的处理,使用时只需要配置一下,在当前环境下需要打的 Log 级别,在代码中直接使用即可,或者在生产中将这类信息都保留。
上面使用的每一项都不是太复杂,或者难度特别大,自己做也都可以。但在资源有限的情况下,初创团队更重要的是投入更大的时间和精力在业务上。这个框架是集成了开发中一些通用的内容,减少了很多额外的开发工作。
虽然看上去 Laravel 框架非常美好,但美好都是有代价的,Laravel 框架非常的慢。因为它提供了各种封装、加载了很多东西,导致一个空的接口响应时间也需要几十毫秒。所以更适合量不大的初创产品。但与此同时,美芽也正在积极解决效率问题。
再来看 iOS 类库,REST 只是定义了标准,而没有提供完整的工具来实现,通常服务端返回的是 JSON 格式数据,客户端将收到的 JSON 格式转换成 NSDictionary,再由 NSDictionary 转换成 NSObject, 这可能对应到客户端的一个 Model 或者一个具体对象;客户端发请求到服务端时,就需要将 NSObject 转化成 NSDictionary, 再转换成 JSON 格式,然后发送到服务端。这个过程非常烦锁,而且需要对每个类都实现一套 encode decode 的逻辑,而实质上我们关心的只是 Value 而已。我们引进了 Mantle 库,这个库会帮你实现这一系列封包解包的过程。
再加上 TMCache AFNetworking 这两个库(前者用来实现快速的对象缓存,而后者基本上每个 iOS 程序员都熟悉的一个网络框架),就能实现整个产品的 Model 层。
最后介绍一套开源软件 ELK(Elasticsearch Logstash Kibana),这一套软件主要用来处理日志,进而分析问题,其中Elasticsearch 负责存储+搜索、Logstash 负责搜集、Kibana 负责展示,通过 ELK我们可以随时观察事件的发生,并作出及时妥当的处理。
比如客户端访问服务器时返回系统繁忙这些错误,或者是帖子加载过慢这些问题都可以通过这一套软件展示出来。虽然看似复杂,但实际操作却极其简单,现在我们主要分析程序内部日志,Nginx access log 和 Nginx error log。
这些日志会先通过 logstash 采集,存储到 ES 上,再通过 Kibana 展示出来。
同时,这一套工具还有对运营数据分析的支持。接入这套工具,美芽实现了各种业务的简单统计,包括注册统计、地区分析、时间分析、用户活跃时间等等。接入 ELK 也是非常简单的,只需将原始数据导入,调用一些查询语句就可以展现,只需要调用一下接口、填入统计数据即可,业务上不需要做什么操作和修改,更无需修改数据库和代码主逻辑。
美芽 整体宏观架构如下图所示:美芽用户通过 APP 或者PC 版跟 Nginx 交互再访问 API,这里所产生的日志都会写文件,在文件写入之后就会有 Logstash Agent 会翻译成固定的格式存到 Elsearch, 同时 API 内部也会将日志会先输出到消息队列中,由消息队列再异步输出到 Logstash Agent,Dashboard 会调用 kibana 的接口,以运营能接受的形式展现出来,如下图:
会管理线上服务器的客户端开发,才是一名好运维。
特别介绍下美芽的运维,尽一人多用之能事,运维也兼做开发。在代码的上线流程中并不需要运维的参与,但软件安装、配置更新就需要运维处理。美芽的运维,主要是通过 Saltstack,用 YAML 来描述服务器的状态。YAML 作为一种比较简洁的数据格式,是纯文本的,也就是说服务器的所有状态都可以进行版本管理,服务器的状态可根据每个版本里面的配置文件体现出来服务器当前是什么样的状态。
看下面一个简单的示例,下图是一个 logstash 的 saltstack 配置:
首先是有一个 master 节点,这是一个中心节点,然后有很多的 minion 节点需要被更新。
执行以下一条命令就可以将所有的 minion 更新了,命令有两个参数,一个是 prod,是一个分组的概念,比如五台服务器,把五台服务器都放到分组里面;后面一个参数 production, 代表生产环境。操作时,前半部分是针对五台服务器做的工作。