内容简介:nginScript系列:使用nginScript将客户端重定向到新服务器
这是nginScript系列文章的第二篇,将介绍如何使用nginScript将客户端循序渐进地重定向到新的服务器。查看第一篇“nginScript简介”。
nginScript的一个关键优势在于它提供了读取和设置NGINX配置变量的能力。变量可以用于自定义路由规则。也就是说,我们可以使用JavaScript来实现复杂的功能,这些功能可以直接对请求的处理产生影响。
将客户端重定向到新的应用服务器
在这篇文章里,我们将介绍如何使用nginScript来实现优雅的服务器间切换。我们不打算进行“一次性”的切换,而是定义了一个时间窗口,客户端在事件窗口内循序渐进地切换到新的服务器。我们可以逐渐地自动给新服务器增加流量。
我们定义了一个两个小时的时间窗口,我们希望切换就在这两个小时内完成,也就是下午5点到7点。我们预期在第一个12分钟内,有10%的客户端被重定向到新的服务器,24分钟之后有20%,并以此类推。下图展示了切换过程。
(点击放大图像)
在两个小时内将客户端从旧的服务器重定向到新的服务器
这种“渐进式切换”要求已经切换到新服务器的客户端不能又回到旧的服务器,也就是说,一旦一个客户端被重定向到新的服务器,那么从今以后它就一直被定向到那里。
我们会在稍后描述完整的配置,不过简单地说,NGINX和NGINX Plus在处理已经被切换过来的请求时,会遵循如下规则。
- 如果切换时间窗口还没有开始,那么请求就被重定向到旧的服务器。
- 如果切换时间窗口已经结束,那么请求就被重定向到新的服务器。
- 如果切换在进行当中,那么:
- 计算当前时间在切换时间窗口中的位置。
- 计算客户端IP地址的散列值。
- 计算散列值在所有散列值中的位置。
- 如果散列值的位置比切换时间窗口的当前位置要大,那么请求就被重定向到新的服务器,否则重定向到旧的服务器。
为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
以上所述就是小编给大家介绍的《nginScript系列:使用nginScript将客户端重定向到新服务器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 支付宝客户端架构解析:iOS 客户端启动性能优化初探
- 自己动手做数据库客户端: BashSQL开源数据库客户端
- 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
- 客户端HTTP缓存
- 简析移动客户端安全
- 配置Hadoop集群客户端
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Probability and Computing
Michael Mitzenmacher、Eli Upfal / Cambridge University Press / 2005-01-31 / USD 66.00
Assuming only an elementary background in discrete mathematics, this textbook is an excellent introduction to the probabilistic techniques and paradigms used in the development of probabilistic algori......一起来看看 《Probability and Computing》 这本书的介绍吧!