nginScript系列:使用nginScript将客户端重定向到新服务器

栏目: 服务器 · 发布时间: 6年前

内容简介:这是nginScript系列文章的第二篇,将介绍如何使用nginScript将客户端循序渐进地重定向到新的服务器。查看第一篇“nginScript简介”。nginScript的一个关键优势在于它提供了读取和设置NGINX配置变量的能力。变量可以用于自定义路由规则。也就是说,我们可以使用JavaScript来实现复杂的功能,这些功能可以直接对请求的处理产生影响。在这篇文章里,我们将介绍如何使用nginScript来实现优雅的服务器间切换。我们不打算进行“一次性”的切换,而是定义了一个时间窗口,客户端在事件窗

这是nginScript系列文章的第二篇,将介绍如何使用nginScript将客户端循序渐进地重定向到新的服务器。查看第一篇“nginScript简介”。

nginScript的一个关键优势在于它提供了读取和设置NGINX配置变量的能力。变量可以用于自定义路由规则。也就是说,我们可以使用JavaScript来实现复杂的功能,这些功能可以直接对请求的处理产生影响。

将客户端重定向到新的应用服务器

在这篇文章里,我们将介绍如何使用nginScript来实现优雅的服务器间切换。我们不打算进行“一次性”的切换,而是定义了一个时间窗口,客户端在事件窗口内循序渐进地切换到新的服务器。我们可以逐渐地自动给新服务器增加流量。

我们定义了一个两个小时的时间窗口,我们希望切换就在这两个小时内完成,也就是下午5点到7点。我们预期在第一个12分钟内,有10%的客户端被重定向到新的服务器,24分钟之后有20%,并以此类推。下图展示了切换过程。

(点击放大图像)

nginScript系列:使用nginScript将客户端重定向到新服务器

在两个小时内将客户端从旧的服务器重定向到新的服务器

这种“渐进式切换”要求已经切换到新服务器的客户端不能又回到旧的服务器,也就是说,一旦一个客户端被重定向到新的服务器,那么从今以后它就一直被定向到那里。

我们会在稍后描述完整的配置,不过简单地说,NGINX和NGINX Plus在处理已经被切换过来的请求时,会遵循如下规则。

  • 如果切换时间窗口还没有开始,那么请求就被重定向到旧的服务器。
  • 如果切换时间窗口已经结束,那么请求就被重定向到新的服务器。
  • 如果切换在进行当中,那么:
    1. 计算当前时间在切换时间窗口中的位置。
    2. 计算客户端IP地址的散列值。
    3. 计算散列值在所有散列值中的位置。
    4. 如果散列值的位置比切换时间窗口的当前位置要大,那么请求就被重定向到新的服务器,否则重定向到旧的服务器。

为HTTP应用配置NGINX和NGINX Plus

在这个例子里,我们将使用NGINX和NGINX Plus作为一个Web应用服务器的反向代理,所以所有的配置都是关于HTTP的。

首先,我们分别为旧应用程序和新应用程序所在的服务器定义单独的upstream配置块。虽然切换过程是渐进式的,NGINX和NGINX Plus在切换期间会一直充当负载均衡器的角色。

upstream old {
    server 10.0.0.1;
    server 10.0.0.2;
}

upstream new {
    server 10.0.0.9;
    server 10.0.0.10;
}

接下来,我们定义前端的服务,NGINX和NGINX Plus通过它们将呈现内容发送给客户端。

js_include /etc/nginx/progressive_transition.js;
js_set $upstream transitionStatus; # 基于时间窗口位置返回"old|new"

server {
    listen 80;

    location / {
        set $transition_window_start "Wed, 31 Aug 2016 17:00:00 +0100";
        set $transition_window_end   "Wed, 31 Aug 2016 19:00:00 +0100";

        proxy_pass http://$upstream;
        error_log /var/log/nginx/transition.log info; # 启用 nginScript日志
    }
}

我们使用nginScript来决定应该使用哪个upstream组,所以我们需要指定nginScript代码的位置。在NGINX Plus R11及其后的版本里,所有的nginScript代码必须被放置在单独的文件里,然后通过js_include指令来指定它们的位置。

js_set指令用于设置$upstream变量。要注意,这个指令并不是要让NGINX或NGINX Plus去调用nginScript函数transitionStatus。NGINX变量是按需进行计算的,也就是在处理请求期间用到变量时才会进行计算。所以,js_set指令是要告诉NGINX或NGINX Plus在必要的时候如何计算$upstream变量。

server代码块定义NGINX和NGINX Plus如何处理HTTP请求。listen指令告诉NGINX和NGINX Plus对80端口(默认HTTP端口)进行监听,不过生产环境一般配置成SSL/TLS来保护传输中的数据。

location代码块的作用域包括了整个应用空间(/)。在这个代码块里,我们使用了set指令和两个新的变量$transition_window_start和$transition_window_end来定义切换时间窗口。时间可以被声明成 RFC 2822格式 (例子里所示)或 ISO 8601格式 (包含毫秒)。两种格式必须包含它们各自的本地时区标识符。因为JavaScript的Date.now函数总是返回UTC日期和时间,所以只有提供本地时区才能进行准确的时间比较。

proxy_pass指令将请求重定向到upstream组,transitionStatus函数会对它进行计算。

最后,error_log指令启用了nginScript事件日志,级别为info及以上(默认情况下,只有warn及以上级别的事件会被记录下来)。将这个指令放在location代码块里,并指定单独的日志文件,这样就可以避免将主要的错误日志与其他info日志消息混杂在一起。

HTTP应用的nginScript代码

我们假设你已经启用了nginScript模块。

我们将nginScript代码放在 /etc/nginx/progressive_transition.js 文件里,正如js_include指令所指定的那样。所有的函数都包含在这个文件里。

被调用的函数必须在函数调用者之前出现,于是我们定义了一个函数用于返回客户端IP地址的散列值。如果应用服务器的主要用户处于相同的局域网内,那么我们的客户端就会有相似的IP地址,所以我们的散列函数需要为小区间的输入值返回平均分布的散列值。

在这个例子里,我们使用了 FNV-1a散列算法 ,这个算法短小精悍,很快,而且具有 良好的平均分布能力 。它的另一个优势在于,它返回的是一个32位的正整数,这样可以很方便地计算每一个客户端IP地址在输出区间的位置。下面的代码是FNV-1a算法的JavaScript实现。

function fnv32a(str) {
  var hval = 2166136261;
  for (var i = 0; i < str.length; ++i ) {
    hval ^= str.charCodeAt(i);
    hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
  }
  return hval >>> 0;
}

接下来,我们定义transitionStatus函数,这个函数将js_set指令里的$upstream变量设置到NGINX配置里。

function transitionStatus(req) {
  var vars, window_start, window_end, time_now, timepos, numhash, hashpos;

  // 从NGINX配置里获取切换时间窗口
  vars = req.variables;
  window_start = new Date(vars.transition_window_start);
  window_end = new Date(vars.transition_window_end);

  // 是否处于切换时间窗口内?
  time_now = Date.now();
  if ( time_now < window_start ) {
    return "old";
  } else if ( time_now > window_end ) {
    return "new";
  } else { // 处于切换时间窗口内
    // 计算切换时间窗口内的位置 (0-1)
    timepos = (time_now - window_start) / (window_end - window_start);

    // 获取客户端IP地址的散列值
    numhash = fnv32a(req.remoteAddress);

    // 计算散列值在输出区间里的位置 (0-1)
    hashpos = numhash / 4294967295; // Upper bound is 32 bits
    req.log("timepos = " + timepos + ", hashpos = " + hashpos); //error_log [info]

    // 需要切换这个客户端吗?
    if ( timepos > hashpos ) {
      return "new";
    } else {
      return "old";
    }
  }
}

transitionStatus函数只有一个参数req,这个参数代表的是一个HTTP request对象。request对象的variables属性包含了NGINX的所有配置变量,包括用于设置切换时间窗口的$transition_window_start和$transition_window_end。

外面的if/else代码块检查切换时间窗口是否启动、结束或者正在进行当中。如果在进行当中,我们通过向fnv32a函数传递req.remoteAddress来获取客户端IP地址的散列值。

然后我们计算散列值在区间中的位置。因为FNV-1a算法返回的是一个32位的正整数,我们可以直接将散列值除以4,294,967,295(32位整数的十进制表示)。

这个时候,我们调用req.log()来记录散列位置和切换时间窗口的当前位置。我们使用info级别将这些信息记录到之前在NGINX和NGINX Plus里配置的error_log文件里,并生成如下所示的日志条目。其中js:前缀表示从JavaScript代码中获得的日志条目。

2016/09/08 17:44:48 [info] 41325#41325: *84 js: timepos = 0.373333, hashpos = 0.840858

最后,我们比较散列值在输出区间中的位置和切换时间窗口的当前位置,并返回相应的upstream组的名字。

总结

在这篇文章里,我们介绍了如何使用nginScript复杂的编程式配置循序渐进地切换客户端到新的服务器。通过部署自定义逻辑来实现可控地选择合适的上游组,这只是nginScript提供的众多解决方案里一个特性。

我们将继续扩展和加强nginScript的能力,以便为NGINX何NGINX Plus提供更加强大的编程式配置解决方案。

查看英文原文: Using nginScript to Progressively Transition Clients to a New Server


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数字化生存

数字化生存

尼葛洛庞帝 / 胡泳 等 / 海南出版社 / 1997-2 / 16.80元

《数字化生存》可以说是二十世纪信息技术及理念发展的圣经,此书的流行和传播对上个世纪信息时代的启蒙、发展产生了深远的影响,本书深入浅出地讲解了信息技术的基本概念、趋势和应用、巨大的价值和数字时代的宏伟蓝图,阐明了信息技术、互联网对时代和人们生活的影响和价值。作者尼葛洛庞帝成为了信息技术投资和趋势分析领域的教父,他的知名度和因此带来的无形价值不可估量。一起来看看 《数字化生存》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具