Thrift 个人实战--Thrift 服务化 Client的改造

前言:
  Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还是有一定距离, 本系列将对Thrift作代码解读和框架扩充, 使得它更加贴近生产环境. 本文主要讲解thrift的服务化改造, 这边侧重于阐述对client(服务调用方)的改造和设计思想.

基础概念:
  传统对client的优化, 主要是Client Manager化, 优化方式包括引入连接池, 支持Failover/LoadBalance机制. 这部分内容可以参考flume sdk文章, 里面对client的优化, 细心到了极致.
  PRC服务化, 对于client(服务调用方)而言, 应该隐藏client和server端的交互细节(包括failover/loadbalance), 唯一需要暴露/使用的是服务方提供的接口. 简而言之, 通过service接口进行rpc服务, 而不是采用client的api去访问.
  用thrift api作为例子

// *) Client API 调用
(EchoService.Client)client.echo("hello lilei");	 ---(1)
// *) Service 接口 调用
(EchoService.Iface)service.echo("hello lilei");	 ---(2)

  评注: (1) Client API的方式, 不推荐, (2) Service接口的方式(服务化), 推荐

面向接口编程:
  先来看下thrift生成的类有那些

namespace java mmxf.thrift

service EchoSerivce {
  string echo(1: string msg);
}

  其生成的类有如下所示:

// *) Thrift生成的EchoService代码, 省略了函数和具体实现
public class EchoSerivce {
  // *) 接口类Iface, 同步接口
  public interface Iface {}
  // *) 接口类AsyncIface, 异步接口
  public interface AsyncIface {}

  // *) 具体类, 同步Client
  public static class Client {}
  // *) 具体类, 异步Client
  public static class AsyncClient {}
}

  评注: EchoService.Iface就是同步EchoSerivce的接口定义, 而EchoService.Client则是与服务端交互的具体客户端实例.
  面向接口编程, 采用装饰者模式(Decorator Pattern, 接口+组合), 借助实现EchoService.Iface接口, 握有EchoService.Client实例的方式去实现. 这样能达到服务化的初步雏形, 但这远远不够.

服务化的基本特征:
  RPC Client服务化的基本特征(个人观点), 可以分为如下:
  1). 泛型化, 作为一个服务框架存在, 不而是只用于具体模块
  2). 内部封装的client需要实现client-manager化, 即支持连接池/failover/loadbalance
  3). 通过订阅服务的方式, 透明的调用服务提供方(不需要知道服务提供方的server ip:port 列表)
  本文主要阐述思路, 服务订阅放在后续的文章, 弱化Client-Manager, 但支持泛型化来实现一个简单的client service解决方案.

解决方案:
  对泛型Thrift Service的支持, 采用JDK自带的动态代理来实现.

public interface InvocationHandler {
  public Object invoke(Object proxy, Method method, Object[] args);
}

  评注: Object proxy: 指被代理的对象, Method: 要调用的方法, Object[] args: 方法调用时所需要的参数

public class DynamicClientProxy<T> implements InvocationHandler {

  // *) 引入类Class, 以及RpcServer配置
  public Object createProxy(Class<T> ts, RpcServerConfiguration configuration) {
  // *) 静态类Proxy生成动态代理实例
    return Proxy.newProxyInstance(ts.getClassLoader(), ts.getInterfaces(), this);
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
    // *) 外循环是简单的failover机制, 用于失败时重试
    for ( ServerNode serverNode : serverNodes ) {
      // *) 创建TSocket对象
      TSocket tsocket = new TSocket(ip, port);
      tsocket.setTimeout(timeout);

      // *) 二进制协议
      TProtocol protocol = new TBinaryProtocol(tsocket);

      // *) 借助反射(构造函数)来产生实例对象
      Class[] argsClass = new Class[] {
            TProtocol.class
        };
      Constructor<T> cons = (Constructor<T>) ts.getConstructor(argsClass);
      T client = (T)cons.newInstance(protocol);

      tsocket.open();
      // *) 反射调用, 这个最重要
      return method.invoke(client, args);
    }

  }

}

  评注: 上述代码中省略了不少, 大致是如上代码所述的思路, 具体的代码详见附件.

  创建定义DynamicClientProxy<T> 泛型类, 用于动态代理对象的创建.
  调用代码如下所示:

RpcServerConfiguration configuration = new RpcServerConfiguration();
configuration.getServerNodes().add(new ServerNode("127.0.0.1", 9010));

DynamicClientProxy<EchoService.Client> proxy =
		new DynamicClientProxy<EchoService.Client>();
EchoService.Iface service = (EchoService.Iface)
		proxy.createProxy(EchoService.Client.class, configuration);
service.echo("hello dynamic");

  评注: 是不是简洁了不少, 对泛型的支持也比较优雅.

继续改进: 

  上述的泛型代码虽然灵活了不少, 但需要硬编码, 是否可以借助spring来实现配置优化呢?
  首先我们引入DynamicClientProxyFactory类, 该类用于产生具体的代理类

public class DynamicClientProxyFactory {

  public static Object createIface(String clazzIfaceName, List<String> servers) {
    // *) 内部类的表, 不用‘.‘, 而使用‘$‘分割
    int idx = clazzIfaceName.lastIndexOf(‘$‘);
    // *) 创建内部类Iface
    String clazzClientName = clazzIfaceName.substring(0, idx) + "$Client";
    Class clientClazz = Class.forName(clazzClientName);

    // *) 创建代理对象
    DynamicClientProxy proxy = new DynamicClientProxy();
    return proxy.createProxy(clientClazz, configuration);
  }

}

  同时spring中, 我们采用如下的方式来配置service接口的bean, 这边采用了Bean Factory的方式创建实例对象

<bean id="echoService" class="com.lighting.rpc.core.client.DynamicClientProxyFactory" factory-method="createIface">
  <constructor-arg>
    <value>mmxf.thrift.EchoService$Iface</value>
  </constructor-arg>
  <constructor-arg>
    <list>
      <value>127.0.0.1:9000</value>
      <value>127.0.0.1:9001</value>
    </list>
  </constructor-arg>
</bean>

  评注: 具体的参数是服务类的接口, 以及server ip:port列表. 同时采用@Resource的方式来注册这个service bean即可.

服务体验:

编写测试用例

ClassPathXmlApplicationContext applicationContext =
	new ClassPathXmlApplicationContext("application_context.xml");

EchoService.Iface service = (EchoService.Iface)
	applicationContext.getBean("echoService");

System.out.println(service.echo("lilei"));

评注: 是不是很简单明了.

总结: 
  RPC服务化方便编程, 也隐藏了服务端/客户端的交互细节. 另一个好处是方便测试, 使用stub, 模拟各种异常和交互. 当然使用Client也可以, 不过这需要借助bug级神奇Mockito. 总得来说RPC服务话, 对rpc服务调用方而言, 大大降低了开发门槛和难度.

当前的不足:
  1). 没有实现对象池, 若实现了ThriftClientObjectPool, 代码的整体架构会显得更加简单.
  2). 没有使用订阅服务列表, 使得在配置中, 需要指定ip:port列表.
后续:
  后续会编写发布/订阅服务列表的实现方案, 这部分需要和服务端编写一起讲述, 并实现ThriftClient的对象池. 敬请期待.

代码下载链接: http://download.csdn.net/download/mmstar/7699445

Thrift 个人实战--Thrift 服务化 Client的改造

时间: 2024-11-06 09:36:21

Thrift 个人实战--Thrift 服务化 Client的改造的相关文章

Thrift 个人实战--Thrift 网络服务模型(转)

前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还是有一定距离, 本系列将对Thrift作代码解读和框架扩充, 使得它更加贴近生产环境. 本文主要讲解Thrift的高性能网络框架模型, 讲解各种网络模型的特点和区别. Thrift 高性能网络服务模型1). TServer类层次体系TSimpleServer/TThreadPoolServer是阻塞

Thrift 个人实战--Thrift 网络服务模型

前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还是有一定距离, 本系列将对Thrift作代码解读和框架扩充, 使得它更加贴近生产环境. 本文主要讲解Thrift的高性能网络框架模型, 讲解各种网络模型的特点和区别. Thrift 高性能网络服务模型1). TServer类层次体系TSimpleServer/TThreadPoolServer是阻塞

Thrift 个人实战--Thrift RPC服务框架日志的优化

前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还是有一定距离, 本系列将对Thrift作代码解读和框架扩充, 使得它更加贴近生产环境. 本文讲述RPC服务框架中, 日志的重要性, 以及logid的引入. 日志不仅包含丰富的数据(就看是否会挖掘), 而且还是线上服务问题追踪和排查错误最好的方式. 日志级别 采用大家喜闻乐见的log4j作为该RPC服

Thrift 个人实战--初次体验Thrift

前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还是有一定距离, 本系列将对Thrift作代码解读和框架扩充, 使得它更加贴近生产环境. 本文主要讲解Thrift的初体验, 使得开发者对thrift有个初步的认识. Thrift 软件栈 Thrift对软件栈的定义非常的清晰, 使得各个组件能够松散的耦合, 针对不同的应用场景, 选择不同是方式去搭建

Thrift 个人实战--RPC服务的发布订阅实现(基于Zookeeper服务)

前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还是有一定距离, 本系列将对Thrift作代码解读和框架扩充, 使得它更加贴近生产环境. 本文讲述如何借用zookeeper来实现中介角色, 使得服务端和客户端解耦, 并让RPC服务平台化发展. 基础架构: RPC服务往平台化的方向发展, 会屏蔽掉更多的服务细节(服务的IP地址集群, 集群的扩容和迁移

Thrift 个人实战--初次体验Thrift(转)

前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还是有一定距离, 本系列将对Thrift作代码解读和框架扩充, 使得它更加贴近生产环境. 本文主要讲解Thrift的初体验, 使得开发者对thrift有个初步的认识. Thrift 软件栈 Thrift对软件栈的定义非常的清晰, 使得各个组件能够松散的耦合, 针对不同的应用场景, 选择不同是方式去搭建

Thrift RPC实战(三) thrift序列化揭秘

本文主要讲解Thrift的序列化机制, 看看thrift作为数据交换格式是如何工作的? 1.构造应用场景: 1). 首先我们先来定义下thrift的简单结构. 1 2 3 4 5 namespace java com.yangyang.thrift.api struct Pair { ? ? 1: required string key ? ? 2: required string value } required修饰符你肯定能猜测到它的意义, 但是你是否有没有这样的疑惑, "1",

Thrift Java实战

官网示例: http://thrift.apache.org/tutorial/java 软件下载: http://thrift.apache.org/download 学习教程: http://jnb.ociweb.com/jnb/jnbJun2009.html Thrift与其他传输方式的比较 xml与JSON相比体积太大,但是xml传统,也不算复杂. json体积较小,新颖,但不够完善. thrift体积超小,使用起来比较麻烦,不如前两者轻便,但是对于1.高并发.2.数据传输量大.3.多语

创建Thrift Server和Thrift Client

1.创建Server package cn.horace.thrift.server; import cn.horace.thrift.idl.IUserService; import cn.horace.thrift.rpc.IUserServiceImpl; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocolFactory; import org.apac