7

觀察 Round-Robin DNS 查詢結果變化

 8 months ago
source link: https://blog.darkthread.net/blog/round-robin-dns-test/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

觀察 Round-Robin DNS 查詢結果變化-黑暗執行緒

Round-Robin DNS (DNS 輪循、循環 DNS) 是非常古老的負載平衡手法,其原理是 DNS 伺服器回應 DNS 請求時,返回結果來自一長串 IP 位址清單,並在每次回應時抽換 IP 組合跟掉換 IP 順序,一般來說,客戶端會採用回應結果的第一個 IP,如此,要連上 www.xxx.yyy 的不同客戶端便會導向不同 IP 連到不同主機,達到分散流量及工作負載的效果。

雖然這種做法無法平均分配負載(將流量導向空閒主機)、不會避開故障中的伺服器,並可能因客戶端快取減弱效果,但因為容易實現,至今仍普遍使用。(延伸閱讀:現代 CDN 會使用 BGP Anycast 實現負載平衡,Internet 跟你想的不一樣:IP 地址不是唯一,有許多相同 IP 的主機散落各地)

以微軟的 login.microsoftonline.com 為例,nslookup 可得到 8 個 IPv4 地址,反覆查詢,IP 項目及順序會改變:

Fig1_638401497693300901.png

好奇心起:這個 IP 清單包含多少個不同 IP?每次查詢 IP 的變化有特殊規律嗎?

做個實驗試試,我寫了以下 PowerShell 從 nslookup 輸出擷取 IPv4 地址,10 秒查一次。

for ($i = 0; $i -lt 128; $i++) {
    $capture = $false
    $ips = @()
    . nslookup.exe login.microsoftonline.com 8.8.8.8 | ForEach-Object {
        if ($_ -match 'Addresses') {
            $capture = $true
        } 
        elseif ($capture -and $_ -match '(\d+\.\d+\.\d+\.\d+)') {
            $ips += $matches[1]
        }
    } 
    Write-Output ($ips -join ',')
    Start-Sleep -Seconds 10
}

.\run-100-times-dns.ps1 | Out-File -FilePath 'dns-results.txt' 將執行結果輸出到 dns-results.txt。有趣的是,「未經授權的回答:」是透過 stderr 輸出,不會寫入檔案而是顯示在螢幕上。

然後我用 Vue 寫了個簡單查詢,方便觀察各 IP 出現次數、順序。線上展示

Fig2_638401497699220478.png

由結果可知,login.microsoftonline.com 背後共有 20 個 IP,每次出現 8 個 128 次總數 1024,1024 / 20 = 理論上每個 IP 會出現 51.2 次,接近實測的數字。而順序分佈也呈現隨機。點選最上方 IP [1] 可觀察該 IP 出現在哪些資料列,點選列號 [2] 可觀察相同 IP 組合重複出現的位置。依觀察結果,相同 IP 組合出現次數介於 1 到 7 次,而且偏集中,前後很少超過五分鐘(每列相隔十秒,也就是序號差小於 30)。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Round-Robin DNS Test</title>
    <script src="https://unpkg.com/vue@3"></script>
    <style>
        body {
            font-family: Arial, Helvetica, sans-serif; font-size: 9pt;
        }
        .iplist {
            display: flex; flex-direction: row; flex-wrap: wrap;
        }

        .iplist>span {
            margin-right: 2px; padding: 2px; border: 1px solid #ccc;
            width: 120px; text-align: center;
        }

        table {
            margin-top: 12px; border-collapse: collapse;
        }

        tr:nth-child(odd) {
            background-color: #eee;
        }

        td {
            border: 1px solid #ccc; padding: 4px;
        }

        td.sn {
            text-align: right;
        }

        .focus {
            text-shadow: 1px 1px 2px black; font-weight: bolder;
            background-color: black!important;
            color: yellow!important;
        }
        .link {
            cursor: pointer;
        }
        #app {
            width: 800px;
        }
.dc-0  { color:#e6194B; } .dbc-0  { color:#ffffff; background-color:#e6194Ba0; }
.dc-1  { color:#3cb44b; } .dbc-1  { color:#ffffff; background-color:#3cb44ba0; }
/* ... 省略 ... */
.dc-20 { color:#ffffff; } .dbc-20 { color:#000000; background-color:#ffffffa0; }
.dc-21 { color:#000000; } .dbc-21 { color:#ffffff; background-color:#000000a0; }        
    </style>
    <script type="application/x-data" id="data">
        40.126.38.19,20.190.166.68,20.190.166.66,20.190.166.132,20.190.166.67,40.126.38.22,20.190.166.131,40.126.38.20
        20.190.141.33,20.190.141.32,20.190.141.39,20.190.141.38,20.190.141.37,20.190.141.35,40.126.13.8,40.126.13.9
        ... 省略 ...
        40.126.13.9,20.190.141.38,40.126.13.8,20.190.141.33,20.190.141.35,20.190.141.39,20.190.141.32,20.190.141.37               
    </script>
</head>

<body>
    <div id="app">
        <div class="iplist">
            <span v-for="ip in list" @click="focusIp = ip" class="link" 
                :class="css(ip)">{{ip}} ({{counts[ip]}})</span>
        </div>
        <table>
            <tr v-for="(line,idx) in data"  >
                <td class="sn link" @click="focusLine = line" :class="{'focus':focusLine == line}">
                    {{idx+1}}. ({{dupLineCount[line]}}) 
                </td>
                <td v-for="ip in line.split(',')" :class="css(ip)">{{ip}}</td>
            </tr>
        </table>
    </div>
    <script>
        const data = [];
        const ipChk = {};
        const counts = {};
        const dupLineCount = {};
        let c = 0;
        document.head.querySelector('script[id=data]').innerText.split('\n').forEach(function (line) {
            line = line.trim();
            if (!line) return;
            if (line in dupLineCount) dupLineCount[line]++;
            else dupLineCount[line] = 1;
            var ips = line.split(',');
            ips.forEach(function (ip) {
                if (!(ip in ipChk)) { ipChk[ip] = c++; counts[ip] = 0; }
                else counts[ip]++;
            });
            data.push(line);
        });
        const ips = Object.keys(ipChk);
        ips.sort();
        const app = Vue.createApp({
            data: function () {
                return {
                    data: data,
                    list: ips,
                    counts: counts,
                    focusLine: '',
                    dupLineCount: dupLineCount,
                    focusIp: ''
                };
            },
            methods: {
                css: function (ip) {
                    return (this.focusIp == ip ? 'focus ' : '') +
                        'dbc-' + (ipChk[ip] % 22);
                }
            }
        });
        app.mount('#app');
    </script>
</body>


</html>

就醬,我又完成一次無聊的實驗,累積一些 Coding 經驗。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK