package com.github.ltsopensource.remoting; import com.github.ltsopensource.core.domain.Pair; import com.github.ltsopensource.core.factory.NamedThreadFactory; import com.github.ltsopensource.core.logger.Logger; import com.github.ltsopensource.core.logger.LoggerFactory; import com.github.ltsopensource.remoting.common.RemotingHelper; import com.github.ltsopensource.remoting.exception.*; import com.github.ltsopensource.remoting.protocol.RemotingCommand; import java.net.SocketAddress; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Remoting客户端实现 */ public abstract class AbstractRemotingClient extends AbstractRemoting implements RemotingClient { protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractRemotingClient.class); private static final long LockTimeoutMillis = 3000; protected final RemotingClientConfig remotingClientConfig; private final Lock lockChannelTables = new ReentrantLock(); private final ConcurrentHashMap<String /* addr */, ChannelWrapper> channelTables = new ConcurrentHashMap<String, ChannelWrapper>(); // 定时器 private final Timer timer = new Timer("ClientHouseKeepingService", true); // 处理Callback应答器 private final ExecutorService publicExecutor; public AbstractRemotingClient(final RemotingClientConfig remotingClientConfig, final ChannelEventListener channelEventListener) { super(remotingClientConfig.getClientOnewaySemaphoreValue(), remotingClientConfig .getClientAsyncSemaphoreValue(), channelEventListener); this.remotingClientConfig = remotingClientConfig; int publicThreadNums = remotingClientConfig.getClientCallbackExecutorThreads(); if (publicThreadNums <= 0) { publicThreadNums = 4; } this.publicExecutor = Executors.newFixedThreadPool(publicThreadNums, new NamedThreadFactory("RemotingClientPublicExecutor", true)); } @Override public void start() throws RemotingException { clientStart(); // 每隔1秒扫描下异步调用超时情况 this.timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { try { AbstractRemotingClient.this.scanResponseTable(); } catch (Exception e) { LOGGER.error("scanResponseTable exception", e); } } }, 1000 * 3, 1000); if (this.channelEventListener != null) { this.remotingEventExecutor.start(); } } protected abstract void clientStart() throws RemotingException; @Override public void shutdown() { try { this.timer.cancel(); for (ChannelWrapper cw : this.channelTables.values()) { this.closeChannel(null, cw.getChannel()); } this.channelTables.clear(); if (this.remotingEventExecutor != null) { this.remotingEventExecutor.shutdown(); } clientShutdown(); } catch (Exception e) { LOGGER.error("NettyRemotingClient shutdown exception, ", e); } if (this.publicExecutor != null) { try { this.publicExecutor.shutdown(); } catch (Exception e) { LOGGER.error("NettyRemotingServer shutdown exception, ", e); } } } protected abstract void clientShutdown(); private Channel getAndCreateChannel(final String addr) throws InterruptedException { ChannelWrapper cw = this.channelTables.get(addr); if (cw != null && cw.isConnected()) { return cw.getChannel(); } return this.createChannel(addr); } private Channel createChannel(final String addr) throws InterruptedException { ChannelWrapper cw = this.channelTables.get(addr); if (cw != null && cw.isConnected()) { return cw.getChannel(); } // 进入临界区后,不能有阻塞操作,网络连接采用异步方式 if (this.lockChannelTables.tryLock(LockTimeoutMillis, TimeUnit.MILLISECONDS)) { try { boolean createNewConnection = false; cw = this.channelTables.get(addr); if (cw != null) { // channel正常 if (cw.isConnected()) { return cw.getChannel(); } // 正在连接,退出锁等待 else if (!cw.getChannelFuture().isDone()) { createNewConnection = false; } // 说明连接不成功 else { this.channelTables.remove(addr); createNewConnection = true; } } // ChannelWrapper不存在 else { createNewConnection = true; } if (createNewConnection) { ChannelFuture channelFuture = connect(RemotingHelper.string2SocketAddress(addr)); LOGGER.info("createChannel: begin to connect remote host[{}] asynchronously", addr); cw = new ChannelWrapper(channelFuture); this.channelTables.put(addr, cw); } } catch (Exception e) { LOGGER.error("createChannel: create channel exception", e); } finally { this.lockChannelTables.unlock(); } } else { LOGGER.warn("createChannel: try to lock channel table, but timeout, {}ms", LockTimeoutMillis); } if (cw != null) { ChannelFuture channelFuture = cw.getChannelFuture(); if (channelFuture.awaitUninterruptibly(this.remotingClientConfig.getConnectTimeoutMillis())) { if (cw.isConnected()) { LOGGER.info("createChannel: connect remote host[{}] success, {}", addr, channelFuture.toString()); return cw.getChannel(); } else { LOGGER.warn( "createChannel: connect remote host[" + addr + "] failed, " + channelFuture.toString(), channelFuture.cause()); } } else { LOGGER.warn("createChannel: connect remote host[{}] timeout {}ms, {}", addr, this.remotingClientConfig.getConnectTimeoutMillis(), channelFuture.toString()); } } return null; } protected abstract ChannelFuture connect(SocketAddress socketAddress); public void closeChannel(final String addr, final Channel channel) { if (null == channel) return; final String addrRemote = null == addr ? RemotingHelper.parseChannelRemoteAddr(channel) : addr; try { if (this.lockChannelTables.tryLock(LockTimeoutMillis, TimeUnit.MILLISECONDS)) { try { boolean removeItemFromTable = true; final ChannelWrapper prevCW = this.channelTables.get(addrRemote); LOGGER.info("closeChannel: begin close the channel[{}] Found: {}", addrRemote, (prevCW != null)); if (null == prevCW) { LOGGER.info( "closeChannel: the channel[{}] has been removed from the channel table before", addrRemote); removeItemFromTable = false; } else if (prevCW.getChannel() != channel) { LOGGER.info( "closeChannel: the channel[{}] has been closed before, and has been created again, nothing to do.", addrRemote); removeItemFromTable = false; } if (removeItemFromTable) { this.channelTables.remove(addrRemote); LOGGER.info("closeChannel: the channel[{}] was removed from channel table", addrRemote); } RemotingHelper.closeChannel(channel); } catch (Exception e) { LOGGER.error("closeChannel: close the channel exception", e); } finally { this.lockChannelTables.unlock(); } } else { LOGGER.warn("closeChannel: try to lock channel table, but timeout, {}ms", LockTimeoutMillis); } } catch (InterruptedException e) { LOGGER.error("closeChannel exception", e); } } public void closeChannel(final Channel channel) { if (null == channel) return; try { if (this.lockChannelTables.tryLock(LockTimeoutMillis, TimeUnit.MILLISECONDS)) { try { boolean removeItemFromTable = true; ChannelWrapper prevCW = null; String addrRemote = null; for (String key : channelTables.keySet()) { ChannelWrapper prev = this.channelTables.get(key); if (prev.getChannel() != null) { if (prev.getChannel() == channel) { prevCW = prev; addrRemote = key; break; } } } if (null == prevCW) { LOGGER.info( "eventCloseChannel: the channel has been removed from the channel table before"); removeItemFromTable = false; } if (removeItemFromTable) { this.channelTables.remove(addrRemote); LOGGER.info("closeChannel: the channel[{}] was removed from channel table", addrRemote); RemotingHelper.closeChannel(channel); } } catch (Exception e) { LOGGER.error("closeChannel: close the channel exception", e); } finally { this.lockChannelTables.unlock(); } } else { LOGGER.warn("closeChannel: try to lock channel table, but timeout, {}ms", LockTimeoutMillis); } } catch (InterruptedException e) { LOGGER.error("closeChannel exception", e); } } @Override public void registerProcessor(int requestCode, RemotingProcessor processor, ExecutorService executor) { ExecutorService executorThis = executor; if (null == executor) { executorThis = this.publicExecutor; } Pair<RemotingProcessor, ExecutorService> pair = new Pair<RemotingProcessor, ExecutorService>(processor, executorThis); this.processorTable.put(requestCode, pair); } @Override public void registerDefaultProcessor(RemotingProcessor processor, ExecutorService executor) { this.defaultRequestProcessor = new Pair<RemotingProcessor, ExecutorService>(processor, executor); } @Override public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis) throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException { final Channel channel = this.getAndCreateChannel(addr); if (channel != null && channel.isConnected()) { try { return this.invokeSyncImpl(channel, request, timeoutMillis); } catch (RemotingSendRequestException e) { LOGGER.warn("invokeSync: send request exception, so close the channel[{}]", addr); this.closeChannel(addr, channel); throw e; } catch (RemotingTimeoutException e) { LOGGER.warn("invokeSync: wait response timeout exception, the channel[{}]", addr); // 超时异常如果关闭连接可能会产生连锁反应 // this.closeChannel(addr, channel); throw e; } } else { this.closeChannel(addr, channel); throw new RemotingConnectException(addr); } } @Override public void invokeAsync(String addr, RemotingCommand request, long timeoutMillis, AsyncCallback asyncCallback) throws InterruptedException, RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { final Channel channel = this.getAndCreateChannel(addr); if (channel != null && channel.isConnected()) { try { this.invokeAsyncImpl(channel, request, timeoutMillis, asyncCallback); } catch (RemotingSendRequestException e) { LOGGER.warn("invokeAsync: send request exception, so close the channel[{}]", addr); this.closeChannel(addr, channel); throw e; } } else { this.closeChannel(addr, channel); throw new RemotingConnectException(addr); } } @Override public void invokeOneway(String addr, RemotingCommand request, long timeoutMillis) throws InterruptedException, RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException { final Channel channel = this.getAndCreateChannel(addr); if (channel != null && channel.isConnected()) { try { this.invokeOnewayImpl(channel, request, timeoutMillis); } catch (RemotingSendRequestException e) { LOGGER.warn("invokeOneway: send request exception, so close the channel[{}]", addr); this.closeChannel(addr, channel); throw e; } } else { this.closeChannel(addr, channel); throw new RemotingConnectException(addr); } } @Override protected ExecutorService getCallbackExecutor() { return this.publicExecutor; } private class ChannelWrapper { private final ChannelFuture channelFuture; public ChannelWrapper(ChannelFuture channelFuture) { this.channelFuture = channelFuture; } public boolean isConnected() { return channelFuture.isConnected(); } private Channel getChannel() { return channelFuture.getChannel(); } private ChannelFuture getChannelFuture() { return this.channelFuture; } } }