java高併發處理
在java web專案開發者,最難解決的是高併發問題,我為搞併發解決方案,想出了一個解決方案。
a.應用層面:讀寫分離、快取、佇列、叢集、令牌、系統拆分、隔離、系統升級(可水平擴容方向)。
b.時間換空間:降低單次請求時間,這樣在單位時間內系統併發就會提升。
c.空間換時間:拉長整體處理業務時間,換取後臺系統容量空間。
1.使用快取伺服器
使用Redis作為快取伺服器的,剛開始的時候會滿足需要,隨著專案的增大快取資料的增多就會查詢和插入更慢這時就要考慮Redis叢集方案了
使用Redis分散式要保證資料都能能夠平均的快取到每一臺機器,首先想到的做法是對資料進行分片,因為Redis是key-value儲存的,首先想到的是Hash分片,可能的做法是對key進行雜湊運算,得到一個long值對分散式的數量取模會得到一個一個對應
但是取模的hash演算法是有問題的如果叢集數量不變的話沒有什麼問題,一旦增加一臺機器或者一臺機器掛掉,導致機器數量變化,就會導致計算的出的資料庫對映亂掉,不能正確存取資料了。
因為這個問題引入我們說的一致性雜湊演算法,這個雜湊演算法具有的特徵
1.均衡性:也有人把它定義為平衡性,是指雜湊的結果能夠儘可能分佈到所有的節點中去,這樣可以有效的利用每個節點上的資源。
2.單調性:對於單調性有很多翻譯讓我非常的不解,而我想要的是當節點數量變化時雜湊的結果應儘可能的保護已分配的內容不會被重新分派到新的節點。
3.分散性和負載:這兩個其實是差不多的意思,就是要求一致性雜湊演算法對 key 雜湊應儘可能的避免重複。 一致性雜湊就資料結構是建立一個排序的環形資料結構,有許多個區域,先讓每一臺伺服器都分佈環上,取每一個伺服器的特效做雜湊執行,得到的值放進環中,進行排序這樣就能根據雜湊特徵找到對應的真是伺服器,能夠讓把伺服器平均的分佈到環上。 第一個特徵均衡性:就是儘量的讓資料平均的分部到每一個伺服器,不讓某臺機器壓力特別打,或者乾脆沒活幹,因為這個原因,我們的每一個伺服器都新增幾個虛擬伺服器,比如真是伺服器叫node1那麼第一個伺服器的虛擬伺服器就叫node1-1,node1-2...,根據這些特徵進行雜湊運算也分佈到環中,這樣就能把伺服器平均的分佈到環中。 第二個特徵單調性:因為伺服器都在環中,資料的key進行雜湊運算得到一個值,跟環中的伺服器的雜湊值進行比較,取離當前值最接近的雜湊值物件的伺服器,這樣就是獲取伺服器的原理了,我們是做了一個偷懶的工作,伺服器雜湊進行排序,以順時針方式得到一個剛好大於key雜湊的伺服器。 單調性是在不管新增節點還是刪除節點,原來對應的伺服器不變,因為這個環很大,伺服器是零星分佈的,這樣增加或者刪除一個節點只有受影響的都是當前節點,但是key對應的資料庫是不變的,也不能說不變,是把變化變得儘可能的小。第三個特徵分散性和負載:指伺服器在環中儘可能的分散,儘可能的讓資料平均分佈到不同的伺服器,我們就是使用虛擬節點的方式解決的。
public final class MurmurHash { public MurmurHash() { } private byte[] toBytesWithoutEncoding(String str) { int len = str.length(); int pos = 0; byte[] buf = new byte[len << 1]; for (int i = 0; i < len; i++) { char c = str.charAt(i); buf[pos++] = (byte) (c & 0xFF); buf[pos++] = (byte) (c >> 8); } return buf; } public int hashcode(String str) { byte[] bytes = toBytesWithoutEncoding(str); return hash32(bytes, bytes.length); } /** * * Generates 32 bit hash from byte array of the given length and * seed. * * * @param data byte array to hash * @param length length of the array * to hash * @param seed initial seed value * @return 32 bit hash of the * given array */ public int hash32(final byte[] data, int length, int seed) { // 'm' and 'r' are mixing constants generated offline. // They're not really 'magic', they just happen to work well. final int m = 0x5bd1e995; final int r = 24; // Initialize the hash to a random value int h = seed ^ length; int length4 = length / 4; for (int i = 0; i < length4; i++) { final int i4 = i * 4; int k = (data[i4 + 0] & 0xff) + ((data[i4 + 1] & 0xff) << 8) + ((data[i4 + 2] & 0xff) << 16) + ((data[i4 + 3] & 0xff) << 24); k *= m; k ^= k >>> r; k *= m; h *= m; h ^= k; } // Handle the last few bytes of the input array switch (length % 4) { case 3: h ^= (data[(length & ~3) + 2] & 0xff) << 16; case 2: h ^= (data[(length & ~3) + 1] & 0xff) << 8; case 1: h ^= (data[length & ~3] & 0xff); h *= m; } h ^= h >>> 13; h *= m; h ^= h >>> 15; return h; } /** * * Generates 32 bit hash from byte array with default seed value. * * @param * data byte array to hash * @param length length of the array to hash * @return * 32 bit hash of the given array */ public int hash32(final byte[] data, int length) { return hash32(data, length, 0x9747b28c); } public int hash32(final String data) { byte[] bytes = toBytesWithoutEncoding(data); return hash32(bytes, bytes.length, 0x9747b28c); } /** * * Generates 64 bit hash from byte array of the given length and seed. * * * @param data byte array to hash * @param length length of the array to * hash * @param seed initial seed value * @return 64 bit hash of the * given array */ public long hash64(final byte[] data, int length, int seed) { final long m = 0xc6a4a7935bd1e995L; final int r = 47; long h = (seed & 0xffffffffl) ^ (length * m); int length8 = length / 8; for (int i = 0; i < length8; i++) { final int i8 = i * 8; long k = ((long) data[i8 + 0] & 0xff) + (((long) data[i8 + 1] & 0xff) << 8) + (((long) data[i8 + 2] & 0xff) << 16) + (((long) data[i8 + 3] & 0xff) << 24) + (((long) data[i8 + 4] & 0xff) << 32) + (((long) data[i8 + 5] & 0xff) << 40) + (((long) data[i8 + 6] & 0xff) << 48) + (((long) data[i8 + 7] & 0xff) << 56); k *= m; k ^= k >>> r; k *= m; h ^= k; h *= m; } switch (length % 8) { case 7: h ^= (long) (data[(length & ~7) + 6] & 0xff) << 48; case 6: h ^= (long) (data[(length & ~7) + 5] & 0xff) << 40; case 5: h ^= (long) (data[(length & ~7) + 4] & 0xff) << 32; case 4: h ^= (long) (data[(length & ~7) + 3] & 0xff) << 24; case 3: h ^= (long) (data[(length & ~7) + 2] & 0xff) << 16; case 2: h ^= (long) (data[(length & ~7) + 1] & 0xff) << 8; case 1: h ^= (long) (data[length & ~7] & 0xff); h *= m; } ; h ^= h >>> r; h *= m; h ^= h >>> r; return h; } /** * * Generates 64 bit hash from byte array with default seed value. * * @param * data byte array to hash * @param length length of the array to hash * @return * 64 bit hash of the given string */ public long hash64(final byte[] data, int length) { return hash64(data, length, 0xe17a1465); } public long hash64(final String data) { byte[] bytes = toBytesWithoutEncoding(data); return hash64(bytes, bytes.length); } }
2.動靜態資源分離,緩解tomcat伺服器壓力
nginx 這個輕量級、高效能的 web server 主要可以幹兩件事情: 〉直接作為http server(代替apache,對PHP需要FastCGI處理器支援); 〉另外一個功能就是作為反向代理伺服器實現負載均衡 以下我們就來舉例說明如何使用 nginx 實現負載均衡。因為nginx在處理併發方面的優勢,現在這個應用非常常見。當然了Apache的 mod_proxy和mod_cache結合使用也可以實現對多臺app server的反向代理和負載均衡,但是在併發處理方面apache還是沒有 nginx擅長。 1)環境: a. 我們本地是Windows系統,然後使用VirutalBox安裝一個虛擬的Linux系統。 在本地的Windows系統上分別安裝nginx(偵聽8080埠)和apache(偵聽80埠)。在虛擬的Linux系統上安裝apache(偵聽80埠)。 這樣我們相當於擁有了1臺nginx在前端作為反向代理伺服器;後面有2臺apache作為應用程式伺服器(可以看作是小型的server cluster。;-) ); b. nginx用來作為反向代理伺服器,放置到兩臺apache之前,作為使用者訪問的入口; nginx僅僅處理靜態頁面,動態的頁面(php請求)統統都交付給後臺的兩臺apache來處理。 也就是說,可以把我們網站的靜態頁面或者檔案放置到nginx的目錄下;動態的頁面和資料庫訪問都保留到後臺的apache伺服器上。 c. 如下介紹兩種方法實現server cluster的負載均衡。 我們假設前端nginx(為127.0.0.1:80)僅僅包含一個靜態頁面index.html; 後臺的兩個apache伺服器(分別為localhost:80和158.37.70.143:80),一臺根目錄放置phpMyAdmin資料夾和test.php(裡面測試程式碼為print “server1“;),另一臺根目錄僅僅放置一個test.php(裡面測試程式碼為 print “server2“;)。 2)針對不同請求 的負載均衡: a. 在最簡單地構建反向代理的時候 (nginx僅僅處理靜態不處理動態內容,動態內容交給後臺的apache server來處理),我們具體的設定為:在nginx.conf中修改: 複製程式碼 程式碼如下: location ~ \.php$ { proxy_pass 158.37.70.143:80 ; } 〉 這樣當客戶端訪問localhost:8080/index.html的時候,前端的nginx會自動進行響應; 〉當用戶訪問localhost:8080/test.php的時候(這個時候nginx目錄下根本就沒有該檔案),但是通過上面的設定 location ~ \.php$(表示正則表示式匹配以.php結尾的檔案,詳情參看location是如何定義和匹配的 http://wiki.nginx.org/NginxHttpCoreModule) ,nginx伺服器會自動pass給 158.37.70.143的apache伺服器了。該伺服器下的test.php就會被自動解析,然後將html的結果頁面返回給nginx,然後 nginx進行顯示(如果nginx使用memcached模組或者squid還可以支援快取),輸出結果為列印server2。
伺服器架構圖