【EJB】Developing EJB Applications -- Chapter 8(EJB集群)

EJB集群

8.1 关于企业JAVABEANS(EJBS)

       EJB组件可以集群用于高可用性场景。它们使用不同于HTTP组件的协议,因此它们以不同的方式进行聚类。 EJB 2和3有状态和无状态的bean可以聚类。

      有关单身的信息,请参阅“JBoss EAP开发指南”中的“HA Singleton服务”


8.2 部署集群EJBS

       JBossEAP 7.0的HA配置文件中提供了集群支持。 启动启用了HA功能的独立服务器,包括使用standalone-ha.xml(甚至独立的full-ha.xml)进行启动:

./standalone.sh -server-config=standalone-ha.xml

       这将启动具有HA功能的服务器的单个实例。

 

       显然,为了能够看到集群的好处,您将需要多个服务器实例。 所以让我们开始另一个具有HA功能的服务器。 服务器的另一个实例可以在同一台机器上或其他机器上。 如果它在同一台机器上,您将需要处理两件事情 -

 

       *传递第二个实例的端口偏移

       *确保每个服务器实例都有一个唯一的jboss.node.name系统属性。

 

       您可以通过将以下两个系统属性传递到启动命令来执行此操作:

./standalone.sh -server-config=standalone-ha.xml -
Djboss.socket.binding.port-offset= -
Djboss.node.name=

       按照您将EJB部署部署到此实例的方式,您可以随时使用。


       警告
       仅在集群服务器的独立实例的一个节点上部署应用程序并不意味着它将自动部署到其他集群实例。您将不得不在其他独立集群实例上明确部署它。或者您可以以域模式启动服务器,以便部署可以部署到服务器组中的所有服务器。


       现在,您已经在两个实例上部署了具有集群EJB的应用程序,现在EJB可以利用集群功能。


       注意
       启动JBoss EAP 7,如果JBoss EAP使用HA配置文件启动,则SFSB的状态将被复制。您不再需要使用@Clustered注释来启用群集行为。
       通过使用EJB 3.2规范中新增的@Stateful(passivationCapable = false)注释您的bean,可以在每个EJB的基础上禁用此行为。或全局,通过ejb3子系统。


8.3 垃圾邮件

       群集EJB具有故障切换功能。 @Stateful EJB的状态在集群节点之间进行复制,以便如果集群中的某个节点关闭,则某个其他节点将能够接管该调用。


8.4 远程客户

       独立的远程客户机可以使用JNDI方法或本地JBoss EJB客户端API与服务器进行通信。 要注意的是,当您调用集群EJB部署时,您不必列出集群中的所有服务器(由于集群中群集节点添加的动态特性,显然不可行)。


       远程客户端必须仅列出具有集群功能的其中一个服务器。 该服务器将作为客户端和群集节点之间的集群拓扑通信的起点。


       请注意,您必须在jboss-ejb-client.properties配置文件中配置ejb集群:

remote.clusters=ejb
remote.cluster.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
remote.cluster.ejb.connect.options.org.xnio.Options.SSL_ENABLED=false

8.5 集群拓扑通信

       当客户端连接到服务器时,如果服务器具有集群功能,则JBoss EJB客户端实现与服务器内部通信进行集群拓扑信息通信。例如,让我们假设我们将服务器X列为要连接的初始服务器。当客户端连接到服务器X时,服务器将向客户端发送异步集群拓扑消息。此拓扑消息由集群名称和属于集群的节点信息组成。节点信息包括要连接的节点地址和端口号。所以在这个例子中,服务器X将发回由属于集群的其他服务器Y组成的集群拓扑。

 

       在有状态的集群EJB的情况下,典型的调用流程涉及为状态bean创建一个会话,当对该bean进行JNDI查找时,会发生这种情况,然后调用返回的代理。在内部查找状态bean会触发从客户端到服务器的同步会话创建请求。在这种情况下,会话创建请求转到服务器X,因为我们已经在jboss-ejb-client.properties文件中配置了与服务器X的初始连接。由于服务器X是集群的,它将返回一个会话ID并发回该会话的关联。在集群服务器的情况下,关联性等于有状态bean在服务器端所属的集群的名称。对于非群集bean,关联性是创建会话的节点名称。这种亲和力稍后将帮助EJB客户端将代理服务器上的调用(如适用)路由到群集bean中的某个节点或非群集bean的特定节点。当此会话创建请求正在进行时,服务器X还将发回包含集群拓扑的异步消息。 JBoss EJB客户端实现将记录此拓扑信息,并在需要时将其用于连接创建到集群内的节点,并对这些节点进行路由调用。

 

       现在我们知道集群拓扑信息是如何从服务器传递给客户端的,看看故障转移如何工作。让我们继续以服务器X作为我们的起点和查询有状态bean并调用它的客户端应用程序的示例。在这些调用期间,客户端从服务器收集集群拓扑信息。现在让我们假设某些原因,服务器X下降,客户端应用程序随后在代理上调用。 JBoss EJB客户端实现在这个阶段必须了解亲和度,在这种情况下,它是一个集群关系。由于集群拓扑信息具有,它知道集群具有两个节点服务器X和服务器Y.当调用到达时,它看到服务器X已关闭。因此,它使用选择器从集群节点中获取合适的节点。当选择器从集群中返回一个节点时,JBoss EJB客户端实现将创建一个到该节点的连接(如果之前尚未创建),并创建一个EJB接收器。因为在我们的示例中,集群中唯一的其他节点是服务器Y,所以选择器将返回该节点,JBoss EJB客户机实现将使用它来创建一个EJB接收器,并使用该接收器传递对该节点的调用代理。有效地,调用现在已经故障转移到群集中的不同节点。

 

 

8.6 远程客户在另一个实例

       到目前为止,我们讨论了远程独立客户机,通常使用EJB客户端API或基于jboss-ejb-client.properties的方法来配置和部署集群bean的服务器。现在让我们考虑客户端是部署在另一个JBoss EAP实例上的应用程序,并且想要在部署在另一个JBoss EAP实例上的集群状态bean上进行调用的情况。在这个例子中,我们考虑一个涉及三台服务器的情况。服务器X和Y都属于集群,并在其上部署了集群EJB。让我们考虑另一个服务器实例服务器C,它可能有也可能没有集群能力。该服务器C作为客户端,在该客户端上有部署需要在部署在服务器X和Y上的群集bean上进行调用,并实现故障切换。

 

       这些配置在jboss-ejb-client.xml文件中完成,该文件指向与其他服务器的远程出站连接。 jboss-ejb-client.xml文件中的这个配置在服务器C的部署中,因为这是我们的客户端。客户端配置不需要指向所有群集节点,而只是其中一个将作为通信的起点。所以在这种情况下,我们可以在服务器C到服务器X上创建远程出站连接,并使用服务器X作为通信的起点。就像在远程独立客户机的情况下一样,当服务器C上的应用程序查找有状态的bean时,会话创建请求将被发送到服务器X,该服务器将发回会话标识和集群亲和性。此外,服务器X异步地向包含集群拓扑的服务器C发送消息。该拓扑信息将包括服务器Y的节点信息,因为服务器Y与服务器X一起属于集群。代理服务器上的后续调用将被适当路由到集群中的节点。如果服务器X关闭,如前所述,将选择与集群不同的节点,并将调用转发到该节点。

 

       可以看出,另一个JBossEAP实例上的远程独立客户机和远程客户端在故障切换方面都相似。


8.7 STANDALONE和服务器端客户端配置

       要将EJB客户端连接到集群EJB应用程序,您需要扩展独立的EJB客户端或者在服务器端的EJB客户机中的现有配置,以包括集群连接配置。 用于独立EJB客户端的jboss-ejb-client.properties,或者用于服务器端应用程序的jboss-ejb-client.xml文件必须扩展为包含集群配置。

 

       注意

       EJB客户端是在远程服务器上使用EJB的任何程序。 当客户端调用远程服务器的EJB本身在服务器内运行时,客户端是服务器端。换句话说,调用另一个JBoss EAP实例的JBoss EAP实例将被视为服务器端的客户端。

 

       此示例显示独立EJB客户端所需的其他群集配置。

remote.clusters=ejb
remote.cluster.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
remote.cluster.ejb.connect.options.org.xnio.Options.SSL_ENABLED=false
remote.cluster.ejb.username=test
remote.cluster.ejb.password=password

       如果应用程序使用远程出站连接,则需要配置jboss-ejb-client.xml文件并添加群集配置,如以下示例所示:


  
    
      
      
      
      
    

    

    
      
      
        
        
        
        
      
    
  

       有关远程出站连接的详细信息,请参阅“JBoss EAP配置指南”中的“关于远程子系统”。

       注意

       对于安全连接,您需要将凭据添加到群集配置,以避免身份验证异常

 

8.8 实施用于EJB呼叫的自定义负载平衡策略

       可以实现备用或定制的负载平衡策略,以平衡应用程序在服务器之间的EJB调用。

 

       您可以为EJB调用实现AllClusterNodeSelector。 AllClusterNodeSelector的节点选择行为类似于默认选择器,除了AllClusterNodeSelector即使在大型集群(节点数> 20)的情况下也使用所有可用的集群节点。 如果未连接的集群节点返回,则会自动打开。 以下示例显示AllClusterNodeSelector实现:

package org.jboss.as.quickstarts.ejb.clients.selector;

import java.util.Arrays;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.ejb.client.ClusterNodeSelector;
public class AllClusterNodeSelector implements ClusterNodeSelector {
  private static final Logger LOGGER = Logger.getLogger(AllClusterNodeSelector.class.getName());

  @Override
  public String selectNode(final String clusterName, final String[] connectedNodes, final String[] availableNodes) {
    if(LOGGER.isLoggable(Level.FINER)) {
      LOGGER.finer("INSTANCE "+this+ " : cluster:"+clusterName+" connected:"+Arrays.deepToString(connectedNodes)+" available:"+Arrays.deepToString(availableNodes));
    }

    if (availableNodes.length == 1) {
        return availableNodes[0];
    }
    final Random random = new Random();
    final int randomSelection = random.nextInt(availableNodes.length);
    return availableNodes[randomSelection];
  }
}

       您还可以为EJB调用实现SimpleLoadFactorNodeSelector。SimpleLoadFactorNodeSelector中的负载平衡基于负载因子发生。负载因子(2/3/4)根据节点的名称(A / B / C)计算,而不考虑每个节点的负载。以下示例显示SimpleLoadFactorNodeSelector实现:

package org.jboss.as.quickstarts.ejb.clients.selector;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.ejb.client.DeploymentNodeSelector;
public class SimpleLoadFactorNodeSelector implements DeploymentNodeSelector {
  private static final Logger LOGGER = Logger.getLogger(SimpleLoadFactorNodeSelector.class.getName());
  private final Map[]> nodes = new HashMap[]>();
  private final Map cursor = new HashMap();

  private ArrayList calculateNodes(Collection eligibleNodes) {
    ArrayList nodeList = new ArrayList();

    for (String string : eligibleNodes) {
      if(string.contains("A") || string.contains("2")) {
        nodeList.add(string);
        nodeList.add(string);
      } else if(string.contains("B") || string.contains("3")) {
        nodeList.add(string);
        nodeList.add(string);
        nodeList.add(string);
      } else if(string.contains("C") || string.contains("4")) {
        nodeList.add(string);
        nodeList.add(string);
        nodeList.add(string);
        nodeList.add(string);
      }
    }
    return nodeList;
  }

  @SuppressWarnings("unchecked")
  private void checkNodeNames(String[] eligibleNodes, String key) {
    if(!nodes.containsKey(key) || nodes.get(key)[0].size() != eligibleNodes.length || !nodes.get(key)[0].containsAll(Arrays.asList(eligibleNodes))) {
      // must be synchronized as the client might call it concurrent
      synchronized (nodes) {
        if(!nodes.containsKey(key) || nodes.get(key)[0].size() != eligibleNodes.length || !nodes.get(key)[0].containsAll(Arrays.asList(eligibleNodes))) {
          ArrayList nodeList = new ArrayList();
          nodeList.addAll(Arrays.asList(eligibleNodes));

          nodes.put(key, new List[] { nodeList, calculateNodes(nodeList) });
        }
      }
    }
  }
   private synchronized String nextNode(String key) {
    Integer c = cursor.get(key);
    List nodeList = nodes.get(key)[1];

    if(c == null || c >= nodeList.size()) {
      c = Integer.valueOf(0);
    }

    String node = nodeList.get(c);
    cursor.put(key, Integer.valueOf(c + 1));

    return node;
  }

  @Override
  public String selectNode(String[] eligibleNodes, String appName, String moduleName, String distinctName) {
    if (LOGGER.isLoggable(Level.FINER)) {
      LOGGER.finer("INSTANCE " + this + " : nodes:" + Arrays.deepToString(eligibleNodes) + " appName:" + appName + " moduleName:" + moduleName
          + " distinctName:" + distinctName);
    }

    // if there is only one there is no sense to choice
    if (eligibleNodes.length == 1) {
      return eligibleNodes[0];
    }
    final String key = appName + "|" + moduleName + "|" + distinctName;

    checkNodeNames(eligibleNodes, key);
    return nextNode(key);
  }
}

       配置jboss-ejb-client.properties文件

       您需要使用实现类(AllClusterNodeSelector或SimpleLoadFactorNodeSelector)的名称添加remote.cluster.ejb.clusternode.selector属性。选择器将在调用时看到可用的所有配置的服务器。以下示例使用AllClusterNodeSelector作为集群节点选择器:

remote.clusters=ejb
remote.cluster.ejb.clusternode.selector=org.jboss.as.quickstarts.ejb.clients.selector.AllClusterNodeSelector
remote.cluster.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
remote.cluster.ejb.connect.options.org.xnio.Options.SSL_ENABLED=false
remote.cluster.ejb.username=test
remote.cluster.ejb.password=password

remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
remote.connections=one,two
remote.connection.one.host=localhost
remote.connection.one.port = 8080
remote.connection.one.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
remote.connection.one.username=user
remote.connection.one.password=user123
remote.connection.two.host=localhost
remote.connection.two.port = 8180
remote.connection.two.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

       使用EJB Client API

Properties p = new Properties();
p.put("remote.clusters", "ejb");
p.put("remote.cluster.ejb.clusternode.selector", "org.jboss.as.quickstarts.ejb.clients.selector.AllClusterNodeSelector");
p.put("remote.cluster.ejb.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false");
p.put("remote.cluster.ejb.connect.options.org.xnio.Options.SSL_ENABLED", "false");
p.put("remote.cluster.ejb.username", "test");
p.put("remote.cluster.ejb.password", "password");

p.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false");
p.put("remote.connections", "one,two");
p.put("remote.connection.one.port", "8080");
p.put("remote.connection.one.host", "localhost");
p.put("remote.connection.two.port", "8180");
p.put("remote.connection.two.host", "localhost");

EJBClientConfiguration cc = new PropertiesBasedEJBClientConfiguration(p);
ContextSelector selector = new ConfigBasedEJBClientContextSelector(cc);
EJBClientContext.setSelector(selector);

p = new Properties();
p.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
InitialContext context = new InitialContext(p);


       您需要将属性remote.cluster.ejb.clusternode.selector添加到PropertiesBasedEJBClientConfiguration构造函数的列表中。以下示例使用AllClusterNodeSelector作为集群节点选择器:

        配置jboss-ejb-client.xml文件

 

      要使用服务器到服务器通信的负载平衡策略,请将该类与应用程序一起打包,并将其配置在位于META-INF文件夹中的jboss-ejb-client.xml设置中。以下示例使用AllClusterNodeSelector作为集群节点选择器:


  
    
      
      
    

  
   
     
     
        
           
           
        
      
   
  

        要使用具有安全性的上述配置,您需要将ejb-security-realm-1添加到客户端 - 服务器配置。以下示例显示用于添加安全领域(ejb-security-realm-1)的CLI命令,该值是用户“test”的base64编码密码:

core-service=management/security-realm=ejb-security-realm-1:add()
core-service=management/security-realm=ejb-security-realm-1/server-identity=secret:add(value=cXVpY2sxMjMr)

        如果负载平衡策略应用于服务器到服务器的通信,则该类可以与应用程序一起打包或作为模块进行打包。此类在位于顶级EAR存档的META-INF目录中的jboss-ejb-client设置文件中配置。以下示例使用RoundRobinNodeSelector作为部署节点选择器。


    
        
            
        
        ...
    

        注意

        如果您正在运行独立服务器,请使用起始选项-Djboss.node.name =或服务器配置文件standalone.xml配置服务器名称。确保服务器名称是唯一的。如果您正在运行托管域,主机控制器将自动验证名称是唯一的。


        原文地址:

https://access.redhat.com/documentation/en-us/red_hat_jboss_enterprise_application_platform/7.0/html/developing_ejb_applications/clustered_enterprise_javabeans




你可能感兴趣的:(【Developing,EJB,Applications】)