Exemple #1
0
public abstract class AbstractServer implements Server {

  protected final Logger logger = LoggerLoader.getLogger(this.getClass());
  RequestProcessor requestProcessor = null;
  ServerConfig serverConfig = null;
  boolean useLastPort =
      ConfigManagerLoader.getConfigManager().getBooleanValue("pigeon.port.uselast", true);

  public abstract void doStart(ServerConfig serverConfig);

  public abstract void doStop();

  public abstract <T> void doAddService(ProviderConfig<T> providerConfig);

  public abstract <T> void doRemoveService(ProviderConfig<T> providerConfig);

  private class InnerConfigChangeListener implements ConfigChangeListener {

    @Override
    public void onKeyUpdated(String key, String value) {
      if (key.endsWith("pigeon.provider.processtype")) {}
    }

    @Override
    public void onKeyAdded(String key, String value) {}

    @Override
    public void onKeyRemoved(String key) {}
  }

  public void start(ServerConfig serverConfig) {
    if (logger.isInfoEnabled()) {
      logger.info("server config:" + serverConfig);
    }
    ConfigManagerLoader.getConfigManager()
        .registerConfigChangeListener(new InnerConfigChangeListener());
    requestProcessor = RequestProcessorFactory.selectProcessor(serverConfig);
    doStart(serverConfig);
    if (requestProcessor != null) {
      requestProcessor.start();
    }
    this.serverConfig = serverConfig;
  }

  public void stop() {
    doStop();
    if (requestProcessor != null) {
      requestProcessor.stop();
    }
  }

  @Override
  public <T> void addService(ProviderConfig<T> providerConfig) {
    requestProcessor.addService(providerConfig);
    doAddService(providerConfig);
  }

  @Override
  public <T> void removeService(ProviderConfig<T> providerConfig) {
    requestProcessor.removeService(providerConfig);
    doRemoveService(providerConfig);
  }

  public RequestProcessor getRequestProcessor() {
    return requestProcessor;
  }

  @Override
  public ServerConfig getServerConfig() {
    return serverConfig;
  }

  @Override
  public Future<InvocationResponse> processRequest(
      InvocationRequest request, ProviderContext providerContext) {
    return requestProcessor.processRequest(request, providerContext);
  }

  public int getAvailablePort(int port) {
    int lastPort = port;
    if (!useLastPort) {
      lastPort = NetUtils.getAvailablePort(lastPort);
    } else {
      String filePath = LoggerLoader.LOG_ROOT + "/pigeon-port.conf";
      File file = new File(filePath);
      Properties properties = null;
      String key = null;
      try {
        key = this.getClass().getResource("/").getPath() + port;
        if (file.exists()) {
          try {
            properties = FileUtils.readFile(new FileInputStream(file));
            String strLastPort = properties.getProperty(key);
            if (StringUtils.isNotBlank(strLastPort)) {
              lastPort = Integer.parseInt(strLastPort);
            }
          } catch (Throwable e) {
          }
        }
      } catch (RuntimeException e) {
      }
      lastPort = NetUtils.getAvailablePort(lastPort);
      if (properties == null) {
        properties = new Properties();
      }
      if (key != null) {
        properties.put(key, lastPort);
      }
      try {
        FileUtils.writeFile(file, properties);
      } catch (IOException e) {
      }
    }
    return lastPort;
  }
}
/** Created by chenchongze on 16/5/20. */
public enum RequestQualityManager {
  INSTANCE;

  private RequestQualityManager() {}

  private static final Logger logger = LoggerLoader.getLogger(RequestQualityManager.class);

  private static final ConfigManager configManager = ConfigManagerLoader.getConfigManager();
  private static final String KEY_REQUEST_QUALITY_AUTO = "pigeon.invoker.request.quality.auto";
  private static final String KEY_REQUEST_QUALITY_FAILED_PERCENT_GOOD =
      "pigeon.invoker.request.quality.failed.percent.good";
  private static final String KEY_REQUEST_QUALITY_FAILED_PERCENT_NORMAL =
      "pigeon.invoker.request.quality.failed.percent.normal";
  private static final String KEY_REQUEST_QUALITY_THRESHOLD_TOTAL =
      "pigeon.invoker.request.quality.threshold.total";

  static {
    configManager.getBooleanValue(KEY_REQUEST_QUALITY_AUTO, false);
    configManager.getIntValue(KEY_REQUEST_QUALITY_THRESHOLD_TOTAL, 20);
    configManager.getFloatValue(KEY_REQUEST_QUALITY_FAILED_PERCENT_GOOD, 1f);
    configManager.getFloatValue(KEY_REQUEST_QUALITY_FAILED_PERCENT_NORMAL, 5f);
  }

  // hosts --> ( requestUrl:serviceName#method --> second --> { total, failed } )
  private ConcurrentHashMap<String, ConcurrentHashMap<String, ConcurrentHashMap<Integer, Quality>>>
      addrReqUrlSecondQualities =
          new ConcurrentHashMap<
              String, ConcurrentHashMap<String, ConcurrentHashMap<Integer, Quality>>>();

  // hosts --> ( requestUrl:serviceName#method --> { total, failed } )
  private volatile ConcurrentHashMap<String, ConcurrentHashMap<String, Quality>>
      addrReqUrlQualities = null;

  public ConcurrentHashMap<String, ConcurrentHashMap<String, ConcurrentHashMap<Integer, Quality>>>
      getAddrReqUrlSecondQualities() {
    return addrReqUrlSecondQualities;
  }

  public ConcurrentHashMap<String, ConcurrentHashMap<String, Quality>> getAddrReqUrlQualities() {
    return addrReqUrlQualities;
  }

  public void setAddrReqUrlQualities(
      ConcurrentHashMap<String, ConcurrentHashMap<String, Quality>> addrReqUrlQualities) {
    this.addrReqUrlQualities = addrReqUrlQualities;
  }

  public void addClientRequest(InvokerContext context, boolean failed) {
    if (configManager.getBooleanValue(KEY_REQUEST_QUALITY_AUTO, false)
        && context.getClient() != null) {

      String address = context.getClient().getAddress();
      ConcurrentHashMap<String, ConcurrentHashMap<Integer, Quality>> requestSecondQuality =
          addrReqUrlSecondQualities.get(address);
      if (requestSecondQuality == null) {
        requestSecondQuality = new ConcurrentHashMap<String, ConcurrentHashMap<Integer, Quality>>();
        ConcurrentHashMap<String, ConcurrentHashMap<Integer, Quality>> last =
            addrReqUrlSecondQualities.putIfAbsent(address, requestSecondQuality);
        if (last != null) {
          requestSecondQuality = last;
        }
      }

      String requestUrl = getRequestUrl(context);
      ConcurrentHashMap<Integer, Quality> secondQuality = requestSecondQuality.get(requestUrl);
      if (secondQuality == null) {
        secondQuality = new ConcurrentHashMap<Integer, Quality>();
        ConcurrentHashMap<Integer, Quality> last =
            requestSecondQuality.putIfAbsent(requestUrl, secondQuality);
        if (last != null) {
          secondQuality = last;
        }
      }

      int currentSecond = Calendar.getInstance().get(Calendar.SECOND);
      Quality quality = secondQuality.get(currentSecond);
      if (quality == null) {
        quality = new Quality(0, 0);
        Quality last = secondQuality.putIfAbsent(currentSecond, quality);
        if (last != null) {
          quality = last;
        }
      }

      quality.total.incrementAndGet();
      if (failed) {
        quality.failed.incrementAndGet();
      }
    }
  }

  public void removeClientQualities(String address) {
    addrReqUrlSecondQualities.remove(address);
  }

  private String getRequestUrl(InvokerContext context) {
    return context.getInvokerConfig().getUrl() + "#" + context.getMethodName();
  }

  private String getRequestUrl(InvocationRequest request) {
    return request.getServiceName() + "#" + request.getMethodName();
  }

  /**
   * 根据方法的服务质量过滤,优先保留服务质量good的clients,数量低于least时加入服务质量normal的clients
   *
   * @param clientList
   * @param request
   * @param least 最少保留个数
   * @return
   */
  public List<Client> getQualityPreferClients(
      List<Client> clientList, InvocationRequest request, float least) {
    // 筛选good,normal,bad clients
    // 直接进行服务质量路由,先只保留服务质量good的,如果不够(比如少于1个),加入服务质量normal的
    if (!CollectionUtils.isEmpty(addrReqUrlQualities)) {
      String requestUrl = getRequestUrl(request);

      Map<RequrlQuality, List<Client>> filterQualityClientsMap =
          new HashMap<RequrlQuality, List<Client>>();
      for (RequrlQuality reqQuality : RequrlQuality.values()) {
        filterQualityClientsMap.put(reqQuality, new ArrayList<Client>());
      }

      for (Client client : clientList) {
        if (addrReqUrlQualities.containsKey(client.getAddress())) {
          ConcurrentHashMap<String, Quality> reqUrlQualities =
              addrReqUrlQualities.get(client.getAddress());
          if (reqUrlQualities.containsKey(requestUrl)) {
            Quality quality = reqUrlQualities.get(requestUrl);

            switch (quality.getQuality()) {
              case REQURL_QUALITY_GOOD:
                filterQualityClientsMap.get(RequrlQuality.REQURL_QUALITY_GOOD).add(client);
                break;
              case REQURL_QUALITY_NORNAL:
                filterQualityClientsMap.get(RequrlQuality.REQURL_QUALITY_NORNAL).add(client);
                break;
              case REQURL_QUALITY_BAD:
                filterQualityClientsMap.get(RequrlQuality.REQURL_QUALITY_BAD).add(client);
                break;
              default:
                // never be here
                break;
            }
          }
        }
      }

      List<Client> filterQualityClients = new ArrayList<Client>();
      filterQualityClients.addAll(filterQualityClientsMap.get(RequrlQuality.REQURL_QUALITY_GOOD));

      if (filterQualityClients.size() < least) {
        filterQualityClients.addAll(
            filterQualityClientsMap.get(RequrlQuality.REQURL_QUALITY_NORNAL));
      }

      return filterQualityClients;
    }

    return clientList;
  }

  public boolean isEnableRequestQualityRoute() {
    return configManager.getBooleanValue(KEY_REQUEST_QUALITY_AUTO, false);
  }

  public static class Quality {

    private RequrlQuality quality = RequrlQuality.REQURL_QUALITY_GOOD;
    private AtomicInteger failed = new AtomicInteger();
    private AtomicInteger total = new AtomicInteger();

    public Quality() {}

    public Quality(int total, int failed) {
      this.total.set(total);
      this.failed.set(failed);
    }

    public AtomicInteger getFailed() {
      return failed;
    }

    public int getFailedValue() {
      return failed.get();
    }

    public void setFailed(int failed) {
      this.failed.set(failed);
    }

    public AtomicInteger getTotal() {
      return total;
    }

    public int getTotalValue() {
      return total.get();
    }

    public void setTotal(int total) {
      this.total.set(total);
    }

    public float getFailedPercent() {
      if (total.get() > 0) {
        return failed.get() * 100 / total.get();
      } else {
        return 0;
      }
    }

    public void clear() {
      total.set(0);
      failed.set(0);
      quality = RequrlQuality.REQURL_QUALITY_GOOD;
    }

    public RequrlQuality getQuality() {

      if (getTotalValue() > configManager.getIntValue(KEY_REQUEST_QUALITY_THRESHOLD_TOTAL, 20)) {
        float failedRate = getFailedPercent();

        if (failedRate < configManager.getFloatValue(KEY_REQUEST_QUALITY_FAILED_PERCENT_GOOD, 1f)) {
          quality = RequrlQuality.REQURL_QUALITY_GOOD;
        } else if (failedRate
                >= configManager.getFloatValue(KEY_REQUEST_QUALITY_FAILED_PERCENT_GOOD, 1f)
            && failedRate
                < configManager.getFloatValue(KEY_REQUEST_QUALITY_FAILED_PERCENT_NORMAL, 5f)) {
          quality = RequrlQuality.REQURL_QUALITY_NORNAL;
        } else if (failedRate
            >= configManager.getFloatValue(KEY_REQUEST_QUALITY_FAILED_PERCENT_NORMAL, 5f)) {
          quality = RequrlQuality.REQURL_QUALITY_BAD;
        }
      }

      return quality;
    }

    public int getQualityValue() {
      return getQuality().getValue();
    }
  }

  private enum RequrlQuality {
    REQURL_QUALITY_GOOD(0),
    REQURL_QUALITY_NORNAL(1),
    REQURL_QUALITY_BAD(2);

    private int value;

    private RequrlQuality(int value) {
      this.value = value;
    }

    public int getValue() {
      return value;
    }
  }

  public static void main(String[] args) {
    Quality quality = new Quality(105, 1);
    System.out.println(quality.getQualityValue());
    final ConcurrentHashMap<String, ConcurrentHashMap<String, Quality>> addrReqUrlQualities =
        new ConcurrentHashMap<String, ConcurrentHashMap<String, Quality>>();
    new Thread() {
      @Override
      public void run() {
        RequestQualityManager.INSTANCE.setAddrReqUrlQualities(addrReqUrlQualities);
      }
    }.start();

    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      logger.warn("[main] thread interupt", e);
    }
    System.out.println(RequestQualityManager.INSTANCE.getAddrReqUrlQualities());
  }
}
public class AnnotationBean
    implements DisposableBean,
        BeanFactoryPostProcessor,
        BeanPostProcessor,
        ApplicationContextAware {

  private static final Logger logger = LoggerLoader.getLogger(AnnotationBean.class);

  private String annotationPackage =
      ConfigManagerLoader.getConfigManager()
          .getStringValue("pigeon.provider.interface.packages", "com.dianping");

  private String[] annotationPackages = new String[] {"com.dianping"};

  private final ConcurrentMap<String, InvokerConfig<?>> invokerConfigs =
      new ConcurrentHashMap<String, InvokerConfig<?>>();

  public String getPackage() {
    return annotationPackage;
  }

  public void setPackage(String annotationPackage) {
    this.annotationPackage = annotationPackage;
    this.annotationPackages =
        (annotationPackage == null || annotationPackage.length() == 0)
            ? null
            : Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
  }

  private ApplicationContext applicationContext;

  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
  }

  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
      throws BeansException {
    if (annotationPackage == null || annotationPackage.length() == 0) {
      return;
    }
    if (beanFactory instanceof BeanDefinitionRegistry) {
      try {
        // init scanner
        Class<?> scannerClass =
            ClassUtils.loadClass(
                "org.springframework.context.annotation.ClassPathBeanDefinitionScanner");
        Object scanner =
            scannerClass
                .getConstructor(new Class<?>[] {BeanDefinitionRegistry.class, boolean.class})
                .newInstance(new Object[] {(BeanDefinitionRegistry) beanFactory, true});
        // add filter
        Class<?> filterClass =
            ClassUtils.loadClass("org.springframework.core.type.filter.AnnotationTypeFilter");
        Object filter = filterClass.getConstructor(Class.class).newInstance(Service.class);
        Method addIncludeFilter =
            scannerClass.getMethod(
                "addIncludeFilter",
                ClassUtils.loadClass("org.springframework.core.type.filter.TypeFilter"));
        addIncludeFilter.invoke(scanner, filter);
        // scan packages
        String[] packages = Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
        Method scan = scannerClass.getMethod("scan", new Class<?>[] {String[].class});
        scan.invoke(scanner, new Object[] {packages});
      } catch (Throwable e) {
        // spring 2.0
      }
    }
  }

  public int getDefaultPort(int port) {
    if (port == 4040) {
      try {
        String app = ConfigManagerLoader.getConfigManager().getAppName();
        if (StringUtils.isNotBlank(app)) {
          return LangUtils.hash(app, 6000, 2000);
        }
      } catch (Throwable t) {
      }
    }
    return port;
  }

  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (!isMatchPackage(bean)) {
      return bean;
    }
    Class<?> beanClass = bean.getClass();
    int idxCglib =
        beanClass.getName().contains("$$EnhancerByCGLIB")
            ? beanClass.getName().indexOf("$$EnhancerByCGLIB")
            : beanClass.getName().indexOf("$$EnhancerBySpringCGLIB");
    if (idxCglib != -1) {
      try {
        beanClass = ClassUtils.loadClass(beanClass.getName().substring(0, idxCglib));
      } catch (ClassNotFoundException e) {
        throw new IllegalStateException(
            "Failed to export remote service class " + beanClass.getName(), e);
      }
    }
    Service service = beanClass.getAnnotation(Service.class);
    if (service != null) {
      Class serviceInterface = service.interfaceClass();
      if (void.class.equals(service.interfaceClass())) {
        serviceInterface = ServiceConfigUtils.getServiceInterface(beanClass);
      }
      if (serviceInterface == null) {
        serviceInterface = beanClass;
      }
      ProviderConfig<Object> providerConfig = new ProviderConfig<Object>(serviceInterface, bean);
      providerConfig.setService(bean);
      providerConfig.setUrl(service.url());
      providerConfig.setVersion(service.version());
      providerConfig.setSharedPool(service.useSharedPool());
      providerConfig.setActives(service.actives());

      ServerConfig serverConfig = new ServerConfig();
      serverConfig.setPort(getDefaultPort(service.port()));
      serverConfig.setGroup(service.group());
      serverConfig.setAutoSelectPort(service.autoSelectPort());
      providerConfig.setServerConfig(serverConfig);
      ServiceFactory.addService(providerConfig);
    }
    postProcessBeforeInitialization(bean, beanName);
    return bean;
  }

  public Object postProcessBeforeInitialization(Object bean, String beanName)
      throws BeansException {
    if (!isMatchPackage(bean)) {
      return bean;
    }
    Method[] methods = bean.getClass().getMethods();
    for (Method method : methods) {
      String name = method.getName();
      if (name.length() > 3
          && name.startsWith("set")
          && method.getParameterTypes().length == 1
          && Modifier.isPublic(method.getModifiers())
          && !Modifier.isStatic(method.getModifiers())) {
        try {
          Reference reference = method.getAnnotation(Reference.class);
          if (reference != null) {
            Object value = refer(reference, method.getParameterTypes()[0]);
            if (value != null) {
              method.invoke(bean, new Object[] {});
            }
          }
        } catch (Throwable e) {
          logger.error(
              "Failed to init remote service reference at method "
                  + name
                  + " in class "
                  + bean.getClass().getName()
                  + ", cause: "
                  + e.getMessage(),
              e);
        }
      }
    }
    Class<?> superClass = bean.getClass().getSuperclass();
    while (superClass != null && isMatchPackage(superClass)) {
      referFields(bean, superClass.getDeclaredFields());
      superClass = superClass.getSuperclass();
    }
    referFields(bean, bean.getClass().getDeclaredFields());

    return bean;
  }

  private void referFields(Object bean, Field[] fields) {
    for (Field field : fields) {
      try {
        if (!field.isAccessible()) {
          field.setAccessible(true);
        }
        Reference reference = field.getAnnotation(Reference.class);
        if (reference != null) {
          Object value = refer(reference, field.getType());
          if (value != null) {
            field.set(bean, value);
          }
        }
      } catch (Throwable e) {
        logger.error(
            "Failed to init remote service reference at field "
                + field.getName()
                + " in class "
                + bean.getClass().getName()
                + ", cause: "
                + e.getMessage(),
            e);
      }
    }
  }

  private Object refer(
      Reference reference, Class<?> referenceClass) { // method.getParameterTypes()[0]
    String interfaceName;
    if (!void.class.equals(reference.interfaceClass())) {
      interfaceName = reference.interfaceClass().getName();
    } else if (referenceClass.isInterface()) {
      interfaceName = referenceClass.getName();
    } else {
      throw new IllegalStateException(
          "The @Reference undefined interfaceClass or interfaceName, and the property type "
              + referenceClass.getName()
              + " is not a interface.");
    }
    String callbackClassName = reference.callback();
    InvocationCallback callback = null;
    if (StringUtils.isNotBlank(callbackClassName)) {
      Class<?> clazz;
      try {
        clazz = ClassUtils.loadClass(callbackClassName);
      } catch (ClassNotFoundException e) {
        throw new IllegalStateException(
            "The @Reference undefined callback "
                + callbackClassName
                + ", is not a ServiceCallback interface.");
      }
      if (!InvocationCallback.class.isAssignableFrom(clazz)) {
        throw new IllegalStateException(
            "The @Reference undefined callback "
                + callbackClassName
                + ", is not a ServiceCallback interface.");
      }
      try {
        callback = (InvocationCallback) clazz.newInstance();
      } catch (InstantiationException e) {
        throw new IllegalStateException(
            "The @Reference undefined callback "
                + callbackClassName
                + ", is not a ServiceCallback interface.");
      } catch (IllegalAccessException e) {
        throw new IllegalStateException(
            "The @Reference undefined callback "
                + callbackClassName
                + ", is not a ServiceCallback interface.");
      }
    }
    String key =
        reference.group()
            + "/"
            + reference.url()
            + "@"
            + interfaceName
            + ":"
            + reference.version()
            + ":"
            + reference.serialize()
            + ":"
            + reference.protocol()
            + ":"
            + reference.timeout()
            + ":"
            + reference.callType();
    InvokerConfig<?> invokerConfig = invokerConfigs.get(key);
    if (invokerConfig == null) {
      invokerConfig =
          new InvokerConfig(
              referenceClass,
              reference.url(),
              reference.timeout(),
              reference.callType(),
              reference.serialize(),
              callback,
              reference.group(),
              false,
              reference.loadbalance(),
              reference.cluster(),
              reference.retries(),
              reference.timeoutRetry(),
              reference.vip(),
              reference.version(),
              reference.protocol());
      invokerConfig.setSecret(reference.secret());
      invokerConfigs.putIfAbsent(key, invokerConfig);
      invokerConfig = invokerConfigs.get(key);
    }
    return ServiceFactory.getService(invokerConfig);
  }

  private boolean isMatchPackage(Object bean) {
    if (annotationPackages == null || annotationPackages.length == 0) {
      return true;
    }
    String beanClassName = bean.getClass().getName();
    for (String pkg : annotationPackages) {
      if (beanClassName.startsWith(pkg)) {
        return true;
      }
    }
    return false;
  }

  private boolean isMatchPackage(Class type) {
    String beanClassName = type.getName();
    for (String pkg : annotationPackages) {
      if (beanClassName.startsWith(pkg)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public void destroy() throws Exception {}
}
Exemple #4
0
public class NettyClient extends AbstractClient {

  private static final Logger logger = LoggerLoader.getLogger(NettyClient.class);

  private ClientBootstrap bootstrap;

  private Channel channel;

  private String host;

  private int port = ServerConfig.DEFAULT_PORT;

  private String address;

  private volatile boolean connected = false;

  private volatile boolean closed = false;

  private volatile boolean active = true;

  private ConnectInfo connectInfo;

  public static final int CLIENT_CONNECTIONS = Runtime.getRuntime().availableProcessors();

  private static ConfigManager configManager = ConfigManagerLoader.getConfigManager();

  private static ExecutorService bossExecutor =
      Executors.newCachedThreadPool(new DefaultThreadFactory("Pigeon-Netty-Client-Boss"));

  private static ExecutorService workExecutor =
      Executors.newCachedThreadPool(new DefaultThreadFactory("Pigeon-Netty-Client-Worker"));

  private static final int bossCount =
      configManager.getIntValue("pigeon.invoker.netty.bosscount", 1);

  private static final int workerCount =
      configManager.getIntValue(
          "pigeon.invoker.netty.workercount", Runtime.getRuntime().availableProcessors() * 2);

  private static ChannelFactory channelFactory =
      new NioClientSocketChannelFactory(bossExecutor, workExecutor, bossCount, workerCount);

  private static final int connectTimeout =
      configManager.getIntValue(Constants.KEY_CONNECT_TIMEOUT, Constants.DEFAULT_CONNECT_TIMEOUT);

  public int getWriteBufferHighWater() {
    return configManager.getIntValue(
        Constants.KEY_WRITE_BUFFER_HIGH_WATER, Constants.DEFAULT_WRITE_BUFFER_HIGH_WATER);
  }

  public int getWriteBufferLowWater() {
    return configManager.getIntValue(
        Constants.KEY_WRITE_BUFFER_LOW_WATER, Constants.DEFAULT_WRITE_BUFFER_LOW_WATER);
  }

  public NettyClient(ConnectInfo connectInfo) {
    this.host = connectInfo.getHost();
    this.port = connectInfo.getPort();
    this.connectInfo = connectInfo;
    this.address = host + ":" + port;

    this.bootstrap = new ClientBootstrap(channelFactory);
    this.bootstrap.setPipelineFactory(new NettyClientPipelineFactory(this));
    this.bootstrap.setOption("tcpNoDelay", true);
    this.bootstrap.setOption("keepAlive", true);
    this.bootstrap.setOption("reuseAddress", true);
    this.bootstrap.setOption("connectTimeoutMillis", connectTimeout);
    this.bootstrap.setOption("writeBufferHighWaterMark", getWriteBufferHighWater());
    this.bootstrap.setOption("writeBufferLowWaterMark", getWriteBufferLowWater());
  }

  public synchronized void connect() {
    if (this.connected) {
      return;
    }
    logger.info("client is connecting to " + this.host + ":" + this.port);
    ChannelFuture future = null;
    try {
      future = bootstrap.connect(new InetSocketAddress(host, port));
      if (future.awaitUninterruptibly(connectTimeout, TimeUnit.MILLISECONDS)) {
        if (future.isSuccess()) {
          Channel newChannel = future.getChannel();
          try {
            // 关闭旧的连接
            Channel oldChannel = this.channel;
            if (oldChannel != null) {
              logger.info("close old netty channel " + oldChannel);
              try {
                oldChannel.close();
              } catch (Throwable t) {
              }
            }
          } finally {
            this.channel = newChannel;
          }
          logger.info("client is connected to " + this.host + ":" + this.port);
          this.connected = true;
        } else {
          logger.info("client is not connected to " + this.host + ":" + this.port);
        }
      } else {
        logger.info("timeout while connecting to " + this.host + ":" + this.port);
      }
    } catch (Throwable e) {
      logger.info("error while connecting to " + this.host + ":" + this.port, e);
    }
  }

  @Override
  public InvocationResponse doWrite(InvocationRequest request, Callback callback)
      throws NetworkException {
    Object[] msg = new Object[] {request, callback};
    ChannelFuture future = null;
    if (channel == null) {
      logger.error("channel is null ^^^^^^^^^^^^^^");
    } else {
      try {
        future = channel.write(msg);
      } catch (Exception e) {
        throw new NetworkException("remote call failed:" + request, e);
      }
      if (request.getMessageType() == Constants.MESSAGE_TYPE_SERVICE
          || request.getMessageType() == Constants.MESSAGE_TYPE_HEART) {
        future.addListener(new MsgWriteListener(request));
      }
    }
    return null;
  }

  public void connectionException(Object attachment, Throwable e) {
    this.connected = false;
    connectionException(this, attachment, e);
  }

  private void connectionException(Client client, Object attachment, Throwable e) {
    logger.info("exception while connecting to " + client, e);
    if (attachment == null) {
      return;
    }
    Object[] msg = (Object[]) attachment;
    if (msg[0] instanceof InvokerContext) {
      InvokerContext invokerContext = (InvokerContext) msg[0];
      InvocationRequest request = invokerContext.getRequest();
      if (request.getMessageType() == Constants.MESSAGE_TYPE_SERVICE && msg[1] != null) {
        try {
          Callback callback = (Callback) msg[1];
          if (client != null) {
            client.write(request, callback);
          } else {
            logger.error("no client found with service:" + request.getServiceName());
          }
        } catch (Throwable ex) {
          logger.error("", ex);
        }
        logger.error("", e);
      }
    }
  }

  /** @return the connected */
  public boolean isConnected() {
    return connected;
  }

  public boolean isActive() {
    return active && HeartBeatListener.isActiveAddress(address);
  }

  public void setActive(boolean active) {
    this.active = active;
  }

  @Override
  public boolean isWritable() {
    return this.channel.isWritable();
  }

  /** @return the host */
  public String getHost() {
    return host;
  }

  public int getPort() {

    return this.port;
  }

  /** @return the address */
  public String getAddress() {
    return address;
  }

  public boolean equals(Object obj) {
    if (obj instanceof NettyClient) {
      NettyClient nc = (NettyClient) obj;
      return this.address.equals(nc.getAddress());
    } else {
      return super.equals(obj);
    }
  }

  @Override
  public int hashCode() {
    return address.hashCode();
  }

  @Override
  public void close() {
    logger.info("close client:" + this.host + ":" + this.port);
    closed = true;
    channel.close();
  }

  @Override
  public String toString() {
    return this.getAddress() + ", connected:" + this.isConnected() + ", active:" + this.isActive();
  }

  public class MsgWriteListener implements ChannelFutureListener {

    private InvocationRequest request;

    public MsgWriteListener(InvocationRequest request) {
      this.request = request;
    }

    public void operationComplete(ChannelFuture future) throws Exception {
      if (future.isSuccess()) {
        return;
      }
      if (request.getMessageType() != Constants.MESSAGE_TYPE_HEART) {
        connected = false;
      }
      InvocationResponse response = ProviderUtils.createFailResponse(request, future.getCause());
      processResponse(response);
    }
  }

  @Override
  public ConnectInfo getConnectInfo() {
    return connectInfo;
  }

  @Override
  public boolean isDisposable() {
    return false;
  }

  @Override
  public void dispose() {}

  @Override
  public String getProtocol() {
    return Constants.PROTOCOL_DEFAULT;
  }
}
/** @author qi.yin 2016/09/23 上午10:31. */
public class DefaultNettyChannel implements NettyChannel {

  private static final Logger logger = LoggerLoader.getLogger(NettyChannel.class);

  private ReentrantLock connectLock = new ReentrantLock();

  private int timeout;

  private volatile Channel channel;

  private ClientBootstrap bootstrap;

  private InetSocketAddress localAddress;

  private InetSocketAddress remoteAddress;

  private String remoteAddressString;

  public DefaultNettyChannel(
      ClientBootstrap bootstrap, String remoteHost, int remotePort, int timeout) {
    this.bootstrap = bootstrap;
    this.remoteAddress = new InetSocketAddress(remoteHost, remotePort);
    this.remoteAddressString = NetUtils.toAddress(remoteHost, remotePort);
    this.timeout = timeout;
  }

  @Override
  public void connect() throws NetworkException {
    connectLock.lock();
    try {
      if (isAvaliable()) {
        logger.info("[connect] is connected to remote " + remoteAddress + ".");
        return;
      }

      ChannelFuture future = bootstrap.connect(remoteAddress);

      try {
        if (future.awaitUninterruptibly(timeout, TimeUnit.MILLISECONDS)) {

          if (future.isSuccess()) {
            disConnect();
            this.channel = future.getChannel();
            localAddress = (InetSocketAddress) this.channel.getLocalAddress();
          } else {
            logger.info("[connect] connected to remote " + remoteAddress + " failed.");
            throw new NetworkException("connected to remote " + remoteAddress + " failed.");
          }

        } else {
          logger.info("[connect] timeout connecting to remote " + remoteAddress + ".");
          throw new NetworkException("timeout connecting to remote " + remoteAddress + ".");
        }

      } catch (Throwable e) {
        logger.info("[connect] error connecting to remote " + remoteAddress + ".", e);

        throw new NetworkException("error connecting to remote " + remoteAddress + ".", e);
      } finally {
        if (!isConnected()) {
          future.cancel();
        }
      }
    } finally {
      connectLock.unlock();
    }
  }

  @Override
  public void disConnect() {
    connectLock.lock();
    try {
      if (this.channel != null) {
        this.channel.close();
      }
    } catch (Throwable e) {
      logger.error("[disConnect] error disConnecting channel. ", e);
    } finally {
      connectLock.unlock();
    }
  }

  @Override
  public ChannelFuture write0(Object message) throws NetworkException {
    if (!isAvaliable()) {
      throw new NetworkException("[write0] channel is null or channel is close.");
    }

    return channel.write(message);
  }

  @Override
  public void write(Object message) throws NetworkException {
    write0(message);
  }

  private boolean isConnected() {
    if (this.channel != null) {
      return this.channel.isConnected();
    }
    return false;
  }

  @Override
  public boolean isAvaliable() {
    return channel != null && channel.isConnected();
  }

  public boolean isWritable() {
    return channel.isWritable();
  }

  @Override
  public InetSocketAddress getLocalAddress() {
    return localAddress;
  }

  @Override
  public InetSocketAddress getRemoteAddress() {
    return remoteAddress;
  }

  @Override
  public String getRemoteAddressString() {
    return this.remoteAddressString;
  }

  public int getTimeout() {
    return timeout;
  }

  public String toString() {
    return "NettyChannel[avaliable = "
        + isAvaliable()
        + "localAddress="
        + localAddress.toString()
        + "remoteAddress= "
        + remoteAddress.toString()
        + "]";
  }
}