Thrift RPC实战(五)Spring集成Thrift,实现服务端和客户端代理

本文主要讲解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<I extends Iface>{}

    //异步处理器AsyncProcessor

     public static class AsyncProcessor<I extends AsyncIface>{}

    //定义了一个对象,里面封装了 对外暴露的接口方法,

     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中配置

<!-- 服务端注册 -->
<bean  class="com.yangyang.thrift.proxy.ThriftServerProxy">
    <property name="port" value="8080" />
    <property name="serviceInterface" value="com.yangyang.thrift.api.UserService" />
    <property name="serviceImplObject" ref="userServiceImpl" />
</bean>

当然userServiceImpl需要提前声明,例如:

<bean id="userServiceImpl" class="com.yangyang.thrift.service.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中配置如下:

<!-- 连接池配置 -->
<bean id="connectionProvider" class="com.yangyang.thrift.pool.ConnectionProviderImpl">
    <property name="serviceIP" value="127.0.0.1" />
    <property name="servicePort" value="8080" />
    <property name="maxActive" value="10" />
    <property name="maxIdle" value="10" />
    <property name="testOnBorrow" value="true" />
    <property name="testOnReturn" value="true" />
    <property name="testWhileIdle" value="true" />
    <property name="conTimeOut" value="2000" />
</bean>

<bean id="connectionManager" class="com.yangyang.thrift.pool.ConnectionManager">
    <property name="connectionProvider"  ref="connectionProvider"/>
</bean>

<bean id="thriftClientProxy" class="com.yangyang.thrift.proxy.ThriftClientProxy">
    <property name="connectionManager" ref="connectionManager"/>
</bean>

连接池采用commons-pool提供的连接池,在spring启动的时候,注入到bean中,

关键代码如下:

// 对象池
objectPool = new GenericObjectPool<TTransport>();
((GenericObjectPool<TTransport>) objectPool).setMaxActive(maxActive);
((GenericObjectPool<TTransport>) objectPool).setMaxIdle(maxIdle);
((GenericObjectPool<TTransport>) objectPool).setMinIdle(minIdle);
((GenericObjectPool<TTransport>) objectPool).setMaxWait(maxWait);
((GenericObjectPool<TTransport>) objectPool).setTestOnBorrow(testOnBorrow);
((GenericObjectPool<TTransport>) objectPool).setTestOnReturn(testOnReturn);
((GenericObjectPool<TTransport>) objectPool).setTestWhileIdle(testWhileIdle);
((GenericObjectPool<TTransport>) objectPool).setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_BLOCK);
// 设置factory
ThriftPoolableObjectFactory thriftPoolableObjectFactory = new ThriftPoolableObjectFactory(serviceIP, servicePort, conTimeOut);
((GenericObjectPool<TTransport>) 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

欢迎大家扫码关注我的微信公众号,与大家一起分享技术与成长中的故事。

《Thrift RPC实战(五)Spring集成Thrift,实现服务端和客户端代理》 我的微信公众号.jpg

    原文作者:shunyang
    原文地址: https://www.jianshu.com/p/397d7b278e65
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞