大型互联网架构与集群技术(Java方向)

java架构必须掌握的几点技术?


关于学习架构,必须会的几点技术
1. java反射技术
2. xml文件处理
3. properties属性文件处理
4. 线程安全机制
5. annocation注解
6. 设计模式
7. 代理机制(aop)
8. serlvet基础(过滤器等等)
几样比较实用的技术:
1. 模板语言freemarker
2. ognl
3. gson json工具类
大家对于几大框架望而生畏,实际上只要明白他的原理,就会触类旁通,在这里我说说自己的几点拙见!
MVC层(Struts,Springmvc):
非常简单就是采用一个servlet实现统一跳转,配置文件保存了请求、处理类、转发请求的关系(通过xml文件或者注解)
操作流程:页面触发请求,框架通过读取配置文件获取到处理类,然后通过反射实例化该类,进行对应的业务处理
(现在流行的零配置,更是简化了mvc层的编写)
持久层(Mybatis/Hibernate):
mybatis其实就是sql+mybatis逻辑标签
逻辑标签的理解如何大家用过模板语言freemarker或者velocity应该会很容易理解,就是自定义的规则标签
Hibernate略显高级,也很简单,他是采用注解或者配置的方式将实体字段和表字段进行一个配置,在启动的时候动态生成sql
大家可以看下开源项目minidao,就会知道持久层原来写很简单;
所以架构可以简单理解为:
配置+反射+设计模式+脚本+AOP
配置常用做法:
1.xml文件
2.annocation注解
3.properties属性文件
4.零配置思想
脚本处理方法:

可以自己写,也可以采用模板语言freemarker等等



1.大型互联网架构的演化

前言

    一个成熟的大型网站(如淘宝、京东等)的系统架构并不是开始设计就具备完整的高性能、高可用、安全等特性,它总是随着用户量的增加,业务功能的扩展逐渐演变完善的,在这个过程中,开发模式、技术架构、设计思想也发生了很大的变化,就连技术人员也从几个人发展到一个部门甚至一条产品线。所以成熟的系统架构是随业务扩展而完善出来的,并不是一蹴而就;不同业务特征的系统,会有各自的侧重点,例如淘宝,要解决海量的商品信息的搜索、下单、支付,例如腾讯,要解决数亿的用户实时消息传输,百度它要处理海量的搜索请求,他们都有各自的业务特性,系统架构也有所不同。尽管如此我们也可以从这些不同的网站背景下,找出其中共用的技术,这些技术和手段可以广泛运行在大型网站系统的架构中,下面就通过介绍大型网站系统的演化过程,来认识这些技术和手段。

一、最开始的网站架构

    最初的架构,应用程序、数据库、文件都部署在一台服务器上,如图:

image

二、应用、数据、文件分离

    随着业务的扩展,一台服务器已经不能满足性能需求,故将应用程序、数据库、文件各自部署在独立的服务器上,并且根据服务器的用途配置不同的硬件,达到最佳的性能效果。

image

三、利用缓存改善网站性能

    在硬件优化性能的同时,同时也通过软件进行性能优化,在大部分的网站系统中,都会利用缓存技术改善系统的性能,使用缓存主要源于热点数据的存在,大部分网站访问都遵循28原则(即80%的访问请求,最终落在20%的数据上),所以我们可以对热点数据进行缓存,减少这些数据的访问路径,提高用户体验。

251844453265971

    缓存实现常见的方式是本地缓存、分布式缓存。当然还有CDN、反向代理等,这个后面再讲。本地缓存,顾名思义是将数据缓存在应用服务器本地,可以存在内存中,也可以存在文件,OSCache就是常用的本地缓存组件。本地缓存的特点是速度快,但因为本地空间有限所以缓存数据量也有限。分布式缓存的特点是,可以缓存海量的数据,并且扩展非常容易,在门户类网站中常常被使用,速度按理没有本地缓存快,常用的分布式缓存是Memcached、Redis。

四、使用集群改善应用服务器性能

    应用服务器作为网站的入口,会承担大量的请求,我们往往通过应用服务器集群来分担请求数。应用服务器前面部署负载均衡服务器调度用户请求,根据分发策略将请求分发到多个应用服务器节点。

251844471702801

    常用的负载均衡技术硬件的有F5,价格比较贵,软件的有LVS、Nginx、HAProxy。LVS是四层负载均衡,根据目标地址和端口选择内部服务器,Nginx是七层负载均衡和HAProxy支持四层、七层负载均衡,可以根据报文内容选择内部服务器,因此LVS分发路径优于Nginx和HAProxy,性能要高些,而Nginx和HAProxy则更具配置性,如可以用来做动静分离(根据请求报文特征,选择静态资源服务器还是应用服务器)。

五、数据库读写分离和分库分表

    随着用户量的增加,数据库成为最大的瓶颈,改善数据库性能常用的手段是进行读写分离以及分表,读写分离顾名思义就是将数据库分为读库和写库,通过主备功能实现数据同步。分库分表则分为水平切分和垂直切分,水平切换则是对一个数据库特大的表进行拆分,例如用户表。垂直切分则是根据业务不同来切换,如用户业务、商品业务相关的表放在不同的数据库中。

260851219209749

六、使用CDN和反向代理提高网站性能

  假如我们的服务器都部署在成都的机房,对于四川的用户来说访问是较快的,而对于北京的用户访问是较慢的,这是由于四川和北京分别属于电信和联通的不同发达地区,北京用户访问需要通过互联路由器经过较长的路径才能访问到成都的服务器,返回路径也一样,所以数据传输时间比较长。对于这种情况,常常使用CDN解决,CDN将数据内容缓存到运营商的机房,用户访问时先从最近的运营商获取数据,这样大大减少了网络访问的路径。比较专业的CDN运营商有蓝汛、网宿。

  而反向代理,则是部署在网站的机房,当用户请求达到时首先访问反向代理服务器,反向代理服务器将缓存的数据返回给用户,如果没有没有缓存数据才会继续走应用服务器获取,也减少了获取数据的成本。反向代理有Squid,Nginx。

260851254513595

七、使用分布式文件系统

    用户一天天增加,业务量越来越大,产生的文件越来越多,单台的文件服务器已经不能满足需求。需要分布式的文件系统支撑。常用的分布式文件系统有NFS。

260851282647353

八、使用NoSql和搜索引擎

    对于海量数据的查询,我们使用nosql数据库加上搜索引擎可以达到更好的性能。并不是所有的数据都要放在关系型数据中。常用的NOSQL有mongodb和redis,搜索引擎有lucene。

260851321075527

九、将应用服务器进行业务拆分

    随着业务进一步扩展,应用程序变得非常臃肿,这时我们需要将应用程序进行业务拆分,如百度分为新闻、网页、图片等业务。每个业务应用负责相对独立的业务运作。业务之间通过消息进行通信或者同享数据库来实现。

260851352481788

 

十、搭建分布式服务

    这时我们发现各个业务应用都会使用到一些基本的业务服务,例如用户服务、订单服务、支付服务、安全服务,这些服务是支撑各业务应用的基本要素。我们将这些服务抽取出来利用分部式服务框架搭建分布式服务。淘宝的Dubbo是一个不错的选择。

260851397174320

小结

    大型网站的架构是根据业务需求不断完善的,根据不同的业务特征会做特定的设计和考虑,本文只是讲述一个常规大型网站会涉及的一些技术和手段。

载自http://www.cnblogs.com/Creator/

Mysql在网站架构中的演变

V1.0  简单网站架构

一个简单的小型网站或者应用背后的架构可以非常简单,  数据存储只需要一个mysql instance就能满足数据读取和写入需求(这里忽略掉了数据备份的实例),处于这个时间段的网站,一般会把所有的信息存到一个database instance里面。

在这样的架构下,我们来看看数据存储的瓶颈是什么
1.数据量的总大小  一个机器放不下时
2.数据的索引(B+ Tree)一个机器的内存放不下时
3.访问量(读写混合)一个实例不能承受

只有当以上3件事情任何一件或多件满足时,我们才需要考虑往下一级演变。 从此我们可以看出,事实上对于很多小公司小应用,这种架构已经足够满足他们的需求了,初期数据量的准确评估是杜绝过度设计很重要的一环,毕竟没有人愿意为不可能发生的事情而浪费自己的经历。


这里简单举个我的例子,对于用户信息这类表 (3个索引),16G内存能放下大概2000W行数据的索引,简单的读和写混合访问量3000/s左右没有问题,你的应用场景是否

V2.0 垂直拆分

一般当V1.0 遇到瓶颈时,首先最简便的拆分方法就是垂直拆分,何谓垂直?就是从业务角度来看,将关联性不强的数据拆分到不同的instance上,从而达到消除瓶颈的目标。以图中的为例,将用户信息数据,和业务数据拆分到不同的三个实例上。对于重复读类型比较多的场景,我们还可以加一层cache,来减少对DB的压力。

在这样的架构下,我们来看看数据存储的瓶颈是什么?

1.单实例单业务 依然存在V1.0所述瓶颈

遇到瓶颈时可以考虑往本文更高V版本升级, 若是读请求导致达到性能瓶颈可以考虑往V3.0升级, 其他瓶颈考虑往V4.0升级

 

V3.0  主从架构

此类架构主要解决V2.0架构下的读问题,通过给Instance挂数据实时备份的思路来迁移读取的压力,在Mysql的场景下就是通过主从结构,主库抗写压力,通过从库来分担读压力,对于写少读多的应用,V3.0主从架构完全能够胜任

在这样的架构下,我们来看看数据存储的瓶颈是什么?
1.写入量主库不能承受

V4.0  水平拆分

对于V2.0 V3.0方案遇到瓶颈时,都可以通过水平拆分来解决,水平拆分和垂直拆分有较大区别,垂直拆分拆完的结果,在一个实例上是拥有全量数据的,而水平拆分之后,任何实例都只有全量的1/n的数据,以下图Userinfo的拆分为例,将userinfo拆分为3个cluster,每个cluster持有总量的1/3数据,3个cluster数据的总和等于一份完整数据(注:这里不再叫单个实例 而是叫一个cluster 代表包含主从的一个小mysql集群)

 

数据如何路由?

1.Range拆分

 sharding key按连续区间段路由,一般用在有严格自增ID需求的场景上,如Userid, Userid Range的小例子:以userid 3000W 为Range进行拆分   1号cluster  userid 1-3000W  2号cluster  userid   3001W-6000W

2.List拆分

List拆分与Range拆分思路一样,都是通过给不同的sharding key来路由到不同的cluster,但是具体方法有些不同,List主要用来做sharding key不是连续区间的序列落到一个cluster的情况,如以下场景:
假定有20个音像店,分布在4个有经销权的地区,如下表所示:

地区

商店ID 号

北区

3, 5, 6, 9, 17

东区

1, 2, 10, 11, 19, 20

西区

4, 12, 13, 14, 18

中心区

7, 8, 15, 16


业务希望能够把一个地区的所有数据组织到一起来搜索,这种场景List拆分可以轻松搞定

3.Hash拆分

通过对sharding key 进行哈希的方式来进行拆分,常用的哈希方法有除余,字符串哈希等等,除余如按userid%n 的值来决定数据读写哪个cluster,其他哈希类算法这里就不细展开讲了。


数据拆分后引入的问题:

数据水平拆分引入的问题主要是只能通过sharding key来读写操作,例如以userid为sharding key的切分例子,读userid的详细信息时,一定需要先知道userid,这样才能推算出再哪个cluster进而进行查询,假设我需要按username进行检索用户信息,需要引入额外的反向索引机制(类似HBASE二级索引),如在redis上存储username->userid的映射,以username查询的例子变成了先通过查询username->userid,再通过userid查询相应的信息。
实际上这个做法很简单,但是我们不要忽略了一个额外的隐患,那就是数据不一致的隐患。存储在redis里的username->userid和存储在mysql里的userid->username必须需要是一致的,这个保证起来很多时候是一件比较困难的事情,举个例子来说,对于修改用户名这个场景,你需要同时修改redis和mysql,这两个东西是很难做到事务保证的,如mysql操作成功 但是redis却操作失败了(分布式事务引入成本较高),对于互联网应用来说,可用性是最重要的,一致性是其次,所以能够容忍小量的不一致出现. 毕竟从占比来说,这类的不一致的比例可以微乎其微到忽略不计(一般写更新也会采用mq来保证直到成功为止才停止重试操作)

在这样的架构下,我们来看看数据存储的瓶颈是什么?
在这个拆分理念上搭建起来的架构,理论上不存在瓶颈(sharding key能确保各cluster流量相对均衡的前提下),不过确有一件恶心的事情,那就是cluster扩容的时候重做数据的成本,如我原来有3个cluster,但是现在我的数据增长比较快,我需要6个cluster,那么我们需要将每个cluster 一拆为二,一般的做法是
1.摘下一个slave,停同步, 
2.对写记录增量log(实现上可以业务方对写操作 多一次写持久化mq  或者mysql主创建trigger记录写 等等方式)
3.开始对静态slave做数据, 一拆为二
4.回放增量写入,直到追上的所有增量,与原cluster基本保持同步
5.写入切换,由原3 cluster 切换为6cluster 

有没有类似飞机空中加油的感觉,这是一个脏活,累活,容易出问题的活,为了避免这个,我们一般在最开始的时候,设计足够多的sharding cluster来防止可能的cluster扩容这件事情

V5.0  云计算 腾飞(云数据库)  

云计算现在是各大IT公司内部作为节约成本的一个突破口,对于数据存储的mysql来说,如何让其成为一个saas(Software as a Service)是关键点。在MS的官方文档中,把构建一个足够成熟的SAAS(MS简单列出了SAAS应用的4级成熟度)所面临的3个主要挑战:可配置性,可扩展性,多用户存储结构设计称为"three headed monster". 可配置性和多用户存储结构设计在Mysql saas这个问题中并不是特别难办的一件事情,所以这里重点说一下可扩展性。

Mysql作为一个saas服务,在架构演变为V4.0之后,依赖良好的sharding key设计, 已经不再存在扩展性问题,只是他在面对扩容缩容时,有一些脏活需要干,而作为saas,并不能避免扩容缩容这个问题,所以只要能把V4.0的脏活变成 1. 扩容缩容对前端APP透明(业务代码不需要任何改动)  2.扩容缩容全自动化且对在线服务无影响 那么他就拿到了作为Saas的门票.

对于架构实现的关键点,需要满足对业务透明,扩容缩容对业务不需要任何改动,那么就必须eat our own dog food,在你mysql saas内部解决这个问题,一般的做法是我们需要引入一个Proxy,Proxy来解析sql协议,按sharding key 来寻找cluster, 判断是读操作还是写操作来请求主 或者 从,这一切内部的细节都由proxy来屏蔽。
这里借淘宝的图来列举一下proxy需要干哪些事情

百度公开的技术方案中也有类似的解决方案,见文章最后资料部分链接


对于架构实现的关键点,扩容缩容全自动化且对在线服务无影响; 扩容缩容对应到的数据操作即为数据拆分和数据合并,要做到完全自动化有非常多不同的实现方式,总体思路和V4.0介绍的瓶颈部分有关,目前来看这个问题比较好的方案就是实现一个伪装slave的sync slave, 解析mysql同步协议,然后实现数据拆分逻辑,把全量数据进行拆分。具体架构见下图:

其中Sync slave对于Original Master来说,和一个普通的Mysql Slave没有任何区别,也不需要任何额外的区分对待。需要扩容/缩容时,挂上一个Sync slave,开始全量同步+增量同步,等待一段时间追数据。以扩容为例,若扩容后的服务和扩容前数据已经基本同步了,这时候如何做到切换对业务无影响? 其实关键点还是在引入的proxy,这个问题转换为了如何让proxy做热切换后端的问题。这已经变成一个非常好处理的问题了.


网站的性能问题?   


本文将讲述大型网站中一个重要的要素,性能。

什么是性能

    有人说性能就是访问速度快慢,这是最直观的说法,也是用户的真实体验。一个用户从输入网址到按下回车键,看到网页的快慢,这就是性能。对于我们来说,需要去挖掘这个过程,因为这决定我们怎么去做性能优化。

这中间发生了什么?

292005527847777副本

    用户访问网站的整个流程:用户输入网站域名,通过DNS解析,找到目标服务器IP,请求数据经互联网达到目标服务器,目标服务器收到请求数据,进行处理(执行程序、访问数据库、文件服务器等)。处理完成,将响应数据又经互联网返回给用户浏览器,浏览器得到结果进行计算渲染显示给用户。

我们把整个过程,分为三段路径:

1、第一段在用户和浏览器端,主要负责发出用户请求,以及接受响应数据进行计算渲染显示给用户;

2、第二段在网络上,负责对请求数据、响应数据的传输;

3、第三段在网站服务器端,负责对请求数据进行处理(执行程序、访问数据库、文件等),并将结果返回;

第一路径

    第一路径花费的时间包括输入域名发起请求的时间和浏览器收到响应后计算渲染的时间。

输入域名发起请求,实质过程是:

1、用户在浏览器输入要访问的网站域名;

2、本地DNS请求网站授权的DNS服务器对域名进行解析,并得到解析结果即IP地址(并将IP地址缓存起来)。

3、向目标IP地址发出请求。

    从这个过程我们可以看到,优化的地方主要是减少DNS解析次数,而如果用户浏览器设置了缓存,则再第二次访问相同域名的时候就不会去请求DNS服务器,直接用缓存中的IP地址发出请求。因此这个过程主要取决于浏览器的设置。现在主流的浏览器默认设置了DNS的预取功能(DNS Prefetch),当然你也可以主动告知浏览器我的网站需要做DNS预取:

<meta http-equiv="x-dns-prefetch-control" content="on" />

浏览器将数据进行计算渲染的过程:

1、浏览器解析响应数据;

2、浏览器创建DOM树;

3、浏览器下载CSS样式,并应用到DOM树,进行渲染;

4、浏览器下载JS文件,开始解析执行;

5、显示给用户。

    从这个过程,我们可以找出不少可以优化的地方。首先我们可以尽量控制页面大小,使得浏览器解析的时间更短;并且将多个CSS文件、JS文件文件合并压缩减少文件下载的次数和大小;另外注意将CSS放在页面前面,JS访问页面后面,这样便于页面首先能渲染出来,再执行js脚本,对于用户来说有更好的体验。最后我还可以设置浏览器缓存,下次访问时从缓存读取内容,减少http请求。

<meta http-equiv="Cache-Control" content="max-age=5" />

该代码说明了浏览器启用了缓存并在5秒内不会再次访问服务器。注意缓存的设置需要结合你的业务特性来适当配置。

以下是京东商城的HTML简图:

css样式放在html前面,并且进行了合并。

image

大多数的JS文件放在页尾。

image

 

第二路径

    第二路径在网络上,花费的时间同样包括请求数据的传输时间和响应数据的传输时间,这个两个时间取决于数据传输的速度,这里我们要讲一个名词“带宽”。什么是带宽,我们经常说带宽10M,20M是什么意思?我的带宽20M,这意味着什么?我们知道带宽速度分为上行、下行速度,也就是上传和下载的速度。带宽20M对于用户来说则是下载速度20M(20×1024×1024比特率),换算成字节20M/8=2.5M。也就是说20M的带宽下载速度理论可达2.5M/s,而对于家庭用户而言上传速度一般比下载速度小的多,大约是不到十分之一。而对于网站服务器(企业用户)来说,则不然,一般上行速度等于下载速度。这也是运营商根据实际需求分配的,毕竟用户的主要需求是下载数据,而不是上传数据。

    整个流程从传输方式看就是:用户发送请求数据(上传),网站服务器接受请求数据(下载),网站服务器返回响应数据(上传),用户接受响应数据(下载)。对于用户来说,上传数据是很小的(Url参数),而下载数据是较大的(响应数据);对于服务器来说,下载数据是很小的(url参数),上传数据是较大(响应数据)。理解了这个,我们可以解释为什么有时用户反映为什么自己的带宽足够,但打开某些网站仍然很慢,就是因为尽管用户的下载速度很快,但网站服务器的上传速度很慢,这就像一个抽水管和一个出水管,不管抽水管再大,但出水管很小,同样抽到的水量是有限的。了解了这个原理我们来看怎么提高数据传输的速度,首先用户的上传、下载速度我们是无法决定的,我们能决定的是网站服务器的上传、下载速度,所以我们可以做的是适当的增加服务器带宽(带宽是很贵的,盲目的增加只会增加不必要成本)。购买合适的带宽需要根据网站业务特性、规模以及结合运维人员的经验来选择。通常可以考虑的算法,即根据一次响应数据的大小,乘以PV数,除以对应的高峰时间段,从而大致估算出网站带宽的需求。

下面我们继续进一步研究第二路径:

292005539873479副本

    上图表示用户访问网站服务器时网络的大致情况,从图上可以看出假设网站服务器从电信网络接入,而用户A作为电信的宽带用户,则可以通过电信骨干网快速的访问到网站服务器。用户B,用户C作为移动和联通用户需要通过运营商的互联互通经过较长路径才能访问到服务器。

针对这种情况,我们可以采取以下方法来优化:

1、在各运营商发达的地区的IDC(互联网数据中心,可以理解成机房)部署网站服务器,各运营商的用户即可通过各自的骨干网访问服务器。

2、购买代理服务,也就是原来联通用户需要通过联通骨干网——>联通互联互通路由器——>电信骨干网——>网站服务器的过程。通过代理服务,代理服务器直连到电信骨干网,访问网站服务器。

2、在主要地区城市购买CDN服务,缓存对应的数据,用户可先从最近的CDN运营商获取请求数据。

第三路径

第三路径主要是网站服务器内部处理的过程,当中包括执行程序、访问文件、数据库等资源。

这是对于我们来说最可以发挥的地方:

1、使用缓存,根据需要使用本地缓存或分布式缓存;

2、使用异步操作,这种方式不仅可以提高性能,也提高了系统的扩展性;

3、代码优化;

4、存储优化;

缓存

如果缓存数据较少,可以利用OSCache实现本地缓存:

image

当缓存数据过多时,利用Memcached实现分布式缓存:

image

Memcached实现分布式缓存,缓存服务器之间是互不通信的,也就是我们可以方便的通过增加Memcached服务器对系统进行扩展。

异步操作

292005574722311副本

    使用同步请求的方式,在高并发的情况下,会对数据库造成很大的压力,也会让用户感觉响应时间过长。异步请求方式,则可以快速的对用户做出响应,而具体的数据库操作请求,则通过消息队列服务器发送给数据库服务器,做具体的插入操作。插入操作的结果则已其他方式通知客户端。例如一般在订票系统当中,出票行为就是异步完成,最终的出票结果会以邮件或其他方式告知用户。

代码优化

这里就不在详细描述,另一篇随笔《怎样编写高质量的java代码》对代码质量和风格做过大致的介绍,有兴趣可以看一下。

存储优化

大型网站中海量的数据读写对磁盘造成很大压力,系统最大的瓶颈还是在磁盘的读写。可以考虑使用磁盘阵列、分布式储存来改善存储的性能。

性能的指标和测试

    上面通过解析用户访问网站的过程来思考怎么提高用户感知的性能,对于用户来言性能就是快和慢。但对于我们来说,不能这样简单描述,我们需要去量化他,用一些数据指标去衡量它。这里讲到几个名词:响应时间、并发量、吞吐量。

响应时间:就是用户发出请求到收到响应数据的时间;

并发量:就是系统同时能处理多少用户请求;

吞吐量:就是单位时间内系统处理的请求数量;

    为了通俗的了解这三个概念,我们以高速公路的收费站为例子:响应时间是指一辆车经过收费站的时间,也就是车辆从进入收费站、付钱、开闸、离开收费站的时间;并发量是指这个收费站同时能通行多少辆车,可以理解为收费站的出口数量。吞吐量是指:在一段时间内,这个收费站通往了多少了车。

这个例子不晓得恰不恰当。

对于性能测试来说,基本也是围绕这些方面来测试,下图说明了性能测试的过程:

image

    左图表示响应时间和并发用户量的二维坐标图,从图上可以看出,并发用户量在一定量增加时,响应时间很短,并且没有太大的起伏,这表示系统目前处于日常运行期,可以很快处理用户请求(A点之前);随着并发量的增加,系统处于请求高峰期,但仍然可以有序的处理用户请求,响应时间较日常有所增加(A、B之间);当并发量增加到一定数量时,超过了系统的负载能力,系统处于濒临崩溃的边缘(B、C之间),响应时间严重过长,直到系统崩溃。

    右图表示吞吐量与并发用户量的二维坐标图,可以看出,随着并发用户量的增加,吞吐量逐渐增加;在并发量到达一定量时,由于系统处理能力达到最大,吞吐量增加放缓;当并发量超过系统负载时(E点),系统处理能力开始下降,不能再请求增加的用户请求,吞吐量反而降低。

小结

    本文通过用户访问网站的过程,分析了三个路径过程中提高性能的想法和手段,最后介绍了描述性能的指标,并对性能测试做了简要说明。


2.Java集群


越来越多的关键应用运行在J2EE(Java 2, Enterprise Edition)中,这些诸如银行系统和账单处理系统需要高的可用性(High Availability, HA),同时像Google和Yahoo这种大系统需要大的伸缩性。高可用性和伸缩性在今天高速增长的互连接的世界的重要性已经证实了。eBay于 1999年6月停机22小时的事故,中断了约230万的拍卖,使eBay的股票下降了9.2个百分点。

J2EE集群是用来提供高可用性和伸缩性服务,同时支持容错处理的一种流行的技术。但是,由于J2EE规范缺乏对集群的支持,J2EE供应商实现集群的方法也各异。这给J2EE架构师和开发人员带来了很多困难。以下是几个常见的问题:

  • 为什么带集群功能的商业J2EE服务器产品如此昂贵?(10倍于不带集群功能的产品)
  • 为什么基于单服务器环境构建的应用不能在集群中运行?
  • 为什么应用在集群环境中运行得很慢,但在非集群环境中却快得多?
  • 为什么集群的应用移植到其他服务器中失败?

理解这些限制和要素的最佳方法是学习他们的实现方式。

基本术语

在我们讨论不同的集群实现之前,先谈谈几个概念。这有助于理解不同的J2EE集群产品不同的设计结果和概念:

伸缩性(Scalability):

在一些大的系统中,预测最终用户的数量和行为是非常困难的,伸缩性是指系统适应不断增长的用户数的能力。提高这种并发会话能力的一种最直观的方式就 增加资源(CPU,内存,硬盘等),集群是解决这个问题的另一种方式,它允许一组服务器组在一起,像单个服务器一样分担处理一个繁重的任务。

高可用性(High availability):

单一服务器的解决方案并不是一个健壮方式,因为容易出现单点失效。像银行、账单处理这样一些关键的应用程序是不能容忍哪怕是几分钟的死机。它们需要 这样一些服务在任何时间都可以访问并在可预期的合理的时间周期内有响应。集群方案通过在集群中增加的冗余的服务器,使得在其中一台服务器失效后仍能提供服 务,从而获得高的可用性。

负载均衡(Load balancing):

负载均衡是集群的一项关键技术,通过把请求分发给不同的服务器,从而获得高可用性和较好的性能。一个负载均衡器可以是从一个简单的Servlet或 Plug-Ins(例如一个Linux box利用ipchains来实现),到昂贵的内置SSL加速器的硬件。除此之外,负载均衡器还需执行一些其他的重要任务,如“会话胶粘”让一个用户会话 始终存在一个服务器上,“健康检查”用于防止将请求分发到已失效的服务器上。有些负载均衡器也会参与我们下面将要谈到“失效转移”过程。

容错(Fault tolerance):

高可用性意味着对数据正确性的要求不那么高。在J2EE集群中,当一个服务器实例失效后,服务仍然是有效的,这是因为新的请求将被冗余服务器处理。 但是,当一个请求在一个正在失效的服务器中处理时,可能得到不正确的结果。不管有多少个错误,容错的服务应当能确保有严格的正确的行为。

失效转移(Failover):

失效转移是集群中用来获取容错能力的另一项关键的技术。当一个结点失效后,通过选择集群中的另一个结点,处理将会继续而不会终止。转移到另一个结点可以被显式的编码,或是通过底层平台自动地透明地路由到另一个服务器。

等幂方法(Idempotent methods):

等幂方法是指这样一些方法:重复用相同的参数调用都能得到相同的结果。这些方法不会影响系统状态,可以重复调用而不用担心改变系统。例 如:getUsername()就是等幂的,而deleteFile就不是。当我们讨论HTTP Session失效转移和EJB失效转移时,它是一个重要的概念。

什么是J2EE集群

一个天真的问题,不是吗?但我仍要用几句话和图来回答它。通常,J2EE集群技术包括"负载均衡"和"失效转移"。

 

 
图 1  负载均衡

如图1所示,负载均衡意味着有许多客户端向目标对象同时发出请求。负载均衡器在调用者和被调用者之间,分发请求到与原始对象相同的冗余对象中。伸缩性和高可用性就是这样得到的。

 

 
图 2  失效转移

如图2所示,失效转移与负载均衡不同。有时客户端会连续发请求到目标对象,如果请求中间目标对象失效了,失效转移系统将检测到这次失败,并将请求重定向到另一个可用的对象。通过这种方式可以获得容错能力。

如果你想知道更多的有关J2EE集群的知识,你就会问到一个基本的问题,“什么对象可以集群?”和“在我的J2EE代码中哪里会发生负载均衡和失效 转移呢?”。这些都是用来理解J2EE集群的非常好的问题。实际上,并不是所有的对象都能被集群的,并且负载均衡和失效转移并不是在J2EE代码所有地方 都能发生。看看下面的例子代码:

 

 
图 3  例子代码

在Class A的bussiness()方法中,instance1可以负载均衡吗?或是当其失效,可以失效转移到其他B的实例上吗?我想是不行的!对负载均衡和失效 转移来说,必须要有个拦截器在调用者和被调用者之间分发或重定向请求到不同的对象上。Class A和Class B的实例是运行在一个JVM中紧密耦合的,在方法调用间加入分发逻辑非常困难。

什么类型对象可以被集群?——只有那些可以被部署到分布式拓朴结构中的组件。

在我的J2EE代码中,什么地方会有负载均衡和失效转移?——只在你调用分布式组件的方法时。

 

 
图 4  分布式对象

在如图4所示的分布式环境中,调用者和被调用者被分离在有明显边界的不同的运行容器中,这个边界可以是JVM,进程和机器。

当目标对象被客户端调用时,目标对象的功能是在容器中运行的(这就是为什么我们说它是分布式的原因)。客户端和目标对象通过标准的网络协议通信。这些特性就为一些机制提供了机会可以介入到方法调用之间实现负载均衡和失效转移。

如图4,浏览器通过HTTP协议调用JSP对象,JSP运行在WEB服务器中,浏览器只需要返回结果而不关心它是怎么运行的。在上述场景中,一些东 西就可以在浏览器与WEB服务器之间实现负载均衡和失效转移的功能。在J2EE平台,分布式技术包 括:JSP(Servlet),JDBC,EJB,JNDI,JMS,WEB Service等。负载均衡和失效转移就发生在这些分布式方法被调用时。在后续部分我们将详细讨论这些技术。

 

4 WEB层集群实现
WEB层集群是J2EE集群的重要且基本的功能。WEB集群技术包括WEB负载均衡和HTTP Session失效转移。

4.1 WEB负载均衡 
J2EE提供商实现WEB负载均衡有许多方式。基本上,都一个负载均衡器被插入到浏览器和WEB服务器之间,如下图所示。

 

 
图 5  WEB负载均衡

负载均衡器可以是一台硬件,如F5负载均衡器,或仅仅是另一台有负载均衡Plug-Ins的WEB服务器,一个简单的带ipchains的Linux box可以很好的实现负载均衡。不管采用哪种技术,负载均衡器都有以下特性:

4.1.1 实现负载均衡算法

当客户请求到来时,负载均衡器需要决定将如何分发到后台服务器。流行的算法是Round-Robin、Random和Weight Based。负载均衡器尽力使每台服务器实例都获得相同的负载,但是上述算法没有一个可以获得理想的负载相同,因为它们仅仅是依据发送到特定服务器的请求 的个数。一些精密的负载均衡器实现了特殊的算法。它在分发请求之前将检测服务器的工作负载。

  • 健康检测

当一台服务器失效了,负载均衡器应当检测出失效并不再将请求分发到这台服务器上。同样,它也要检测服务器是否恢复正常,并恢复分发请求。

  • 会话胶粘

几乎所有的WEB应用程序都有一些会话状态,可能是简单的记住用户是否登陆,或是包含你的购物车信息。因为HTTP本身是无状态的,会话状态应当存 在服务器的某个地方并与你当前浏览会话相关联,这样当你下次再请求相同WEB应用程序的页面时可以很容易的重新获取。当负载均衡时,最佳的选择就是将特定 的浏览器会话分发到上次相同的服务器实例中,否则,应用程序可能不能正确工作。

因为会话状态存储在特定WEB服务器的内存中,“会话胶粘”对于负荷均衡非常重要。但是,如果其中某台服务器实例因为某种原因失效了(比如关机), 那么这台服务器的会话状态将要丢失。负载均衡器应当检测到这个失效并不再将请求分发给它,但这些请求的会话状态都因为存放在失效的服务器中而丢失了所有信 息,这就将导致错误。会话的失效转移因此而生。

4.2 HTTP Session失效转移 
几乎所有流行 的J2EE供应商都在他们的集群产品中实现了Http Session失效转移,用来保障当某台服务器失效后会话状态不会丢失,使客户端请求能被正确处理。如图6所示,当浏览器访问有状态的WEB应用程序(第 1 ,2步),这个应用程序可能在内存创建了会话对象用于保存信息以供后面的请求使用,同时,发送给浏览器一个唯一的HTTP Session ID用于标识这个会话对象(第3步),浏览器将这个ID保存Cookie中,并当它下次再请求同一WEB应用程序的页面时,会将Cookie发还给服务 器。为了支持会话失效转移,WEB服务器将在一定的时候把会话对象备份到其他地方以防止服务器失效后丢失会话信息(第4步)。负载均衡器检测到这个失败 (第5,6步),并将后续的请求分发到装有相同应用程序的服务器实例中(第7步),由于会话对象已经备份到其他地方了,这个新的服务器实例可以恢复会话 (第8步)正确地处理请求。

 

 
图 6  HTTP Session失效转移

为了实现上述功能,HTTP Session失效转移将带来以下问题:

  • 全局HTTP Session ID

如上所述,HTTP Session ID用于在特定的服务器实例中标识唯一的内存会话对象,在J2EE平台,HTTP Session ID依赖于JVM实例,每个JVM实例拥有几个应用程序,每个应用程序都为不同的用户管着许多会话,HTTP Session ID是在当前JVM实例用于访问相关会话的关键。在会话失效转移的实现中,要求不同的JVM实例不能产生两个相同的HTTP Session ID,这是因为当失效转移发生时,一个JVM的会话将要备份并恢复到另一个中,这样,必须建立全局HTTP Session ID产生机制。

  •  如何备份会话状态

如何备份会话状态是区别J2EE服务器好坏的关键因素。不同的供应商有不同的实现,在后续部分我再详细解释。

  • 备份的频率和粒度

会话的备份是消耗性能的,包括CPU,内存,网络带宽和写入磁盘和数据库的I/O,备份的频率和备份对象的粒度将严重影响性能。

4.2.1 数据库备份方式

几乎所有的J2EE集群产品都允许选择将你的会话对象通过JDBC备份到关系数据库中。如图7所示,这种方式可以让服务器实例非常简单的在正确的时 间序列化会话内容并写到数据库中。当发生会话转移时,另一台可用的服务器接过已失效的服务器工作,从数据库中恢复所有的会话状态。序列化对象是关键点,它 使得内存会话数据可以持久化和传输。要了解更多有关Java对象序列化知识,请参考http://java.sun.com/j2se/1.5.0/docs/guide/serialization/index.html 。

 

 
图 7  备份会话数据到数据库

由于数据库交易是非常昂贵的,这种方法主要缺点是当在会话中保存大量的或大的对象时限制了伸缩性,大多数使用数据库会话持久化的服务器产品都提倡尽量减少用HTTP会话存储对象,但这限制了你的应用程序的架构和设计,特别是你要使用HTTP会话缓存用户数据时。

数据库的方式也有一些优点:

  • 简单,容易实现。分离的请求处理和会话备份处理使集群更好管理和健壮。
  • 会话可以失效转移到任何一台服务器,因为数据库是共享的。
  • 当整个集群失效时,会话数据依旧幸免。

4.2.2 内存复制方式 
因为性能的原因,一些J2EE服务器(Tomcat,Jboss,WebLogic,WebSphere)提供了另一种实现:内存复制

 

 
图 8  对会话状态进行内存复制

基于内存的会话持久化将会话信息保存在一台或是多台备份服务器中,而不是保存数据库中(如图8)。这种方式因为性能高而非常流行。同数据库方式相 比,直接在原服务器和备份服务器之间网络通信是非常轻量的。同时注意在使用方式中,数据库方式中的“恢复”阶段是不需要的,因为在备份后,所有会话数据都 已经存在备份服务器的内存中了,已经可以处理请求。

“Java Groups”是当前Tomcat和Jboss集群所使用的通信层。Java Groups是用于实现可靠组通信和管理的工具包。它提供了诸如“组成员协议”和“消息广播”等核心特性,这些都对集群的工作非常有用。有关Java Groups的信息,请参考:http://www.jgroups.org/javagroupsnew/docs/index.html 。

4.2.3 Tomcat方式:多服务器复制

内存复制也存在许多不同的方式,第一种方法就是将会话数据复制到集群中的所有结点,Tomcat5采用的就是这种方式。

 

 
图9  多服务器复制

如图9所示,当一个服务器实例的会话改变后,将备份到其他所有的服务器上。当一台服务器失效后,负载均衡器可以选择其他任何一台可用的服务器实例。 但这种方式限制了伸缩性,如果集群中有很多的服务器实例,那么网络通信的代价就不能被忽略,这将严重降低性能,并且网络也将成为系统的瓶颈。

4.2.4 WebLogic,Jboss和Websphere的方式:对等服务器复制

由于性能和伸缩性的原因,WebLogic,Jboss和Webshpere采用了其他方式实现内存复制。每台服务器任意选择一台服务器备份其内存中的会话信息。如图10所示。

在这种方式中,每台服务器都有一台自己的对等服务器,而不是其他所有的服务器,这种方式消除在集群中加入过多服务器实例的话影响伸缩性的问题。

 

 
图 10  对等服务器复制

尽管这种方式实现失效转移有很高的性能和伸缩性,但它仍有一些限制:

  • 它给负载均衡器带来了更多的复杂性。当一台服务失效后,负载均衡器必须知道那台服务是这台己失效服务器的对等备份服务器。这将缩小了负载均衡器的选择范围,同时有些硬件也不能满足这种要求。
  •  除了处理正常的请求外,服务器还将负责复制的任务。由于备份会话数据的任务也需要占用CPU的周期,所以每台服务器的请求处理能力也降低了。
  • 在没有发生失效转移的时候,备份服务器上大量用于备份的内存是个浪费。同时这也将增加了JVM GC的负担。
  • 集群中的服务器实例构成了复制对。这样,当会话所在主服务器失效后,负载均衡器将会话转移到备份服务器,使备份服务器处理两倍的请求,这将造成备份服务器的性能问题。

为了克服上面的4点问题,不同的软件供应商采用了不同的方法,WebLogic采用的复制对不是对每台服务器,而是对每个会话。当一台服务器实例失效后,会话数据己经分散备份到多个备份服务器上,使失效的负载均匀地分布。

4.2.5 IBM的方式:中央状态服务器

Websphere采用不同的方式实现内存复制:备份会话信息到中央的状态服务器,如图11所示:

 

 
图 11  中央状态服务器复制

这与数据库的解决方案非常类似,不同之处在于专用的“会话备份服务器”代替了数据库服务器,这种方式结合了数据库和内存复制两种方式的优点。

  • 将请求处理和会话备份处理分开使用集群更加健壮。
  •  所有的会话数据都备份到专用的服务器上,无需服务器浪费内存用于备份其他服务器的会话。
  • 因为会话备份服务器是在服务器之间共享的,所有失效后可以转移到任何一台服务器上,这样大多数据软硬件负载均衡器都可以使用,更重要的是当一台服务器失效后,负载将均匀的分布到所有实例上。
  • 与重量级的数据库连接相比,应用服务器与备份服务器之间Socket通信是轻量的。这样就比数据库的解决方案有更好的性能和更好的伸缩性。

然而,由于有恢复失效服务器会话数据的这么一个阶段,因此其性能肯定不如两台服务器直接复制解决方案,另外,多出来一台备份服务器也增加了管理的复杂性。也可能由于单台备份服务器造成性能瓶颈。

4.2.6 Sun的方式:特殊数据库

 

 
图 12  特殊数据库复制

Sun JES应用服务器采用了别的方式实现会话失效转移,如图12所示,它看上去很像数据库的方式,因为它采用关系数据库存储会话并通过JDBC访问所有会话数 据。但是JES内部所使用的关系数据库称为HADB,已经为访问会话做了特别优化,并且将几乎所有的会话数据存在内存中。这样,你可以说它更像中央状态服 务器的方式。

4.2.7 性能因素

考虑如下问题:一台WEB服务器中可能运行着许多WEB应用,它们中每一个都可能被成百的并发用户访问,而每个用户都会产生浏览器会话用于访问特定 的应用。所有会话信息都将备份以便服务器失效后能转移到其他服务器实例中。更糟的是,会话会由于一次次的发生以下情况而变化,包括创建、失效、增加属性、 删除属性、修改属性值。甚至是什么都没变,但由于有新的访问而使最后访问时间变了(由此判断什么时候失效会话)。因此,性能在会话失效转移的解决方案中是 个很大的因素。供应商通常会提供一些可调的参数改变服务器行为,使之适应性能需求。

4.2.7.1 备份时机

当客户端的请求被处理后,会话随时改变。由于性能因素,实时备份会话是不明智的。选择备份频率需要平衡。如果备份动作发生得太频繁,性能将急剧下 降。如果两次备份的间隔太长,那么在这间隔之间服务器失效后,很多会话信息将会丢失。不管所有的方式,包括数据库和内存复制,下面是决定备份频率的常用的 选项。

  •  按WEB请求

在WEB请求处理结束后,发生响应之前保存数据。这种方式能够保证在失效后备份的会话数据是最新的。

  • 按固定的时间间隔

会话在后台按固定的时间间隔保存。这种方式不能保证备份的会话数据是最新的。但由于不需在每次请求之后备份数据,因而有更好的性能。

4.2.7.2 备份粒度

当备份会话的时候,你还需要决定多少会话状态需要保存。以下是不同产品所有采用的常用的策略。

  • 整个会话

每次都将保存所有会话。这种方式为正确保存分布式WEB应用的会话提供了最好保证。这种方式简单,内存复制和数据库存储方式都默认采用这种方式。

  • 修改过的会话

当会话被修改过后,则备份整个会话。当“session.setAttribute()”或 “session.removeAttribute()”被调用后,则认为会话被修改过。必须保证这些方法调用才修改会话属性,这并不是J2EE规范后要 求的。但却是正确实现这种方法所需要的。备份修改过的会话减少了会话存储的数量,那些仅仅是读取会话属性的请求将不会触发会话备份的动作。这将带来比备份 整个会话更好的性能。

  • 修改过的属性

仅仅是保存被修改过的会话属性而不是整个会话。这将最小化备份的会话数据。这种方式带来最好的性能及最小的网络通信。为了保证这种方式工作的正确 性,必须依据以下的要点。首先,只能通过调用“setAttribute()”方法修改会话状态,并且会话数据要被序列化和备份。其次,确保属性之间没有 交叉引用。每个唯一的属性键值下的对象图应当被独立地序列化和保存。如果两个独立的键值下的对象存在交叉引用,它们将不能够正确的序列化和反序列化。例如 图13所示,在一个内存复制的集群中,会话中存有“student”和“school”对象,同时“school”对象含有到“student”对象的引 用,某一个时候“school”被修改后备份到备份服务器中。在序列化和反序列化之后,在备份服务器的被还原的“school”对象的版本将包含整个对象 图,包括其引用的“student”对象。但是“student”对象可以被独立的修改,当它被修改后,仅仅只有它自己被备份。在序列化和反序列化之后, 在备份服务器中还原“student”对象,但在此时,它将丢失与“school”对象的连接。尽管这种方式有最好的性能,但上述限制将影响WEB应用程 序的架构和设计。特别是需要用会话保存缓存的复杂的用户数据。

 

 
图 13  会话复制中的交叉引用

4.2.8 其他失效转移的实现

如前面章节所提到的,当会话备份时粒度对于性能非常重要。然而,当前的一些实现,不管是数据库存储还是内存复制,都将使用Java对象序列化技术来 传输Java对象。这就打了个大印子,影响系统的性能,并给WEB应用程序的架构和设计带来很多的限制。一些J2EE供应商便寻找一些特别的手段来更为轻 量地,小印子地实现WEB集群,提供细粒度的分布式对象共享机制用于提高集群的性能。

4.2.8.1 Jrun的Jini技术

Jrun4将它的集群解决方案构在Jini技术之上。Jini为分布式计算而生,它允许在一个单一的分布式计算空间内创建“联合”的设备或组件。 Jini提供查找,注册,租用等分布式系统服务,这对集群环境非常有用。另一种称为JavaSpace的技术构建于Jini之上,它提供了一些用于实现集 群非常有价值的特性,如对象处理,共享,迁移等。要了解有关Jini和JavaSpace更多的信息,请参考http://java.sun.com/products/jini/2_0index.html

4.2.8.2 Tangosol的分布式缓存

Tangosol Coherence提供了一个分布式数据管理平台,它可以嵌入到大多数流行的J2EE服务器中用于实现集群环境。Tangosol Coherence同时也是提供了分布式缓存系统用于在不同的JVM之间有效地共享Java对象。要了解有关Tangosol更多的信息,请参考http://www.tangosol.com

 

5 JNDI集群实现

J2EE规范要求所有的J2EE容器必须提供JNDI规范的实现。JNDI在J2EE应用程序中的主要角色是用来提供一个间接层,这样资源可以很容易被找到,而不用关心细节。这使得J2EE组件更加可复用。

拥用全特性的集群的JNDI对于J2EE集群是非常重要的。所有的EJB调用都开始于在JNDI树上查找它的Home接口,J2EE供应商根据他们的集群结构采用不同的方式实现JNDI集群。

5.1 共享全局JNDI树 
WebLogic和Jboss都有一个全局的,共享的,集群范围的JNDI Context供客户端查找和绑定对象,绑定的全局JNDI Context中对象将通过IP广播的方式在集群中复制,这样当一台服务器实例停机后,被绑定的对象仍然可供查找。

 
图 14  共享的全局JNDI

如图14所示,共享的全局JNDI树实际包括了所有本地JNDI结点上绑定的对象。集群上每个结点都拥有自己的名称服务器,它们与集群中其他服务器相互复制所有的东西。这样每个名称服务器上都拥有其他名称服务器对象树的拷贝。这种冗余结构使得全局JNDI树高可用。

实际上,集群的JNDI树可以被用做两个目的。可以被管理员用来部署对象和服务。在一台服务中部署完EJB模块或JDBC&JMS服务 后,JNDI树上的所有对象都将复制到其他服务器实例中。在运行期,程序可以JNDI API访问JNDI树存储或者获取对象,这些对象也将被全局复制。

5.2 独立JNDI
Jboss和WebLogic都采 用了共享全局JNDI树的方式,Sun JES和IBM WebSphere等采用了每个服务器都拥有独立的JNDI树的方式。在使用独立JNDI树的集群中,成员服务器不必知道或关心集群中其他服务器。这是否 意味着不想把JNDI集群呢?所有EJB访问都开始于在JNDI树上查找它们的Home接口,如果没有可集群的JNDI树,集群就根本无用。

实际上,如果J2EE应用程序是相似的,独立的JNDI树仍然可以获得高可用性。当集群中所有实例都有相同的设置以及都部署相同的应用程序集,我们说集群是“相似的”,这种情况下,一种被称为代理的特殊管理工具可以帮助我们获取高可用性,如图15所示:

 

 
图 15  独立JNDI

不管是Sun JES还是WebSphere都在集群的实例上安装了结点代理,当部署EJB模块或绑定其他JNDI服务,管理控制员可以向所有的代理发出命令,以此实现与共享全局JNDI相同的效果。

但是独立JNDI的方案不能支持复制在运行期绑定或获取的对象。有以下几个原因:JNDI在J2EE应用程序中的主要角色是用来提供管理外部资源的 间接层,并不是用来做数据存储。如果有这样的需求,可以采用具有HA(高可用性)特性的独立的LDAP服务器或数据库。Sun和IBM自己都有这样拥有集 群特性的独立的LDAP服务器产品。

5.3 中心JNDI
少数J2EE产品使用了中心JNDI方案,这种方案中名称服务器驻留在单一服务器中,所有的服务器实例都注册它们相同的EJB组件和管理对象到单一的服务器中。

名称服务器实现了高可用性,这对客户端是透明的。所有的客户端都在这台服务器中查找EJB组件,但是这种结构意味着复杂的安装和管理,慢慢被多数供应商抛弃。

5.4 初始化访问JNDI服务器 
当然客户端要访问JNDI服务器的时候,它们需要知道远程JNDI服务器的主机名/IP地址和端口,在全局或是独立JNDI树的方案中,有多个JNDI服务器。客户端第一次访问时应该连接哪个呢?如何获得负载均衡和失效转移呢?

从技术上说,一个软件或硬件负载均衡器可以设在远程客户端和所有的JNDI服务器之间执行负载均和失效转移。但是,很少有供应商实现这种方式,这里有些简单的方案:

  • Sun JES和Jboss 实现JNDI集群是在“java.naming.provider.url”属性中设置一列用逗号分隔的URL,如 java.naming.provider.url=server1:1100,server2:1100:server3.1100:server4.1100 客户端将挨个联系列表中的服务器,一旦其中一个响应了便中止。
  • Jboss同时也实现了自动发现的特性,当设置属性串“java.naming.provider.url”为空时,客户端将试图通过网络广播来发现在一个JNDI服务器。

6 EJB集群实现

EJB是J2EE技术中重要的部分,并且EJB集群是实现J2EE集群最大的挑战。

EJB技术是为分布式计算而生。它们可以在独立的服务器中运行。Web服务器组件或富客户端可以从其他的机器通过标准协议(RMI/IIOP)来访 问EJB。你可以象调用你本地Java对象的方法一样调用远程EJB的方法。实际上,RMI/IIOP完全掩盖了你正在调用的对象是本地的还是远程的,这 被称作本地/远程透明性。

 
图 16  EJB调用机制

上图显示了远程EJB的调用机制。当客户端想使用EJB,它不能直接调用,相反,客户端只能调用一个被称为“stub”的本地对象,它扮演了到远程 对象代理的角色,并且有远程对象相同的接口。这个对象负责接受本地方法调用,并且这些调用通过网络代理到远程EJB。这些对象在客户JVM中运行,并且知 道如何通过RMI/IIOP跨过网络查找真正的对象。要了解有关EJB更多的信息,请参考http://java.sun.com/products/ejb/

为解释EJB集群的实现,我们先看看在J2EE代码中如何使用EJB的。为了调用EJB,我们需要

  • 在JNDI服务器中查找EJBHome stub
  • 使用EJBHome stub查找或创建EJB对象,这样获得一个EJBObject stub
  • 在EJBObject stub上调用方法

负载均衡和失效转移可以在JNDI查找时发生,这我们已在上面阐述了。在EJB stub(包括EJBHome和EJBObject)的方法调用时,供应商采用以下三种方式实现EJB负载均衡和失效转移。

6.1 智能存根(Smart stub)
正如我们所知,客户端可以通过存根对象(stub)来访门远程的EJB,这个对象可以通过JNDI树获取,甚至客户端可能透明地从WEB服务器上下载存根类文件。

这样存根就可以在运行期动态地用程序生成,而其类文件也不必在客户端环境的classpath或库中。(因为它是可以被下载的)

 
图 17  智能存根

如图17所示,BEA WebLogic和Jboss通过在存根代码中组合几种行为来实现EJB集群,而这些都是在客户端透明运行的(客户端不需要了解这些代码)。这种技术叫做智能存根。

智能存根确实很聪明,它包含一组它可以访问的目标实例,可以检测这些实例的任何失效,它也包含了复杂的负载均衡和失效转移的逻辑,用来分发请求到目 标实例。而且,如果集群拓朴改变了的话(比如新增或删除了服务器实例),存根可以更新它的目标实例清单来反映新的拓朴,而不需要手工重新配置。

使用智能存根实现集群有以下优点:

  • 因为EJB存根运行在客户端,所以它可以节省许多服务器资源。
  • 负载均衡是在客户端代码中,并且与客户端的生命周期相关。这样就消除了负载均衡器的单点失效。如果负载均衡器失效了,就意味着客户端也失效了,这种情况是能接受的。
  • 存根可动态的自动下载更新,这意味着零维护。

6.2 IIOP运行期库 
Sun JES Application Server使用了另一种方法实现EJB集群。负载均衡和失效转移逻辑是在IIOP运行库中实现的。比如,JES修改了“ORBSocketFactory”的实现,使它能支持集群。如图18所示。

 
图 18  IIOP运行期

“ORBSocketFactory”的修改版有实现负载均衡和失效转移的所有逻辑和算法,这样就保持了存根干净和小。因为是在运行库中实现的,这 样就比存根的方式更容易获得系统资源。但这种方式需要在客户端运行一个特殊的运行库,这样就给与其他J2EE产品进行互操作时带来了麻烦。

6.3 拦截器代理
IBM Webshpere采用了定位服务精灵(Location Service Daemon-LSD),它对EJB客户端扮演了拦截器代理的角色,如图19所示。


 

图 19  拦截器代理

在这种方式中,客户端通过JNDI查找获取存根,这个存根将信息路由到LSD,而不是运行了EJB的应用程序服务器。这样LSD接收到所有进来的请求,根据负载均衡和失效转移的策略来判断应该将它发送到哪个服务器实例。这种方式增加的安装和维护集群的额外的管理工作。

6.4 EJB的集群支持 
要调用EJB的方法,要涉及到两种存根对象,一是EJBHome接口,另一个是EJBObject接口。这意味着EJB潜在的需要在两层上实现在负载均衡和失效转移。

  • 当客户端使用EJBHome存根创建或查找EJB对象时
  •  当客户端调用EJB对象上的方法时。

6.4.1 EJBHome存根支持集群

EJBHome接口用于在EJB容器中创建和查找EJB实例,而EJBHome存根是EJBHome接口的客户端代理。EJBHome接口不需维持 任何客户端的状态信息。这样,来自不同EJB容器中的相同EJBHome接口对于客户端来说是一致的。当客户端执行create()或find()调用的 时候,home存根根据负载均衡和失效转移算法从多个相同的服务器实例中选择一个,并将调用发送到该服务器的home接口上。

6.4.2 EJBObject存根支持集群

当EJBHome接口创建一个EJB实例,它将返回EJBObject的存根给客户端,并让用户调用EJB的业务方法。集群中已有一组可用的服务器 实例,并在上面的部署了bean,但是不能将EJBObject存根对象向EJBObject接口发出调用转发到任意一个服务器实例上,这取决于EJB的 类型。

无状态会话Bean是最简单的情况,因为不涉及到状态,所有的实例都可以认为是一样的,这样对EJBObject的方法调用可负载均衡和失效转移到任意一个服务器实例上。

集群的有状态会话Bean与无状态会话Bean有一点不同,正如我们所知,有状态会话Bean对于客户端连续的请求会持有状态信息。从技术上来说, 集群有状态会话Bean与集群HTTP Session是一样的。在常规时间,EJBObject存根将不会把请求负载均衡到不同的服务器实例。相反,它将胶粘到最初创建的服务器实例上,我们称 这个实例为“主实例”。在执行过程中,会话状态会从主实例备份到其他服务上去。如果主实例失效了,备份服务器将接管它。

实体Bean本质上是无状态的,尽管它可以处理有状态的请求。所有的信息都将通过实体Bean本身的机制备份到数据库中。这样看起来实体Bean可 以象无状态会话Bean一样很容易获取负载均衡和失效转移。但实际,实体Bean多数情况下是不负载均衡和失效转移的。根据设计模式的建议,实体Bean 被会话外观包装。这样,多数对实体Bean的访问都是进程内会话Bean通过本地接口完成的,而不是远程客户端。这使得负载均衡和失效转移没有意义。

 

JMS和数据库连接的集群支持

除JSP,Servlet,JNDI和EJB之外,在J2EE中还有其他的分布式对象。这些对象在集群的实现中可能支持,可能不支持。

当前,一些数据库产品,如Oracle RAC,支持集群环境并可以部署到多复制,同步的数据库实例中。然而,JDBC是一个高度状态化的协议并且它的事务状态直接与客户端和服务器的 Socket连接绑定,所以很难获取集群能力。如果一个JDBC连接失效了,与该失效连接相关的所有JDBC对象也就失效了。客户端代码需要进行重连的动 作。BEA WebLogic使用JDBC多池(multipool)技术来简化这种重连过程。

JMS被多数J2EE服务器所支持,但支持得并不完全,负载均衡和失效转移仅仅被JMS代理所实现,很少有产品在JMS Destination中的消息有失效转移的功能。

 

8 J2EE集群的神话

8.1 失效转移可以完全避免错误——否定

在Jboss的文档中,整个章节都在警告你“你真的需要HTTP会话的复制吗?”。是的,有时没有失效转移的高可用性的解决方案也是可接受并且是廉价的。失效转移并不是你想象的那么强壮。

那么失效转移到底给你带来了什么?你可能想失效转移可以避免错误。你看,没有会话的失效转移,当一个服务器实例失效后,会话数据将丢失而导致错误。通过失效转移,会话可以从备份中恢复,而请求可以被其他服务器实例所处理,用户根本意识不到失效。这是事实,但这是有条件的!

回想一样我们定义的“失效转移”,我们定义了一个条件是失效转移是在“两个方法调用之间”发生的。这是说如果你有两个连续的对远程对象的方法调用,失效转移只会在第一调用成功后并且第二调用的请求发出前才能发生。

这样,当远程服务器在处理请求的过程中失效了会发生什么呢?答案是:多数情况处理将会停止而客户端将会看到错误信息。除非这个方法是等幂的(Idempotent),只有这个方法是等幂的,一些负载均衡器更智能,它会重试这些方法并将它失效转移到其他实例上。

为什么“等幂”重要呢,因为客户端不知道当失效发生的时候请求被执行到什么地方。是才刚刚初始化还是差不多就要完成了?客户端没法判断!如果方法不是等幂的,在相同方法上的两次调用可能会两次修改系统的状态,而使得系统出现不一致的情形。

你可能想所有在事务中的方法都是等幂的,毕竟,如果错误发生,事务将被回滚,事务状态的改变都将被复位。但事实上事务的边界可能不包括所有的远程方法调用过程。如果事务已经在服务器上提交了而返回给客户端时网络崩溃怎么办呢?客户端不知道服务器的事务是否是成功了。

在一些应用程序中,将所有的方法都做成等幂的是不可能的。这样,你只能通过失效转移减少错误,而不是避免它们。拿在线商店为例,假设每台服务器可以 同时处理100个在线用户的请求,当一台服务器失效了,没有失效转移的解决方案将丢失100个用户的会话数据并激怒这些用户。而有失效转移的解决方案中, 当失效发生的时候有20个用户正在处理请求,这样20个用户将被失效激怒。而其他80个用户正处于思考时间或在两个方法调用之间,这些用户可以透明地获得 失效转移。这样,你就需做以下的考虑:

  • 激怒20个用户和激怒100个用户造成影响的区别。
  • 采用失效转移和不采用失效转移产品成本的区别

8.2 独立应用可以透明的迁移到集群结构中——否定

尽管一些供应商宣称他们的J2EE产品有这样的灵活性。不要相信他们!事实你要在开始系统设计时就要准备集群,而这将影响开发和测试的所有阶段。

8.2.1 Http Session

在集群环境中,如我前面提到的,使用HTTP Session有很多限制,这取决于你的应用程序服务器采用了那种会话失效转移的机制。第一个重要的限制就是所有保存的HTTP Session中的对象必须是可序列化的,这将限制设计和应用程序结构。一些设计模式和MVC框架会用HTTP Session保存一些不序列化的对象(如ServletContext,EJB本地接口和WEB服务引用),这样的设计不能在集群中工作。第二,对象的 序列的反序列化对性能的影响很大,特别是数据库保存的方式。在这样的环境中,应该避免在会话中保存大的或是众多的对象。如果你采用了内存复制的方式,如前 所述你必须小心在会话中属性的交叉引用。其他在集群环境中的主要区别是在会话不管任何属性修改,你必须调用“setAttribute()”方法。这个方 法调用在独立的系统中是可选的。这个方法的目的是区别已修改的属性和那些没用到属性,这样系统可以只为失效转移备份必要的数据,从而提高性能。

8.2.2 缓存

我经历过的大多数J2EE项目都用了缓存来提高性能,同时流行的应用程序服务器也都提供了不同程度的缓存用来加快应用程序的速度。但这些缓存都是为 那些典型的独立环境设计的,只能在单JVM实例中工作。我们需要缓存是因为一些对象很“重”,创建它需花费大量的时间和资源。因此我们维护了对象池用于重 用这些对象,而不需要在后面创建。我们只有当维护缓存比创建对象更廉价时才能获得性能的提高。在集群环境,每个JVM实例都要维护一份缓存的拷贝,这些拷 贝必须同步以维持所有服务器实例状态的一致性。有时这种类型的同步会比没有缓存带来更糟的性能。

8.2.3 Static变量

当我们设计J2EE应用程序时,在架构上经常会使用一些设计模式。这些如“Singleton”的设计模式会用到静态变量来在多对象之间共享状态。 这种方式在单服务中工作得很好,但在集群环境将失效。集群中的每个实例都会在它的JVM实例中维护一份静态变量的拷贝,这样就破坏了模式的机制。一个使用 静态变量的例子就是用它来保持在线用户数。用静态变量来保存在线用户数是一个很简单的办法,当用户进入或离开时就增加和减少它。这种方式在单服务器中绝对 是好的,但在集群环境将失效。在集群中更好的方式是将所有状态保存到数据库。

8.2.4 外部资源

尽管J2EE规范不支持,但为各种目的仍然会用外部I/O的操作。例如,一些应用会使用文件系统来保存用户上传的文件,或是创建一个动态配置的 XML文件。在集群环境是没有办法来在其他实例之间来复制这些文件的。为了在集群中工作,办法是用数据库作为外部文件的存放点,另外也可以使用SAN(存 储区域网,Storage Area Network)作为存放点。

8.2.5 特殊服务

一些特殊的服务只在独立的环境中才有意义,定时服务就一个很好例子,这种服务可以在一个固定的间隔时间有规律的触发。定时服务常用于执行一些自动化 管理任务。如日志文件滚动,系统数据备份,数据库一致性检查和冗余数据清理等。一些基于事件的服务也很难被迁移到集群环境中。初始化服务就是个好例子,它 只在整个系统启动时才发生。邮件通知服务也一样,它在一些警告条件下触发。

这些服务是被事件而不是被请求触发的,而且只被执行一次。这些服务使得负载均衡和失效转移在集群中没多少意义。

一些产品准备了这些服务,如Jboss使用了“集群单例设施”来协调所有实例,保证执行这些服务一次且仅有一次。基于你所选择的平台,一些特殊的服务可能会是把你的应用迁移到集群结构中的障碍。

8.3 分布式结构比并置结构更灵活——不一定

J2EE技术,尤其是EJB,天生就是用来做分布式计算。解耦业务功能,重用远程组件,这些使得多层应用非常流行。但是我们不能将所有的东西都分布。一些J2EE架构师认为Web层与EJB层并置得越近越好。这些计论后面会继续。先让我解释一下。

 
图 20  分布式结构

如图20所示,这是一个分布式结构。当请求来了,负载均衡器将请求分发到不同服务器中的不同WEB容器,如果请求包含了EJB调用,WEB容器将重发EJB调用到不同的EJB容器。这样,请求将被负载均衡和失效转移两次。

一些人看分布式结构,他们会指出:

  • 第二次负载均衡没有必要,因为它不会使任务分配更平坦。每个服务器实例都有它们自己的WEB容器和EJB容器。把EJB容器用来处理来自其他实例WEB容器的请求比只在服务器内部调用并没有什么优势。
  • 第二次失效转移没有必要,因为它不能是高可用性。多数供应商实现J2EE服务器都会在同一服务器中运行的WEB容器和EJB容器放在一个JVM实例中。如果EJB容器失效了,多数情况下在同一个JVM中的WEB容器也将同时失效。
  • 性能将下降。想像一下对你的应用的一次调用包含一组对EJB的调用,如果你负载均衡了这些EJB,这将跨越每个服务器实例,导致许多不必要的服务器到服务器的交互。还有,如果这个方法在事务范围内,那么事务边界将包含许多服务器实例,这将严重影响性能。

实际上在运行期,多数的供应商(包括Sun JES,WebLogic和Jboss)都会优化EJB调用机制,使请求首先选择同一个服务器中的EJB容器。这样,如图21所示,我们只在第一层 (WEB层)做负载均衡,然后调用相同服务器上的服务。这种结构我们称之为并置结构。技术上说,并置结构是分布式结构的一种特例。

 
图 21  并置结构

一个有趣的问题是,既然多数的部署在运行期都演进成了并置结构,为什么不用本地接口代替远程接口,这将大提高性能。你当然可以,但是记住,当你使用 本地接口后,WEB组件和EJB耦合得很紧,而方法调用也是直接的而不通过RMI/IIOP。负载均衡和失效转移分发器没有机会介入本地接口调用。 “WEB+EJB”整体处理负载均衡和失效转移。

但不幸的是,在集群中使用本地接口在多数J2EE服务器中有局限性。使用本地接口的EJB是本地对象,是不可序列化的,这一个限制就使本地引用不能 保存在HTTP Session中。一些产品,如Sun JES,会将本地接口区别看待,使它们可以序列化。这样就可以用在HTTP Session中。

另一个有趣的问题是,既然并置结构这么流行并且有好的性能,为什么还要分布式结构呢?这在多数情况下是有道理的,但有时分布式结构是不可替代的。

  • EJB不仅被WEB容器使用,富客户端也会使用它。
  • EJB组件和WEB组件需在不同的安全级别上,并需要物理分离。这样防火墙将被设置用于保护运行EJB的重要机器。
  • WEB层和EJB层极端不对称使得分布式结构是更好的选择。比如,一些EJB组件非常复杂并且很消耗资源,它们只能运行在昂贵的大型服务 器上,另一方面,WEB组件(HTML,JSP和Servlet)简单得只需廉价的PC服务器就能满足要求。在这种情况下,专门的WEB服务器可以用来接 受客户端连接请求,很快处理静态数据(HTML和图像)和简单的WEB组件(JSP和Servlet)。大型服务器只被用来做复杂计算。这将更好的利用投 资。

9 结论 

集群与独立环境不同,J2EE供应商采用不同的 方法来实现集群。如果你的项目为做到高伸缩性而使用集群,你应该在你的项目开始的时候就做准备。选择符合你的需求的正确的J2EE产品。选择正确的第三方 软件和框架并确保它们能支持集群。最后设计正确的架构使得能从集群中受益而不是受害。


转载地址:https://blog.csdn.net/he90227/article/details/44857667


相关文章:

亿级Web系统搭建——单机到分布式集群(强烈推荐★★★★★)

https://kb.cnblogs.com/page/509402/

分布式服务器集群架构方案思考

http://homeway.me/2014/12/10/think-about-distributed-clusters/


猜你喜欢

转载自blog.csdn.net/mrleeapple/article/details/80424015