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的改造的更多相关文章
- Thrift 个人实战--Thrift 的序列化机制
前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...
- Thrift 个人实战--Thrift 网络服务模型
前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...
- Thrift 个人实战--Thrift 网络服务模型(转)
前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...
- Thrift 个人实战--Thrift RPC服务框架日志的优化
前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...
- Thrift RPC实战(一).初次体验Thrift
1.前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码,主要特点: 开发速度快: 通过编写RPC接口ID ...
- Thrift RPC实战(三) thrift序列化揭秘
本文主要讲解Thrift的序列化机制, 看看thrift作为数据交换格式是如何工作的? 1.构造应用场景: 1). 首先我们先来定义下thrift的简单结构. 1 2 3 4 5 namespace ...
- Thrift入门初探--thrift安装及java入门实例
什么是thrift? 简单来说,是Facebook公布的一款开源跨语言的RPC框架. 那么问题来了. 什么是RPC框架? RPC全称为Remote Procedure Call,意为远程过程调用. 假 ...
- 【thrift】thrift入门初探--thrift安装及java入门实例
转载:https://www.cnblogs.com/fingerboy/p/6424248.html 公司的一些平台服务框架底层封装了thrift提供服务,最近项目不是很紧,于是研究了一下,刚刚入门 ...
- 【Thrift一】Thrift安装部署
Thrift安装部署 Thrift安装部署 下载源码包 安装g++ 解压Thrift安装包 安装boost开发工具 测试(python版) 下载源码包 wget http://apache.fayea ...
随机推荐
- 【leetcode❤python】 28. Implement strStr()
#-*- coding: UTF-8 -*- #题意:大海捞刀,在长字符串中找出短字符串#AC源码:滑动窗口双指针的方法class Solution(object): def strStr(se ...
- Arduino学习笔记二:修改LED点灯程序
看了开源社区的LED控制程序,开始上手代码编写,修改,下载以及调试,原文地址:http://www.arduino.cn/thread-1072-1-1.html,这个帖子写的比较通俗易懂. 自己移植 ...
- Python学习(14)模块二
一:_name_ Python中if _name_ == '_main_'的解析 当你打开一个.py文件时,经常会在代码的最下面看到if __name__ == '__main__':,现在就来介 绍 ...
- 用spring的InitializingBean作初始化
org.springframework.beans.factory包下有一个接口是InitializingBean 只有一个方法: /** * Invoked by a BeanFactory af ...
- linux共享库
linux共享库 linux中共享库一般以.so.x.y.z 命名,其中x,y,z分别为主版本号.次版本号.发布版本号.同一个库,主版本号不同则相互不兼容:主版本相同,次版本号高的库比次版本号低的库有 ...
- 笨办法学 Python (Learn Python The Hard Way)
最近在看:笨办法学 Python (Learn Python The Hard Way) Contents: 译者前言 前言:笨办法更简单 习题 0: 准备工作 习题 1: 第一个程序 习题 2: 注 ...
- a标签中的点击事件
我们常用的在a标签中有点击事件:1. a href="javascript:js_method();" 这 是我们平台上常用的方法,但是这种方法在传递this等参数的时候很容易出问 ...
- PDF 补丁丁 0.4.3.1518 测试版发布:书签编辑器新增升级书签功能、优化PDF文档阅览器
新的 PDF 补丁丁测试版上线啦! 新版本增加了升级书签的功能(见工具栏的“←”按钮),可以方便地将下级书签升级为上级书签. 另外,新版本还增强了书签编辑器功能中的 PDF阅读器,从之前的单页阅读模式 ...
- 9_13学习完整修改和查询&&实体类,数据访问类
完整修改和查询:中间变量运用. 1.先查 2.执行操作 ---------------------------------------------------- namespace ADO.NET_小 ...
- iOS开发UI篇—Quartz2D(自定义UIImageView控件)
iOS开发UI篇—Quartz2D(自定义UIImageView控件) 一.实现思路 Quartz2D最大的用途在于自定义View(自定义UI控件),当系统的View不能满足我们使用需求的时候,自定义 ...