001package com.avaje.ebeaninternal.server.cluster.socket;
002
003import com.avaje.ebeaninternal.server.cluster.ClusterBroadcast;
004import com.avaje.ebeaninternal.server.cluster.ClusterManager;
005import com.avaje.ebeaninternal.server.cluster.SocketConfig;
006import com.avaje.ebeaninternal.server.cluster.message.ClusterMessage;
007import com.avaje.ebeaninternal.server.cluster.message.MessageReadWrite;
008import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012import java.io.EOFException;
013import java.io.IOException;
014import java.io.InterruptedIOException;
015import java.net.InetSocketAddress;
016import java.util.HashMap;
017import java.util.List;
018import java.util.concurrent.atomic.AtomicLong;
019
020/**
021 * Broadcast messages across the cluster using sockets.
022 */
023public class SocketClusterBroadcast implements ClusterBroadcast {
024
025  private static final Logger clusterLogger = LoggerFactory.getLogger("org.avaje.ebean.Cluster");
026
027  private static final Logger logger = LoggerFactory.getLogger(SocketClusterBroadcast.class);
028
029  private final SocketClient local;
030
031  private final HashMap<String, SocketClient> clientMap;
032
033  private final SocketClusterListener listener;
034
035  private final SocketClient[] members;
036
037  private final MessageReadWrite messageReadWrite;
038
039  private final AtomicLong countOutgoing = new AtomicLong();
040
041  private final AtomicLong countIncoming = new AtomicLong();
042
043  public SocketClusterBroadcast(ClusterManager manager, SocketConfig config) {
044
045    this.messageReadWrite = new MessageReadWrite(manager);
046
047    String localHostPort = config.getLocalHostPort();
048    List<String> members = config.getMembers();
049    clusterLogger.info("Clustering using local[{}] members[{}]",localHostPort, members);
050
051    this.local = new SocketClient(parseFullName(localHostPort));
052    this.clientMap = new HashMap<String, SocketClient>();
053
054    for (String memberHostPort : members) {
055      InetSocketAddress member = parseFullName(memberHostPort);
056      SocketClient client = new SocketClient(member);
057      if (!local.getHostPort().equalsIgnoreCase(client.getHostPort())) {
058        // don't add the local one ...
059        clientMap.put(client.getHostPort(), client);
060      }
061    }
062
063    this.members = clientMap.values().toArray(new SocketClient[clientMap.size()]);
064    this.listener = new SocketClusterListener(this, local.getPort(), config.getCoreThreads(), config.getMaxThreads(), config.getThreadPoolName());
065  }
066
067  String getHostPort() {
068    return local.getHostPort();
069  }
070
071  /**
072   * Return the current status of this instance.
073   */
074  public SocketClusterStatus getStatus() {
075
076    // count of online members
077    int currentGroupSize = 0;
078    for (int i = 0; i < members.length; i++) {
079      if (members[i].isOnline()) {
080        ++currentGroupSize;
081      }
082    }
083    long txnIn = countIncoming.get();
084    long txnOut = countOutgoing.get();
085
086    return new SocketClusterStatus(currentGroupSize, txnIn, txnOut);
087  }
088
089  public void startup() {
090    listener.startListening();
091    register();
092  }
093
094  public void shutdown() {
095    deregister();
096    listener.shutdown();
097  }
098
099  /**
100   * Register with all the other members of the Cluster.
101   */
102  private void register() {
103    ClusterMessage h = ClusterMessage.register(local.getHostPort(), true);
104    for (int i = 0; i < members.length; i++) {
105      boolean online = members[i].register(h);
106      clusterLogger.info("Register as online with member [{}]", members[i].getHostPort(), online);
107    }
108  }
109
110  private void send(SocketClient client, ClusterMessage msg) {
111
112    try {
113      // alternative would be to connect/disconnect here but prefer to use keep alive
114      if (logger.isTraceEnabled()) {
115        logger.trace("... send to member {} broadcast msg: {}", client, msg);
116      }
117      client.send(msg);
118
119    } catch (Exception ex) {
120      logger.error("Error sending message", ex);
121      try {
122        client.reconnect();
123      } catch (IOException e) {
124        logger.error("Error trying to reconnect", ex);
125      }
126    }
127  }
128
129  private void setMemberOnline(String fullName, boolean online) throws IOException {
130    synchronized (clientMap) {
131      clusterLogger.info("Cluster member [{}] online[{}]", fullName, online);
132      SocketClient member = clientMap.get(fullName);
133      member.setOnline(online);
134    }
135  }
136
137  /**
138   * Send the payload to all the members of the cluster.
139   */
140  public void broadcast(RemoteTransactionEvent remoteTransEvent) {
141    try {
142      countOutgoing.incrementAndGet();
143      byte[] data = messageReadWrite.write(remoteTransEvent);
144      ClusterMessage msg = ClusterMessage.transEvent(data);
145      broadcast(msg);
146    } catch (Exception e) {
147      logger.error("Error sending RemoteTransactionEvent " + remoteTransEvent + " to cluster members.", e);
148    }
149  }
150
151  private void broadcast(ClusterMessage msg) {
152    for (int i = 0; i < members.length; i++) {
153      send(members[i], msg);
154    }
155  }
156
157  /**
158   * Leave the cluster.
159   */
160  private void deregister() {
161    clusterLogger.info("Leaving cluster");
162    ClusterMessage h = ClusterMessage.register(local.getHostPort(), false);
163    broadcast(h);
164    for (int i = 0; i < members.length; i++) {
165      members[i].disconnect();
166    }
167  }
168
169  /**
170   * Process an incoming Cluster message.
171   */
172  boolean process(SocketConnection request) throws ClassNotFoundException {
173
174    try {
175      ClusterMessage message = ClusterMessage.read(request.getDataInputStream());
176      if (logger.isTraceEnabled()) {
177        logger.trace("... received msg: {}", message);
178      }
179
180      if (message.isRegisterEvent()) {
181        setMemberOnline(message.getRegisterHost(), message.isRegister());
182
183      } else {
184        countIncoming.incrementAndGet();
185        RemoteTransactionEvent transEvent = messageReadWrite.read(message.getData());
186        transEvent.run();
187      }
188
189      // instance shutting down
190      return message.isRegisterEvent() && !message.isRegister();
191
192    } catch (InterruptedIOException e) {
193      logger.info("Timeout waiting for message", e);
194      try {
195        request.disconnect();
196      } catch (IOException ex) {
197        logger.info("Error disconnecting after timeout", ex);
198      }
199      return true;
200
201    } catch (EOFException e) {
202      logger.debug("EOF disconnecting");
203      return true;
204    } catch (IOException e) {
205      logger.info("IO Error waiting/reading message", e);
206      return true;
207    }
208  }
209
210  /**
211   * Parse a host:port into a InetSocketAddress.
212   */
213  private InetSocketAddress parseFullName(String hostAndPort) {
214
215    try {
216      hostAndPort = hostAndPort.trim();
217      int colonPos = hostAndPort.indexOf(":");
218      if (colonPos == -1) {
219        String msg = "No colon \":\" in " + hostAndPort;
220        throw new IllegalArgumentException(msg);
221      }
222      String host = hostAndPort.substring(0, colonPos);
223      String sPort = hostAndPort.substring(colonPos + 1, hostAndPort.length());
224      int port = Integer.parseInt(sPort);
225
226      return new InetSocketAddress(host, port);
227
228    } catch (Exception ex) {
229      throw new RuntimeException("Error parsing [" + hostAndPort + "] for the form [host:port]", ex);
230    }
231  }
232
233}