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

2017 年 5 月 15 日

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

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

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

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

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

(点击放大图像)

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

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

我们会在稍后描述完整的配置,不过简单地说,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 指令用于设置 upstreamNGINXNGINXPlusnginScripttransitionStatusNGINXjssetNGINXNGINXPlusupstream 变量。

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

location 代码块的作用域包括了整个应用空间(/)。在这个代码块里,我们使用了 set 指令和两个新的变量 transitionwindowstarttransition_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 的所有配置变量,包括用于设置切换时间窗口的 transitionwindowstarttransition_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

2017 年 5 月 15 日 17:20 1086
用户头像

发布了 321 篇内容, 共 106.1 次阅读, 收获喜欢 96 次。

关注

评论

发布
暂无评论
发现更多内容

Vol.1 Java初探,新手必看!

Lanpeng2020

编程 新手指南

原创 | 使用JUnit、AssertJ和Mockito编写单元测试和实践TDD (十)在项目中准备测试环境

编程道与术

Java 编程 软件测试 TDD 单元测试

Spring Security 两种资源放行策略,千万别用错了!

江南一点雨

Java spring springboot springsecurity

如何用一台电脑制作一部动画短片?

zhoo299

动画 CG

多线程与线程安全(实例讲解)

YoungZY

Java 多线程 线程安全

你为什么“啃不动”你手中的技术书?

图灵社区

Java Python 算法 HTTP R语言

redis过期策略和内存淘汰机制

wjchenge

联邦学习与推荐系统

博文视点Broadview

人工智能 深度学习 大数据 联邦学习 推荐系统

Anaconda与虚拟环境

halapano

Python virtualenv Anaconda

Gartner 【RPA市场竞争格局】:中国厂商首次进入国际视野

人称T客

python实现·十大排序算法之计数排序(Counting Sort)

南风以南

Python 排序算法 计数排序

你的团队想做出什么成果?

姜戈

团队管理

揭秘神经拟态计算:缘何成为AI界新宠?

飞天鱼2017

终于,我也到了和Eclipse说再见的时候,难说再见

Dimple

Java eclipse IDEA

Vol.3 人工智能这么热,你必须知道一点儿!

Lanpeng2020

人工智能

健身一周年:持续锻炼带来无法想象的改变

Breeze

学习 职业 专注 健身

你真的会用Mac中的Finder吗

Winann

macos 效率 App Mac

管理规划篇

姜戈

团队管理 团队组织

突破困局

Neco.W

感悟 工作 创业心态

自己常用的一些快捷键 windows10

halapano

Windows技巧

使用<input>标签实现六个格子验证码输入框

brave heart

JavaScript vue.js 前端

源码分析 | Mybatis接口没有实现类为什么可以执行增删改查

小傅哥

Java 源码分析 小傅哥 mybatis 编程思维

你的团队是干什么的?

姜戈

团队管理 团队职能

100天从 Python 小白到大神最良心的学习资源!

JackTian

Python GitHub 学习资源 Python-100-Days Python-Core-50-Courses

一致性算法 Raft 简述

架构精进之路

raft 一致性算法

ARTS week 2

锈蠢刀

宕机原因千千万,被雷劈了最无奈

田晓旭

实现元素等高: Flexbox vs. Grid

寇云

CSS css3

码农远程办公指北

大伟

Vol.2 谷歌不只有搜索

Lanpeng2020

谷歌Google

Android原生人脸识别Camera2+FaceDetector 快速实现人脸跟踪

sar

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