某电商网站Java面试题-2(高级开发,架构级别)

出自:http://my.oschina.net/huangzhuang/blog/380171

一、Java线程池的具体实现,用过哪些Java多线程并发控制框架?

答:(1)引用来源:http://www.open-open.com/lib/view/open1406778349171.html

参考源码

JDK源码类:java.util.concurrent.ThreadPoolExecutor

Jetty6源码类:org.mortbay.thread.QueuedThreadPool

Jetty8源码类:org.eclipse.jetty.util.thread.QueuedThreadPool

Tomcat源码类:org.apache.tomcat.util.threads.ThreadPoolExecutor

(2)几种Java线程池的实现算法分析,引用来源:http://www.open-open.com/lib/view/open1406778349171.html

二、多线程的锁是加在哪里的,synchronized与static synchronized 的区别?

答:(引用来源:http://www.cnblogs.com/shipengzhi/articles/2223100.html)

(1)Java中每个对象都有一个内置锁

当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。

    当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。

    一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

(2)synchronized与static synchronized 的区别

      synchronized是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,注意这里是“类的当前实例”, 类的两个不同实例就没有这种约束了。那么static synchronized恰好就是要控制类的所有实例的访问了,static synchronized是限制线程同时访问jvm中该类的所有实例同时访问对应的代码快。实际上,在类中某方法或某代码块中有 synchronized,那么在生成一个该类实例后,改类也就有一个监视快,放置线程并发访问改实例synchronized保护快,而static synchronized则是所有该类的实例公用一个监视快了,也也就是两个的区别了,也就是synchronized相当于 this.synchronized,而

static synchronized相当于Something.synchronized.

      一个日本作者-结成浩的《java多线程设计模式》有这样的一个例子:

?
1
2
3
4
5
6
       pulbic  class  Something(){
          public  synchronized  void  isSyncA(){}
          public  synchronized  void  isSyncB(){}
          public  static  synchronized  void  cSyncA(){}
          public  static  synchronized  void  cSyncB(){}
      }

   那么,加入有Something类的两个实例a与b,那么下列组方法何以被1个以上线程同时访问呢

   a.   x.isSyncA()与x.isSyncB() 

   b.   x.isSyncA()与y.isSyncA()

   c.   x.cSyncA()与y.cSyncB()

   d.   x.isSyncA()与Something.cSyncA()

    这里,很清楚的可以判断:

   a,都是对同一个实例的synchronized域访问,因此不能被同时访问

   b,是针对不同实例的,因此可以同时被访问

   c,因为是static synchronized,所以不同实例之间仍然会被限制,相当于Something.isSyncA()与   Something.isSyncB()了,因此不能被同时访问。

   那么,第d呢?,书上的 答案是可以被同时访问的,答案理由是synchronzied的是实例方法与synchronzied的类方法由于锁定(lock)不同的原因。

   个人分析也就是synchronized 与static synchronized 相当于两帮派,各自管各自,相互之间就无约束了,可以被同时访问。目前还不是分清楚java内部设计synchronzied是怎么样实现的。

    结论:A: synchronized static是某个类的范围,synchronized static cSync{}防止多个线程同时访问这个    类中的synchronized static 方法。它可以对类的所有对象实例起作用。

               B: synchronized 是某实例的范围,synchronized isSync(){}防止多个线程同时访问这个实例中的synchronized 方法。

     2.synchronized方法与synchronized代码快的区别

      synchronized methods(){} 与synchronized(this){}之间没有什么区别,只是 synchronized methods(){} 便于阅读理解,而synchronized(this){}可以更精确的控制冲突限制访问区域,有时候表现更高效率。

 3.synchronized关键字是不能继承的

     这个在http://www.learndiary.com/archives/diaries/2910.htm一文中看到的,我想这一点也是很值得注意的,继承时子类的覆盖方法必须显示定义成synchronized。

三、Spring加载类的注解有哪几种?

答:(1)@Autowired

@Autowired可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作。以上两种不同实现方式中,@Autowired的标注位置不同,它们都会在Spring在初始化userManagerImpl这个bean时,自动装配userDao这个属性,区别是:第一种实现中,Spring会直接将UserDao类型的唯一一个bean赋值给userDao这个成员变量;第二种实现中,Spring会调用 setUserDao方法来将UserDao类型的唯一一个bean装配到userDao这个属性。

要使@Autowired能够工作,还需要在配置文件中加入以下代码

?
1
<bean  class = "org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"  />

(2)@Resource(JSR-250标准注解,推荐使用它来代替Spring专有的@Autowired注解) 

Spring 不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource、@PostConstruct以及@PreDestroy。 

@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName自动注入罢了。@Resource有两个属性是比较重要的,分别是name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。 

@Resource装配顺序 

如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常

如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常

如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常

如果既没有指定name,又没有指定type,则自动按照byName方式进行装配(见2);如果没有匹配,则回退为一个原始类型(UserDao)进行匹配,如果匹配则自动装配 。

@Component(不推荐使用)、@Repository、@Service、@Controller 

    只需要在对应的类上加上一个@Component注解,就将该类定义为一个Bean了:

?
1
2
3
4
@Component    
public  class  UserDaoImpl  extends  HibernateDaoSupport  implements  UserDao {    
     ...    
}

使用@Component注解定义的Bean,默认的名称(id)是小写开头的非限定类名。如这里定义的Bean名称就是userDaoImpl。你也可以指定Bean的名称: 

@Component("userDao") 

@Component是所有受Spring管理组件的通用形式,Spring还提供了更加细化的注解形式:@Repository、 @Service、@Controller,它们分别对应存储层Bean,业务层Bean,和展示层Bean。目前版本(2.5)中,这些注解与 @Component的语义是一样的,完全通用,在Spring以后的版本中可能会给它们追加更多的语义。所以,我们推荐使用@Repository、 @Service、@Controller来替代@Component。

四、详细说明Spring的事务传播特性

答:

我们都知道事务的概念,那么事务的传播特性是什么呢?(此处着重介绍传播特性的概念,关于传播特性的相关配置就不介绍了,可以查看spring的官方文档) 

在我们用SSH开发项目的时候,我们一般都是将事务设置在Service层 那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。 

如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的 Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用的这个方法与我本身的方法处在同一个事务中,否则如果保证事物的一致性。事务的传播特性就是解决这个问题的,“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的一种:PROPGATION_REQUIRED:这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作使由Spring来帮助我们完成的。 

以前没有Spring帮助我们完成事务的时候我们必须自己手动的控制事务,例如当我们项目中仅仅使用hibernate,而没有集成进 spring的时候,我们在一个service层中调用其他的业务逻辑方法,为了保证事物必须也要把当前的hibernate session传递到下一个方法中,或者采用ThreadLocal的方法,将session传递给下一个方法,其实都是一个目的。现在这个工作由 spring来帮助我们完成,就可以让我们更加的专注于我们的业务逻辑。而不用去关心事务的问题。 

默认情况下当发生RuntimeException的情况下,事务才会回滚,所以要注意一下 如果你在程序发生错误的情况下,有自己的异常处理机制定义自己的Exception,必须从RuntimeException类继承 这样事务才会回滚!

Spring事务传播特性总结:

1.只要定义为spring的bean就可以对里面的方法使用@Transactional注解。 

2.Spring的事务传播是Spring特有的。不是对底层jdbc的代理。

3.使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在[方法调用之前决定是否开启一个事务],并在[方法执行之后]决定事务提交或回滚事务。 

4.Spring支持的PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:

PROPAGATION_REQUIRES_NEW:二个事务没有信赖关系,不会存在A事务的成功取决于B事务的情况。有可能存在A提交B失败。A失败(比如执行到doSomeThingB的时候抛出异常)B提交,AB都提交,AB都失败的可能。

PROPAGATION_NESTED:与PROPAGATION_REQUIRES_NEW不同的是,内嵌事务B会信赖A。即存在A失败B失败。A成功,B失败。A成功,B成功。而不存在A失败,B成功。

5. 特别注意PROPAGATION_NESTED的使用条件:使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;而 nestedTransactionAllowed属性值默认为false;

6.特别注意PROPAGATION_REQUIRES_NEW的使用条件:JtaTransactionManager作为事务管理器

的AOP简介与事务传播特性总结 2009-10-26 16:56srping用到的另外一项技术就是AOP(Aspect-Oriented Programming, 面向切面编程),它是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程)的补充。AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点。在应用 AOP 编程时, 仍然需要在定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里。每个事物逻辑位于一个位置, 代码不分散,便于维护和升级,业务模块更简洁, 只包含核心业务代码。 

现实中使用spring最多的就是声明式事务配置功能。下面就来了解其aop在事务上应用。首先要了解的就是AOP中的一些概念: 

Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面是横切性关注点的抽象。 

joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器)。 

Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义。 

Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知。 

Target(目标对象):代理的目标对象。 

Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入。 

Introduction(引入):在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field。 

所谓AOP,我的理解就是应该是这样一个过程,首先需要定义一个切面,这个切面是一个类,里面的方法就是关注点(也是通知),或者说里面的方法就是用来在执行目标对象方法时需要执行的前置通知,后置通知,异常通知,最终通知,环绕通知等等。有了切面和通知,要应用到目标对象,就需要定义这些通知的切入点,换句话说就是需要对哪些方法进行拦截,而这些被拦截的方法就是连接点,所谓连接点也就是在动态执行过程,被织入切面的方法(至少在spring中只能对方法进行拦截)。因此,在动态过程中通知的执行就属于织入过程,而被织入这些通知的对象就是目标对象了。 

通常应用中,被织入的都是事务处理,对事务的织入不是普通简单的织入,它有着事务特有的特性—— 

事务的传播特性: 

1. PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务。如果没有事务则开启新的事物。 

2. PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。 

3. PROPAGATION_MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。 

4. PROPAGATION_REQUIRES_NEW: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。 

5. PROPAGATION_NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务。 

6. PROPAGATION_NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常 

7.(spring)PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。 

这些都是事务特有的特性,比如前面分析的,如果两个在代码上不相关的操作,需要放在同一个事务中,这就需要利用到传播特性了,这时后调用的方法的传播特性的值就应该是 PROPAGATION_REQUIRED。在spring中只需要进行这样的配置,就实现了生命式的事物处理。

通常应用中,被织入的都是事务处理,对事务的织入不是普通简单的织入,它有着事务特有的特性—— 

事务的传播特性: 

1. PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务。如果没有事务则开启新的事物。 

2. PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。 

3. PROPAGATION_MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。 

4. PROPAGATION_REQUIRES_NEW: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。 

5. PROPAGATION_NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务。 

6. PROPAGATION_NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常 

7.(spring)PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。 

这些都是事务特有的特性,比如前面分析的,如果两个在代码上不相关的操作,需要放在同一个事务中,这就需要利用到传播特性了,这时后调用的方法的传播特性的值就应该是PROPAGATION_REQUIRED。在spring中只需要进行这样的配置,就实现了生命式的事物处理。

化成表格的事务的传播特性

事务属性事务1事务2
Required

事务1

事务2

事务1

RequiredNew

事务1

事务2

事务2

Support

事务1

事务1

Mandatory

事务1

抛异常

事务1

NOSupport

事务1

Never

事务1

抛异常

最后一点需要提及的就是Spring事务的隔离级别: 

1. ISOLATION_DEFAULT:这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。 

2. ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。 

3. ISOLATION_READ_COMMITTED:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 

4. ISOLATION_REPEATABLE_READ:这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。 

5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。 

除了第一个是spring特有的,另外四个与JDBC的隔离级别相对应。第二种隔离级别会产生脏读,不可重复读和幻像读,特别是脏读,一般情况下是不允许的,所以这种隔离级别是很少用到的。大多说数据库的默认格里基本是第三种。它能消除脏读,但是可重复读保证不了。第四种隔离级别也有一些数据库作为默认的隔离级别,比如MySQL。最后一种用的地方不多,除非是多数据访问的要求特别高,否则轻易不要用它,因为它会严重影响数据库的性能。

五、你对Spring的IoC和AOP是否有过深入了解?

答:(引用来源:Spring技术内幕——深入解析Spring架构与设计原理(一)IOC实现原理,http://www.iteye.com/topic/493965)

IOC的基础 

    下面我们从IOC/AOP开始,它们是Spring平台实现的核心部分;虽然,我们一开始大多只是在这个层面上,做一些配置和外部特性的使用工作,但对这两个核心模块工作原理和运作机制的理解,对深入理解Spring平台,却是至关重要的;因为,它们同时也是Spring其他模块实现的基础。从 Spring要做到的目标,也就是从简化Java EE开发的出发点来看,简单的来说,它是通过对POJO开发的支持,来具体实现的;具体的说,Spring通过为应用开发提供基于POJO的开发模式,把应用开发和复杂的Java EE服务,实现解耦,并通过提高单元测试的覆盖率,从而有效的提高整个应用的开发质量。这样一来,实际上,就需要把为POJO提供支持的,各种Java EE服务支持抽象到应用平台中去,去封装起来;而这种封装功能的实现,在Spring中,就是由IOC容器以及AOP来具体提供的,这两个模块,在很大程度上,体现了Spring作为应用开发平台的核心价值。它们的实现,是Rod.Johnson在他的另一本著作《Expert One-on-One J2EE Development without EJB》 中,所提到Without EJB设计思想的体现;同时也深刻的体现了Spring背后的设计理念。 

    从更深一点的技术层面上来看,因为Spring是一个基于Java语言的应用平台,如果我们能够对Java计算模型,比如像JVM虚拟机实现技术的基本原理有一些了解,会让我们对Spring实现的理解,更加的深入,这些JVM虚拟机的特性使用,包括像反射机制,代理类,字节码技术等等。它们都是在 Spring实现中,涉及到的一些Java计算环境的底层技术;尽管对应用开发人员来说,可能不会直接去涉及这些JVM虚拟机底层实现的工作,但是了解这些背景知识,或多或少,对我们了解整个Spring平台的应用背景有很大的帮助;打个比方来说,就像我们在大学中,学习的那些关于计算机组织和系统方面的基本知识,比如像数字电路,计算机组成原理,汇编语言,操作系统等等这些基本课程的学习。虽然,坦率的来说,对我们这些大多数课程的学习者,在以后的工作中,可能并没有太多的机会,直接从事这么如此底层的技术开发工作;但具备这些知识背景,为我们深入理解基于这些基础技术构架起来的应用系统,毫无疑问,是不可缺少的。随着JVM虚拟机技术的发展,可以设想到的是,更多虚拟机级别的基本特性,将会持续的被应用平台开发者所关注和采用,这也是我们在学习平台实现的过程中,非常值得注意的一点,因为这些底层技术实现,毫无疑问,会对Spring应用平台的开发路线,产品策略产生重大的影响。同时,在使用 Spring作为应用平台的时候,如果需要更深层次的开发和性能调优,这些底层的知识,也是我们知识库中不可缺少的部分。有了这些底层知识,理解整个系统,想来就应该障碍不大了。 

    IOC的一点认识 

    对Spring IOC的理解离不开对依赖反转模式的理解,我们知道,关于如何反转对依赖的控制,把控制权从具体业务对象手中转交到平台或者框架中,是解决面向对象系统设计复杂性和提高面向对象系统可测试性的一个有效的解决方案。这个问题触发了IoC设计模式的发展,是IoC容器要解决的核心问题。同时,也是产品化的 IoC容器出现的推动力。而我觉得Spring的IoC容器,就是一个开源的实现依赖反转模式的产品。 

    那具体什么是IoC容器呢?它在Spring框架中到底长什么样?说了这么多,其实对IoC容器的使用者来说,我们常常接触到的BeanFactory和 ApplicationContext都可以看成是容器的具体表现形式。这些就是IoC容器,或者说在Spring中提IoC容器,从实现来说,指的是一个容器系列。这也就是说,我们通常所说的IoC容器,如果深入到Spring的实现去看,会发现IoC容器实际上代表着一系列功能各异的容器产品。只是容器的功能有大有小,有各自的特点。打个比方来说,就像是百货商店里出售的商品,我们举水桶为例子,在商店中出售的水桶有大有小;制作材料也各不相同,有金属的,有塑料的等等,总之是各式各样,但只要能装水,具备水桶的基本特性,那就可以作为水桶来出售来让用户使用。这在Spring中也是一样,它有各式各样的IoC容器的实现供用户选择和使用;使用什么样的容器完全取决于用户的需要,但在使用之前如果能够了解容器的基本情况,那会对容器的使用是非常有帮助的;就像我们在购买商品时进行的对商品的考察和挑选那样。 

    我们从最基本的XmlBeanFactory看起,它是容器系列的最底层实现,这个容器的实现与我们在Spring应用中用到的那些上下文相比,有一个非常明显的特点,它只提供了最基本的IoC容器的功能。从它的名字中可以看出,这个IoC容器可以读取以XML形式定义的BeanDefinition。理解这一点有助于我们理解ApplicationContext与基本的BeanFactory之间的区别和联系。我们可以认为直接的 BeanFactory实现是IoC容器的基本形式,而各种ApplicationContext的实现是IoC容器的高级表现形式。 

仔细阅读XmlBeanFactory的源码,在一开始的注释里面已经对 XmlBeanFactory的功能做了简要的说明,从代码的注释还可以看到,这是Rod Johnson在2001年就写下的代码,可见这个类应该是Spring的元老类了。它是继承DefaultListableBeanFactory这个类的,这个DefaultListableBeanFactory就是一个很值得注意的容器! 

?
1
2
3
4
5
6
7
8
9
10
public  class  XmlBeanFactory  extends  DefaultListableBeanFactory {  
     private  final  XmlBeanDefinitionReader reader =  new  XmlBeanDefinitionReader( this );  
     public  XmlBeanFactory(Resource resource)  throws  BeansException {  
         this (resource,  null );  
     }  
     public  XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)  throws  BeansException {  
         super (parentBeanFactory);  
         this .reader.loadBeanDefinitions(resource);  
     }  
}

XmlBeanFactory的功能是建立在DefaultListableBeanFactory这个基本容器的基础上的,在这个基本容器的基础上实现了其他诸如XML读取的附加功能。对于这些功能的实现原理,看一看XmlBeanFactory的代码实现就能很容易地理解。在如下的代码中可以看到,在XmlBeanFactory构造方法中需要得到Resource对象。对XmlBeanDefinitionReader对象的初始化,以及使用这个这个对象来完成loadBeanDefinitions的调用,就是这个调用启动了从Resource中载入BeanDefinitions的过程,这个loadBeanDefinitions同时也是IoC容器初始化的重要组成部分。 


简单来说,IoC容器的初始化包括BeanDefinition的Resouce定位、载入和注册这三个基本的过程。我觉得重点是在载入和对 BeanDefinition做解析的这个过程。可以从DefaultListableBeanFactory来入手看看IoC容器是怎样完成 BeanDefinition载入的。在refresh调用完成以后,可以看到loadDefinition的调用:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public  abstract  class  AbstractXmlApplicationContext  extends  AbstractRefreshableConfigApplicationContext {  
     public  AbstractXmlApplicationContext() {  
     }  
     public  AbstractXmlApplicationContext(ApplicationContext parent) {  
         super (parent);  
     }  
     //这里是实现loadBeanDefinitions的地方  
     protected  void  loadBeanDefinitions(DefaultListableBeanFactory beanFactory)  throws  IOException {  
         // Create a new XmlBeanDefinitionReader for the given BeanFactory.  
         // 创建 XmlBeanDefinitionReader,并通过回调设置到 BeanFactory中去,创建BeanFactory的使用的也是 DefaultListableBeanFactory。  
         XmlBeanDefinitionReader beanDefinitionReader =  new  XmlBeanDefinitionReader(beanFactory);  
   
         // Configure the bean definition reader with this context's  
         // resource loading environment.  
         // 这里设置 XmlBeanDefinitionReader, 为XmlBeanDefinitionReader 配置ResourceLoader,因为DefaultResourceLoader是父类,所以this可以直接被使用  
         beanDefinitionReader.setResourceLoader( this );  
         beanDefinitionReader.setEntityResolver( new  ResourceEntityResolver( this ));  
   
         // Allow a subclass to provide custom initialization of the reader,  
         // then proceed with actually loading the bean definitions.  
     // 这是启动Bean定义信息载入的过程  
         initBeanDefinitionReader(beanDefinitionReader);  
         loadBeanDefinitions(beanDefinitionReader);  
     }  
   
     protected  void  initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {  
     }

 这里使用 XmlBeanDefinitionReader来载入BeanDefinition到容器中,如以下代码清单所示: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
     //这里是调用的入口。  
     public  int  loadBeanDefinitions(Resource resource)  throws  BeanDefinitionStoreException {  
         return  loadBeanDefinitions( new  EncodedResource(resource));  
     }  
     //这里是载入XML形式的BeanDefinition的地方。  
     public  int  loadBeanDefinitions(EncodedResource encodedResource)  throws  BeanDefinitionStoreException {  
         Assert.notNull(encodedResource,  "EncodedResource must not be null" );  
         if  (logger.isInfoEnabled()) {  
             logger.info( "Loading XML bean definitions from "  + encodedResource.getResource());  
         }  
   
         Set<EncodedResource> currentResources =  this .resourcesCurrentlyBeingLoaded.get();  
         if  (currentResources ==  null ) {  
             currentResources =  new  HashSet<EncodedResource>( 4 );  
             this .resourcesCurrentlyBeingLoaded.set(currentResources);  
         }  
         if  (!currentResources.add(encodedResource)) {  
             throw  new  BeanDefinitionStoreException(  
                     "Detected recursive loading of "  + encodedResource +  " - check your import definitions!" );  
         }  
         //这里得到XML文件,并得到IO的InputSource准备进行读取。  
         try  {  
             InputStream inputStream = encodedResource.getResource().getInputStream();  
             try  {  
                 InputSource inputSource =  new  InputSource(inputStream);  
                 if  (encodedResource.getEncoding() !=  null ) {  
                     inputSource.setEncoding(encodedResource.getEncoding());  
                 }  
                 return  doLoadBeanDefinitions(inputSource, encodedResource.getResource());  
             }  
             finally  {  
                 inputStream.close();  
             }  
         }  
         catch  (IOException ex) {  
             throw  new  BeanDefinitionStoreException(  
                     "IOException parsing XML document from "  + encodedResource.getResource(), ex);  
         }  
         finally  {  
             currentResources.remove(encodedResource);  
             if  (currentResources.isEmpty()) {  
                 this .resourcesCurrentlyBeingLoaded.set( null );  
             }  
         }  
     }  
//具体的读取过程可以在doLoadBeanDefinitions方法中找到:  
     //这是从特定的XML文件中实际载入BeanDefinition的地方  
     protected  int  doLoadBeanDefinitions(InputSource inputSource, Resource resource)  
             throws  BeanDefinitionStoreException {  
         try  {  
             int  validationMode = getValidationModeForResource(resource);  
             //这里取得XML文件的Document对象,这个解析过程是由 documentLoader完成的,这个documentLoader是DefaultDocumentLoader,在定义documentLoader的地方创建  
             Document doc =  this .documentLoader.loadDocument(  
                     inputSource, getEntityResolver(),  this .errorHandler, validationMode, isNamespaceAware());  
             //这里启动的是对BeanDefinition解析的详细过程,这个解析会使用到Spring的Bean配置规则,是我们下面需要详细关注的地方。  
             return  registerBeanDefinitions(doc, resource);  
         }  
         catch  (BeanDefinitionStoreException ex) {  
             throw  ex;  
         }  
         catch  (SAXParseException ex) {  
             throw  new  XmlBeanDefinitionStoreException(resource.getDescription(),  
                     "Line "  + ex.getLineNumber() +  " in XML document from "  + resource +  " is invalid" , ex);  
         }  
         catch  (SAXException ex) {  
             throw  new  XmlBeanDefinitionStoreException(resource.getDescription(),  
                     "XML document from "  + resource +  " is invalid" , ex);  
         }  
         catch  (ParserConfigurationException ex) {  
             throw  new  BeanDefinitionStoreException(resource.getDescription(),  
                     "Parser configuration exception parsing XML from "  + resource, ex);  
         }  
         catch  (IOException ex) {  
             throw  new  BeanDefinitionStoreException(resource.getDescription(),  
                     "IOException parsing XML document from "  + resource, ex);  
         }  
         catch  (Throwable ex) {  
             throw  new  BeanDefinitionStoreException(resource.getDescription(),  
                     "Unexpected exception parsing XML document from "  + resource, ex);  
         }  
     }


关于具体的Spring BeanDefinition的解析,是在BeanDefinitionParserDelegate中完成的。这个类里包含了各种Spring Bean定义规则的处理,感兴趣的同学可以仔细研究。我们举一个例子来分析这个处理过程,比如我们最熟悉的对Bean元素的处理是怎样完成的,也就是我们在XML定义文件中出现的<bean></bean>这个最常见的元素信息是怎样被处理的。在这里,我们会看到那些熟悉的 BeanDefinition定义的处理,比如id、name、aliase等属性元素。把这些元素的值从XML文件相应的元素的属性中读取出来以后,会被设置到生成的BeanDefinitionHolder中去。这些属性的解析还是比较简单的。对于其他元素配置的解析,比如各种Bean的属性配置,通过一个较为复杂的解析过程,这个过程是由parseBeanDefinitionElement来完成的。解析完成以后,会把解析结果放到 BeanDefinition对象中并设置到BeanDefinitionHolder中去,如以下清单所示: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public  BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
         //这里取得在<bean>元素中定义的id、name和aliase属性的值
         String id = ele.getAttribute(ID_ATTRIBUTE);
         String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
 
         List<String> aliases =  new  ArrayList<String>();
         if  (StringUtils.hasLength(nameAttr)) {
             String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS);
             aliases.addAll(Arrays.asList(nameArr));
         }
 
         String beanName = id;
         if  (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
             beanName = aliases.remove( 0 );
             if  (logger.isDebugEnabled()) {
                 logger.debug( "No XML 'id' specified - using '"  + beanName +
                         "' as bean name and "  + aliases +  " as aliases" );
             }
         }
 
         if  (containingBean ==  null ) {
             checkNameUniqueness(beanName, aliases, ele);
         }
 
         //这个方法会引发对bean元素的详细解析
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
         if  (beanDefinition !=  null ) {
             if  (!StringUtils.hasText(beanName)) {
                 try  {
                     if  (containingBean !=  null ) {
                         beanName = BeanDefinitionReaderUtils.generateBeanName(
                                 beanDefinition,  this .readerContext.getRegistry(),  true );
                     }
                     else  {
                         beanName =  this .readerContext.generateBeanName(beanDefinition);
                         // Register an alias for the plain bean class name, if still possible,
                         // if the generator returned the class name plus a suffix.
                         // This is expected for Spring 1.2/2.0 backwards compatibility.
                         String beanClassName = beanDefinition.getBeanClassName();
                         if  (beanClassName !=  null  &&
                                 beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                                 ! this .readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                             aliases.add(beanClassName);
                         }
                     }
                     if  (logger.isDebugEnabled()) {
                         logger.debug( "Neither XML 'id' nor 'name' specified - "  +
                                 "using generated bean name ["  + beanName +  "]" );
                     }
                 }
                 catch  (Exception ex) {
                     error(ex.getMessage(), ele);
                     return  null ;
                 }
             }
             String[] aliasesArray = StringUtils.toStringArray(aliases);
             return  new  BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
         }
 
         return  null ;
     }

在具体生成BeanDefinition以后。我们举一个对property进行解析的例子来完成对整个BeanDefinition载入过程的分析,还是在类BeanDefinitionParserDelegate的代码中,它对BeanDefinition中的定义一层一层地进行解析,比如从属性元素集合到具体的每一个属性元素,然后才是对具体的属性值的处理。根据解析结果,对这些属性值的处理会封装成PropertyValue对象并设置到 BeanDefinition对象中去,如以下代码清单所示。 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
  * 这里对指定bean元素的property子元素集合进行解析。
  */
public  void  parsePropertyElements(Element beanEle, BeanDefinition bd) {
     //遍历所有bean元素下定义的property元素
     NodeList nl = beanEle.getChildNodes();
     for  ( int  i =  0 ; i < nl.getLength(); i++) {
         Node node = nl.item(i);
         if  (node  instanceof  Element && DomUtils.nodeNameEquals(node, PROPERTY_ELEMENT)) {
             //在判断是property元素后对该property元素进行解析的过程
             parsePropertyElement((Element) node, bd);
         }
     }
}
public  void  parsePropertyElement(Element ele, BeanDefinition bd) {
     //这里取得property的名字
     String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
     if  (!StringUtils.hasLength(propertyName)) {
         error( "Tag 'property' must have a 'name' attribute" , ele);
         return ;
     }
     this .parseState.push( new  PropertyEntry(propertyName));
     try  {
         //如果同一个bean中已经有同名的存在,则不进行解析,直接返回。也就是说,如果在同一个bean中有同名的property设置,那么起作用的只是第一个。
         if  (bd.getPropertyValues().contains(propertyName)) {
             error( "Multiple 'property' definitions for property '"  + propertyName +  "'" , ele);
             return ;
         }
         //这里是解析property值的地方,返回的对象对应对Bean定义的property属性设置的解析结果,这个解析结果会封装到PropertyValue对象中,然后设置到BeanDefinitionHolder中去。
         Object val = parsePropertyValue(ele, bd, propertyName);
         PropertyValue pv =  new  PropertyValue(propertyName, val);
         parseMetaElements(ele, pv);
         pv.setSource(extractSource(ele));
         bd.getPropertyValues().addPropertyValue(pv);
     }
     finally  {
         this .parseState.pop();
     }
}
/**
  * 这里取得property元素的值,也许是一个list或其他。
  */
public  Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
     String elementName = (propertyName !=  null ) ?
                     "<property> element for property '"  + propertyName +  "'"  :
                     "<constructor-arg> element" ;
 
     // Should only have one child element: ref, value, list, etc.
     NodeList nl = ele.getChildNodes();
     Element subElement =  null ;
     for  ( int  i =  0 ; i < nl.getLength(); i++) {
         Node node = nl.item(i);
         if  (node  instanceof  Element && !DomUtils.nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
                 !DomUtils.nodeNameEquals(node, META_ELEMENT)) {
             // Child element is what we're looking for.
             if  (subElement !=  null ) {
                 error(elementName +  " must not contain more than one sub-element" , ele);
             }
             else  {
                 subElement = (Element) node;
             }
         }
     }
     //这里判断property的属性,是ref还是value,不允许同时是ref和value。
     boolean  hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
     boolean  hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
     if  ((hasRefAttribute && hasValueAttribute) ||
             ((hasRefAttribute || hasValueAttribute) && subElement !=  null )) {
         error(elementName +
                 " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element" , ele);
     }
     //如果是ref,创建一个ref的数据对象RuntimeBeanReference,这个对象封装了ref的信息。
     if  (hasRefAttribute) {
         String refName = ele.getAttribute(REF_ATTRIBUTE);
         if  (!StringUtils.hasText(refName)) {
             error(elementName +  " contains empty 'ref' attribute" , ele);
         }
         RuntimeBeanReference ref =  new  RuntimeBeanReference(refName);
         ref.setSource(extractSource(ele));
         return  ref;
     //如果是value,创建一个value的数据对象TypedStringValue ,这个对象封装了value的信息。
     else  if  (hasValueAttribute) {
         TypedStringValue valueHolder =  new  TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
         valueHolder.setSource(extractSource(ele));
         return  valueHolder;
     //如果还有子元素,触发对子元素的解析
     else  if  (subElement !=  null ) {
         return  parsePropertySubElement(subElement, bd);
     }
     else  {
         // Neither child element nor "ref" or "value" attribute found.
         error(elementName +  " must specify a ref or value" , ele);
         return  null ;
     }
}

比如,再往下看,我们看到像List这样的属性配置是怎样被解析的,依然在BeanDefinitionParserDelegate中:返回的是一个List 对象,这个List是Spring定义的ManagedList,作为封装List这类配置定义的数据封装,如以下代码清单所示。 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public  List parseListElement(Element collectionEle, BeanDefinition bd) {
     String defaultElementType = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE);
     NodeList nl = collectionEle.getChildNodes();
     ManagedList<Object> target =  new  ManagedList<Object>(nl.getLength());
     target.setSource(extractSource(collectionEle));
     target.setElementTypeName(defaultElementType);
     target.setMergeEnabled(parseMergeAttribute(collectionEle));
     //具体的List元素的解析过程。
     parseCollectionElements(nl, target, bd, defaultElementType);
     return  target;
}
protected  void  parseCollectionElements(
         NodeList elementNodes, Collection<Object> target, BeanDefinition bd, String defaultElementType) {
     //遍历所有的元素节点,并判断其类型是否为Element。
     for  ( int  i =  0 ; i < elementNodes.getLength(); i++) {
         Node node = elementNodes.item(i);
         if  (node  instanceof  Element && !DomUtils.nodeNameEquals(node, DESCRIPTION_ELEMENT)) {
     //加入到target中去,target是一个ManagedList,同时触发对下一层子元素的解析过程,这是一个递归的调用。
             target.add(parsePropertySubElement((Element) node, bd, defaultElementType));
         }
     }
}

    经过这样一层一层的解析,我们在XML文件中定义的BeanDefinition就被整个给载入到了IoC容器中,并在容器中建立了数据映射。在IoC容器中建立了对应的数据结构,或者说可以看成是POJO对象在IoC容器中的映像,这些数据结构可以以AbstractBeanDefinition为入口,让IoC容器执行索引、查询和操作。 

    对核心数据结构的定义和处理应该可以看成是一个软件的核心部分了。所以,这里的BeanDefinition的载入可以说是IoC容器的核心,如果说IoC容器是Spring的核心,那么这些BeanDefinition就是Spring的核心的核心了!

六、web服务器集群,session如何同步?

答:(引用来源:http://www.poluoluo.com/server/201404/271126.html)

在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话,一个登录用户,一会是登录状态,一会又不是登录状态。所以本文就根据这种情况给出三种不同的方法来解决这个问题: 

一、利用数据库同步session 

在做多服务器session同步时我没有用这种方法,如果非要用这种方法的话,我想过二种方法: 

1,用一个低端电脑建个数据库专门存放web服务器的session,或者,把这个专门的数据库建在文件服务器上,用户访问web服务器时,会去这个专门的数据库check一下session的情况,以达到session同步的目的。 

2,这种方法是把存放session的表和其他数据库表放在一起,如果mysql也做了集群了话,每个mysql节点都要有这张表,并且这张session表的数据表要实时同步。 

说明:用数据库来同步session,会加大数据库的负担,数据库本来就是容易产生瓶颈的地方,如果把session还放到数据库里面,无疑是雪上加霜。上面的二种方法,第一点方法较好,把放session的表独立开来,减轻了真正数据库的负担 

二、利用cookie同步session 

session是文件的形势存放在服务器端的,cookie是文件的形势存在客户端的,怎么实现同步呢?方法很简单,就是把用户访问页面产生的 session放到cookie里面,就是以cookie为中转站。你访问web服务器A,产生了session把它放到cookie里面了,你访问被分配到web服务器B,这个时候,web服务器B先判断服务器有没有这个session,如果没有,在去看看客户端的cookie里面有没有这个 session,如果也没有,说明session真的不存,如果cookie里面有,就把cookie里面的sessoin同步到web服务器B,这样就可以实现session的同步了。 

说明:这种方法实现起来简单,方便,也不会加大数据库的负担,但是如果客户端把cookie禁掉了的话,那么session就无从同步了,这样会给网站带来损失;cookie的安全性不高,虽然它已经加了密,但是还是可以伪造的。 

三、利用memcache同步session 

memcache可以做分布式,如果没有这功能,他也不能用来做session同步。他可以把web服务器中的内存组合起来,成为一个"内存池",不管是哪个服务器产生的sessoin都可以放到这个"内存池"中,其他的都可以使用。 

优点:以这种方式来同步session,不会加大数据库的负担,并且安全性比用cookie大大的提高,把session放到内存里面,比从文件中读取要快很多。 

缺点:memcache把内存分成很多种规格的存储块,有块就有大小,这种方式也就决定了,memcache不能完全利用内存,会产生内存碎片,如果存储块不足,还会产生内存溢出。 

四、总结 

上面三种方法都是可行的 

第一种方法,最影响系统速度的那种,不推荐使用; 

第二种方法,效果不错,不过安全隐患一样的存在; 

第三种方法,个人觉得第三种方法是最好的,推荐大家使用


(引用来源:http://haotushu.sinaapp.com/post-503.html)

1、不使用session,换用cookie

session是存放在服务器端的,cookie是存放在客户端的,我们可以把用户访问页面产生的session放到cookie里面,就是以 cookie为中转站。你访问web服务器A,产生了session然后把它放到cookie里面,当你的请求被分配到B服务器时,服务器B先判断服务器有没有这个session,如果没有,再去看看客户端的cookie里面有没有这个session,如果也没有,说明session真的不存,如果 cookie里面有,就把cookie里面的sessoin同步到服务器B,这样就可以实现session的同步了。

说明:这种方法实现起来简单,方便,也不会加大数据库的负担,但是如果客户端把cookie禁掉了的话,那么session就无从同步了,这样会给网站带来损失;cookie的安全性不高,虽然它已经加了密,但是还是可以伪造的。

2、session存在数据库(MySQL等)中

PHP可以配置将session保存在数据库中,这种方法是把存放session的表和其他数据库表放在一起,如果mysql也做了集群了话,每个mysql节点都要有这张表,并且这张session表的数据表要实时同步。

说明:用数据库来同步session,会加大数据库的IO,增加数据库的负担。而且数据库读写速度较慢,不利于session的适时同步。

3、session存在memcache或者redis中

memcache可以做分布式,php配置文件中设置存储方式为memcache,这样php自己会建立一个session集群,将session数据存储在memcache中。

说明:以这种方式来同步session,不会加大数据库的负担,并且安全性比用cookie大大的提高,把session放到内存里面,比从文件中读取要快很多。但是memcache把内存分成很多种规格的存储块,有块就有大小,这种方式也就决定了,memcache不能完全利用内存,会产生内存碎片,如果存储块不足,还会产生内存溢出。

4、ip_hash

nginx中的ip_hash技术能够将某个ip的请求定向到同一台后端应用服务器,这样一来这个ip下的某个客户端和某个后端就能建立起稳固的session,ip_hash是在upstream配置中定义的:

[plain] view plaincopyprint?

upstream backend {  

    server 127.0.0.1:8001;  

    server 127.0.0.1:8002;  

    ip_hash;  

}  

说明:因为这种方式只能用IP来分配后端,所以要求nginx一定要是最前端的服务器,否则nginx会取不到真实的客户端ip,那ip_hash 就失效了。例如在服务器架构中使用squid做前端高速缓存,那么nginx取到的就是squid服务器的ip,用这个ip来做ip_hash肯定是不对的。再有,如果nginx的后端还有其他的负载均衡,将请求又分流了,那么对于某个客户端的请求,肯定不能定位到同一台应用服务器(例如php的 fast-cgi服务器等),这样也不能做到session共享,如果在nginx后面再做负载均衡,我们可以再搭一台squid,然后再直接到应用服务器,或者用 location作一次分流,将需要session的部分请求通过ip_hash分流,剩下的走其它后端。

5、upstream_hash

upstream_hash 是nginx 的一个第三方模块,支持采用nginx 内部的各种变量作hash,然后针对生成的hash 值,用求余的方式分布到后端(backend)服务器上,达到负载均衡的目的。该模块一般情况下是用做url_hash的,但是也可以做session共享。

假如前端是squid,他会将ip加入x_forwarded_for这个http_header里,用upstream_hash可以用这个头做因子,将请求定向到指定的后端:

在文档中是使用$request_uri做因子,稍微改一下:

hash $http_x_forwarded_for;

这样就改成了利用x_forwarded_for这个头作因子,在nginx新版本中可支持读取cookie值,所以也可以改成:

hash $cookie_jsessionid;

假如在php中配置的session为无cookie方式,配合nginx自己的一个userid_module模块就可以用nginx自发一个cookie,可参见userid模块的英文文档:

http://wiki.nginx.org/NginxHttpUserIdModule

6、通过nfs方式,把所有集群挂到一起,这种方案对访问用户比较多的网站不太合适,

在操作的时候也得设置session的路径,分目录存放,以免文件太大,使访问速度变慢。


七、java,.net,php等多编程语言客户端,如何共享session?

答:(引用来源:http://blog.163.com/lgh_2002/blog/static/44017526201051211612628/)

1. 客户端cookie加密

简单,高效。比较好的方法是自己采用cookie机制来实现一个session,在应用中使用此session实现。

   问题:session中数据不能太多,最好只有个用户id。

   参考实现:

http://rollerweblogger.org/

2. application server的session复制

   可能大部分应用服务器都提供了session复制的功能来实现集群,tomcat,jboss,was都提供了这样的功能。

   问题:

性能随着服务器增加急剧下降,而且容易引起广播风暴;

session数据需要序列化,影响性能。

如何序列化,可以参考 

对象的序列化和反序列化

http://www.linuxtone.org/thread-1195-1-1.html

应用服务器-JBoss 4.0.2集群指南

3. 使用数据库保存session

   使用数据库来保存session,就算服务器宕机了也没事,session照样在。

   问题:

程序需要定制; 

每次请求都进行数据库读写开销不小(使用内存数据库可以提高性能,宕机就会丢失数据。可供选择的内存数据库有BerkeleyDB,Mysql的内存表);

数据库是一个单点,当然可以做数据库的ha来解决这个问题。

4. 使用共享存储来保存session

   和数据库类似,就算服务器宕机了也没事,session照样在。使用nfs或windows文件共享都可以,或者专用的共享存储设备。

   问题:

程序需要定制; 

频繁的进行数据的序列化和反序列化,性能是否有影响;

共享存储是一个单点,这个可以通过raid来解决。

5. 使用memcached来保存session

   这种方式跟数据库类似,不过因为是内存存取的,性能自然要比数据库好多了。

   问题:

程序需要定制,增加了工作量;

存入memcached中的数据都需要序列化,效率较低;

          memcached服务器一死,所有session全丢。memchached能不能做HA? 我也不知道,网站上没提。

参考资料:

应用memcached保存session会话信息

正确认识memcached的缓存失效

扩展Tomcat 6.x,使用memcached存放session信息

6. 使用terracotta来保存session

   跟memcached类似,但是数据不需要序列化,并且是Find-Grained Changes,性能更好。配置对原来的应用完全透明,原有程序几乎不用做任何修改。而且terracotta本身支持HA。

问题:terracotta的HA本身进行数据复制性能如何?

参考资料:

JVM-level clustering

Terracotta集群Tomcat实现Session同步

使用Terracotta和Tomcat建立ACTIVE-PASSIVE模式的集群

用Spring Web Flow和Terracotta搭建Web应用

Terracotta实战示例——集群RIFE

Terracotta近况:转向开源,接受度,Hibernate支持

八、分布式框架dubbo的注册中心zk(zookeeper)节点有哪几种?

答:(引用来源:http://blog.csdn.net/heyutao007/article/details/38741207)

ZooKeeper 节点是有生命周期的,这取决于节点的类型。在 ZooKeeper 中,节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),以及时序节点(SEQUENTIAL ),具体在节点创建过程中,一般是组合使用,可以生成以下 4 种节点类型。

持久节点(PERSISTENT)

所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。

持久顺序节点(PERSISTENT_SEQUENTIAL)

这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。

临时节点(EPHEMERAL)

和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。

临时顺序节点(EPHEMERAL_SEQUENTIAL)

 可以用来实现分布式锁

客户端调用create()方法创建名为“_locknode_/guid-lock-”的节点,需要注意的是,这里节点的创建类型需要设置为EPHEMERAL_SEQUENTIAL。

客户端调用getChildren(“_locknode_”)方法来获取所有已经创建的子节点,注意,这里不注册任何Watcher。

客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点序号最小,那么就认为这个客户端获得了锁。

如果在步骤3中发现自己并非所有子节点中最小的,说明自己还没有获取到锁。此时客户端需要找到比自己小的那个节点,然后对其调用exist()方法,同时注册事件监听。

之后当这个被关注的节点被移除了,客户端会收到相应的通知。这个时候客户端需要再次调用getChildren(“_locknode_”)方法来获取所有已经创建的子节点,确保自己确实是最小的节点了,然后进入步骤3。


九、dubbo框架中,服务消费方调用服务提供方时,软负载均衡算法有哪几种,如何实现的?

答:(引用来源:http://my.oschina.net/u/593721/blog/174402)

Random LoadBalance

随机,按权重设置随机概率。

在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin LoadBalance

轮循,按公约后的权重设置轮循比率。

存在慢的提供者累积请求问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

LeastActive LoadBalance

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。

使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHash LoadBalance

一致性Hash,相同参数的请求总是发到同一提供者。

当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

算法参见:http://en.wikipedia.org/wiki/Consistent_hashing。

缺省只对第一个参数Hash,如果要修改,请配置<dubbo:parameter key="hash.arguments" value="0,1" />

缺省用160份虚拟节点,如果要修改,请配置<dubbo:parameter key="hash.nodes" value="320" />

这几种算法是很常见的,如果想知道更多的负载均衡算法,可以看看nginx的配置

  • 2
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值