背景
  新功能发布到测试环境,验证 ok,发布到正式环境,因为正式环境上还没有数据,用了假数据验证,也 ok(只能说前端的功能 ok)。第二天,产品接到客户,需要用这个新上线的功能,我有点忐忑,因为用户量比较大,涉及到的 money 也比较多。客户的数据添加后,果然出事了 ORZ,正式环境访问不到数据,于是周日回到工位,打开我的代码编辑器...
  问题
  我发现这个错是因为我注释了一行代码引起的,我为什么要注释这行代码呢?原因如下:
  有个服务叫 L5,它是做负载均衡用的,主要特征如下:
  1、名字服务:以SID(由模块ID和命令字ID组成)为关键字,通过SID取得真正的IP和端口地址,使得IP和端口配置对调用者透明,运维变更配置更方便;
  2、负载均衡:以请求成功率和请求延时这两个关键指标进行动态权重计算,动态均衡各个被调服务器的负载,达到较好的整体服务质量;
  3、故障容错:迅速自动屏蔽错误率高或有故障的机器,并进行适时探测,待故障恢复后自动恢复;
  4、过载保护:实现对单台机器或者整个模块机器的过载保护能力,防止雪崩现象。
  参考
  比较抽象,简单说一下我的理解:
  前端请求 node 端,node 端请求提供数据的服务端,提供数据的服务端有很多个,它们都装着相同的数据,是怕哪个机子负载过重,或者 down 掉了。node 端怎么才能知道哪个机器负载、错误率比较低呢?这个要靠 l5 了,假如现在有三台提供数据的机器:10.xxx.xxx.16、10.xxx.xxx.17、10.xxx.xxx.18,公司这个 l5 系统可以为这三台机器申请 l5 的配置 id(实际上要比这个复杂一点,这里屏蔽了具体的业务逻辑),比如现在申请的 id 是 00001,那么 node 去请求数据的时候是这样的:
  request({
  type: 'get',
  url: 'http://10.xxx.xxx.xxx:8080/getData'
  l5config: 00001,
  }).done(function(){
  // ..
  })
  因为有了这个 l5config,那么 10.xxx.xxx.xxx:8080 这个 ip 不重要了,任意的 ip、域名、端口都可以,实际上会根据 l5 的配置去找 16、17、18 这三台,具体请求哪一台,l5 的系统会去找负载低、错误率低的机器。
  这是我简单理解的 l5
  问题详情:
  前端去请求 node 端,node 端会被部署在开发环境、测试环境、正式环境上,正式环境通过 l5 去请求了 16、17、18 这三台机获取数据,但是 16、17、18 这三台机的数据都是正式的,平时测试同学一般操作不了这里的数据,所以出现了 10.xxx.xxx.55 这台机器,用来提供测试环境的数据。我当时是这么干的:
  // 测试环境
  request({
  type: 'get',
  url: 'http://10.xxx.xxx.xxx:55/getData'
  //l5config: 00001,
  }).done(function(){
  // ..
  })
  测试环境里我注释了 l5 的配置,因为 55 这台机器没有申请一个 l5 的配置 id,我直接通过 url 的方式去请求了,想着等到代码要发布到正式环境时,我把注释取消掉,结果发布时忘了取消注释,那么等到这份代码被发布到正式环境,不是去请求 16、17、18 这三台提供正式数据的机器了,而是请求了 55 这台提供测试数据的机器,结果客户的数据找不到了。
  解决办法
  当时临时取消了注释,因为是周日,与 leader 沟通后,走了免测发布流程,暂时解决了问题。但是这样并不完美,leader 说发布文件不要依赖于文件的修改,要从机制上杜绝,要依赖于环境变量。这里我们要说说 NODE_ENV 这个环境变量。
  NODE_ENV
  这个变量的起源于 express,express 生成的 app.js 中通常都会有这么一段语句:
  if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
  message: err.message,
  error: err
  });
  });
  }
  这里的 app.get('env') 是访问了 process.env.NODE_ENV,这个 NODE_ENV 并不是一个特殊的变量,默认为 undefined,据说是 express 盛行之后,这个变量便盛行了,process.env 中还可以访问其它的环境变量,比如 JAVA_HOME、CLASSPATH、Path 等等。在 windows、linux 中,设置环境变量指的也是这个。
  来看一下现在的需求:开发环境、测试环境、正式环境对应有三台机器,在这三台机器中,需要各自有个环境变量来标识哪台机器是哪个环境,然后根据环境变量引入不同的配置文件。因为目前的项目为 ALPHA,所以定义了一个叫 ALPHA_NODE_ENV 的环境变量,而没有使用 NODE_ENV 这个变量(担心这个变量太火被别的业务使用了,然后被我一改,其它业务瘫痪了)
  设置 ALPHA_NODE_ENV
  这里为开发机、测试机配置 ALPHA_NODE_ENV 环境变量为 development、testing,undefined 视为正式环境
  linux 中的环境变量是这样设置的:
  在根目录下,进入 etc 目录,修改 profile 文件,在里面增加一条语句:export ALPHA_NODE_EVN=development,测试环境的设置为 testing。修改完成后,在命令行中输入 source ./profile, 这样修改的环境变量生效了。点这里查看参考文档,注意如果是通过命令行来设置,而不改变文件的话,那么只在当前命令窗口生效,新开的命令窗口不生效。
  修改环境变量的时候,因为用的是 vim,这里附上一篇使用文档
  相应的文件配置
  这里新增了三个文件:
  1、developmentConfig.js
  2、testingConfig.js
  3、productConfig.js
  三个文件配置不一样,比如:
  // developmentConfig.js
  this.l5 = {
  '10.xxx.xxx.16': undefined
  }
  // testingConfig.js
  this.l5 = {
  '10.xxx.xxx.16': undefined
  }
  // productionConfig.js
  this.l5 = {
  '10.xxx.xxx.16': 00001
  }
  有一个总的 config.js:
  // 区分环境配置
  var alphaNodeEnv = process.env.ALPHA_NODE_ENV;
  this.alphaConfig = require("./"+ (alphaNodeEnv? alphaNodeEnv : "production") + "Config.js");
  调用的时候:
  var config = require('./config.js');
  request({
  type: 'get',
  url: 'http://10.xxx.xxx.xxx:8080/getData'
  l5config: config.alphaConfig.l5['10.xxx.xxx.16'], // 这样在开发环境和测试环境里是 undefined,在正式环境会通过 l5 请求数据。
  }).done(function(){
  // ..
  })
  这样写的好处有:
  1、可能不同的接口对应的 l5 配置不一样,那么这里的 config.alphaConfig.l5['10.xxx.xxx.16'] 中的 10.xxx.xxx.16 相当于一个对应特定 l5 的 key,如果不同的 l5 可以在文件中增加配置了,比如多了一个请求天气的接口,也需要通过 l5,那么:
  // productionConfig.js
  this.l5 = {
  '10.xxx.xxx.16': 00001,
  '10.xxx.xxx.66': 00002, // 其实这里的 key 是什么都无所谓了,只要能被调用到行
  }
  2、新增一个环境,比如预发布环境,那么只需要在预发布环境设置环境变量 ALPHA_NODE_ENV 为 preDeployment,增加 preDeployment.js 即可,却不需要改动原有的代码。
  总结
  发布文件不要依赖于环境的修改,可以依赖于环境选择不同的配置,发布后要全面地验证功能,不能掉以轻心。