本文主要讲解thrift的服务化改造, 这边侧重于阐述对client(服务调用方)的改造和设计思想.
1.基础概念:
传统对client的优化, 主要是Client Manager化, 优化方式包括引入连接池, 支持Failover/LoadBalance机制. 上一篇文章中我们已经实践了thrift服务客户端对象池的技术改造
PRC服务化, 对于client(服务调用方)而言, 应该隐藏client和server端的交互细节(包括failover/loadbalance), 唯一需要暴露/使用的是服务方提供的接口. 简而言之, 通过service接口进行rpc服务, 而不是采用client的api去访问.
用thrift api作为例子
// *) Client API调用
(HelloService.Client)client.hello("csy"); ---(1)
// *) Service 接口调用
(HelloService.Iface)service.hello("csy"); ---(2)
面向接口编程:
先来看下thrift生成的类有那些
namespace java com.yangyang.thrift.api
service HelloService {
string hello(1: string name);
}
其生成的类有如下所示:
//Thrift生成的HelloService代码, 省略了函数和具体实现
public class HelloService {
//接口类Iface, 同步接口
public interface Iface{}
// 接口类AsyncIface, 异步接口
public interface AsyncIface{}
//具体类, 同步Client
public static class Client{}
//具体类, 异步Client
public static class AsyncClient{}
//同步处理器Processor
public static class Processor{}
//异步处理器AsyncProcessor
public static class AsyncProcessor{}
//定义了一个对象,里面封装了 对外暴露的接口方法,
public static class hello_args{}
//实际调用 我们的业务方法并返回结果
public static class hello_result{}
}
评注: HelloService.Iface就是同步HelloService的接口定义, 而HelloService.Client则是与服务端交互的具体客户端实例.
面向接口编程, 采用装饰者模式(Decorator Pattern, 接口+组合), 借助实现HelloService.Iface接口, 握有HelloService.Client实例的方式去实现. 这样能达到服务化的初步雏形, 但这远远不够.
2.服务化的基本特征:
RPC Client服务化的基本特征(个人观点), 可以分为如下:
1). 泛型化, 作为一个服务框架存在, 而不是只用于具体模块
2). 内部封装的client需要实现client-manager化, 即支持连接池/failover/loadbalance
3). 通过订阅服务的方式, 透明的调用服务提供方(不需要知道服务提供方的server ip:port 列表)
本文主要阐述思路, 服务订阅放在后续的文章, 弱化Client-Manager, 但支持泛型化来实现一个简单的client service解决方案.
3服务化改造解决方案:
3.1服务端改造:
对泛型Thrift Service的支持, 通过采用spring配置以及反射的方式来实现.
对于一个服务提供者来说,需要提供端口,接口以及接口实现类,因此在接口中spring中配置
当然userServiceImpl需要提前声明,例如:
接下来定义ThriftServerProxy类,定义bean中需要用到的3个属性,接下来通过反射来实现服务的启动。
TServerSocket serverTransport = new TServerSocket(getPort());
// 实现类处理类class
Class Processor = Class.forName(getServiceInterface() + "$Processor");
// 接口
Class Iface = Class.forName(getServiceInterface() + "$Iface");
// 接口构造方法类
Constructor con = Processor.getConstructor(Iface);
// 实现类处理类
TProcessor processor = (TProcessor) con.newInstance(serviceImplObject);
TBinaryProtocol.Factory protFactory = new TBinaryProtocol.Factory(true, true);
TThreadPoolServer.Args args = new TThreadPoolServer.Args(serverTransport);
args.protocolFactory(protFactory);
args.processor(processor);
TServer server = new TThreadPoolServer(args);
logger.info("Starting server on port " + getPort() + " ...");
System.out.println("Starting server on port " + getPort() + " ...");
server.serve();
编写服务端测试:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-applicationContext-server.xml");
ThriftServerProxy thriftServerProxy = (ThriftServerProxy) context.getBean(ThriftServerProxy.class);
thriftServerProxy.start();
3.2客户端改造:
对于客户端,从连接池里面获取一个可用的服务端连接,通过反射的方式获取客户端,在spring-client.xml中配置如下:
连接池采用commons-pool提供的连接池,在spring启动的时候,注入到bean中,
关键代码如下:
// 对象池
objectPool = new GenericObjectPool();
((GenericObjectPool) objectPool).setMaxActive(maxActive);
((GenericObjectPool) objectPool).setMaxIdle(maxIdle);
((GenericObjectPool) objectPool).setMinIdle(minIdle);
((GenericObjectPool) objectPool).setMaxWait(maxWait);
((GenericObjectPool) objectPool).setTestOnBorrow(testOnBorrow);
((GenericObjectPool) objectPool).setTestOnReturn(testOnReturn);
((GenericObjectPool) objectPool).setTestWhileIdle(testWhileIdle);
((GenericObjectPool) objectPool).setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_BLOCK);
// 设置factory
ThriftPoolableObjectFactory thriftPoolableObjectFactory = new ThriftPoolableObjectFactory(serviceIP, servicePort, conTimeOut);
((GenericObjectPool) objectPool).setFactory(thriftPoolableObjectFactory);
客户端的代理对象获取client代码:
public Object getClient(Class clazz) {
Object result = null;
try {
TTransport transport = connectionManager.getSocket();
TProtocol protocol = new TBinaryProtocol(transport);
Class client = Class.forName(clazz.getName() + "$Client");
Constructor con = client.getConstructor(TProtocol.class);
result = con.newInstance(protocol);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
客户端测试:
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-applicationContext-client.xml");
ThriftClientProxy thriftClientProxy = (ThriftClientProxy) context.getBean(ThriftClientProxy.class);
UserService.Iface client = (UserService.Iface)thriftClientProxy.getClient(UserService.class);
UserRequest request = new UserRequest();
request.setId("10000");
UserResponse urp = client.userInfo(request);
System.out.println(urp);
当前的不足:
没有使用订阅服务列表, 使得在配置中, 需要指定ip:port列表,后续会通过zookeeper编写发布/订阅服务列表的实现方案。
参考demo地址如下:
码云:http://git.oschina.net/shunyang/thrift-all/tree/master/thrift-spring
github:https://github.com/shunyang/thrift-all/tree/master/thrift-spring
欢迎大家扫码关注我的微信公众号,与大家一起分享技术与成长中的故事。