1.基礎架構:
RPC服務往平臺化的方向發展, 會屏蔽掉更多的服務細節(服務的IP地址集群, 集群的擴容和遷移), 只暴露服務接口. 這部分的演化, 使得server端和client端完全的解耦合. 兩者的交互通過ConfigServer(MetaServer)的中介角色來搭線.
注: 該圖源自dubbo的官網
這邊借助Zookeeper來扮演該角色, server扮演發布者的角色, 而client扮演訂閱者的角色.
2.Zookeeper基礎:
Zookeeper是分布式應用協作服務. 它實現了paxos的一致性算法, 在命名管理/配置推送/數據同步/主從切換方面扮演重要的角色.
其數據組織類似文件系統的目錄結構:
每個節點被稱為znode, 為znode節點依據其特性, 又可以分為如下類型:
1). PERSISTENT: 永久節點
2). EPHEMERAL: 臨時節點, 會隨session(client disconnect)的消失而消失
3). PERSISTENT_SEQUENTIAL: 永久節點, 其節點的名字編號是單調遞增的
4). EPHEMERAL_SEQUENTIAL: 臨時節點, 其節點的名字編號是單調遞增的
注: 臨時節點不能成為父節點
Watcher觀察模式, client可以注冊對節點的狀態/內容變更的事件回調機制. 其Event事件的兩類屬性需要關注下:
1). KeeperState: Disconnected,SyncConnected,Expired
2). EventType: None,NodeCreated,NodeDeleted,NodeDataChanged,NodeChildrenChanged
3.RPC服務端:
作為具體業務服務的RPC服務發布方, 對其自身的服務描述由以下元素構成.
1). namespace: 命名空間,來區分不同應用
2). service: 服務接口, 采用發布方的類全名來表示
3). version: 版本號
借鑒了Maven的GAV坐標系, 三維坐標系更符合服務平臺化的大環境.
- 數據模型的設計
具體RPC服務的注冊路徑為: /rpc/{namespace}/{service}/{version}, 該路徑上的節點都是永久節點
RPC服務集群節點的注冊路徑為: /rpc/{namespace}/{service}/{version}/{ip:port:weight}, 末尾的節點是臨時節點
4.Thrift服務基于zookeeper實現發布訂閱改造
4.1改造的功能點
對于Thrift服務化的改造,主要是客戶端,可以從如下幾個方面進行:
- 1.服務端的服務注冊,客戶端自動發現,無需手工修改配置,這里我們使用zookeeper,但由于zookeeper本身提供的客戶端使用較為復雜,因此采用curator-recipes工具類進行處理服務的注冊與發現。
- 2.客戶端使用連接池對服務調用進行管理,提升性能,這里我們使用Apache Commons項目commons-pool,可以大大減少代碼的復雜度。
- 3.關于Failover/LoadBalance,由于zookeeper的watcher,當服務端不可用是及時通知客戶端,并移除不可用的服務節點,而LoadBalance有很多算法,這里我們采用隨機加權方式,也是常有的負載算法,至于其他的算法介紹參考:常見的負載均衡的基本算法。
- 4.使thrift服務的注冊和發現可以基于spring配置,可以提供很多的便利。
- 5.其他的改造如:
1)通過動態代理實現client和server端的交互細節透明化,讓用戶只需通過服務方提供的接口進行訪問
2)Thrift通過兩種方式調用服務Client和Iface
// *) Client API 調用
(EchoService.Client)client.echo("hello lilei"); ---(1)
// *) Service 接口 調用
(EchoService.Iface)service.echo("hello lilei"); ---(2)
Client API的方式, 不推薦, 我們推薦Service接口的方式(服務化)。
4.2實現相關的改造
4.2.1 pom.xml引入依賴jar包
<dependencies>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.9.3</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.7.1</version>
</dependency>
4.2.2 使用zookeeper管理服務節點配置
1)定義Zookeeper的客戶端的管理
package com.yangyang.thrift.rpc.zookeeper;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.FactoryBean;
/**
* Created by chenshunyang on 2016/10/21.
* 獲取zookeeper客戶端鏈接
*/
public class ZookeeperFactory implements FactoryBean<CuratorFramework>{
private String zkHosts;
// session超時
private int sessionTimeout = 30000;
private int connectionTimeout = 30000;
// 共享一個zk鏈接
private boolean singleton = true;
private CuratorFramework zkClient;
// 全局path前綴,常用來區分不同的應用
private String namespace;
private final static String ROOT = "thrift";
//注意一定要實現屬性的set方法,否則在spring bean注入的地方會拿不到值
public void setZkHosts(String zkHosts) {
this.zkHosts = zkHosts;
}
public void setSessionTimeout(int sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public void setSingleton(boolean singleton) {
this.singleton = singleton;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public void setZkClient(CuratorFramework zkClient) {
this.zkClient = zkClient;
}
public CuratorFramework getObject() throws Exception {
if (singleton){
if (zkClient == null){
zkClient = create();
zkClient.start();
}
return zkClient;
}
return create();
}
private CuratorFramework create() throws Exception {
if (namespace == null || namespace == ""){
namespace = ROOT;
}else {
namespace = ROOT+"/"+namespace;
}
return create(zkHosts, sessionTimeout, connectionTimeout, namespace);
}
public static CuratorFramework create(String zkHosts, int sessionTimeout, int connectionTimeout, String namespace) {
return CuratorFrameworkFactory.builder()
.connectString(zkHosts)
.sessionTimeoutMs(sessionTimeout)
.connectionTimeoutMs(connectionTimeout)
.canBeReadOnly(true)
.namespace(namespace)
.retryPolicy(new ExponentialBackoffRetry(1000,Integer.MAX_VALUE))
.defaultData(null)
.build();
}
public Class<?> getObjectType() {
return CuratorFramework.class;
}
public boolean isSingleton() {
return singleton;
}
public void close() {
if (zkClient != null) {
zkClient.close();
}
}
}
2).服務端注冊服務
由于服務端配置需要獲取本機的IP地址,因此定義IP獲取接口
package com.yangyang.thrift.rpc.zookeeper;
/**
* 解析thrift-server端IP地址,用于注冊服務
* 1) 可以從一個物理機器或者虛機的特殊文件中解析
* 2) 可以獲取指定網卡序號的Ip
* 3) 其他
* Created by chenshunyang on 2016/10/21.
*/
public interface ThriftServerIpResolve {
String getServerIp() throws Exception;
void reset();
//當IP變更時,將會調用reset方法
static interface IpRestCalllBack{
public void rest(String newIp);
}
}
可以對該接口做不同的實現,下面我們基于網卡獲取IP地址,也可以通過配置serverIp
package com.yangyang.thrift.rpc.zookeeper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
* 基于網卡獲取IP地址
* Created by chenshunyang on 2016/10/21.
*/
public class ThriftServerIpLocalNetworkResolve implements ThriftServerIpResolve{
private Logger logger = LoggerFactory.getLogger(getClass());
//緩存
private String serverIp;
@Override
public String getServerIp() throws Exception {
if (serverIp != null){
return serverIp;
}
// 一個主機有多個網絡接口
try {
Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
while (netInterfaces.hasMoreElements()) {
NetworkInterface netInterface = netInterfaces.nextElement();
// 每個網絡接口,都會有多個"網絡地址",比如一定會有lookback地址,會有siteLocal地址等.以及IPV4或者IPV6 .
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
if(address instanceof Inet6Address){
continue;
}
if (address.isSiteLocalAddress() && !address.isLoopbackAddress()) {
serverIp = address.getHostAddress();
logger.info("resolve server ip :"+ serverIp);
continue;
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
return serverIp;
}
@Override
public void reset() {
serverIp = null;
}
}
接下來我們定義發布服務接口,并實現將服務信息(服務接口、版本號,IP、port、weight)發布到zookeeper中。
package com.yangyang.thrift.rpc.zookeeper;
import java.net.InetSocketAddress;
import java.util.List;
/**
* 獲取服務地址接口
* Created by chenshunyang on 2016/10/21.
*/
public interface ThriftServerAddressProvider {
//獲取服務名稱
String getService();
/**
* 獲取所有服務端地址
* @return
*/
List<InetSocketAddress> findServerAddressList();
/**
* 選取一個合適的address,可以隨機獲取等'
* 內部可以使用合適的算法.
* @return
*/
InetSocketAddress selector();
void close();
}
實現:ThriftServerAddressRegisterZookeeper.java
package com.yangyang.thrift.rpc.zookeeper;
import com.yangyang.thrift.rpc.ThriftException;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.zookeeper.CreateMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 注冊服務列表到Zookeeper
* Created by chenshunyang on 2016/10/21.
*/
public class ThriftServerAddressRegisterZookeeper implements ThriftServerAddressRegister{
private Logger logger = LoggerFactory.getLogger(getClass());
private CuratorFramework zkClient;
public ThriftServerAddressRegisterZookeeper(){}
public ThriftServerAddressRegisterZookeeper(CuratorFramework zkClient){
this.zkClient = zkClient;
}
public void setZkClient(CuratorFramework zkClient) {
this.zkClient = zkClient;
}
@Override
public void register(String service, String version, String address) {
if(zkClient.getState() == CuratorFrameworkState.LATENT){
zkClient.start();
}
if (version == null || version == ""){
version ="1.0.0";
}
//創建臨時節點
try {
zkClient.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL)
.forPath("/"+service+"/"+version+"/"+address);
} catch (Exception e) {
logger.error("register service address to zookeeper exception:{}",e);
throw new ThriftException("register service address to zookeeper exception:{}", e);
}
}
public void close(){
zkClient.close();
}
}
3).客戶端發現服務
定義獲取服務地址接口
package com.yangyang.thrift.rpc.zookeeper;
/**
* 發布服務地址及端口到服務注冊中心,這里是zookeeper服務器
* Created by chenshunyang on 2016/10/21.
*/
public interface ThriftServerAddressRegister {
/**
* 發布服務接口
* @param service 服務接口名稱,一個產品中不能重復
* @param version 服務接口的版本號,默認1.0.0
* @param address 服務發布的地址和端口
*/
void register(String service, String version, String address);
}
基于zookeeper服務地址自動發現實現:ThriftServerAddressProviderZookeeper.java
package com.yangyang.thrift.rpc.zookeeper;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import java.io.Closeable;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.CountDownLatch;
/**
* @author chenshunyang
* 使用zookeeper作為"config"中心,使用apache-curator方法庫來簡化zookeeper開發
*/
public class ThriftServerAddressProviderZookeeper implements ThriftServerAddressProvider, InitializingBean ,Closeable{
private Logger logger = LoggerFactory.getLogger(getClass());
// 注冊服務
private String service;
// 服務版本號
private String version = "1.0.0";
//zk客戶端
private CuratorFramework zkClient;
private CountDownLatch countDownLatch= new CountDownLatch(1);//避免 zk還沒有連接上,就去調用服務
private PathChildrenCache cachedPath;
// 用來保存當前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_WEIGHT = 1;
public void setService(String service) {
this.service = service;
}
public void setVersion(String version) {
this.version = version;
}
public void setZkClient(CuratorFramework zkClient) {
this.zkClient = zkClient;
}
public ThriftServerAddressProviderZookeeper() {
}
public ThriftServerAddressProviderZookeeper(CuratorFramework zkClient) {
this.zkClient = zkClient;
}
@Override
public void afterPropertiesSet() throws Exception {
// 如果zk尚未啟動,則啟動
if (zkClient.getState() == CuratorFrameworkState.LATENT) {
zkClient.start();
}
buildPathChildrenCache(zkClient, getServicePath(), true);
cachedPath.start(StartMode.POST_INITIALIZED_EVENT);
countDownLatch.await();
}
private String getServicePath(){
return "/" + service + "/" + version;
}
private void buildPathChildrenCache(final 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:
logger.info("Connection is reconection.");
break;
case CONNECTION_SUSPENDED:
logger.info("Connection is suspended.");
break;
case CONNECTION_LOST:
logger.warn("Connection error,waiting...");
return;
case INITIALIZED:
// countDownLatch.countDown();
logger.warn("Connection init ...");
default:
//
}
// 任何節點的時機數據變動,都會rebuild,此處為一個"簡單的"做法.
cachedPath.rebuild();
rebuild();
countDownLatch.countDown();
}
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();
logger.error("thrift server-cluster error....");
return;
}
List<InetSocketAddress> current = new ArrayList<InetSocketAddress>();
String path = null;
for (ChildData data : children) {
path = data.getPath();
logger.debug("get path:"+path);
path = path.substring(getServicePath().length()+1);
logger.debug("get serviceAddress:"+path);
String address = new String(path.getBytes(), "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 weight = DEFAULT_WEIGHT;
if (hostname.length == 3) {
weight = Integer.valueOf(hostname[2]);
}
String ip = hostname[0];
Integer port = Integer.valueOf(hostname[1]);
List<InetSocketAddress> result = new ArrayList<InetSocketAddress>();
// 根據優先級,將ip:port添加多次到地址集中,然后隨機取地址實現負載
for (int i = 0; i < weight; i++) {
result.add(new InetSocketAddress(ip, port));
}
return result;
}
@Override
public List<InetSocketAddress> findServerAddressList() {
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();
}
@Override
public void close() {
try {
cachedPath.close();
zkClient.close();
} catch (Exception e) {
}
}
@Override
public String getService() {
return service;
}
}
對此接口還做了一種實現,通過配置獲取服務地址,參考附件:FixedAddressProvider.java
4.3 服務端服務注冊實現
package com.yangyang.thrift.rpc;
import com.yangyang.thrift.rpc.zookeeper.ThriftServerAddressRegister;
import com.yangyang.thrift.rpc.zookeeper.ThriftServerIpLocalNetworkResolve;
import com.yangyang.thrift.rpc.zookeeper.ThriftServerIpResolve;
import org.apache.thrift.TProcessor;
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadedSelectorServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.springframework.beans.factory.InitializingBean;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.reflect.Constructor;
/**
* 服務端注冊服務工廠
* Created by chenshunyang on 2016/10/21.
*/
public class ThriftServiceServerFactory implements InitializingBean{
// 服務注冊本機端口
private Integer port = 8299;
// 優先級
private Integer weight = 1;// default
// 服務實現類
private Object service;// serice實現類
//服務版本號
private String version;
//服務注冊
private ThriftServerAddressRegister thriftServerAddressRegister;
// 解析本機IP
private ThriftServerIpResolve thriftServerIpResolve;
private ServerThread serverThread;
public void setPort(Integer port) {
this.port = port;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
public void setService(Object service) {
this.service = service;
}
public void setVersion(String version) {
this.version = version;
}
public void setThriftServerAddressRegister(ThriftServerAddressRegister thriftServerAddressRegister) {
this.thriftServerAddressRegister = thriftServerAddressRegister;
}
@Override
public void afterPropertiesSet() throws Exception {
if (thriftServerIpResolve == null){
thriftServerIpResolve = new ThriftServerIpLocalNetworkResolve();
}
String serverIP = thriftServerIpResolve.getServerIp();
if (serverIP == null || serverIP.equals("")){
throw new ThriftException("cant find server ip...");
}
String hostname = serverIP + ":" + port + ":" + weight;
Class<?> serviceClass = service.getClass();
// 獲取實現類接口
Class<?>[] interfaces = serviceClass.getInterfaces();
if (interfaces.length == 0) {
throw new IllegalClassFormatException("service-class should implements Iface");
}
// reflect,load "Processor";
TProcessor processor = null;
String serviceName = null;
for (Class<?> clazz : interfaces) {
String cname = clazz.getSimpleName();
if (!cname.equals("Iface")) {
continue;
}
serviceName = clazz.getEnclosingClass().getName();
String pname = serviceName + "$Processor";
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> pclass = classLoader.loadClass(pname);
if (!TProcessor.class.isAssignableFrom(pclass)) {
continue;
}
Constructor<?> constructor = pclass.getConstructor(clazz);
processor = (TProcessor) constructor.newInstance(service);
break;
} catch (Exception e) {
//
}
}
if (processor == null) {
throw new IllegalClassFormatException("service-class should implements Iface");
}
//需要單獨的線程,因為serve方法是阻塞的.
serverThread = new ServerThread(processor, port);
serverThread.start();
// 注冊服務
if (thriftServerAddressRegister != null) {
thriftServerAddressRegister.register(serviceName, version, hostname);
}
}
class ServerThread extends Thread {
private TServer server;
ServerThread(TProcessor processor, int port) throws Exception {
TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(port);
TThreadedSelectorServer.Args tArgs = new TThreadedSelectorServer.Args(serverTransport);
TProcessorFactory processorFactory = new TProcessorFactory(processor);
tArgs.processorFactory(processorFactory);
tArgs.transportFactory(new TFramedTransport.Factory());
tArgs.protocolFactory( new TBinaryProtocol.Factory(true, true));
server = new TThreadedSelectorServer(tArgs);
}
@Override
public void run(){
try{
//啟動服務
server.serve();
}catch(Exception e){
//
}
}
public void stopServer(){
server.stop();
}
}
public void close() {
serverThread.stopServer();
}
}
4.4 客戶端獲取服務代理及連接池實現
客戶端連接池實現:ThriftClientPoolFactory.java
package com.yangyang.thrift.rpc;
import com.yangyang.thrift.rpc.zookeeper.ThriftServerAddressProvider;
import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.thrift.TServiceClient;
import org.apache.thrift.TServiceClientFactory;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
/**
* 連接池,thrift-client for spring
*/
public class ThriftClientPoolFactory extends BasePoolableObjectFactory<TServiceClient> {
private Logger logger = LoggerFactory.getLogger(getClass());
private final ThriftServerAddressProvider serverAddressProvider;
private final TServiceClientFactory<TServiceClient> clientFactory;
private PoolOperationCallBack callback;
protected ThriftClientPoolFactory(ThriftServerAddressProvider addressProvider, TServiceClientFactory<TServiceClient> clientFactory) throws Exception {
this.serverAddressProvider = addressProvider;
this.clientFactory = clientFactory;
}
protected ThriftClientPoolFactory(ThriftServerAddressProvider addressProvider, TServiceClientFactory<TServiceClient> clientFactory,
PoolOperationCallBack callback) throws Exception {
this.serverAddressProvider = addressProvider;
this.clientFactory = clientFactory;
this.callback = callback;
}
static interface PoolOperationCallBack {
// 銷毀client之前執行
void destroy(TServiceClient client);
// 創建成功是執行
void make(TServiceClient client);
}
@Override
public void destroyObject(TServiceClient client) throws Exception {
if (callback != null) {
try {
callback.destroy(client);
} catch (Exception e) {
logger.warn("destroyObject:{}", e);
}
}
logger.info("destroyObject:{}", client);
TTransport pin = client.getInputProtocol().getTransport();
pin.close();
TTransport pout = client.getOutputProtocol().getTransport();
pout.close();
}
@Override
public void activateObject(TServiceClient client) throws Exception {
}
@Override
public void passivateObject(TServiceClient client) throws Exception {
}
@Override
public boolean validateObject(TServiceClient client) {
TTransport pin = client.getInputProtocol().getTransport();
logger.info("validateObject input:{}", pin.isOpen());
TTransport pout = client.getOutputProtocol().getTransport();
logger.info("validateObject output:{}", pout.isOpen());
return pin.isOpen() && pout.isOpen();
}
@Override
public TServiceClient makeObject() throws Exception {
InetSocketAddress address = serverAddressProvider.selector();
if(address==null){
new ThriftException("No provider available for remote service");
}
TSocket tsocket = new TSocket(address.getHostName(), address.getPort());
TTransport transport = new TFramedTransport(tsocket);
TProtocol protocol = new TBinaryProtocol(transport);
TServiceClient client = this.clientFactory.getClient(protocol);
transport.open();
if (callback != null) {
try {
callback.make(client);
} catch (Exception e) {
logger.warn("makeObject:{}", e);
}
}
return client;
}
}
客戶端服務代理工廠實現:ThriftServiceClientProxyFactory.java
package com.yangyang.thrift.rpc;
import com.yangyang.thrift.rpc.zookeeper.ThriftServerAddressProvider;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.thrift.TServiceClient;
import org.apache.thrift.TServiceClientFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import java.io.Closeable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* 客戶端代理工廠
*/
public class ThriftServiceClientProxyFactory implements FactoryBean, InitializingBean,Closeable {
private Logger logger = LoggerFactory.getLogger(getClass());
private Integer maxActive = 32;// 最大活躍連接數
private Integer idleTime = 180000;// ms,default 3 min,鏈接空閑時間, -1,關閉空閑檢測
private ThriftServerAddressProvider serverAddressProvider;
private Object proxyClient;
private Class<?> objectClass;
private GenericObjectPool<TServiceClient> pool;
private ThriftClientPoolFactory.PoolOperationCallBack callback = new ThriftClientPoolFactory.PoolOperationCallBack() {
@Override
public void make(TServiceClient client) {
logger.info("create");
}
@Override
public void destroy(TServiceClient client) {
logger.info("destroy");
}
};
public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
}
public void setIdleTime(Integer idleTime) {
this.idleTime = idleTime;
}
public void setServerAddressProvider(ThriftServerAddressProvider serverAddressProvider) {
this.serverAddressProvider = serverAddressProvider;
}
@Override
public void afterPropertiesSet() throws Exception {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 加載Iface接口
objectClass = classLoader.loadClass(serverAddressProvider.getService() + "$Iface");
// 加載Client.Factory類
Class<TServiceClientFactory<TServiceClient>> fi = (Class<TServiceClientFactory<TServiceClient>>) classLoader.loadClass(serverAddressProvider.getService() + "$Client$Factory");
TServiceClientFactory<TServiceClient> clientFactory = fi.newInstance();
ThriftClientPoolFactory clientPool = new ThriftClientPoolFactory(serverAddressProvider, clientFactory, callback);
pool = new GenericObjectPool<TServiceClient>(clientPool, makePoolConfig());
// InvocationHandler handler = makeProxyHandler();//方式1
InvocationHandler handler = makeProxyHandler2();//方式2
proxyClient = Proxy.newProxyInstance(classLoader, new Class[] { objectClass }, handler);
}
private InvocationHandler makeProxyHandler() throws Exception{
ThriftServiceClient2Proxy handler = null;
TServiceClient client = pool.borrowObject();
try {
handler = new ThriftServiceClient2Proxy(client);
pool.returnObject(client);
}catch (Exception e){
pool.invalidateObject(client);
throw new ThriftException("獲取代理對象失敗");
}
return handler;
}
private InvocationHandler makeProxyHandler2() throws Exception{
ThriftServiceClientProxy handler = new ThriftServiceClientProxy(pool);
return handler;
}
private GenericObjectPool.Config makePoolConfig() {
GenericObjectPool.Config poolConfig = new GenericObjectPool.Config();
poolConfig.maxActive = maxActive;
poolConfig.maxIdle = 1;
poolConfig.minIdle = 0;
poolConfig.minEvictableIdleTimeMillis = idleTime;
poolConfig.timeBetweenEvictionRunsMillis = idleTime * 2L;
poolConfig.testOnBorrow=true;
poolConfig.testOnReturn=false;
poolConfig.testWhileIdle=false;
return poolConfig;
}
@Override
public Object getObject() throws Exception {
return proxyClient;
}
@Override
public Class<?> getObjectType() {
return objectClass;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void close() {
if(pool!=null){
try {
pool.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (serverAddressProvider != null) {
serverAddressProvider.close();
}
}
}
下面我們看一下服務端和客戶端的配置;
服務端spring-context-thrift-server.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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
default-lazy-init="false">
<!-- zookeeper -->
<bean id="thriftZookeeper" class="com.yangyang.thrift.rpc.zookeeper.ZookeeperFactory"
destroy-method="close">
<property name="zkHosts" value="127.0.0.1:2181" />
<property name="namespace" value="com.yangyang.thrift.rpc.demo" />
<property name="connectionTimeout" value="3000" />
<property name="sessionTimeout" value="3000" />
<property name="singleton" value="true" />
</bean>
<bean id="sericeAddressRegister" class="com.yangyang.thrift.rpc.zookeeper.ThriftServerAddressRegisterZookeeper"
destroy-method="close">
<property name="zkClient" ref="thriftZookeeper" />
</bean>
<bean id="echoSerivceImpl" class="com.yangyang.thrift.rpc.demo.EchoSerivceImpl" />
<bean id="echoSerivce" class="com.yangyang.thrift.rpc.ThriftServiceServerFactory" destroy-method="close">
<property name="service" ref="echoSerivceImpl" />
<property name="port" value="9000" />
<property name="version" value="1.0.0" />
<property name="weight" value="1" />
<property name="thriftServerAddressRegister" ref="sericeAddressRegister" />
</bean>
<bean id="echoSerivce1" class="com.yangyang.thrift.rpc.ThriftServiceServerFactory" destroy-method="close">
<property name="service" ref="echoSerivceImpl" />
<property name="port" value="9001" />
<property name="version" value="1.0.0" />
<property name="weight" value="1" />
<property name="thriftServerAddressRegister" ref="sericeAddressRegister" />
</bean>
<bean id="echoSerivce2" class="com.yangyang.thrift.rpc.ThriftServiceServerFactory" destroy-method="close">
<property name="service" ref="echoSerivceImpl" />
<property name="port" value="9002" />
<property name="version" value="1.0.0" />
<property name="weight" value="1" />
<property name="thriftServerAddressRegister" ref="sericeAddressRegister" />
</bean>
</beans>
客戶端:spring-context-thrift-client.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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"
default-lazy-init="false">
<!-- fixedAddress -->
<!--
<bean id="fixedAddressProvider" class="com.yangyang.thrift.rpc.zookeeper.FixedAddressProvider">
<property name="service" value="com.yangyang.thrift.rpc.demo.EchoSerivce" />
<property name="serverAddress" value="192.168.36.215:9001:1,192.168.36.215:9002:2,192.168.36.215:9003:3" />
</bean>
<bean id="echoSerivce" class="com.yangyang.thrift.rpc.ThriftServiceClientProxyFactory">
<property name="maxActive" value="5" />
<property name="idleTime" value="10000" />
<property name="serverAddressProvider" ref="fixedAddressProvider" />
</bean>
-->
<!-- zookeeper -->
<bean id="thriftZookeeper" class="com.yangyang.thrift.rpc.zookeeper.ZookeeperFactory"
destroy-method="close">
<property name="zkHosts" value="127.0.0.1:2181" />
<property name="namespace" value="com.yangyang.thrift.rpc.demo" />
<property name="connectionTimeout" value="3000" />
<property name="sessionTimeout" value="3000" />
<property name="singleton" value="true" />
</bean>
<bean id="echoSerivce" class="com.yangyang.thrift.rpc.ThriftServiceClientProxyFactory" destroy-method="close">
<property name="maxActive" value="5" />
<property name="idleTime" value="1800000" />
<property name="serverAddressProvider">
<bean class="com.yangyang.thrift.rpc.zookeeper.ThriftServerAddressProviderZookeeper">
<property name="service" value="com.yangyang.thrift.rpc.demo.EchoSerivce" />
<property name="version" value="1.0.0" />
<property name="zkClient" ref="thriftZookeeper" />
</bean>
</property>
</bean>
</beans>
4.5服務端啟動類
package com.yangyang.thrift.rpc.demo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
//服務端啟動
public class Server {
public static void main(String[] args) {
try {
new ClassPathXmlApplicationContext("classpath:spring-context-thrift-server.xml");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("服務啟動成功");
}
}
4.6 客戶端啟動類
package com.yangyang.thrift.rpc.demo;
import com.yangyang.thrift.rpc.ThriftServiceClientProxyFactory;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Map;
import java.util.Map.Entry;
//客戶端調用
@SuppressWarnings("resource")
public class Client {
public static void main(String[] args) {
//simple();
spring();
}
public static void spring() {
try {
final ApplicationContext context = new ClassPathXmlApplicationContext("spring-context-thrift-client.xml");
EchoSerivce.Iface echoSerivce = (EchoSerivce.Iface) context.getBean("echoSerivce");
System.out.println(echoSerivce.echo("hello--echo"));
//關閉連接的鉤子
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
Map<String,ThriftServiceClientProxyFactory>
clientMap = context.getBeansOfType(ThriftServiceClientProxyFactory.class);
for(Entry<String, ThriftServiceClientProxyFactory> client : clientMap.entrySet()){
System.out.println("serviceName : "+client.getKey() + ",class obj: "+client.getValue());
client.getValue().close();
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
運行服務端后,我們可以看見zookeeper注冊了多個服務地址。
詳細demo地址:
碼云:http://git.oschina.net/shunyang/thrift-all/tree/master/thrift-zookeeper-rpc
github:https://github.com/shunyang/thrift-all/tree/master/thrift-zookeeper-rpc
本文參考鏈接:
http://blog.csdn.net/zhu_tianwei/article/details/44115667
歡迎大家掃碼關注我的微信公眾號,與大家一起分享技術與成長中的故事。