JAVA RPC (七) 手把手从零教你写一个生产级RPC之client请求
上节说了关于通用请求代理,实际上对spring的bean引用都是通过koalasClientProxy来实现的,那么在代理方法中才是我们实际的发送逻辑,咱们先看一下原生的thrift请求是什么样的。
public void startClient(String userName) {
TTransport transport = null;
try {
//transport = new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT);
transport = new TFramedTransport(new TSocket(SERVER_IP, SERVER_PORT, TIMEOUT));
// 协议要和服务端一致
TProtocol protocol = new TBinaryProtocol(transport);
//TProtocol protocol = new TCompactProtocol(transport);
// TProtocol protocol = new TJSONProtocol(transport);
Client client = new Client(protocol);
transport.open();
String result = client.sayHello(userName);
System.out.println("Thrify client result =: " + result);
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} finally {
if (null != transport) {
transport.close();
}
}
}
用同步调用的例子来说明,首先需要new 一个TSocket对象,这个对象其实就是thrift维护的socket对象,内部封装维护了Socket模型,相信大家对原生Socket已经很了解了,这次不在过多阐述,熟悉Socket的朋友一定知道每次需要调用socket.open的时候,需要和远程server进行三个握手来建立通信,三次通信的代价是巨大的,对于跨异地调用或者是跨机房调用都是巨大的开销,我们不可能每次通信都和远程server进行三次握手,那么我们可以将已经握手的TCP连接放入连接池中,每次从连接池中获取到socket然后在进行调用,使用完毕之后再放入连接池,原理和mysql连接池DBCP和阿里的德鲁伊等等一个道理关于连接池相关的内容大家可以参照我git代码中的AbstractBaseIcluster相关实现即可。
我们将socket对象缓存到缓存池中,每次请求都是TCP复用,这样将极大的提升请求速度,这也是作为企业级RPC不可或缺的一部分。
看一下核心实现
@Override
public Object invoke(MethodInvocation invocation) throws InvocationTargetException, IllegalAccessException { Method method = invocation.getMethod ();
String methodName = method.getName ();
Object[] args = invocation.getArguments (); Class<?>[] parameterTypes = method.getParameterTypes ();
if (method.getDeclaringClass () == Object.class) {
try {
return method.invoke ( this, args );
} catch (IllegalAccessException e) {
LOG.error ( e.getMessage (), e );
return null;
}
}
if ("toString".equals ( methodName ) && parameterTypes.length == 0) {
return this.toString ();
}
if ("hashCode".equals ( methodName ) && parameterTypes.length == 0) {
return this.hashCode ();
}
if ("equals".equals ( methodName ) && parameterTypes.length == 1) {
return this.equals ( args[0] );
} boolean serviceTop =false; Transaction transaction=null;
if(TraceThreadContext.get () ==null){
serviceTop=true;
transaction = Cat.newTransaction("Service", method.getDeclaringClass ().getName ().concat ( "." ).concat ( methodName ).concat ( ".top" )); MessageTree tree = Cat.getManager().getThreadLocalMessageTree();
String messageId = tree.getMessageId(); if (messageId == null) {
messageId = Cat.createMessageId();
tree.setMessageId(messageId);
} String childId = Cat.getProducer().createRpcServerId("default"); String root = tree.getRootMessageId(); if (root == null) {
root = messageId;
}
Cat.logEvent(CatConstants.TYPE_REMOTE_CALL, "", Event.SUCCESS, childId); KoalasTrace koalasTrace = new KoalasTrace ( );
koalasTrace.setChildId (childId );
koalasTrace.setParentId ( messageId);
koalasTrace.setRootId ( root );
TraceThreadContext.set (koalasTrace);
} else{
KoalasTrace currentKoalasTrace = TraceThreadContext.get ();
String child_Id = Cat.getProducer().createRpcServerId("default");
Cat.logEvent(CatConstants.TYPE_REMOTE_CALL, "", Event.SUCCESS, child_Id);
currentKoalasTrace.setChildId ( child_Id );
}
try {
TTransport socket = null;
int currTryTimes = 0;
while (currTryTimes++ < retryTimes) {
ServerObject serverObject = icluster.getObjectForRemote ();
if (serverObject == null) throw new IllegalStateException("no server list to use :" + koalasClientProxy.getServiceInterface ().getName ());
GenericObjectPool<TTransport> genericObjectPool = serverObject.getGenericObjectPool ();
try {
long before = System.currentTimeMillis ();
socket = genericObjectPool.borrowObject ();
long after = System.currentTimeMillis ();
LOG.debug ( "get Object from pool with {} ms", after - before );
} catch (Exception e) {
if (socket != null)
genericObjectPool.returnObject ( socket );
LOG.error ( e.getMessage (), e );
if(transaction!=null)
transaction.setStatus ( e );
throw new IllegalStateException("borrowObject error :" + koalasClientProxy.getServiceInterface ().getName ());
} Object obj = koalasClientProxy.getInterfaceClientInstance ( socket, serverObject.getRemoteServer ().getServer () ); if (obj instanceof TAsyncClient) {
((TAsyncClient) obj).setTimeout ( asyncTimeOut );
if (args.length < 1) {
genericObjectPool.returnObject ( socket );
throw new IllegalStateException ( "args number error" );
} Object argslast = args[args.length - 1];
if (!(argslast instanceof AsyncMethodCallback)) {
genericObjectPool.returnObject ( socket );
throw new IllegalStateException ( "args type error" );
} AsyncMethodCallback callback = (AsyncMethodCallback) argslast;
ReleaseResourcesKoalasAsyncCallBack releaseResourcesKoalasAsyncCallBack = new ReleaseResourcesKoalasAsyncCallBack ( callback, serverObject, socket );
args[args.length - 1] = releaseResourcesKoalasAsyncCallBack; }
try {
Object o = method.invoke ( obj, args );
if (socket instanceof TSocket) {
genericObjectPool.returnObject ( socket ); }
if(transaction!=null)
transaction.setStatus ( Transaction.SUCCESS );
return o;
} catch (Exception e) {
Throwable cause = (e.getCause () == null) ? e : e.getCause (); boolean ifreturn = false;
if (cause instanceof TApplicationException) {
if (((TApplicationException) cause).getType () == 6666) {
LOG.info ( "the server{}--serverName【{}】 thread pool is busy ,retry it!", serverObject.getRemoteServer (),koalasClientProxy.getServiceInterface ().getName () );
if (socket != null) {
genericObjectPool.returnObject ( socket );
ifreturn = true;
}
Thread.yield ();
if (retryRequest)
continue;
} if (((TApplicationException) cause).getType () == 9999) {
LOG.error ( "rsa error with service--{}--serverName【{}】", serverObject.getRemoteServer (),koalasClientProxy.getServiceInterface ().getName () );
if (socket != null) {
genericObjectPool.returnObject ( socket );
}
if(transaction!=null)
transaction.setStatus ( cause );
throw new IllegalStateException("rsa error with service" + serverObject.getRemoteServer ().toString ()+koalasClientProxy.getServiceInterface ().getName () );
} if (((TApplicationException) cause).getType () == 6699) {
LOG.error ( "this client is not rsa support,please get the privateKey and publickey with server--{}--serverName【{}】", serverObject.getRemoteServer (),koalasClientProxy.getServiceInterface ().getName () );
if (socket != null) {
genericObjectPool.returnObject ( socket );
}
if(transaction!=null)
transaction.setStatus ( cause );
throw new IllegalStateException("this client is not rsa support,please get the privateKey and publickey with server" + serverObject.getRemoteServer ().toString ()+koalasClientProxy.getServiceInterface ().getName ());
} if (((TApplicationException) cause).getType () == TApplicationException.INTERNAL_ERROR) {
LOG.error ( "this server is error please take the error log with server--{}--serverName【{}】the remote server error message data【{}】", serverObject.getRemoteServer (),koalasClientProxy.getServiceInterface ().getName (),((TApplicationException) cause).getMessage () );
if (socket != null) {
genericObjectPool.returnObject ( socket );
}
if(transaction!=null)
transaction.setStatus ( cause );
throw new IllegalStateException("this server is error please take the error log with server" + serverObject.getRemoteServer ()+koalasClientProxy.getServiceInterface ().getName ());
} if (((TApplicationException) cause).getType () == TApplicationException.MISSING_RESULT) {
if (socket != null) {
genericObjectPool.returnObject ( socket );
}
return null;
}
} if (cause instanceof RSAException) {
LOG.error ( "this client privateKey or publicKey is error,please check it! --{}--serverName【{}】", serverObject.getRemoteServer (),koalasClientProxy.getServiceInterface ().getName () );
if (socket != null) {
genericObjectPool.returnObject ( socket );
}
if(transaction!=null)
transaction.setStatus ( cause );
throw new IllegalStateException("this client privateKey or publicKey is error,please check it!" + serverObject.getRemoteServer ()+ koalasClientProxy.getServiceInterface ().getName ());
} if(cause instanceof OutMaxLengthException){
LOG.error ( (cause ).getMessage (),cause );
if (socket != null) {
genericObjectPool.returnObject ( socket );
}
if(transaction!=null)
transaction.setStatus ( cause );
throw new IllegalStateException("to big content!" + serverObject.getRemoteServer ()+ koalasClientProxy.getServiceInterface ().getName ());
} if (cause.getCause () != null && cause.getCause () instanceof ConnectException) {
LOG.error ( "the server {}--serverName【{}】 maybe is shutdown ,retry it!", serverObject.getRemoteServer (),koalasClientProxy.getServiceInterface ().getName () );
try {
if (socket != null) {
genericObjectPool.returnObject ( socket );
ifreturn = true;
} if (retryRequest)
continue;
} catch (Exception e1) {
LOG.error ( "invalidateObject error!", e1 );
}
} if (cause.getCause () != null && cause.getCause () instanceof SocketTimeoutException) {
LOG.error ( "read timeout SocketTimeoutException,retry it! {}--serverName【{}】", serverObject.getRemoteServer (),koalasClientProxy.getServiceInterface ().getName () );
if (socket != null) {
try {
genericObjectPool.invalidateObject ( socket );
ifreturn = true;
} catch (Exception e1) {
LOG.error ( "invalidateObject error ,", e );
if(transaction!=null)
transaction.setStatus ( e1 );
throw new IllegalStateException("SocketTimeout and invalidateObject error" + serverObject.getRemoteServer () + koalasClientProxy.getServiceInterface ().getName ());
}
}
if (retryRequest)
continue;
} if(cause instanceof TTransportException){
if(((TTransportException) cause).getType () == TTransportException.END_OF_FILE){
LOG.error ( "TTransportException,END_OF_FILE! {}--serverName【{}】", serverObject.getRemoteServer (),koalasClientProxy.getServiceInterface ().getName () );
if (socket != null) {
try {
genericObjectPool.invalidateObject ( socket );
} catch (Exception e1) {
LOG.error ( "invalidateObject error", e );
if(transaction!=null)
transaction.setStatus ( e1 );
throw new IllegalStateException("TTransportException and invalidateObject error" + serverObject.getRemoteServer () + koalasClientProxy.getServiceInterface ().getName ());
}
}
if(transaction!=null)
transaction.setStatus ( cause );
throw new IllegalStateException("the remote server error!" + serverObject.getRemoteServer () + koalasClientProxy.getServiceInterface ().getName ());
}
if(cause.getCause ()!=null && cause.getCause () instanceof SocketException){
if(genericObjectPool.isClosed ()){
LOG.warn ( "serverObject {} is close!,retry it",serverObject );
if (retryRequest)
continue;
}
}
} if (socket != null && !ifreturn)
genericObjectPool.returnObject ( socket );
LOG.error ( "invoke server error,server ip -【{}】,port -【{}】--serverName【{}】", serverObject.getRemoteServer ().getIp (), serverObject.getRemoteServer ().getPort (),koalasClientProxy.getServiceInterface ().getName () );
if(transaction!=null)
transaction.setStatus ( cause );
throw e;
}
}
IllegalStateException finallyException = new IllegalStateException("error!retry time out of:" + retryTimes + "!!! " + koalasClientProxy.getServiceInterface ().getName () );
if(transaction!=null)
transaction.setStatus ( finallyException );
throw finallyException;
} finally {
if(transaction!=null)
transaction.complete ();
if(serviceTop)
TraceThreadContext.remove ();
}
}
首先是线加入cat埋点,生成服务链路。这个地方先不用关注,接下来在重试循环体中来实现发送逻辑,当连接超时异常和服务端拒绝异常等异常时会进行重试
socket = genericObjectPool.borrowObject ();
获取socket连接
Object obj = koalasClientProxy.getInterfaceClientInstance ( socket, serverObject.getRemoteServer ().getServer () );
public Object getInterfaceClientInstance(TTransport socket,String server) { if (!async) {
Class<?> clazz = getSynClientClass ();
try {
if (synConstructor == null) {
synConstructor = clazz.getDeclaredConstructor ( TProtocol.class );
}
TTransport transport = new TKoalasFramedTransport ( socket, maxLength_ );
if(this.getPrivateKey ()!=null && this.getPublicKey () != null){
((TKoalasFramedTransport) transport).setRsa ( (byte) 1 );
((TKoalasFramedTransport) transport).setPrivateKey ( this.privateKey );
((TKoalasFramedTransport) transport).setPublicKey ( this.publicKey );
} TProtocol protocol = new KoalasBinaryProtocol ( transport ); return synConstructor.newInstance ( protocol ); } catch (NoSuchMethodException e) {
logger.error ( "the clazz can't find the Constructor with TProtocol.class" );
} catch (InstantiationException e) {
logger.error ( "get InstantiationException", e );
} catch (IllegalAccessException e) {
logger.error ( "get IllegalAccessException", e );
} catch (InvocationTargetException e) {
logger.error ( "get InvocationTargetException", e );
}
} else {
if (null == asyncClientManagerList) {
synchronized (this) {
if (null == asyncClientManagerList) {
asyncClientManagerList = new ArrayList<> ();
for (int i = 0; i < asyncSelectorThreadCount; i++) {
try {
asyncClientManagerList.add(new TAsyncClientManager());
} catch (IOException e) {
e.printStackTrace ();
}
}
}
}
}
Class<?> clazz = getAsyncClientClass (); if (asyncConstructor == null) {
try {
asyncConstructor = clazz.getDeclaredConstructor ( TProtocolFactory.class, TAsyncClientManager.class, TNonblockingTransport.class );
} catch (NoSuchMethodException e) {
e.printStackTrace ();
}
} try {
return asyncConstructor.newInstance ( new KoalasBinaryProtocol.Factory (), asyncClientManagerList.get (socket.hashCode () % asyncSelectorThreadCount), socket );
} catch (InstantiationException e) {
logger.error ( "get InstantiationException", e );
} catch (IllegalAccessException e) {
logger.error ( "get IllegalAccessException", e );
} catch (InvocationTargetException e) {
logger.error ( "get InvocationTargetException", e );
} }
return null;
}
获取Thrift发送对象,也就是原生thrift代码中的xxxxx.client,它才是最终的发送对象,然后反射调用服务端,获取结果后返回给调用方,这样一个client端的同步调用逻辑就全部完成了
https://gitee.com/a1234567891/koalas-rpc
koalas-RPC 个人作品,提供大家交流学习,有意见请私信,欢迎拍砖。客户端采用thrift协议,服务端支持netty和thrift的TThreadedSelectorServer半同步半异步线程模型,支持动态扩容,服务上下线,权重动态,可用性配置,页面流量统计等,持续为个人以及中小型公司提供可靠的RPC框架技术方案
更多学习内容请加高级java QQ群:825199617
JAVA RPC (七) 手把手从零教你写一个生产级RPC之client请求的更多相关文章
- JAVA RPC (六) 之手把手从零教你写一个生产级RPC之client的代理
首先对于RPC来讲,最主要的无非三点[SERVER IO模型].[序列化协议].[client连接池复用],之前的博客大家应该对thrift有一个大致的了解了,那么我们现在来说一说如何将thrift的 ...
- 只有20行Javascript代码!手把手教你写一个页面模板引擎
http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...
- 半个小时教你写一个装(bi)逼(she)之地图搜租房
半个小时教你写一个装(bi)逼(she)之地图搜租房 首先需要一个Python3环境,怎么准备我就不多说了,实在不会的出门右转看一下廖雪峰老师的博客. HTML部分 代码来自:高德API+Python ...
- 【vps】教你写一个属于自己的随机图API
[vps]教你写一个自己的随机图API 前言 刚刚开始使用halo博客的时候,我就发现halo博客系统是可以使用随机图当背景的,所以也是使用了网上一些比较火的随机图API. 在上次发现了各种图片API ...
- 手把手教你写一个java的orm(五)
生成sql:where 上一篇里我们实现了生成insert的sql,下面要开始实现update,delete,select的sql语句了.但是这些语句有一个比较麻烦的地方是:它们一般后面都会有wher ...
- 手把手教你写一个java的orm(一)
写之前的说明 其实吧. 这个东西已经写好了,地址在:https://github.com/hjx601496320/JdbcPlus 这系列文章算是我写的过程的总结吧.(恩系列,说明我可能会写好久,╮ ...
- 手把手教你写一个RPC
1.1 RPC 是什么 定义:RPC(Remote Procedure Call Protocol)--远程过程调用协议 ,RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数 ...
- 教你写个简单到的 Redis Client 框架 - .NET Core
目录 1,关于 Redis RESP 定义数据类型 2,定义异步消息状态机 3,定义命令发送模板 4,定义 Redis Client 5,实现简单的 RESP 解析 6,实现命令发送客户端 7,如何使 ...
- 手把手教你写一个java的orm(完)
生成sql:select 上一篇讲了怎样生成一个sql中where的一部分,之后我们要做事情就简单很多了,就只要像最开始一样的生成各种sql语句就好了,之后只要再加上我们需要的条件,一个完整的sql就 ...
随机推荐
- Docker入门-docker-compose使用(二)
Docker Docker容器大行其道,直接通过 docker pull + 启动参数的方式运行比较麻烦, 可以通过docker-compose插件快速创建容器 1.安装docker-compose ...
- 在Linux中调试段错误(core dumped)
在Linux中调试段错误(core dumped) 在作比赛的时候经常遇到段错误, 但是一般都采用的是printf打印信息这种笨方法,而且定位bug比较慢,今天尝试利用gdb工具调试段错误. 段错误( ...
- LNMP环境搭建:Nginx安装、测试与域名配置
Nginx作为一款优秀的Web Server软件同时也是一款优秀的负载均衡或前端反向代理.缓存服务软件 2.编译安装Nginx (1)安装Nginx依赖函数库pcre pcre为“perl兼容正则表达 ...
- 51nod--1079 中国剩余定理
题目: 1079 中国剩余定理 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 一个正整数K,给出K Mod 一些质数的结果,求符合条件的最小的K.例如,K ...
- 结构体重载运算符&srand&rand
先上代码,再按代码讲解 #include<stdio.h>#include<string.h>#include<stdlib.h>#include<time. ...
- Java框架中Struts框架的优缺点
Struts 优缺点优点:1. 实现 MVC 模式,结构清晰,使开发者只关注业务逻辑的实现.2.有丰富的 tag 可以用 ,Struts 的标记库(Taglib),如能灵活动用,则能大大提高开发效率3 ...
- 末学者daylight__Linux磁盘管理及LVM
一.硬盘接口 从整体的角度上,硬盘接口分为IDE.SATA.SCSI和SAS四种,IDE接口硬盘多用于家用产品中,也部分应用于服务器,SCSI接口的硬盘则主要应用于服务器市场,而SAS只在高端服务器上 ...
- Nhibernate 使用 (一)
一:介绍 NHibernate 是一个基于.Net 的针对关系型数据库的对象持久化类库.Nhibernate 来源于非常优秀的基于Java的Hibernate 关系型持久化工具.NHibernate ...
- node+mongoose+vue
app.js 入门 let express = require('express'); let app = express(); let allowCrossDomain = function (re ...
- 如何在cocos中为节点添加监听事件
一般在监听键盘事件时,可是采用以下方式来监听键盘事件: 以及记得定义取消监听的函数(这个摧毁函数会自己调用吗?): 同时这里还有一种传统的监听方式: 但是cocos官方的文档建议我们不要使用这种方式, ...