微軟的坑:Url重寫竟然會引起IIS核心模式快取不工作

weixin_34015860發表於2014-05-28

萬萬沒有想到!當初為了解決使用負載均衡時記錄客戶端IP地址的問題,在IIS URL Rewrite Module中增加了一條URL重寫規則(詳見遷入阿里雲後遇到的Request.UserHostAddress記錄IP地址問題):

<rewrite>
    <allowedServerVariables>
        <add name="REMOTE_ADDR" />
    </allowedServerVariables>
    <globalRules>
        <rule name="HTTP_X_Forwarded_For-to-REMOTE_ADDR" enabled="true">
            <match url=".*" />
            <serverVariables>
                <set name="REMOTE_ADDR" value="{HTTP_X_Forwarded_For}" />
            </serverVariables>
            <action type="None" />
            <conditions>
                <add input="{HTTP_X_Forwarded_For}" pattern="^$" negate="true" />
            </conditions>
        </rule>
    </globalRules>
</rewrite>

這竟然造成http.sys的核心模式快取(kernel mode caching)被IIS URL Rewrite Module禁用,禁用理由是重寫規則中用到了影響快取安全的伺服器變數(cache unsafe server variable)——{HTTP_X_forwarded_For}。

URL重寫竟然能影響到處於核心模式的http.sys,誰能想到?微軟想到了,而且做到了!

當知道這個真相後,真的很惱火!平時誰會注意http.sys的快取是否正常工作,如果不是因為最近在解決“黑色1秒”問題,估計再過十年也不會發現。

那我們是怎麼發現的呢?藉助於Windows效能監視器(Performance Monitor)。

針對http.sys kernel mode cache的效能監視器

在新增了HTTP Service的三個監測專案——TotalUrisCached, UriCachedHits, UriCacheMisses之後發現,TotalUrisCached與UriCachedHits值一直是0,而UriCacheMisses的值巨大無比,一看就知道kernel mode caching出問題了。

後來發現一個命令可以更輕鬆地進行檢測:

netsh http show cachestate

如果出現上圖的畫面,說明kernel mode caching沒幹活。

當我們禁用了讓kernel mode caching罷工的URL重寫規則後,用瀏覽器訪問一個網址,然後Ctrl+F5重新整理2次(10秒內被訪問2次就會被http.sys快取),然後執行命令netsh http show cachestate:

netsh http show cachestate

從上圖中可以看出請求的內容被http.sys成功快取了。

那核心模式快取失效會帶來什麼影響呢?

誰都知道這會影響了網站的處理效能,而對我們來說還有一個重大影響——在“黑色1秒”問題的排查過程中,它讓我們作出了錯誤的判斷,以為“黑色1秒”期間http.sys程式卡住了(依據快取沒工作),詳見“黑色30秒”走了,“黑色1秒”來了,真相也許大白了。現在看來,如果解決了http.sys快取問題,“黑色1秒”期間IIS日誌中很可能有快取輸出的記錄,如果真是這樣,那引發“黑色1秒”的環節可能在WAS(Windows Process Activation Service),這將把我們帶向不同的問題排查方向。

那如何解決這個問題呢?

目前只想到兩個方式:

1. 棄用IIS URL Rewrite Module,但目前未找到更好的選擇。

2. 讓阿里雲修改SLB的轉發規則,將http headers中的REMOTE_ADDR修改為真實的客戶端IP。

3. 修改程式碼,不通過Request.UserHostAddress獲取IP。

【最終選擇的解決方法】

寫了兩個擴充套件方法:

namespace System.Web
{
    public static class HttpRequestExtension
    {
        //針對WebForm
        public static string GetUserIp(this HttpRequest request)
        {
            var ip = request.ServerVariables["HTTP_X_FORWARDED_FOR"];
            if(string.IsNullOrEmpty(ip))
            {
                ip = request.UserHostAddress;
            }
            return ip;
        }

        //針對MVC
        public static string GetUserIp(this HttpRequestBase request)
        {
            var ip = request.ServerVariables["HTTP_X_FORWARDED_FOR"];
            if (string.IsNullOrEmpty(ip))
            {
                ip = request.UserHostAddress;
            }
            return ip;
        }
    }
}

然後將程式碼中所有呼叫Request.UserHostAddress的地方改為呼叫擴充套件方法Request.GetUserIp()。

【參考資料】

URL Rewrite Module 1.1 for IIS 7

Working with HTTP.SYS or Kernel Mode Caching in Internet Information Services 6.0

URL Rewrite Module Configuration Reference

相關文章