com.weibo.api.motan.transport.netty4.client.Netty4Client.java Source code

Java tutorial

Introduction

Here is the source code for com.weibo.api.motan.transport.netty4.client.Netty4Client.java

Source

/*
 *  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.netty4.client;

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.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.URL;
import com.weibo.api.motan.transport.AbstractPoolClient;
import com.weibo.api.motan.transport.Channel;
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;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.apache.commons.pool.BasePoolableObjectFactory;

import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * <pre>
 *       netty client 
 *          1)  timeout  connecttimeoutsotimeout, application timeout
 *          2 
 *        3 
 *          4 ? (netty channel: writeQueue)
 *          5 ?
 *          6 RPC ?? OOM
 * </pre>
 */
public class Netty4Client extends AbstractPoolClient implements StatisticCallback {
    // 
    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 Netty4Client(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 async = url.getMethodParameter(request.getMethodName(), request.getParamtersDesc(),
                URLParamType.async.getName(), URLParamType.async.getBooleanValue());
        return request(request, async);
    }

    @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
     * <p>
     * <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()) {
            LoggerUtil.warn("NettyServer ServerChannel already Opened: url=" + url);
            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();

        NioEventLoopGroup group = new NioEventLoopGroup();
        bootstrap.group(group).channel(NioSocketChannel.class)
                .handler(new Netty4ClientInitializer(url, codec, this));

        bootstrap.option(ChannelOption.TCP_NODELAY, true);
        bootstrap.option(ChannelOption.SO_KEEPALIVE, true);

        /* ?connectTimeout500msnetty nio?BossThread?
           ?timeout?
         */
        int timeout = getUrl().getIntParameter(URLParamType.requestTimeout.getName(),
                URLParamType.requestTimeout.getIntValue());
        if (timeout <= 0) {
            throw new MotanFrameworkException("NettyClient init Error: timeout(" + timeout + ") <= 0 is forbid.",
                    MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
        }
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout);
    }

    @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);
    }

    /**
     * 
     * <p>
     * <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;
                }
            }
        }
    }

    /**
     * ? 
     * <p>
     * <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
     * <p>
     * <pre>
     *
     *       ?NETTY_CLIENT_MAX_REQUEST?throw reject exception
     *
     * </pre>
     *
     * @param requestId
     * @param nettyResponseFuture
     * @throws MotanServiceException
     */
    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);
                }
            }
        }
    }
}