JAVA RPC (七) 之手把手从零教你写一个生产级RPC之client的代理

首先对于RPC来讲,最主要的无非三点【SERVER IO模型】、【序列化协议】、【client连接池复用】,之前的博客大家应该对thrift有一个大致的了解了,那么我们现在来说一说如何将thrift的序列化和传输使用到生产中。先放一张作者自己写的一个rpc架构图。

 

 

分成几个主要部分:

1:server启动zk注册

2:client监听watch节点变动维护本地缓存,构建tcp连接池。

3:通过java aop代理获得接口代理实现,从而通过thrift序列化传输二进制自定义协议的字节流

4:server通过reactor主从模型来实现高性能服务端。

5:调用端数据上报,形成trace链路,数据大盘,TP99,可用率等。

 

先看一下使用方式

客户端有两种,第一种xml 使用

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:koalas="http://www.koalas.com/schema/ch"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
                           http://www.koalas.com/schema/ch
                           http://www.koalas.com/schema/ch.xsd">

    <koalas:client id="koalasService"
               serviceInterface="thrift.service.koalasService" 
zkPath
="127.0.0.1:2181"/> </beans>

 

package thrift.service;

import org.apache.thrift.TException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import thrift.domain.KoalasRequest;
import thrift.domain.koalasRespone;

@Service("testService")
public class TestService {

    @Autowired
    KoalasTestService.Iface koalastestService;

    public void getRemoteRpc() throws TException {

        KoalasRequest request= new KoalasRequest (  );
        request.setxxxx1 ( 1 );
        request.setxxxx2( 1 );
        request.setxxxxx3 ( 1 );
        request.setxxxx4 ( "你好" );
        request.setxxxx5 ( 1 );
        KoalastestRespone respone = koalastestService.getRPC (  request);
        System.out.println (respone);
     }
}

 

第二种使用方式 注解形式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:koalas="http://www.koalas.com/schema/ch"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
                           http://www.koalas.com/schema/ch
                           http://www.koalas.com/schema/ch.xsd">

    <koalas:annotation package="thrift.annotation.client.impl"/>
</beans>
@Service("testServiceSync")
public class TestServiceSync {

    @KoalasClient(zkPath = "127.0.0.1:2181",readTimeout = 5000*1000)
    KoalasTestService.Iface koalastestService;

    public void getRemoteRpc() throws TException {
        KoalasRequest request= new KoalasRequest (  );
        //request.setSource ( 10 );
        request.setxxxxx1 ( 1 );
        request.setxxxxx2 ( 1 );
        request.setxxxxx3 ( 1 );
        request.setxxxxx4 ( "你好啊-我是注解实现的" );
        request.setxxxxx5 ( 1 );
        KoalasRespone respone = koalastestService.getRPC (  request);
        System.out.println (respone);
     }

}

简单吧,一行xml配合一个注解,client的实现这样就完成了。。。关于服务端的使用去看我git上面的wiki吧,博客中不做多说明了,这次主要讲源码。

 

首先看client的主要代理类入口client.proxyfactory.KoalasClientProxy,这个类的作用是所有的client服务端通过thrift的代理都通过他来完成,放出源码如下

package client.proxyfactory;

import client.cluster.ILoadBalancer;
import client.cluster.Icluster;
import client.cluster.impl.DirectClisterImpl;
import client.cluster.impl.RandomLoadBalancer;
import client.cluster.impl.ZookeeperClisterImpl;
import client.invoker.KoalsaMothodInterceptor;
import client.invoker.LocalMockInterceptor;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.thrift.async.TAsyncClientManager;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.transport.TNonblockingTransport;
import org.apache.thrift.transport.TTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import protocol.KoalasBinaryProtocol;
import transport.TKoalasFramedTransport;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Copyright (C) 2018
 * All rights reserved
 * User: yulong.zhang
 * Date:2018年09月18日17:44:58
 */
public class KoalasClientProxy implements FactoryBean<Object>, ApplicationContextAware, InitializingBean {
    private final static Logger logger = LoggerFactory.getLogger ( KoalasClientProxy.class );
    public static final String ASYNC_IFACE = "AsyncIface";
    public static final String IFACE = "Iface";
    public static final String CLIENT = "Client";
    public static final String ASYNC_CLIENT = "AsyncClient";
    //请求体最大长度
    public static final int DEFUAL_MAXLENGTH = 10 * 1024 * 1024;
    //连接超时
    public static final int DEFUAL_CONNTIMEOUT = 5*1000;
    //读取超时
    public static final int DEFUAL_READTIMEOUT = 30*1000;

    //client端service
    private Class<?> serviceInterface;
    // 方式1:zk管理的动态集群,格式192.168.3.253:6666
    private String zkPath;
    // 方式2:指定的server列表,逗号分隔,#分隔权重,格式192.168.3.253:6666#10,192.168.3.253:6667#10
    private String serverIpPorts;

    //代理对象,所有client-server类型统一代理
    private Object loalsServiceProxy;
    //spring上下文对象
    private ApplicationContext applicationContext;
    // 同步还是异步,默认同步。
    private boolean async = false;
    //连接超时时间
    private int connTimeout=DEFUAL_CONNTIMEOUT;
    //读取超时时间
    private int readTimeout=DEFUAL_READTIMEOUT;
    //本地client测试用实现
    private String localMockServiceImpl;
    //重试
    private boolean retryRequest = true;
    private int retryTimes = 3;
    private GenericObjectPoolConfig genericObjectPoolConfig;
    //最大连接数
    private int maxTotal=100;
    //最大闲置数
    private int maxIdle=50;
    //最小闲置数量
    private int minIdle=10;
    private boolean lifo = true;
    private boolean fairness = false;
    private long maxWaitMillis = 30 * 1000;
    //多长时间运行一次
    private long timeBetweenEvictionRunsMillis = 3 * 60 * 1000;
    private long minEvictableIdleTimeMillis = 5 * 60 * 1000;

    //对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,
    //不再根据MinEvictableIdleTimeMillis判断  (默认逐出策略)
    private long softMinEvictableIdleTimeMillis = 10 * 60 * 1000;
    private int numTestsPerEvictionRun = 20;
    private boolean testOnCreate = false;
    private boolean testOnBorrow = false;
    private boolean testOnReturn = false;
    private boolean testWhileIdle = true;
    private Icluster icluster;
    private ILoadBalancer iLoadBalancer;
    private String env="dev";
    AbandonedConfig abandonedConfig;
    private boolean removeAbandonedOnBorrow = true;
    private boolean removeAbandonedOnMaintenance = true;
    private int removeAbandonedTimeout = 30;
    private int maxLength_ = DEFUAL_MAXLENGTH;
    private static int cores = Runtime.getRuntime().availableProcessors();
    private int asyncSelectorThreadCount = cores * 2;
    private static List<TAsyncClientManager> asyncClientManagerList = null;

    private String privateKey;
    private String publicKey;

    public String getPrivateKey() {
        return privateKey;
    }

    public void setPrivateKey(String privateKey) {
        this.privateKey = privateKey;
    }

    public String getPublicKey() {
        return publicKey;
    }

    public void setPublicKey(String publicKey) {
        this.publicKey = publicKey;
    }

    public int getMaxLength_() {
        return maxLength_;
    }

    public void setMaxLength_(int maxLength_) {
        this.maxLength_ = maxLength_;
    }

    public int getMaxTotal() {
        return maxTotal;
    }

    public void setMaxTotal(int maxTotal) {
        this.maxTotal = maxTotal;
    }

    public int getMaxIdle() {
        return maxIdle;
    }

    public void setMaxIdle(int maxIdle) {
        this.maxIdle = maxIdle;
    }

    public int getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }

    public boolean isLifo() {
        return lifo;
    }

    public void setLifo(boolean lifo) {
        this.lifo = lifo;
    }

    public boolean isFairness() {
        return fairness;
    }

    public void setFairness(boolean fairness) {
        this.fairness = fairness;
    }

    public long getMaxWaitMillis() {
        return maxWaitMillis;
    }

    public void setMaxWaitMillis(long maxWaitMillis) {
        this.maxWaitMillis = maxWaitMillis;
    }

    public long getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }

    public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }

    public long getMinEvictableIdleTimeMillis() {
        return minEvictableIdleTimeMillis;
    }

    public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    public long getSoftMinEvictableIdleTimeMillis() {
        return softMinEvictableIdleTimeMillis;
    }

    public void setSoftMinEvictableIdleTimeMillis(long softMinEvictableIdleTimeMillis) {
        this.softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis;
    }

    public int getNumTestsPerEvictionRun() {
        return numTestsPerEvictionRun;
    }

    public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
        this.numTestsPerEvictionRun = numTestsPerEvictionRun;
    }

    public boolean isTestOnCreate() {
        return testOnCreate;
    }

    public void setTestOnCreate(boolean testOnCreate) {
        this.testOnCreate = testOnCreate;
    }

    public boolean isTestOnBorrow() {
        return testOnBorrow;
    }

    public void setTestOnBorrow(boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }

    public boolean isTestOnReturn() {
        return testOnReturn;
    }

    public void setTestOnReturn(boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }

    public boolean isTestWhileIdle() {
        return testWhileIdle;
    }

    public void setTestWhileIdle(boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }

    public String getLocalMockServiceImpl() {
        return localMockServiceImpl;
    }

    public void setLocalMockServiceImpl(String localMockServiceImpl) {
        this.localMockServiceImpl = localMockServiceImpl;
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    public int getConnTimeout() {
        return connTimeout;
    }

    public void setConnTimeout(int connTimeout) {
        this.connTimeout = connTimeout;
    }

    public String getZkPath() {
        return zkPath;
    }

    public void setZkPath(String zkPath) {
        this.zkPath = zkPath;
    }

    public String getServerIpPorts() {
        return serverIpPorts;
    }

    public void setServerIpPorts(String serverIpPorts) {
        this.serverIpPorts = serverIpPorts;
    }

    public boolean isAsync() {
        return async;
    }

    public void setAsync(boolean async) {
        this.async = async;
    }

    public Object getLoalsServiceProxy() {
        return loalsServiceProxy;
    }

    public void setLoalsServiceProxy(Object loalsServiceProxy) {
        this.loalsServiceProxy = loalsServiceProxy;
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public Class<?> getServiceInterface() {
        return serviceInterface;
    }

    public void setServiceInterface(Class<?> serviceInterface) {
        this.serviceInterface = serviceInterface;
    }

    public ILoadBalancer getiLoadBalancer() {
        return iLoadBalancer;
    }

    public void setiLoadBalancer(ILoadBalancer iLoadBalancer) {
        this.iLoadBalancer = iLoadBalancer;
    }

    public boolean isRemoveAbandonedOnBorrow() {
        return removeAbandonedOnBorrow;
    }

    public void setRemoveAbandonedOnBorrow(boolean removeAbandonedOnBorrow) {
        this.removeAbandonedOnBorrow = removeAbandonedOnBorrow;
    }

    public boolean isRemoveAbandonedOnMaintenance() {
        return removeAbandonedOnMaintenance;
    }

    public void setRemoveAbandonedOnMaintenance(boolean removeAbandonedOnMaintenance) {
        this.removeAbandonedOnMaintenance = removeAbandonedOnMaintenance;
    }

    public int getRemoveAbandonedTimeout() {
        return removeAbandonedTimeout;
    }

    public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {
        this.removeAbandonedTimeout = removeAbandonedTimeout;
    }

    public boolean isRetryRequest() {
        return retryRequest;
    }

    public void setRetryRequest(boolean retryRequest) {
        this.retryRequest = retryRequest;
    }

    public int getRetryTimes() {
        return retryTimes;
    }

    public void setRetryTimes(int retryTimes) {
        this.retryTimes = retryTimes;
    }

    public String getEnv() {
        return env;
    }
    public void setEnv(String env) {
        this.env = env;
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public Object getObject(){
        if (getLoalsServiceProxy () == null) throw new RuntimeException ( "the Proxy can't be null" );
        return getLoalsServiceProxy ();
    }

    @Override
    public Class<?> getObjectType() {
        if (serviceInterface == null)
            return null;
        return getIfaceInterface ();
    }

    private Class<?> getIfaceInterface() {
        if (async)
            return getAsyncIfaceInterface ();
        else
            return getSynIfaceInterface ();
    }


    private Constructor<?> synConstructor;
    private Constructor<?> asyncConstructor;

    public Object getInterfaceClientInstance(TTransport socket,String server) {

        if (!async) {
            Class<?> clazz = getSynClientClass ();
            try {
                if (synConstructor == null) {
                    synConstructor = clazz.getDeclaredConstructor ( TProtocol.class );
                }
                TTransport transport  = new TKoalasFramedTransport ( socket, maxLength_ );
                if(this.getPrivateKey ()!=null && this.getPublicKey () != null){
                    ((TKoalasFramedTransport) transport).setRsa ( (byte) 1 );
                    ((TKoalasFramedTransport) transport).setPrivateKey ( this.privateKey );
                    ((TKoalasFramedTransport) transport).setPublicKey ( this.publicKey );
                }

                TProtocol protocol = new KoalasBinaryProtocol ( transport );

                return synConstructor.newInstance ( protocol );

            } catch (NoSuchMethodException e) {
                logger.error ( "the clazz can't find the Constructor with TProtocol.class" );
            } catch (InstantiationException e) {
                logger.error ( "get InstantiationException", e );
            } catch (IllegalAccessException e) {
                logger.error ( "get IllegalAccessException", e );
            } catch (InvocationTargetException e) {
                logger.error ( "get InvocationTargetException", e );
            }
        } else {
                if (null == asyncClientManagerList) {
                    synchronized (this) {
                        if (null == asyncClientManagerList) {
                            asyncClientManagerList = new ArrayList<> ();
                            for (int i = 0; i < asyncSelectorThreadCount; i++) {
                                try {
                                    asyncClientManagerList.add(new TAsyncClientManager());
                                } catch (IOException e) {
                                    e.printStackTrace ();
                                }
                            }
                        }
                    }
                }
            Class<?> clazz = getAsyncClientClass ();

            if (asyncConstructor == null) {
                try {
                    asyncConstructor = clazz.getDeclaredConstructor ( TProtocolFactory.class, TAsyncClientManager.class, TNonblockingTransport.class );
                } catch (NoSuchMethodException e) {
                    e.printStackTrace ();
                }
            }

            try {
                return asyncConstructor.newInstance ( new KoalasBinaryProtocol.Factory (), asyncClientManagerList.get (socket.hashCode () % asyncSelectorThreadCount), socket );
            } catch (InstantiationException e) {
                logger.error ( "get InstantiationException", e );
            } catch (IllegalAccessException e) {
                logger.error ( "get IllegalAccessException", e );
            } catch (InvocationTargetException e) {
                logger.error ( "get InvocationTargetException", e );
            }

        }
        return null;
    }

    private Class<?> getAsyncIfaceInterface() {
        Class<?>[] classes = serviceInterface.getClasses ();
        for (Class c : classes)
            if (c.isMemberClass () && c.isInterface () && c.getSimpleName ().equals ( ASYNC_IFACE )) {
                return c;
            }
        throw new IllegalArgumentException ( "can't find the interface AsyncIface,please make the service with thrift tools!" );
    }

    private Class<?> getSynIfaceInterface() {
        Class<?>[] classes = serviceInterface.getClasses ();
        for (Class c : classes)
            if (c.isMemberClass () && c.isInterface () && c.getSimpleName ().equals ( IFACE )) {
                return c;
            }
        throw new IllegalArgumentException ( "can't find the interface Iface,please make the service with thrift tools" );
    }

    private Class<?> getSynClientClass() {
        Class<?>[] classes = serviceInterface.getClasses ();
        for (Class c : classes)
            if (c.isMemberClass () && !c.isInterface () && c.getSimpleName ().equals ( CLIENT )) {
                return c;
            }
        throw new IllegalArgumentException ( "serviceInterface must contain Sub Class of Client" );
    }

    private Class<?> getAsyncClientClass() {
        Class<?>[] classes = serviceInterface.getClasses ();
        for (Class c : classes)
            if (c.isMemberClass () && !c.isInterface () && c.getSimpleName ().equals ( ASYNC_CLIENT )) {
                return c;
            }
        throw new IllegalArgumentException ( "serviceInterface must contain Sub Class of AsyncClient" );
    }


    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void afterPropertiesSet(){


        if(serviceInterface==null){
            throw  new IllegalArgumentException ( "serviceInterface can't be null" );
        }

        if(zkPath==null && serverIpPorts==null){
            throw  new IllegalArgumentException ( "zkPath or serverIpPorts at least ones can't be null" );
        }

        Class<?> _interface = null;
        if (localMockServiceImpl != null && !StringUtils.isEmpty ( localMockServiceImpl.trim () )) {
            LocalMockInterceptor localMockInterceptor = new LocalMockInterceptor ( localMockServiceImpl );
            _interface = getIfaceInterface ();
            ProxyFactory pf = new ProxyFactory ( _interface, localMockInterceptor );
            setLoalsServiceProxy ( pf.getProxy () );
            return;
        }

        genericObjectPoolConfig = getGenericObjectPoolConfig ();
        abandonedConfig = getAbandonedConfig ();

        if (!StringUtils.isEmpty ( serverIpPorts )) {
            icluster = new DirectClisterImpl ( serverIpPorts, iLoadBalancer == null ? new RandomLoadBalancer () : iLoadBalancer, serviceInterface.getName (), async, connTimeout, readTimeout, genericObjectPoolConfig, abandonedConfig );
        } else{
            icluster = new ZookeeperClisterImpl ( zkPath ,iLoadBalancer == null ? new RandomLoadBalancer () : iLoadBalancer, serviceInterface.getName (),env,async,connTimeout,readTimeout,genericObjectPoolConfig,abandonedConfig);
        }

        KoalsaMothodInterceptor koalsaMothodInterceptor = new KoalsaMothodInterceptor ( icluster, retryTimes, retryRequest, this,readTimeout );
        _interface = getIfaceInterface ();

        loalsServiceProxy = new ProxyFactory ( _interface, koalsaMothodInterceptor ).getProxy ();

        logger.info ( "the service【{}】is start !", serviceInterface.getName () );
    }

    private AbandonedConfig getAbandonedConfig() {
        AbandonedConfig abandonedConfig = new AbandonedConfig ();
        abandonedConfig.setRemoveAbandonedOnBorrow ( isRemoveAbandonedOnBorrow () );
        abandonedConfig.setRemoveAbandonedOnMaintenance ( isRemoveAbandonedOnMaintenance () );
        abandonedConfig.setRemoveAbandonedTimeout ( getRemoveAbandonedTimeout () );
        return abandonedConfig;
    }

    private GenericObjectPoolConfig getGenericObjectPoolConfig() {
        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig ();
        genericObjectPoolConfig.setMaxTotal ( getMaxTotal () );
        genericObjectPoolConfig.setMinIdle ( getMinIdle () );
        genericObjectPoolConfig.setMaxIdle ( maxIdle );
        genericObjectPoolConfig.setMaxWaitMillis ( getMaxWaitMillis () );
        genericObjectPoolConfig.setLifo ( isLifo () );
        genericObjectPoolConfig.setFairness ( isFairness () );
        genericObjectPoolConfig.setMinEvictableIdleTimeMillis ( getMinEvictableIdleTimeMillis () );
        genericObjectPoolConfig.setSoftMinEvictableIdleTimeMillis ( getSoftMinEvictableIdleTimeMillis () );
        genericObjectPoolConfig.setNumTestsPerEvictionRun ( getNumTestsPerEvictionRun () );
        genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis ( getTimeBetweenEvictionRunsMillis () );
        genericObjectPoolConfig.setTestOnCreate ( isTestOnCreate () );
        genericObjectPoolConfig.setTestOnBorrow ( isTestOnBorrow () );
        genericObjectPoolConfig.setTestOnReturn ( isTestOnReturn () );
        genericObjectPoolConfig.setTestWhileIdle ( isTestWhileIdle () );
        return genericObjectPoolConfig;
    }

    public void destroy(){
        if(icluster!= null) icluster.destroy ();
    }

    public static void main(String[] args) {
        String a = "192.168.3.253:6666#10#thrift,192.168.3.253:6667#10#netty";
        System.out.println ( Arrays.toString ( a.split ( "[^0-9a-zA-Z_\\-\\.:#]+" ) ) );
    }
}

 

首先这个类实现了FactoryBean和InitializingBean,FactoryBean的实现方法getObject就是这个代理类本身的实现了,返回的对象为全局的setKoalasServiceProxy,注意一下这块的代码

        koalasServiceProxy = new ProxyFactory ( _interface, koalsaMothodInterceptor ).getProxy ();

可以看出koalasServiceProxy为spring代理出来的对象,代理的接口是interface,也就是我们thrift自动生成class的 xxxxxx.iface

private Class<?> getSynIfaceInterface() {
        Class<?>[] classes = serviceInterface.getClasses ();
        for (Class c : classes)
            if (c.isMemberClass () && c.isInterface () && c.getSimpleName ().equals ( IFACE )) {
                return c;
            }
        throw new IllegalArgumentException ( "can't find the interface Iface,please make the service with thrift tools" );
    }

这也就说为什么在我们的spring bean注入的类为 xxxxx.iface,对spring有一些知识的朋友一定会了解。(不懂的去学呗)

至于反射的实现肯定是在

        KoalsaMothodInterceptor koalsaMothodInterceptor = new KoalsaMothodInterceptor ( icluster, retryTimes, retryRequest, this,readTimeout );

里面了,里面的东西是什么,敬请期待下回分解!

 

https://gitee.com/a1234567891/koalas-rpc

koalas-RPC 个人作品,提供大家交流学习,有意见请私信,欢迎拍砖。客户端采用thrift协议,服务端支持netty和thrift的TThreadedSelectorServer半同步半异步线程模型,支持动态扩容,服务上下线,权重动态,可用性配置,页面流量统计等,持续为个人以及中小型公司提供可靠的RPC框架技术方案

更多学习内容请加高级java QQ群:825199617

posted on 2019-04-25 20:04  张玉龙  阅读(1341)  评论(0编辑  收藏  举报