Java tutorial
/* * Copyright 2009-2016 Weibo, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.weibo.api.motan.transport.netty; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ChannelFactory; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import org.apache.commons.pool.BasePoolableObjectFactory; import com.weibo.api.motan.common.ChannelState; import com.weibo.api.motan.common.MotanConstants; import com.weibo.api.motan.common.URLParamType; import com.weibo.api.motan.core.DefaultThreadFactory; import com.weibo.api.motan.exception.MotanAbstractException; import com.weibo.api.motan.exception.MotanErrorMsgConstant; import com.weibo.api.motan.exception.MotanFrameworkException; import com.weibo.api.motan.exception.MotanServiceException; import com.weibo.api.motan.rpc.DefaultResponse; import com.weibo.api.motan.rpc.Request; import com.weibo.api.motan.rpc.Response; import com.weibo.api.motan.rpc.RpcContext; import com.weibo.api.motan.rpc.URL; import com.weibo.api.motan.transport.AbstractPoolClient; import com.weibo.api.motan.transport.Channel; import com.weibo.api.motan.transport.MessageHandler; import com.weibo.api.motan.transport.TransportException; import com.weibo.api.motan.util.LoggerUtil; import com.weibo.api.motan.util.MotanFrameworkUtil; import com.weibo.api.motan.util.StatisticCallback; import com.weibo.api.motan.util.StatsUtil; /** * * <pre> * netty client * 1) timeout connecttimeoutsotimeout, application timeout * 2 * 3 * 4 ? (netty channel: writeQueue) * 5 ? * 6 RPC ?? OOM * </pre> * * @author zifei * @version 2017-5-17 * */ public class NettyClient extends AbstractPoolClient implements StatisticCallback { //CPU*2 // private static final ChannelFactory channelFactory = new NioClientSocketChannelFactory( // Executors.newCachedThreadPool(new DefaultThreadFactory("nettyClientBoss", true)), // Executors.newCachedThreadPool(new DefaultThreadFactory("nettyClientWorker", true))); // private static ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(4); // request?callback future // ?remove? 1) service? 2) timeout thread cancel protected ConcurrentMap<Long, NettyResponseFuture> callbackMap = new ConcurrentHashMap<Long, NettyResponseFuture>(); private ScheduledFuture<?> timeMonitorFuture = null; // private AtomicLong errorCount = new AtomicLong(0); // private int maxClientConnection = 0; private Bootstrap bootstrap; public NettyClient(URL url) { super(url); maxClientConnection = url.getIntParameter(URLParamType.maxClientConnection.getName(), URLParamType.maxClientConnection.getIntValue()); timeMonitorFuture = scheduledExecutor.scheduleWithFixedDelay( new TimeoutMonitor("timeout_monitor_" + url.getHost() + "_" + url.getPort()), MotanConstants.NETTY_TIMEOUT_TIMER_PERIOD, MotanConstants.NETTY_TIMEOUT_TIMER_PERIOD, TimeUnit.MILLISECONDS); } @Override public Response request(Request request) throws TransportException { if (!isAvailable()) { throw new MotanServiceException( "NettyChannel is unavaliable: url=" + url.getUri() + MotanFrameworkUtil.toString(request)); } boolean isAsync = false; Object async = RpcContext.getContext().getAttribute(MotanConstants.ASYNC_SUFFIX); if (async != null && async instanceof Boolean) { isAsync = (Boolean) async; } return request(request, isAsync); } @Override public void heartbeat(Request request) { // ??closeheartbeat?? if (state.isUnInitState() || state.isCloseState()) { LoggerUtil.warn("NettyClient heartbeat Error: state={} url={}", state.name(), url.getUri()); return; } LoggerUtil.info("NettyClient heartbeat request: url={}", url.getUri()); try { // async request?service is // availableclient?? request(request, true); } catch (Exception e) { LoggerUtil.error("NettyClient heartbeat Error: url=" + url.getUri(), e); } } /** * remote service * * <pre> * 1) get connection from pool * 2) async requset * 3) return connection to pool * 4) check if async return response, true: return ResponseFuture; false: return result * </pre> * * @param request * @param async * @return * @throws TransportException */ private Response request(Request request, boolean async) throws TransportException { Channel channel = null; Response response = null; try { // return channel or throw exception(timeout or connection_fail) channel = borrowObject(); if (channel == null) { LoggerUtil.error("NettyClient borrowObject null: url=" + url.getUri() + " " + MotanFrameworkUtil.toString(request)); return null; } // async request response = channel.request(request); // return channel to pool returnObject(channel); } catch (Exception e) { LoggerUtil.error( "NettyClient request Error: url=" + url.getUri() + " " + MotanFrameworkUtil.toString(request), e); //TODO channel invalidateObject(channel); if (e instanceof MotanAbstractException) { throw (MotanAbstractException) e; } else { throw new MotanServiceException("NettyClient request Error: url=" + url.getUri() + " " + MotanFrameworkUtil.toString(request), e); } } // aysnc or sync result response = asyncResponse(response, async); return response; } /** * asyncfalse??response? * * @param response * @param async * @return */ private Response asyncResponse(Response response, boolean async) { if (async || !(response instanceof NettyResponseFuture)) { return response; } return new DefaultResponse(response); } @Override public synchronized boolean open() { if (isAvailable()) { return true; } // ?netty client bootstrap initClientBootstrap(); // ? initPool(); LoggerUtil.info("NettyClient finish Open: url={}", url); // StatsUtil.registryStatisticCallback(this); // ?? state = ChannelState.ALIVE; return state.isAliveState(); } /** * ? netty clientBootstrap */ private void initClientBootstrap() { bootstrap = new Bootstrap(); // ?connectTimeout500msnetty nio?BossThread? // ?timeout? int timeout = getUrl().getIntParameter(URLParamType.connectTimeout.getName(), URLParamType.connectTimeout.getIntValue()); if (timeout <= 0) { throw new MotanFrameworkException("NettyClient init Error: timeout(" + timeout + ") <= 0 is forbid.", MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR); } // ?? final int maxContentLength = url.getIntParameter(URLParamType.maxContentLength.getName(), URLParamType.maxContentLength.getIntValue()); EventLoopGroup group = new NioEventLoopGroup(); bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_KEEPALIVE, true).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout) .handler(new ChannelInitializer<SocketChannel>() { public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new NettyDecoder(codec, NettyClient.this, maxContentLength)); pipeline.addLast(new NettyEncoder(codec, NettyClient.this)); pipeline.addLast(new NettyChannelHandler(NettyClient.this, new MessageHandler() { @Override public Object handle(Channel channel, Object message) { Response response = (Response) message; NettyResponseFuture responseFuture = NettyClient.this .removeCallback(response.getRequestId()); if (responseFuture == null) { LoggerUtil.warn( "NettyClient has response from server, but resonseFuture not exist, requestId={}", response.getRequestId()); return null; } if (response.getException() != null) { responseFuture.onFailure(response); } else { responseFuture.onSuccess(response); } return null; } })); } }); } @Override public synchronized void close() { close(0); } /** * ?close??timeout */ @Override public synchronized void close(int timeout) { if (state.isCloseState()) { LoggerUtil.info("NettyClient close fail: already close, url={}", url.getUri()); return; } // ?nettyClient?close? if (state.isUnInitState()) { LoggerUtil.info("NettyClient close Fail: don't need to close because node is unInit state: url={}", url.getUri()); return; } try { // ? timeMonitorFuture.cancel(true); // pool.close(); // callback callbackMap.clear(); // close? state = ChannelState.CLOSE; // StatsUtil.unRegistryStatisticCallback(this); LoggerUtil.info("NettyClient close Success: url={}", url.getUri()); } catch (Exception e) { LoggerUtil.error("NettyClient close Error: url=" + url.getUri(), e); } } @Override public boolean isClosed() { return state.isCloseState(); } @Override public boolean isAvailable() { return state.isAliveState(); } @Override public URL getUrl() { return url; } /** * connection factory */ @Override protected BasePoolableObjectFactory createChannelFactory() { return new NettyChannelFactory(this); } /** * * * <pre> * >= maxClientConnection, client???? * </pre> * */ void incrErrorCount() { long count = errorCount.incrementAndGet(); // ?????maxClientConnection?? if (count >= maxClientConnection && state.isAliveState()) { synchronized (this) { count = errorCount.longValue(); if (count >= maxClientConnection && state.isAliveState()) { LoggerUtil.error("NettyClient unavailable Error: url=" + url.getIdentity() + " " + url.getServerPortStr()); state = ChannelState.UNALIVE; } } } } /** * ? * * <pre> * ?? * </pre> * */ void resetErrorCount() { errorCount.set(0); if (state.isAliveState()) { return; } synchronized (this) { if (state.isAliveState()) { return; } // unalive? close uninit if (state.isUnAliveState()) { long count = errorCount.longValue(); // ?errorCount? if (count < maxClientConnection) { state = ChannelState.ALIVE; LoggerUtil.info("NettyClient recover available: url=" + url.getIdentity() + " " + url.getServerPortStr()); } } } } /** * resposne * * <pre> * * ?NETTY_CLIENT_MAX_REQUEST?throw reject exception * * </pre> * * @throws MotanServiceException * @param requestId * @param nettyResponseFuture */ public void registerCallback(long requestId, NettyResponseFuture nettyResponseFuture) { if (this.callbackMap.size() >= MotanConstants.NETTY_CLIENT_MAX_REQUEST) { // reject request, prevent from OutOfMemoryError throw new MotanServiceException("NettyClient over of max concurrent request, drop request, url: " + url.getUri() + " requestId=" + requestId, MotanErrorMsgConstant.SERVICE_REJECT); } this.callbackMap.put(requestId, nettyResponseFuture); } /** * ? */ @Override public String statisticCallback() { //??????100??log if (isAvailable() && callbackMap.size() < 100) { return null; } return String.format("identity: %s available: %s concurrent_count: %s", url.getIdentity(), isAvailable(), callbackMap.size()); } /** * response * * @param requestId * @return */ public NettyResponseFuture removeCallback(long requestId) { return callbackMap.remove(requestId); } public Bootstrap getBootstrap() { return bootstrap; } /** * * * @author maijunsheng * */ class TimeoutMonitor implements Runnable { private String name; public TimeoutMonitor(String name) { this.name = name; } public void run() { long currentTime = System.currentTimeMillis(); for (Map.Entry<Long, NettyResponseFuture> entry : callbackMap.entrySet()) { try { NettyResponseFuture future = entry.getValue(); if (future.getCreateTime() + future.getTimeout() < currentTime) { // timeout: remove from callback list, and then cancel removeCallback(entry.getKey()); future.cancel(); } } catch (Exception e) { LoggerUtil.error(name + " clear timeout future Error: uri=" + url.getUri() + " requestId=" + entry.getKey(), e); } } } } }