ASP.NET 負載均衡 StateServer Session共享問題(經驗記錄)


  (源地址:http://www.cnblogs.com/ryhan/p/3748976.html)

  

  最近在改造公司的一個系統 支持F5硬件負載,由於系統后面還跟了個異步工具,需要將Admin上傳的文件保存本地后發送到FTP上,並記錄位置,異步工具根據位置進行下載。對於工具異步導出的文件,需要在Admin上進行下載,采用的方式是異步工具生成文件包,分發到Admin下的文件夾里(將Admin下的文件發布FTP)並更新數據庫記錄,Admin展示可下載的包,並鏈接下載。 到此為止 ,完成系統上的功能性改造。

  下面,另一件本來以為很容易的事情,就來了。。。也是這件事情,讓我產生了 一定要寫下此篇文章的動力。。。

 

  ----正文開始----

 

  現網公網端口只開80,且公網端口走F5負載,方式為輪詢。既然為負載就肯定要考慮Session保存問題。

  初始設想:

    使用Memcache共享緩存,並自己開發一個索引器,在索引器寫好以后,突然發現如何標識客戶端是個問題,需要自己設定cookies標識,要自己攔截請求等等等。。。,方案不好搞了。。

  

  改動方案:

    ASP.NET的網站,首選肯定是微軟整出來的功能、組件。 經過問度娘,得到兩個比較靠譜的方式(不需要自己寫代碼,修改sessionState配置即可)

    1. 使用StateServer,選擇其中一個Admin所在的服務器,開啟ASP.NET 狀態服務

    2. 使用SQLServer ,在數據庫整一庫,專門保存Session,然后用作業清理過期的登錄信息。

    (以上配置是ASP.NET關於存儲Session方式的其中兩個,具體可百度。。。)    

 

狀態服務(運行services.msc):

  

 

  

我最終選擇了方案1,原因是覺得這個簡單,靠譜~~ ,好吧,以下還原我的整個配置、處理過程。。。。

  

  1.首先,狀態服務端口是42424。 修改web.config配置:

<sessionState mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424" cookieless="false" timeout="30" />

   完事后,啟動調測,登錄成功,表示存儲OK。

 

  2.果斷發布版本,在服務器A、B上分別部署,並將狀態服務IP都指向A。然后打開登錄頁面。。。

無法向會話狀態服務器發出會話狀態請求。請確保 ASP.NET State Service (ASP.NET 狀態服務)已啟動,並且客戶端端口與服務器端口相同。如果服務器位於遠程計算機上,請檢查 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters\AllowRemoteConnection 的值,確保服務器接受遠程請求。如果服務器位於本地計算機上,並且上面提到的注冊表值不存在或者設置為 0,則狀態服務器連接字符串必須使用“localhost”或“127.0.0.1”作為服務器名稱。 

 圖:

  

 額?黃頁??為毛??? 好吧看來需要修改注冊表AllowRemoteConnection ,把0改成1,然后重啟狀態服務。再次打開頁面。。。哈哈。。終於顯示了。。。A、B分別登錄各種操作都是正常的。。。表示當時很開心。。。

 

圖:

 

  3.公司研發環境內沒有F5,只能使用Nginx進行模擬負載。那就下載下來啟動。。。每次啟動都掛,說是神馬創建日志文件失敗。。。。好吧再問度娘,有人說要把CMD.EXE拷貝到那nginx-1.2.5下面,然后在把nginx.exe拖到CMD里面執行,額 果然好了可以啟動。。。

 

 

然后把CMD拷貝到根目錄

 

  百度下,配置好復雜有點亂,那就刪刪刪除,最后配置就幾句話,測試了下,滿足了我的需求(這種配置情況下,每次負載轉發請求都是輪詢的 就是ABABAB。。。):

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    upstream xx.xx.xx.xx{        
         server   192.168.xx.x2:2014;
         server   192.168.xx.x1:2014;
            }
            
    server {
        listen       2014;
        server_name  xx.xx.xx.xx;
        location / {
            proxy_pass http://xx.xx.xx.xx;
           # access_log off;
            proxy_connect_timeout       10;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
   }       

}

  

   4.負載啟動后,果斷訪問虛擬主機,額。。。悲劇的事情,從此處開始。。。。

 

   a.正確的密碼帳號,登錄后頁面刷了下,沒登錄成功

   b.驗證碼 偶爾不正確,搞不懂了。。

   c.偶爾登錄成功,各種操作也OK。

   d.在用X帳號登錄第一次時,頁面刷新了下,然后用了Y帳號登錄,成功。 刷了下頁面,嗯? 怎么編寫X了,再刷一次,我擦怎么又是Y了。然后。。。發現了,這次緩存有問題了。。。。session貌似沒共享?  度娘都是說這樣可以共享的,但是為毛不行。。。

 

  ------------------------------------------------------------------------------------------------------------------------

  中間各種測試,各種分析,最終結論是要么A的session沒發給B的服務器,要么就是哪地方掛了,果斷把狀態服務給關了,然后兩個頁面都掛了。看來,兩個頁面都用了這狀態服務器,但是為啥不行呢。。。

  果斷在增加一個sessiontest.aspx頁面,輸出當前登錄的用戶,圖片驗證碼、Cookies、SessionID信息。

  打開虛擬主機頁面,刷新:

  第一次:  

sessionid=ajfjs245ywd12p45nnjwvqe5 
username= 
userpwd= 
userRole= 
randmKey=2715 
cookies=ASP.NET_SessionId:ajfjs245ywd12p45nnjwvqe5 

  第二次: 

sessionid=ajfjs245ywd12p45nnjwvqe5 
username= 
userpwd= 
userRole= 
randmKey=8432 
cookies=ASP.NET_SessionId:ajfjs245ywd12p45nnjwvqe5 

  第三次:

sessionid=ajfjs245ywd12p45nnjwvqe5 
username= 
userpwd= 
userRole= 
randmKey=2715 
cookies=ASP.NET_SessionId:ajfjs245ywd12p45nnjwvqe5 

  什么情況? 為毛來回變換。。。。猜測,就是這個原因導致每次登錄時系統反饋驗證碼不正確,偶爾登錄成功后,X、Y用戶會來回切換 也是這個。。。

然后用Fiddler構造請求,使用 ASP.NET_SessionId:ajfjs245ywd12p45nnjwvqe5 分別對兩台負載WEB進行發包,得到的結果分別與請求虛擬主機的第一次和第二次結果一致。。。說明兩台負載WEB的Session沒存在一起,或者存在一起了,但是是分開的,壓根沒共享。。。。

 

  好吧問題定位清楚,那就開始排查是什么導致這問題產生的。。。

  ------------------------------------------------------------------------------------------------------------------------

 

  5.拿出神器,開始抓包,發現通信正常,A的SESSION也發給B的服務器了。但是為啥還不行?  

    ------------------------------------------------------------------------------------------------------------------------

  中間各種測試,各種分析,最終 還是沒結論,然后就一邊分析、一邊百度。。。。

  ------------------------------------------------------------------------------------------------------------------------ 

  最終,在一個網頁上看到一段話,才恍然大悟。。。原來雖然我Session共享了,但是A、B存儲的位置不對,每次讀寫都不是一個。囧

  要在 Web 場中的不同 Web 服務器間維護會話狀態,Microsoft“Internet 信息服務”(IIS) 配置數據庫中 Web 站點的應用程序路徑(例如,\LM\W3SVC\2)與 Web 場中所有 Web 服務器必須相同。大小寫也必須相同,因為應用程序路徑是區分大小寫的。在一台 Web 服務器上,承載 ASP.NET 應用程序的 Web 站點的實例 ID 可能是 2(其中應用程序路徑是 \LM\W3SVC\2)。在另一台 Web 服務器上,Web 站點的實例 ID 可能是 3(其中應用程序路徑是 \LM\W3SVC\3)。因此,Web 場中的 Web 服務器之間的應用程序路徑是不同的。我們必須使Web 場Web 站點的實例 ID 相同即可。你可以在IIS中把某一個WEB配置信息保存為一個文件,其他Web 服務器的IIS配置可以來自這一個文件。

  為驗證實際情況,繼續抓包,並把A、B WEB使用的狀態服務改到C上,刷新虛擬主機頁面:

  第一次(A <-> C):

    

  第二次(B <-> C):

  

 

解碼后情況:

第一次:

GET /LM/W3SVC/753869457/ROOT(njUDW6dgPSYnBQVKY6MYyJpdicI=)/ajfjs245ywd12p45nnjwvqe5 HTTP/1.1
Host: 192.168.14.75
Exclusive: acquire

第二次:

GET /LM/W3SVC/753869456/Root(tHayTJV6gf0ZP3QdeL9pR3YM4mg=)/ajfjs245ywd12p45nnjwvqe5 HTTP/1.1
Host: 192.168.14.75
Exclusive: acquire

 

可以看到,第一次和第二次的請求路徑不一樣(紅色部分),cookies ID是一樣的,也就是說狀態服務器上已經保存了兩個負載WEB的Session,但是兩個WEB存儲的路徑不一致。

 

------------------------------------------------------------------------------------------------------------------------

解決方案:

 

1.使用VBS腳本更改IIS站點設置(http://support.microsoft.com/default.aspx?scid=kb;zh-cn;325056)  

 

使用代碼修改 IIS 配置數據庫。
創建一個文本文件,然后將該文件命名為 Moveinstance.vbs。
將下面的腳本代碼添加到 Moveinstance.vbs 中,這些代碼會修改 Web 站點的實例 ID,以便這些實例 ID 是相同的:
Dim WebService
Dim oldstr
Dim newstr
Dim args
Set args = WScript.Arguments
If args.Count < 1 Then
    Wscript.Echo "Must have original instance id and new instance id" &     chr(10) & chr(13) & _
    "usage:  moveinstance.vbs 1 5"  & chr(10) & chr(13) & _
"Moves instance 1 to instance 5"
    WScript.Quit()
End If
Set WebService = GetObject("IIS://LocalHost/W3SVC")
oldstr = args(0) 'old instance
newstr = args(1) 'new instance
WebService.MoveHere oldstr,newstr
WebService.SetInfo
Set WebService = nothing
Set args=nothing
WScript.echo "DONE"
						
保存 Moveinstance.vbs。
在命令提示符下從您在上一步中保存該 .vbs 文件時的同一個位置運行此腳本。

例如,在命令提示符下,鍵入 cscript moveinstance.vbs 1 5。這會在配置數據庫中將 Web 站點的實例 ID 從 1 更改為 5。

注意:分配給 Web 站點的新實例 ID 一定不能已經分配給其他 Web 站點。這會導致不可靠的結果。

 

2.使用MetaEdit工具直接修改(http://support.microsoft.com/kb/240225):

MetaEdit

IIS 4.0 資源工具包介紹 MetaEdit 如下所示:
元數據庫編輯器 (MetaEdit) 是一個工具,它提供的功能類似於 Windows NT 注冊表編輯器。使用 MetaEdit,可以瀏覽和修改配置數據庫中的屬性。請注意,在使用 MetaEdit,您可以進行更改,可能會損壞您的 IIS 配置。請務必認真編輯所有項。
MetaEdit 是附帶可從 Microsoft Press IIS 4.0 資源工具包實用程序之一。

MetaEdit 文檔 (MetaEdit.doc) 位於 IIS 資源工具包光盤上的 \IIS Resource Kit\Utility\MetaEdit 文件夾中。

 

3.將站點A配置導出,在B服務器使用A導出的配置進行發布(需要保證A、B服務器上的結構、目錄 文件一致。)

 

 

另外:

  若要在A、B Web上負載,還必須保證A、B的 machineKey 配置一致,不然也會黃頁。

  我的配置:

<machineKey validationKey="3FF1E929BC0534950B0920A7B59FA698BD02DFE8" decryptionKey="280450BB36319B474C996B506A95AEDF9B51211B1D2B7A77" decryption="3DES" validation="SHA1"/>

  

  在整個過程中,發現好多問題,最終還是解決掉! 另外,一直不用SQLServer存儲Session的原因就是,那建庫腳本老是執行失敗。。

 

                                          by:MR.HAN  2014.05.23

  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM