ingf.github.io icon indicating copy to clipboard operation
ingf.github.io copied to clipboard

微信演出票前后端分离的设计和实践

Open ingf opened this issue 8 years ago • 0 comments

2004 年,Gmail 像一个妙曼的女子来到人间,惊艳了工程师,温柔了用户。随即 Ajax 被提出来,这意味着可以在不重新加载整个网页的情况下,对网页局部进行更新。尤其是近几年,前后端分离的技术实现,犹如殷十娘神剑出鞘,彻底切断了前后端程序员的联系,只剩下了 HTTP,从此前端后台相亲相爱。

微信演出票近两个月恰好完成了一次前后端分离的实践,前端采用 React & Redux 架构实现单页面应用,后台使用 Java & GO,前后端基于 RESTful 风格的 API 通信。之所以要前后端分离,肯定是当前的架构满足不了当前的需求,我们先来看看当前的架构设计。

0b122525-7474-47f9-bcc7-208db76d670b

这是一个比较常见的 WEB 系统层级架构设计模型,微信演出票也是采用这个架构。蓝色表示前端需要做的事情,绿色表示后台需要做的事情,中间有条蓝色的线想要分隔开来,但是并不能真正分隔开,因为前端的视图模板还是需要依赖后台的模板环境。分析现有的开发流程,可以发现当中存在的协作、沟通、效率上存在的不少问题:

  • 后端套页面需要了解 HTML,之后仍然需要前端进行确认
  • 而前端实现 View 层,需要熟悉服务端语言和架构
  • 前端依赖服务端开发环境,调试、维护成本突出
  • 页面上的强耦合缺少灵活性,无法应对业务、需求的快速迭代
  • 前后端职责不清晰,沟通成本高,且容易引入问题
  • 工程化实践较为困难
5eced57a-9d34-4500-bb53-4b2c8401c06d

就像这张图,前后端杂糅在一起,蛋疼···

当然,这些问题已经存在很久了,所以也不是很大的问题(虽然这么说有点不太负责任)因此我们不得不找点其他原因:

  • 性能太低了,而且每次热门演唱会的时候,流量一进来,网站 QPS 上不去,很容易堵死了,领导那里不好交代呀😂
  • 数据接口那一层是 PHP 实现的,但是 PHPer 都准备离职了,演出票团队里只剩下总监会 PHP 了,总不能让总监来写业务代码吧😂😂

那没办法了,所以前后端分离提上议程。

微信演出票:就是在微信里面卖演出票,主要有以下页面:列表页、分类页、搜索页、详情页、场次页、选座页、支付页以及订单相关页面。为了保证进度,还要顾及到产品迭代,我们制定了以下策略:

新老项目共存,老项目尽量不修改,把人力物力集中在重构上面,重构分两期完成:

一期重构列表页、分类页、搜索页等几个不涉及到购买流程的页面。因为团队成员和技术架构都有很大的变化,再加上之前的代码实在是不忍直视,那句话怎么说来着,”Climb the shit mountain”。所以一期先完成一些简单的工作,也当个测试吧,测试一下技术架构是否可行,也测试一下技术团队是否能 hold 住。二期重构购票流程页面,哦,对,再简单说一下技术架构。

后端主要是 PHP、Java,重构后是 Java、GO。 前端主要是 jQuery、自定义的框架,重构后是 React、Redux、react-router、Webpack 等,传说中的 React 全家桶。

整体设计完成之后,就是定义接口了,后端提供接口文档和接口示例,然后就吭哧吭哧敲代码了。三周之后之后,一期如期上线,这非常鼓舞士气,又过了不到两个月,二期延期4天上线,但是在这之前,我们还完成了 QQ 演出票的从无到有,也弄到线上去了,开发速度非常之快。

前后端分离好处很明显:

  • 前后端职责很清晰,前端工作在浏览器端,后端工作在服务端。清晰的分工,可以让开发并行,测试数据的模拟不难,前端可以本地开发。后端则可以专注于业务逻辑的处理,输出 RESTful 等接口
  • 前后端开发的复杂度可控,前端关注前端的 UI 呈现,并实现前端性能优化,而不需要后台哥哥操心,后端则关注业务逻辑本身,提高并发。我们设想中的前后端分离是有搞头的,不仅仅是为了分离而分,而是为了更好的专精、多能、协作、管理而分离
  • 部署相对独立,产品体验可以快速改进
  • 只要 API 规划得得当,一套 API 可以支持多个客户端的业务体系,而前端行有余力实现 A/B test

以上,作为一个有态度的技术团队,肯定不止满足于此。

前后端分离,很多团队都做过这样的工作,其实分离本身并不是很复杂,比较有意义的是整套前后端开发测试上线的架构设计,怎样才能真正提高前端、后台和测试各个团队的效率。

前面已经提到前后端解耦,通过 API 交互,所以前端开发不依赖后端环境,只需要 API 数据即可。因此我们设计了4个环境,开发、测试、预上线和线上环境,每个环境都会有配置各自的 API 地址:

"API": {
    "start": "http://api.show.wepiao.com",
    "test": "http://testapi.show.wepiao.com",
    "pre": "http://preapi.show.wepiao.com",
    "release": "http://api.show.wepiao.com",
}

这个配置会保存在一个配置文件中,每次启动服务或者构建的时候,会根据当前环境,选择相应的 API 地址,这样避免了硬编码,不需要每次上线还要去修改 API 地址。

先看开发环境,在本地起一个服务,提供 HTML 和 JS、CSS 等静态资源,API 地址则对应 start,如果不需要接口开发的话,这个地址可以配置成线上 API 的地址,如果后台接口也需要开发的话,那么可以配置成后台开发同学的服务地址,如果后台接口还没有开发完成,那么就需要前端 Mock 数据了,非常灵活方便。前端 Mock 有很多现有的解决方案,本身也不是很复杂,无需赘言。

测试环境、预上线环境和线上,会将所有的资源编译好,copy 到源服务器即可。这三个环境都是从指定的 API 地址获取数据。为了方便开发测试,我们实现了这样一个页面,想去哪里就点哪里(因为团队到达一定规模了,所以我们新建了三个测试环境,各位可以根据团队规模选择测试环境数量)

2 pic

其核心就是所有环境访问域名一致,都是 http://wechat.show.wepiao.com ,(这样也方便在测试环境测试分享、支付等对域名有限制的项目,甚至可以在4G 网络下访问测试环境)然后通过一个 cookie(cookie 的 key 为 __env__) 标识需要访问的环境,上面那个页面就是来配置这个 cookie 的,当选择了一个环境后,会将该环境对应的值种到 cookie __env__ 中,Nginx 通过解析这个 cookie,然后把请求 proxy_pass 到对应的那个环境去,大概配置如下:

upstream release {
    server 10.250.168.1;
}
upstream pre {
    server 10.250.168.2;
}
upstream test1 {
    server 10.250.168.3;
}
upstream test2 {
    server 10.250.168.4;
}
upstream test3 {
    server 10.250.168.5;
}

server {
    listen 80;
    set $env $cookie___env__ ;
    if ( $cookie___env__ !~ "\w+" ) {
       set $env release;
    }

    location  / {
        proxy_pass http://$env;
    }
}

这段 Nginx 的配置的意思就是,默认是 proxy_pass 到线上环境,因为线上环境真实用户在访问的时候,是不会有这个 cookie 的。只有开发者和测试同学,才会访问到这个页面,选择环境,然后种下 cookie。此时,Nginx 收到请求后,会读取 cookie 中 __env__ 的值,根据这个值将请求 proxy_pass 到对应的环境去。这个Cookie,也可以用于A/B test,根据此Cookie,判断用户是进入A还是进入B。

这种情况下,4G 和外网也可以访问到测试环境,这回带来一些便利性,但同时也存在安全隐患。所以需要做一下过滤,如果请求携带了此Cookie 的话,我们仍然需要再校验一次,判断该请求是否是测试人员发起的。所以需要判断客户端的 IP 是否是公司出口地址,或者维护一个测试人员的 uid 白名单,只要满足其中一个条件,就可以认为是测试人员发起的请求,那就允许他访问测试环境,否则 proxy_pass 到线上环境。

到目前为止,开发测试环境基本搭建完毕,前后端解耦,前端开发测试不依赖后台环境,仅仅通过 RESTful API 通信,后端开发则专注在业务研发和接口输出,测试也能很方便的访问测试环境,团队分工协作、各司其职,还怕效率上不去?但搭建好环境仅仅只是开始,接下来的 A/B test 和 Growth hacking,我们才能真正的打磨我们的产品,为用户提供更好的用户体验。这只是一个大概的思路,具体的一些细节实现,根据微信后台反馈情况吧,如果有需要的话,我们后面在另起一篇详细描述。

ingf avatar May 07 '16 16:05 ingf