Chapter 39. 高可获得性(High Availability)和失效备援(Failover)

高可获得性是指当系统中有一台甚至多台服务器发生故障时还能继续运转的能力

作为高可获得性的一部分,失效备援的含意是 当客户端当前连接的服务器发故障时,客户端可以将连接转到另一台正常的服务器,从而能够继续工作

39.1. 主要-备份对

HornetQ可以将两个服务器以主要-备份对的形式连接在一起。目前HornetQ允许一个 主要服务器有一个备份服务器,一个备份服务器只有一个主要服务器。在正常情况下主要服务器工作,备份服务器只有当 发生失效备援发生时工作。

没有发生失效备援时,主要服务器为客户端提供服务,备份服务器处于待机状态。当客户端在失效备援后连接到备份服务 器时,备份服务器开始激活并开始工作。

39.1.1. 高可获得性(HA)的模式

HornetQ的高可获得性有两种模式:一种模式通过由主服务器日志向备份服务器日志 复制数据。另一种模式则是主服务器与备份服务器间存贮共享

Note

只有持久消息才可以在失效备援时不丢失。所有非持久消息则会丢失。

39.1.1.1. 数据复制

在这种模式下,保存在HornetQ主服务器中日志中的数据被复制到备份服务器日志中。注意我们并不复制 服务器的全部状态,而是只复制日志和其它的持久性质的操作。

复制的操作是异步进行的。数据通过流的方式复制,复制的結果则通过另一个流来返回。通过这样的异步方式 我们可以获得比同步方式更大的呑吐量。

当用户得到确认信息如一个事务已经提交、准备或加滚,或者是一个持久消息被发送时,HornetQ确保这些状态 已经复制到备份服务器上并被持久化。

数据复制这种方式不可避免地影响性能,但是另一方面它不依赖于昂贵的文件共享设备(如SAN)。它实际上是 一种无共享的HA方式。

采用数据复制的失效备援比采用共享存储的失效备援要快,这是因为备份服务器在失效备援时不用重新装载日志。

39.1.1.1.1. 配置

首先在主服务器的 hornetq-configuration.xml文件中配置备份服务器。 配置的参数是backup-connector-ref。这个参数指向一个连接器。这个连接器 也在主服务器上配置。它定义了如何与备份服务器建立连接。

下面就是一个在hornetq-configuration.xml文件中的例子:

  <backup-connector-ref connector-name="backup-connector"/>

  <connectors>
     <!-- 这个连接器用于连接备份服务喝咖啡    -->
     <!-- 备份服务器在主机"192.168.0.11"上,端口"5445" -->
     <connector name="backup-connector">
       <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class>
       <param key="host" value="192.168.0.11"/>
       <param key="port" value="5445"/>
     </connector>
  </connectors>

其次在备份服务器上,我们设置了备份服务器的标志,并且配置了相应的接受器以便主服务器能够建立 连接。同时我们将shared-store参数设为false。

  <backup>true</backup>
  
  <shared-store>false<shared-store>
  
  <acceptors>
     <acceptor name="acceptor">
        <factory-class>org.hornetq.core.remoting.impl.netty.NettyConnectorFactory</factory-class>
        <param key="host" value="192.168.0.11"/>
        <param key="port" value="5445"/>
     </acceptor>
  </acceptors>               
              

为了使备份服务器正常工作,一定要保证它与主服务器有着同样的桥、预定义的队列、集群连接、 广播组和发现组。最简单的作法是拷贝主服务器的全部配置然后再进行上述的修改。

39.1.1.1.2. 备份服务器与主服务器间的同步

为了能正常工作,备份服务器与主服务器必须同步。这意谓着备份服务器不能是当前任意一个备份服 务器。如果你这样做,主服务器将不能成功启动,在日志中会出现异常。

要想将一个现有的服务器配置成一个备份服务器,你需要将主服务器的data 文件夹拷贝到并覆盖这个备份 服务器的相同文件夹,这样做保证了备份服务器与主服务器的持久化数据完全一致。

当失效备援发生后,备份服务器代替主服务器工作,原来的主服务器失效。这时简单的重启主服务 器是不行的。要想将主服务器与备份重新进行同步,就必须先将主服务器和备份服务器同时停止,再将 主服务器的数据拷贝到备份服务器,然后再启动。

HornetQ以后将支持备份与主服务器间的自动同步,无需停止主服务器。

39.1.1.2. 存贮共享

使用存贮共享,主服务器与备份服务器共用相同目录的日志数据,通常是一个共享的 文件系统。这包括转存目录,日志目录,大消息及绑定日志。

当发生失效备援时,工作由备份服务器接管。它首先从共享的文件系统中读取主服务器的持久数据,然后 才能接受客户端的连接请求。

与数据复制方式不同的是这种方式需要一个共享的文件系统,主服务器与备份服务器都可以访问。典型的 高性能的共享系统是存贮区域网络(SAN)系统。我们不建议使用网络附加存贮(NAS),如NFS,来存贮共享 日志(主要的原因是它们比较慢)。

共享存贮的优点是不需要在主服务器与备份服务器之间进行数据复制,因此对性能不会造成影响。

共享存贮的缺点是它需要一个共享文件系统。同时,当备份服务器激活时它需要首先从共享日志中读取相应 的信息,从而占用一定的时间。

如果你需要在一般工作情况下保持高性能,并且拥有一个快速的SAN系统,同时能够容忍较慢的失效备援 过程(取决于数据量在多少),我们建议你采用存贮共享方式的高可获得性。

39.1.1.2.1. 配置

要使用存贮共享模式,在两个服务器的配置文件hornetq-configuration.xml 中将作如下设置:

                   <shared-store>true<shared-store>
                

此外,备份服务器必须显式地指定:

                   <backup>true</backup>
                     

另外,需要将主服务器和备份服务器的日志文件位置指向同一个共享位置。 (参见Section 15.3, “配置消息日志”

如果客户端使用JMS自动失效备援,主服务器除了要配置一个连接器以连接到备份服务器外,还要在 配置文件hornetq-jms.xml中指向这个连接器,如 Section 39.2.1, “自动客户端失效备援”中所解释的那样。

39.1.1.2.2. 备份服务器与主服务器间的同步。

由于主备服务器之间共享存贮,所以它们不需要进行同步。但是它需要主备服务器同时工作以提供 高可获得性。如果一量发生失效备援后,就需要在尽可能早的时间内将备份服务器(处于工作状态)停下来, 然后再启动主服务器和备份服务器。

HornetQ以后将支持自动同步功能,不需要先停止服务器。

39.2. 失效备援的模式

HornetQ定义了两种客户端的失效备援:

  • 自动客户端失效备援

  • 应用层的客户端失效备援

HornetQ还支持100%透明的同一个服务器的自动连接恢复(适用于网络的临时性故障)。这与失效备援很相似, 只不过连接的是同一个服务器,参见Chapter 34, 客户端重新连接与会话恢复

在发生失效备援时,如果客户端有非持久或临时队列的接收者时,这些队列会自动在备份服务器上重新创建。对于 非持久性的队列,备份服务器事先是没有它们的信息的。

39.2.1. 自动客户端失效备援

HornetQ的客户端可以配置主/备份服务器的信息,当客户端与主服务器的连接发生故障时,可以自动检测到故障并 进行失效备援处理,让客户端连接到备份服务器上。备份服务器可以自动重新创建所有在失效备援之前存在的会话与接收 者。客户端不需要进行人工的连接恢复工作,从而节省了客户端的开发工作。

HornetQ的客户端在参数client-failure-check-period(在 Chapter 17, 失效连接的检测中进行了解释)规定的时间内如果没有收到数据包,则认为连接发生故障。 当客户端认为连接故障时,它就会尝试进行失效备援。

HornetQ有几种方法来为客户端配置主/备服务器对的列表。可以采用显式指定的方法,或者采用更为常用的 服务器发现的方法。有关如何配置服务器发现的详细信息,请参见 Section 38.2, “服务器发现”。 关于如何显式指定主/备服务器对的方法,请参见Section 38.5.2, “指定服务器列表以组成集群”中的解释。

要使客户端具备自动失效备援,在客户端的配置中必须要指定重试的次数要大于零(参见 Chapter 34, 客户端重新连接与会话恢复中的解释)。

有时你需要在主服务器正常关机的情况下仍然进行失效备援。如果使用JMS,你需要将HornetQConnectionFactoryFailoverOnServerShutdown属性设为true,或者是在hornetq-jms.xml(参数为failover-on-server-shutdown)文件中进行相应的配置。如果使用的是核心接口,可以在创建 ClientSessionFactoryImpl实例时将上述同名属性设置为true。 这个属性的默认值是false。这表示如果主服务器是正常关机,客户端将不会进行失效备援

Note

默认正常关机不会不会导致失效备援。

使用CTRL-C来关闭HornetQ服务器或JBoss应用服务器属于正常关机,所以不会触发客户端的失效 备援。

要想在这种情况下进行失效备援必须将属性FailoverOnServerShutdown 设为true。

默认情况下至少创建了一个与主服务器的连接后失效备援才会发生。换句话说,如果客户端每一次创建与 主服务器的连接失败,它会根据参数reconnection-attempts的设置进行连接重试,而不是进行失效备援。 如果重试次数超过的该参数的值,则连接失败。

在有些情况下,你可能希望在初始连接失败和情况下自动连接到备份服务器,那么你可以直接在 ClientSessionFactoryImplHornetQConnectionFactory上设置FailoverOnInitialConnection 参数,或者在配置文件中设置failover-on-initial-connection。默认的值是false

有关事务性及非事务性JMS会话的自动失效备援的例子,请参见 Section 11.1.57, “带有数据复制的事务性失效备援”Section 11.1.33, “带有服务器数据复制的非事务失效备援”

39.2.1.1. 关于服务器的复制

HornetQ在主服务器向备份服务器复制时,并不复制服务器的全部状态。所以当一个会话在备份服务器 中重新创建后,它并不知道发送过的消息或通知过的消息。在失效备援的过程中发生的消息发送或通知也可 能丢失。

理论上如果进行全部状态的复制,我们可以提供100%的透明的失效备援,不会失去任何的消息或通知。 但是这样做要付出很大的代价:即所有信息都要进行复制(包括队列,会话等等)。也就是要求复制服务 器的每个状态信息,主服务器的每一步操作都将向其备份进行复制,并且要在全局内保持顺序的一致。这样 做就极难保证高性能和可扩展性,特别是考虑到多线程同时改变主服务器的状态的情况,要进行全状态复制 就更加困难。

一些技术可以用来实现全状态复制,如虚拟同步技术 (virtual synchrony)。但是这些技术往往没有很好的可扩展性,并且将所有操作都 进行序列化,由单一线程进行处理,这样明显地将底了并行处理能力。

另外还有其它一些多线程主动复制技术,比如复制锁状态或复制线程调度等。这些技术使用Java语言非常 难于实现。

因此得出结论,采用大量牺牲性能来换取100%透明的失效备援是得不偿失的。没有100%透明的失效 备援我们仍然可以轻易地保证一次且只有一次的传递。这是通过在发生故障时采用重复检测结合事务重试 来实现的。

39.2.1.2. 失效备援时阻塞调用的处理

如果当发生失效备援时客户端正面进行一个阻塞调用并等待服务器的返回,新创建的会话不会知道这个调用, 因此客户端可能永远也不会得到响应,也就可能一直阻塞在那里。

为了防止这种情况的发生,HornetQ在失效备援时会解除所有的阻塞调用,同时抛出一个 javax.jms.JMSException异常(如果是JMS)或HornetQException异常。异常的错误代码是HornetQException.UNBLOCKED。客户端需要自行处理这个异常,并且进行 必要的操作重试。

如果被解除阻塞的调用是commit()或者prepare(),那么这个事务会被自动地回滚,并且HornetQ 会抛出一个javax.jms.TransactionRolledBackException(如果是JMS) 或都是一个HornetQException,错误代码为 HornetQException.TRANSACTION_ROLLED_BACK(如果是核心接口)。

39.2.1.3. 事务的失效备援处理

如果在一个事务性会话中,在当前事务中消息已经发出或通知,则服务器在这时如果发生失效备援,它不 能保证发出的消息或通知没有丢失。

因此这个事务就会被标记为只回滚,任何尝试提交的操作都会抛出一个javax.jms.TransactionRolledBackException异常(如果是JMS),或者是一 个HornetQException的异常,错误代码为HornetQException.TRANSACTION_ROLLED_BACK(如果是核心接口)。

客户端需要自行处理这些异常,进行必要的回滚处理。注意这里不需要人工将会话进行回滚-此时它已经 被回滚了。用户可以通过同一个会话重试该事务操作。

HornetQ发布包中包括了一个完整的例子来展示如何处理这种情况。参见 Section 11.1.57, “带有数据复制的事务性失效备援”

如果是在提交过程中发生了失效备援,服务器将这个阻塞调用解除。这种情况下客户端很难确定在事故发生 之前事务是否在主服务器中得到了处理。

为了解决这个问题,客户端可以在事务中使用重复检测(Chapter 37, 重复消息检测) ,并且在提交的调用被解除后重新尝试事务操作。如果在失效备援前事务确实在主服务器上已经完成提交,那么 当事务进行重试时,重复检测功能可以保证重复发送的消息被丢弃,这样避免了消息的重复。

Note

通过处理异常和重试,适当处理被解除的阻塞调用并配合重复检测功能,HornetQ可以在故障条件下保证 一次并且只有一次的消息传递,没有消息丢失和消息重复。

39.2.1.4. 非事务会话的失效备援处理

如果会话是非事务性的,那么通过它的消息或通知在故障时可能会丢失。

如果你在非事务会话中要保证一次并且只有一次 的消息传递,你需要使用重复检测功能,并适当处理被解除的阻塞调用。参见 Section 39.2.1.2, “失效备援时阻塞调用的处理”

39.2.2. 连接故障的通知

JMS提供了标准的异步接收连接故障通知的机制:java.jms.ExceptionListener。 请参考JMS的javadoc或者其它JMS教程来进一步了解怎样使用这个接口。

HornetQ的核心接口也提供了一个相似的接口 org.hornet.core.client.SessionFailureListener

任何ExceptionListener或SessionFailureListener的实例,在发生连接故障时,都会被HornetQ 调用,不管该连接是否得到了失效备援、重新连接还是得到了恢复。

39.2.3. 应用层的失效备援

在某些情况下你可能不需要自动的客户端失效备援,希望自己来处理连接故障,使用自己的重新连接方案等。 我们把它称之为应用层失效备援,因为它是发生在应用层的程序中。

为了实现应用层的失效备援,你可以使用监听器(listener)的方式。如果使用的是JMS,你需要在JMS连接上 设置一个ExceptionListener类。这个类在连接发生故障时由HornetQ调用。在这个类 中你可以将旧的连接关闭,使用JNDI查找新的连接工厂并创建新的连接。这里你可以使用 HA-JNDI 来保证新的连接工厂来自于另一个服务器。

请参见Section 11.1.1, “应用层的失效备援(Failover)”。这是一个完整的应用层失效备援的例子。

如果你使用核心接口,则过程也是很相似的:你在核心的ClientSession实例上设置一个 FailureListener,然后在这个类中进行相应的处理即可。