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作为例子
1 2 3 4 | // *) Client API 调用 (EchoService.Client)client.echo( "hello lilei" ); ---( 1 ) // *) Service 接口 调用 (EchoService.Iface)service.echo( "hello lilei" ); ---( 2 ) |
评注: (1) Client API的方式, 不推荐, (2) Service接口的方式(服务化), 推荐
面向接口编程:
先来看下thrift生成的类有那些
1 2 3 4 5 | namespace java mmxf.thrift service EchoSerivce { string echo( 1 : string msg); } |
其生成的类有如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 | // *) 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自带的动态代理来实现.
1 2 3 | public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args); } |
评注: Object proxy: 指被代理的对象, Method: 要调用的方法, Object[] args: 方法调用时所需要的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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> 泛型类, 用于动态代理对象的创建.
调用代码如下所示:
1 2 3 4 5 6 7 8 | 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类, 该类用于产生具体的代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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的方式创建实例对象
1 2 3 4 5 6 7 8 9 10 11 | <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即可.
服务体验:
编写测试用例
1 2 3 4 5 6 7 | 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
posted on 2014-07-31 15:03 mumuxinfei 阅读(7909) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构