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}