Shell 技巧 - 使用 $RANDOM 的 Web 服务器技巧
我刚刚迁移到一个更新、更大的服务器(当然,可以理解为“更贵”,但由于我的流量证明了这一点,我对这次更改感到满意)。为了让事情更有趣,我还刚买了一台新笔记本电脑(MacBook Pro),在这两次迁移之间,我一直在浏览很多旧目录,并且偶然发现了我过去几年编写的各种脚本。
我认为在这里探讨会很有趣的一个脚本是我为一个参与慈善事业的朋友编写的,他想要一种方法,让一个 URL 将人们 50/50 地跳转到两个不同的网页之一——一种迷你负载均衡器,尽管他的应用程序不太一样。
这个脚本的核心部分是 $RANDOM shell 变量,它实际上有点神奇——每次你引用它时,你会发现它都是不同的,即使你实际上并没有给它分配一个新值。例如
$ echo $RANDOM 21960 $ echo $RANDOM 19045 $ echo $RANDOM 2368 $ echo $RANDOM 2425 $ echo $RANDOM 10629
这违反了 shell 的核心用户设计原则,甚至违反了变量的定义(变量应该是可预测的——如果你给它赋值 37,那么在 200 行和 17 次引用之后,它仍然应该具有该值)。其他变量的值会根据你正在执行的操作而更改,而无需你实际为其分配新值,例如 $PWD,但由于那是当前工作目录,如果你在文件系统中移动,那么它的值也会随之更改,这是合乎逻辑的。
然而,$RANDOM 值属于它自己的类别,并且可以非常容易地为你的脚本和用户交互添加一些伪随机性(它是否真的是随机的,这是一个更复杂——令人费解的复杂——问题。如果你有兴趣,可以尝试谷歌搜索“determining the randomness of random numbers”来深入了解那个特别的兔子洞。
在 Bourne Again Shell (bash) 中,$RANDOM 数字的范围在 0..MAXINT (32,767) 之间。为了缩小范围并使其有用,你可以简单地将其除以你寻求的最大数值。
换句话说,例如,如果你想要一个介于 1..10 之间的随机数,请使用 % “余数”函数并调用 expr
$ expr $RANDOM % 10 7 $ expr $RANDOM % 10 5 $ expr $RANDOM % 10 9 $ expr $RANDOM % 10 6 $ expr $RANDOM % 10 8
进一步归纳,现在应该跃然纸上的是,亲爱的读者,如何随机地在两个选项之间做出选择
if [ "$(expr $RANDOM % 2 )" -eq "0" ] ; then conditional expression fi
如果你想成为一个纯粹主义者,你也可以使用 $(( )) 数学符号来编写它,当然,正如你稍后在本专栏中看到的那样。
这足以让我们编写我之前提到的 shell 脚本,即在调用时随机在两个可能的页面之间切换的脚本
#!/usr/local/bin/bash url1="http://www.bing.com/" url2="http://www.google.com/" if [ "$(expr $RANDOM % 2 )" -eq "0" ] ; then echo "Location: $url1"; echo "" else echo "Location: $url2"; echo "" fi exit 0
你能看出这个示例脚本的作用吗?如果你猜到“随机将你重定向到 Google 或 Bing”,那么你是对的!如果不是,那又怎样?回去再读一遍代码!
现在,假设我的朋友说“75% 的时间,我真的想将人们带到 URL1。你能做到吗,Dave?”
这是它可能的样子
if [ "$(expr $RANDOM % 100 )" -lt "75" ] ; then
(或者,更清楚地表示为% 4 -lt 3,就此而言。)
如果你有超过两个选择,你可以使用 case 语句,这使得不均匀分配有点棘手,但在其他方面很简单
case $(( $RANDOM % 4 )) in 0 ) echo $url1; ;; 1 ) echo $url2; ;; 2 ) echo $url3; ;; 3 ) echo $url4; ;; esac
考虑到这一点,我们可以编写一个 n 路负载均衡脚本,这样当人们访问主页时,他们会自动跳转到 n 个可能的服务器之一。
实际上,有趣的步骤是根据服务器负载对它们进行轮询,当然,这可以通过使用 ruptime 命令逐步处理数据来完成。
因此,给定 uptime 的输出
$ ruptime host1 host1 16:51 up 3+53:17, 3 users, load 0.65 0.68 0.51
我们真正想要的是获取按系统繁忙程度排序的主机名列表,这可以通过带有 -rl 标志的 ruptime 生成,如下所示
$ ruptime -r -l host1 down 16+08:34 host4 up 10+13:26, 7 users, load 0.07, 0.39, 1.04 host3 up 14+06:49, 3 users, load 0.10, 0.38, 0.49 host2 up 1+17:40, 4 users, load 0.18, 0.13, 0.09
正如你所见,第一步是筛选掉当前未实际启动的主机,然后获取第一个字段(因为它按系统当前时刻的繁忙程度排序)。
一种方法可能是每次有请求进来时都调用 ruptime,然后只获取第一个值。可以这样做
$ ruptime -rl | grep -v down | head -1 | cut -d\ -f1 host2
问题是系统大约每分钟才报告一次 uptime 信息,并且每秒调用 ruptime 几十次或数百次最终可能会产生问题——最不繁忙的系统将被淹没。如果你获得大量流量,那将不是一个可管理的解决方案。
这就是我们可以让我们的朋友 $RANDOM 重新回到画面中的地方。与其总是简单地选择负载平均值最低的机器,不如让我们随机选择三个最不繁忙的系统之一。核心代码片段如下所示
getline="$(( ( $RANDOM % 3 ) + 1 ))" targethost="$(ruptime -rl | grep -v down | sed -n ${getline}p | cut -d\ -f1)"
通过稍微多一点代码,你可以对其进行偏置,例如,50% 的时间它会选择最不繁忙的系统,33% 的时间它会选择第二不繁忙的系统,17% 的时间它会选择第三不繁忙的系统。随着时间的推移以及负载的移动,这些系统将不断变化,你将获得一个粗略但有效的负载均衡系统。
了解在 shell 脚本中随机选择多个可能路径之一是多么容易,你还能想到什么会有帮助或只是有趣的事情吗?
Dave Taylor 自 1980 年首次登录在线网络以来就一直参与 UNIX。这意味着,是的,他现在即将迎来 30 年的里程碑。你几乎可以在任何在线地方找到他,但请从这里开始:www.DaveTaylorOnline.com。除了他的所有其他项目外,Dave 现在还是一名影评人。你可以在 www.DaveOnFilm.com 阅读他的评论。