在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。
服务器架构图
</div>