Nginx 中 map 模塊的使用及性能測試

背景

最近我操刀了leetcode論壇遷移,整個過程持續了幾周的時間,總算暫時告了一個段落。常使用leetcode論壇的用戶應該已經發現論壇已經大變樣了吧~php

期間遇到了很多坑坑窪窪,未來也還會有好多問題等待去一一解決。關於這個遷移過程當中的收貨,這篇文章中就不細說了,有時間再另開一篇博文。這篇文章主要關注在url-mapping以及它的性能問題。html

:url-mapping的問題從何而來呢?node

舊的論壇和新的論壇是兩個不一樣的discuss框架。前者是phpbb,如今是nodebb。二者的 url routing 徹底不同,好比說同一個topic,在原來的url是 http://hostname/discuss/<topic_id>/<topic_name>,在新的論壇中是 http://hostname/topic/<topic_id>/<topic_slug>(這裏就不討論二者甚至連topic_id都不同的問題了)。python

而在廣袤的互聯網海洋中,舊論壇的url可能處處都存在。咱們不但願在論壇遷移後,用戶點那些連接就失效了。咱們但願的是用戶訪問舊的url能夠被重定向到新論壇的某個地址。因此就產生了url-mapping的問題。nginx

方法

生成url-mapping

感謝nodebb-plugin-import提供了數據遷移之後自動生成url-mapping的方式,省了我本身寫腳本生成這些mapping的時間。每一條mapping大體是這樣的:git

~^/discuss/questions/oj/add-two-numbers\b(\?[^/]*)*/?$  /category/10/add-two-numbers;

其中的slugid的mapping是由插件生成的。regular expression是爲了匹配url中若是有param添加的。github

Nginx Map

官方文檔的demo可能對於剛想上手的同窗來講不是那麼友好,仍是直接看現成的配置學得快:express

http {
  ...

  map_hash_max_size 204800;
  map_hash_bucket_size 204800;
  map $request_uri $new {
     include /path/of/your/map/file;
  }

  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;

  ...
}
server {
  ...

  if ($new) {
    rewrite ^ https://discuss.leetcode.com$new redirect;
  }

  location / {
    ...
  }
  ...
}

在server規則匹配中,$new值不爲空,說明當前要訪問的url已經在http模塊的mapping文件中匹配到了,這個時候就不走各類location模塊了,直接rewrite成新的地址。:這裏要是作成proxy_pass也行,後面的測試中就採用了proxy_pass。但線上的環境,擔憂nginx的壓力太大了,就採用了rewrite方式給它減減壓。服務器

測試

考慮到mapping的條目有點多,幾萬量級,又都是正則匹配。每一個請求來的時候都會先去看看mapping中有沒有,即便mapping使用的是hash的方式也難免會讓我對它的性能產生一些擔心,因此性能測試就必需要來一發了。網絡

測試方案:

  1. 在新機器上跑helloworld

  2. 自動生成隨機100個url-mapping,都重定向到helloworld

  3. 使用abtest分別對helloworld和隨機url做壓測

  4. 增大url-mapping的條目,重複1,2

壓測機器

臨時租了兩臺阿里雲服務器(由於是臨時的,因此我也就不在乎在後文暴露ip了),配置都是:1核,2048M內存,40G硬盤。一臺用做nginx和helloworld程序,一臺專門作abtest。

:abtest也在阿里雲執行只要是爲了在一個數據中心下降網絡延遲。最後發現效果然不錯,rps從100多直接飆升到2700多。

helloworld

採用了nodejs的helloworld:

var http = require('http');
var i = 0;
http.createServer(function (req, res) {
  console.log(i++);
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(1337, "0.0.0.0");
console.log('Server running at http://0.0.0.0:1337/');

url-mapping

生成urlmapping寫了一個python腳本:

import hashlib

m2 = hashlib.md5()
current = "hello world"
f = open('./url.map', 'w')

for i in range(100):
    m2.update(current)
    current = m2.hexdigest()
    f.write('~^/hello/world/' + current + '\\b(\?[^/]*)*/?$\t/;\n')

f.close()

nginx配置:

server {
  listen 80;
  server_name 120.26.138.197;

  location ^~ /{
    if ($new) {
      proxy_pass http://120.26.138.197:1337$new;
      break;
    }

    return 404;
  }
}

abtest

rps測試(request per second):併發壓測使用100000次請求,併發100個用戶的方式。

# 不走nginx
ab -n100000 -c100 120.26.138.197:1337/
# 走nginx
ab -n100000 -c100 120.26.138.197/hello/world/5eb63bbbe01eeed093cb22bb8f5acdc3/
mapping條目 直接訪問(rps) map第一條url(rps) map最後一條url(rps) 不存在的url(rps)
100 2829.44 1819.63 1765.25 9740.53
1000 - 1816.00 1509.52 4094.68
10000 - 1813.22 514.24 658.32
100000 - 1836.02 62.40 65.80

跟預想的同樣,mapping的條目確實會對請求效率產生影響。並且幾萬條的映射在較高併發的狀況下已經到了勉強能用的臨界了。還好之後mapping的條目不會再增長了,而且論壇的併發很難到100的量級。

tpr測試(time per request):由於考慮到服務器比較穩定,減小壓測總數。同時把併發用戶減爲1個。

# 不走nginx
ab -n1000 -c1 120.26.138.197:1337/
# 走nginx
ab -n1000 -c1 120.26.138.197/hello/world/5eb63bbbe01eeed093cb22bb8f5acdc3/
mapping條目 直接訪問(ms) map第一條url(ms) map最後一條url(ms) 不存在的url(ms)
100 0.690 0.922 0.933 0.507
1000 - 0.925 1.043 0.648
10000 - 0.921 2.340 1.915
100000 - 0.926 16.321 15.469

在併發不是很高的時候mapping的條目能夠更多。100000個條目大概只會影響整個請求15ms左右,能夠忽略不計。若是說150ms的延遲是能夠接受的,那麼在一個併發不是很高的狀況下,mapping最多能夠有100w條,仍是不少的。

測試中的不足

  1. 壓測的url請求並不隨機

  2. 全部的url都被重定向到一個地方。不過從結果來看,nginx確實是根據條目一個個請求的。這點倒沒有什麼影響

  3. 沒有測試http://hostname/path?param=xxx這樣類型的url

相關文章
相關標籤/搜索