在java web項目開發者,最難解決的是高併發問題,我爲搞併發解決方案,想出了一個解決方案。
a.應用層面:讀寫分離、緩存、隊列、集羣、令牌、系統拆分、隔離、系統升級(可水平擴容方向)。
b.時間換空間:降低單次請求時間,這樣在單位時間內系統併發就會提升。
c.空間換時間:拉長整體處理業務時間,換取後臺系統容量空間。
1.使用緩存服務器
使用Redis作爲緩存服務器的,剛開始的時候會滿足需要,隨着項目的增大緩存數據的增多就會查詢和插入更慢這時就要考慮Redis集羣方案了
使用Redis分佈式要保證數據都能能夠平均的緩存到每一臺機器,首先想到的做法是對數據進行分片,因爲Redis是key-value存儲的,首先想到的是Hash分片,可能的做法是對key進行哈希運算,得到一個long值對分佈式的數量取模會得到一個一個對應數據庫的一個映射,沒有讀取就可以定位到這臺數據庫,那麼速度但然會提升了。
但是取模的hash算法是有問題的如果集羣數量不變的話沒有什麼問題,一旦增加一臺機器或者一臺機器掛掉,導致機器數量變化,就會導致計算的出的數據庫映射亂掉,不能正確存取數據了。
因爲這個問題引入我們說的一致性哈希算法,這個哈希算法具有的特徵
1.均衡性:也有人把它定義爲平衡性,是指哈希的結果能夠儘可能分佈到所有的節點中去,這樣可以有效的利用每個節點上的資源。
2.單調性:對於單調性有很多翻譯讓我非常的不解,而我想要的是當節點數量變化時哈希的結果應儘可能的保護已分配的內容不會被重新分派到新的節點。
第三個特徵分散性和負載:指服務器在環中儘可能的分散,儘可能的讓數據平均分佈到不同的服務器,我們就是使用虛擬節點的方式解決的。
- 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);
- }
- }
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。