Thrift-client作为服务消费端,由于thrift使用socket通讯,因此它需要面对几个问题:

    1) client端需要知道server端的IP + port,如果是分布式部署,还需要知道所有server的IP + port列表.  

    2) client为了提升性能,不可能只使用一个socket来处理并发请求,当然也不能每个请求都创建一个socket;我们需要使用连接池方案.

    3) 对于java开发工程师而言,基于spring配置thrift服务,可以提供很多的便利.

    4) 基于zookeeper配置管理,那么client端就不需要"硬编码"的配置server的ip + port,可以使用zookeeper来推送每个service的服务地址.

    5) 因为thrift-client端不使用连接池的话,将不能有效的提高并发能力,本文重点描述看如何使用thrift-client连接池。

 

1. pom.xml

<dependencies>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>3.0.7.RELEASE</version>
	</dependency>
	<dependency>
		<groupId>org.apache.zookeeper</groupId>
		<artifactId>zookeeper</artifactId>
		<version>3.4.5</version>
		<!--<exclusions>-->
			<!--<exclusion>-->
				<!--<groupId>log4j</groupId>-->
				<!--<artifactId>log4j</artifactId>-->
			<!--</exclusion>-->
		<!--</exclusions>-->
	</dependency>
	<!--
	<dependency>
		<groupId>com.101tec</groupId>
		<artifactId>zkclient</artifactId>
		<version>0.4</version>
	</dependency>
	-->
	<dependency>
		<groupId>org.apache.thrift</groupId>
		<artifactId>libthrift</artifactId>
		<version>0.9.1</version>
	</dependency>
	<dependency>
		<groupId>org.apache.curator</groupId>
		<artifactId>curator-recipes</artifactId>
		<version>2.3.0</version>
	</dependency>
	<dependency>
		<groupId>commons-pool</groupId>
		<artifactId>commons-pool</artifactId>
		<version>1.6</version>
	</dependency>

</dependencies>

2. spring-thrift-client.xml

    其中zookeeper作为可选项,开发者也可以通过制定serverAddress的方式指定server的地址.

<!-- fixedAddress -->
<!--  
<bean id="userService" class="com.demo.thrift.ThriftServiceClientProxyFactory">
	<property name="service" value="com.demo.service.UserService"></property>
	<property name="serverAddress" value="127.0.0.1:9090:2"></property>
	<property name="maxActive" value="5"></property>
	<property name="idleTime" value="10000"></property>
</bean>
-->
<!-- zookeeper -->
<bean id="thriftZookeeper" class="com.demo.thrift.zookeeper.ZookeeperFactory" destroy-method="close">
	<property name="connectString" value="127.0.0.1:2181"></property>
	<property name="namespace" value="demo/thrift-service"></property>
</bean>
<bean id="userService" class="com.demo.thrift.ThriftServiceClientProxyFactory" destroy-method="close">
	<property name="service" value="com.demo.service.UserService"></property>
	<property name="maxActive" value="5"></property>
	<property name="idleTime" value="1800000"></property>
	<property name="addressProvider">
		<bean class="com.demo.thrift.support.impl.DynamicAddressProvider">
			<property name="configPath" value="UserServiceImpl"></property>
			<property name="zookeeper" ref="thriftZookeeper"></property>
		</bean>
	</property>
</bean>

3. ThriftServiceClientProxyFactory.java

    因为我们要在client端使用连接池方案,那么就需要对client的方法调用过程,进行代理,这个类,就是维护了一个"Client"代理类,并在方法调用时,从"对象池"中取出一个"Client"对象,并在方法实际调用结束后归还给"对象池".  

@SuppressWarnings("rawtypes")
public class ThriftServiceClientProxyFactory implements FactoryBean,InitializingBean {

    private String service;

    private String serverAddress;
    
    private Integer maxActive = 32;//最大活跃连接数
    
    ms,default 3 min,链接空闲时间
    //-1,关闭空闲检测
    private Integer idleTime = 180000;
    private ThriftServerAddressProvider addressProvider;

	private Object proxyClient;
	

    public void setMaxActive(Integer maxActive) {
		this.maxActive = maxActive;
	}


	public void setIdleTime(Integer idleTime) {
		this.idleTime = idleTime;
	}


	public void setService(String service) {
		this.service = service;
	}


    public void setServerAddress(String serverAddress) {
        this.serverAddress = serverAddress;
    }


    public void setAddressProvider(ThriftServerAddressProvider addressProvider) {
		this.addressProvider = addressProvider;
	}

	private Class objectClass;
    
    private GenericObjectPool<TServiceClient> pool;
    
    private PoolOperationCallBack callback = new PoolOperationCallBack() {
		
		@Override
		public void make(TServiceClient client) {
			System.out.println("create");
			
		}
		
		@Override
		public void destroy(TServiceClient client) {
			System.out.println("destroy");
			
		}
	};

    @Override
    public void afterPropertiesSet() throws Exception {
        if(serverAddress != null){
            addressProvider = new FixedAddressProvider(serverAddress);
        }
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        //加载Iface接口
        objectClass = classLoader.loadClass(service + "$Iface");
        //加载Client.Factory类
        Class<TServiceClientFactory<TServiceClient>> fi = (Class<TServiceClientFactory<TServiceClient>>)classLoader.loadClass(service + "$Client$Factory");
        TServiceClientFactory<TServiceClient> clientFactory = fi.newInstance();
        ThriftClientPoolFactory clientPool = new ThriftClientPoolFactory(addressProvider, clientFactory,callback);
        GenericObjectPool.Config poolConfig = new GenericObjectPool.Config();
        poolConfig.maxActive = maxActive;
        poolConfig.minIdle = 0;
        poolConfig.minEvictableIdleTimeMillis = idleTime;
        poolConfig.timeBetweenEvictionRunsMillis = idleTime/2L;
        pool = new GenericObjectPool<TServiceClient>(clientPool,poolConfig);
        proxyClient = Proxy.newProxyInstance(classLoader,new Class[]{objectClass},new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            	//
            	TServiceClient client = pool.borrowObject();
            	try{
            		return method.invoke(client, args);
            	}catch(Exception e){
            		throw e;
            	}finally{
            		pool.returnObject(client);
            	}
            }
        });
    }

    @Override
    public Object getObject() throws Exception {
        return proxyClient;
    }

    @Override
    public Class<?> getObjectType() {
        return objectClass;
    }

    @Override
    public boolean isSingleton() {
        return true;  //To change body of implemented methods use File | Settings | File Templates.
    }
    
    public void close(){
    	if(addressProvider != null){
    		addressProvider.close();
    	}
    }
}

4. ThriftClientPoolFactory.java

    "Client"对象池,对象池中是已经实例化的Client对象,Client对象负责与Thrift server通信.

/**
 * 连接池,thrift-client for spring
 */
public class ThriftClientPoolFactory extends BasePoolableObjectFactory<TServiceClient>{

    private final ThriftServerAddressProvider addressProvider;
    
    private final TServiceClientFactory<TServiceClient> clientFactory;
    
    private PoolOperationCallBack callback;

    protected ThriftClientPoolFactory(ThriftServerAddressProvider addressProvider,TServiceClientFactory<TServiceClient> clientFactory) throws Exception {
        this.addressProvider = addressProvider;
        this.clientFactory = clientFactory;
    }
    
    protected ThriftClientPoolFactory(ThriftServerAddressProvider addressProvider,TServiceClientFactory<TServiceClient> clientFactory,PoolOperationCallBack callback) throws Exception {
        this.addressProvider = addressProvider;
        this.clientFactory = clientFactory;
        this.callback = callback;
    }



    @Override
    public TServiceClient makeObject() throws Exception {
        InetSocketAddress address = addressProvider.selector();
        TSocket tsocket = new TSocket(address.getHostName(),address.getPort());
        TProtocol protocol = new TBinaryProtocol(tsocket);
        TServiceClient client = this.clientFactory.getClient(protocol);
        tsocket.open();
        if(callback != null){
        	try{
        		callback.make(client);
        	}catch(Exception e){
        		//
        	}
        }
        return client;
    }

    public void destroyObject(TServiceClient client) throws Exception {
    	if(callback != null){
    		try{
    			callback.destroy(client);
    		}catch(Exception e){
    			//
    		}
        }
    	TTransport pin = client.getInputProtocol().getTransport();
    	pin.close();
    }

    public boolean validateObject(TServiceClient client) {
    	TTransport pin = client.getInputProtocol().getTransport();
    	return pin.isOpen();
    }
    
    static interface PoolOperationCallBack {
    	//销毁client之前执行
    	void destroy(TServiceClient client);
    	//创建成功是执行
    	void make(TServiceClient client);
    }

}

5. DynamicAddressProvider.java

    将zookeeper作为server地址的提供者,这样客户端就不需要再配置文件中指定一堆ip + port,而且当server服务有更新时,也不需要client端重新配置.

/**
 * 可以动态获取address地址,方案设计参考
 * 1) 可以间歇性的调用一个web-service来获取地址
 * 2) 可以使用事件监听机制,被动的接收消息,来获取最新的地址(比如基于MQ,nio等)
 * 3) 可以基于zookeeper-watcher机制,获取最新地址
 * <p/>
 * 本实例,使用zookeeper作为"config"中心,使用apache-curator方法库来简化zookeeper开发
 * 如下实现,仅供参考
 */
public class DynamicAddressProvider implements ThriftServerAddressProvider, InitializingBean {

    private String configPath;

    private PathChildrenCache cachedPath;

    private CuratorFramework zookeeper;
    
    //用来保存当前provider所接触过的地址记录
    //当zookeeper集群故障时,可以使用trace中地址,作为"备份"
    private Set<String> trace = new HashSet<String>();

    private final List<InetSocketAddress> container = new ArrayList<InetSocketAddress>();

    private Queue<InetSocketAddress> inner = new LinkedList<InetSocketAddress>();
    
    private Object lock = new Object();
    
    private static final Integer DEFAULT_PRIORITY = 1;

    public void setConfigPath(String configPath) {
        this.configPath = configPath;
    }

    public void setZookeeper(CuratorFramework zookeeper) {
        this.zookeeper = zookeeper;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    	//如果zk尚未启动,则启动
    	if(zookeeper.getState() == CuratorFrameworkState.LATENT){
    		zookeeper.start();
    	}
        buildPathChildrenCache(zookeeper, configPath, true);
        cachedPath.start(StartMode.POST_INITIALIZED_EVENT);
    }

    private void buildPathChildrenCache(CuratorFramework client, String path, Boolean cacheData) throws Exception {
        cachedPath = new PathChildrenCache(client, path, cacheData);
        cachedPath.getListenable().addListener(new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                PathChildrenCacheEvent.Type eventType = event.getType();
                switch (eventType) {
//                    case CONNECTION_RECONNECTED:
//                        
//                        break;
                    case CONNECTION_SUSPENDED:
                    case CONNECTION_LOST:
                        System.out.println("Connection error,waiting...");
                        return;
                    default:
                        //
                }
                //任何节点的时机数据变动,都会rebuild,此处为一个"简单的"做法.
                cachedPath.rebuild();
                rebuild();
            }
            
            protected void rebuild() throws Exception {
                List<ChildData> children = cachedPath.getCurrentData();
                if (children == null || children.isEmpty()) {
                    //有可能所有的thrift server都与zookeeper断开了链接
                    //但是,有可能,thrift client与thrift server之间的网络是良好的
                    //因此此处是否需要清空container,是需要多方面考虑的.
                    container.clear();
                    System.out.println("thrift server-cluster error....");
                    return;
                }
                List<InetSocketAddress> current = new ArrayList<InetSocketAddress>();
                for (ChildData data : children) {
                    String address = new String(data.getData(), "utf-8");
                    current.addAll(transfer(address));
                    trace.add(address);
                }
                Collections.shuffle(current);
                synchronized (lock) {
                    container.clear();
                    container.addAll(current);
                    inner.clear();
                    inner.addAll(current);
        			
        		}
            }
        });
    }
    
    
    
    private List<InetSocketAddress> transfer(String address){
    	String[] hostname = address.split(":");
    	Integer priority = DEFAULT_PRIORITY;
        if (hostname.length == 3) {
            priority = Integer.valueOf(hostname[2]);
        }
        String ip = hostname[0];
        Integer port = Integer.valueOf(hostname[1]);
        List<InetSocketAddress> result = new ArrayList<InetSocketAddress>();
        for (int i = 0; i < priority; i++) {
        	result.add(new InetSocketAddress(ip, port));
        }
        return result;
    }


    @Override
    public List<InetSocketAddress> getAll() {
        return Collections.unmodifiableList(container);
    }

    @Override
    public synchronized InetSocketAddress selector() {
        if (inner.isEmpty()) {
        	if(!container.isEmpty()){
        		inner.addAll(container);
        	}else if(!trace.isEmpty()){
        		synchronized (lock) {
        			for(String hostname : trace){
        				container.addAll(transfer(hostname));
        			}
        			Collections.shuffle(container);
        			inner.addAll(container);
				}
        	}
        }
        return inner.poll();//null
    }


    @Override
    public void close() {
        try {
            cachedPath.close();
            zookeeper.close();
        } catch (Exception e) {
            //
        }
    }
}

    到此为止,我们的Thrift基本上就可以顺利运行起来了.更多代码,参见附件.

    Thrift-server端开发与配置,参见[Thrift-server]