先放出连接,喜欢的给个star:https://gitee.com/a1234567891/koalas-rpcphp
一:项目介绍
koalas-RPC 我的做品,提供你们交流学习,有意见请私信,欢迎拍砖。客户端采用thrift协议,服务端支持netty和thrift的TThreadedSelectorServer半同步半异步线程模型,支持动态扩容,服务上下线,权重动态,可用性配置,页面流量统计等,QPS统计,TP90,TP99,TP95等丰富可视化数据,持续为我的以及中小型公司提供可靠的RPC框架技术方案。html
1:为何要写这个RPCjava
市面上常见的RPC框架不少,grpc,motan,dubbo等,可是随着愈来愈多的元素加入,复杂的架构设计等因素似使得这些框架和spring同样,虽然号称是轻量级,可是用起来倒是让咱们很蹩脚,大量的配置,繁杂的API设计,其实,咱们根本用不上这些东西!!! 我也算得上是在不少个互联网企业厮杀过,见过不少不少的内部RPC框架,有些优秀的设计让我很是赞扬,有一天我忽然想着,为何不对这些设计原型进行聚合归类,本身搞一套【轻量级】RPC框架呢,碍于工做缘由,一直没有时间倒腾出空,十一期间工做闲暇,说搞就搞吧,落地不易,不少细节性问题,好比tcp中怎么解决大量的wait-time,如何作到thrift和netty的兼容等等大量细节的优化,但愿源码对你们对认识RPC框架起到推动的做用。东西越写越多,有各类问题欢迎随时拍砖linux
2:为何叫koalasgit
树袋熊英文翻译,但愿考拉RPC给那些不太喜欢动手本身去造轮子的人提供可靠的RPC使用环境github
3:技术栈spring
- thrift 0.8.0
- spring-core-4.2.5,spring-context-4.2.5,spring-beans-4.2.5
- log4j,slf4j
- org.apache.commons(v2.0+)
- io.netty4
- fastJson
- zookeeper
- 点评cat(V3.0.0+ 作数据大盘统计上报等使用,可不配置)
- AOP,反射代理等
4:关于技术选型apache
- 序列化篇 考察了不少个序列化组件,其中包括jdk原生,kryo、hessian、protoStuff,thrift,json等,最终选择了Thrift,缘由以下 原生JDK序列化反序列化效率堪忧,其序列化内容太过全面kryo和hessian,json相对来讲比原生JDK强一些,可是对跨语言支持通常,因此舍弃了,最终想在protoBuf和Thrift协议里面选择一套框架,这俩框架很相通,支持跨语言,须要静态编译等等。可是protoBuf不带RPC服务,本着提供多套服务端模式(thrift rpc,netty)的状况下,最终选择了Thrift协议。
- IO线程模型篇 原生socket能够模拟出简单的RPC框架,可是对于大规模并发,要求吞吐量的系统来讲,也就算得上是一个demo级别的,因此BIO确定是不考虑了,NIO的模型在序列化技术选型的时候已经说了,Thrift自己支持不少个io线程模型,同步,异步,半同步异步等(SimpleServer,TNonblockingServer,THsHaServer,TThreadedSelectorServer,TThreadPoolServer),其中吞吐量最高的确定是半同步半异步的IO模TThreadedSelectorServer了,具体缘由你们可自行google,此次不作多的阐述,选择好了模型以后,发现thrift简直就是神器同样的存在,再一想,对于服务端来讲,IO模型怎么能少得了Netty啊,因此下决心也要支持Netty,可是很遗憾Netty目前没有对Thrift的序列化解析,拆包粘包的处理,可是有protoBuf,和http协议的封装,怎么办,本身在netty上写对thrift的支持呗,虽然工做量大了一些,可是一想netty不就是干这个事儿的嘛- -!
- 服务发现 支持集群的RPC框架里面,像dubbo,或者是其余三方框架,对服务发现都进行的封装,那么自研RPC的话,服务发现就要本身来写了,那么简单小巧容易上手的zookeeper确定是首选了。
5:安装教程json
考拉RPC确保精简,轻量的原则,只须要zk服务器进行服务发现(后续版本服务治理可能须要Datasource),对于zookeeper的各个环境安装教程请自行google,不在本安装教程内特地说明 若是须要cat的数据大盘功能,想更方便的查看服务的调用状况,须要安装cat服务,至于cat的安装就更简单了,就是war包扔在tomcat里面运行,而后配置一些参数便可,固然你也能够不接入cat,单独的做为RPC框架来使用。 CAT接入参考:https://github.com/dianping/catwindows
二:使用说明
1:前期准以及依赖
maven依赖
1 <dependency> 2 <groupId>koalas.rpc</groupId> 3 <artifactId>com.Koalas.rpc</artifactId> 4 <version>Koalas-1.0-SNAPSHOT</version> 5 </dependency>
关于私服的引用问题,记得全局文件不要把全局的依赖都代理掉,由于这么作只能从aliyun的私服上下载项目,因为koalas-rpc中的Cat依赖只在美团点评的私有仓库中存在,这么作会下载依赖失败,因此不要暴力的设置下面的代理作法。
<mirror> <id>nexus-aliyun</id> <mirrorOf>*</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror>
正确的作法是将代理去掉,直接按照做者在pom.xml文件中给定的依赖仓库地址就能够了。
首先须要编写本身的thrift idl文件了,这里多说一句,在群里的小伙伴曾经说过idl文件编写不熟悉,有可能出错 这里顺带说一嘴,thrift的ldl文件和写java的请求体和service几乎没有任何区别,熟能生巧,上手以后很是简单 这里推荐几篇thrift的文章,有兴趣能够看一看 https://blog.csdn.net/lk10207160511/article/details/50450541,https://blog.csdn.net/hrn1216/article/details/51306395 下面截图为测试的thrift文件
更新于2019年06月10日
若是你们实在不乐意手写idl文件,那么做者给你们提供了一个简单的插件。连接: https://pan.baidu.com/s/1d_Raox39zSdFrMGw--VUsQ 提取码: y7yu ,下载以后在src/test/java下面写本身的普通java接口对象,而后一键生成thrfit文件和便后以后的文件(前提条件是须要使用者把thrift编译环境设置到path中,不然不能正常运行),使用方式以下:写好了本身的接口文件以后直接运行ThriftFileBuilderTest测试类中方法。
1 @Test 2 public void testToOutputstream() throws Exception { 3 4 String baseDir = "src/test/java"; 5 Class clazz = ICommonUserService.class; 6 String outPutFile =baseDir.concat ( "/" ).concat (clazz.getPackage ().getName ().replaceAll ( "\\.","/" )).concat ( "/" ); 7 outPutFile=outPutFile.concat ( clazz.getSimpleName () ).concat ( "/" ); 8 outPutFile=outPutFile.concat ( clazz.getSimpleName ()+".thrift" ); 9 10 File file = new File ( outPutFile); 11 if (file.getParentFile() != null && !file.getParentFile().exists()) { 12 file.getParentFile().mkdirs(); 13 file.createNewFile (); 14 } 15 16 this.fileBuilder.setSourceDir(baseDir); 17 18 FileOutputStream fileOutputStream= new FileOutputStream(file); 19 this.fileBuilder.buildToOutputStream(clazz,fileOutputStream); 20 21 excuteThriftCommand(file.getAbsolutePath ()); 22 }
只须要修改clazz的接口就能够了,执行事后在当前包下会生成一个thrift文件和编译事后的class文件,直接使用便可。 test0包是做者的测试包名,改为本身实际的包名就能够了。最后说明的是做者仍是推荐本身练习写idl文件,熟练事后就能够不依赖这个工具了。
1 namespace java thrift.service 2 3 include 'WmCreateAccountRequest.thrift' 4 include 'WmCreateAccountRespone.thrift' 5 6 service WmCreateAccountService { 7 WmCreateAccountRespone.WmCreateAccountRespone getRPC(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 8 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest1(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 9 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest2(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 10 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest3(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 11 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest4(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 12 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest5(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 13 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest6(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 14 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest7(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 15 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest8(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 16 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest9(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 17 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest10(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 18 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest11(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 19 WmCreateAccountRespone.WmCreateAccountRespone koaloasTest12(1:WmCreateAccountRequest.WmCreateAccountRequest wmCreateAccountRequest); 20 }
1 namespace java thrift.domain 2 /** 3 * 测试类 4 **/ 5 struct WmCreateAccountRequest { 6 7 1:i32 source, 8 9 2:i32 accountType, 10 11 3:i64 partnerId, 12 13 4:i32 partnerType, 14 15 5:string partnerName, 16 17 6:i32 poiFlag, 18 } 19 namespace java thrift.domain 20 /** 21 * 测试类 22 **/ 23 struct WmCreateAccountRespone { 24 1:i32 code, 25 2:string message, 26 }
编译器须要你们去下载对应的版本 windows和linux下不一样的编译器,下载地址http://archive.apache.org/dist/thrift/0.8.0/ 下载0.8.0版本便可,0.8.0版本是很老的版本了,可是相对稳定,后续会把thirft版本升级。若是上面地址下载不下来或者失效,能够上做者的网盘上下载zip包,上面有win版本和mac,linux版本的0.8.0的thrift编译器,连接: https://pan.baidu.com/s/1JpLqVbmokTOe30nU_TznWw 提取码: ntye, 编译上面三个文件 thrift -gen java WmCreateAccountService.thrift, thrift -gen java WmCreateAccountRequest.thrift, thrift -gen java WmCreateAccountRespone.thrift 在当前目录下会生成3个java文件 这三个文件分别是请求体,返回体,和服务类,就这么简单 Ok做为开发者而言,全部的准备工做都结束了。下面就开始进入实际开发~
2:xml配置方式
1. 客户端同步调用
首先在你的xml里面配置一下引用
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:koalas="http://www.koalas.com/schema/ch" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.koalas.com/schema/ch http://www.koalas.com/schema/ch.xsd"> <koalas:client id="wmCreateAccountService1" serviceInterface="thrift.service.WmCreateAccountService" zkPath="127.0.0.1:2181"/> </beans>
首先引用koalas的自定义schema,xmlns:koalas和xsi:schemaLocation, 其中serviceInterface为thrift自动生成的java类,zkPath为zk的服务地址,默认是同步调用,接下来就是在java里面的远程调用了。
package thrift.service; import org.apache.thrift.TException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import thrift.domain.WmCreateAccountRequest; import thrift.domain.WmCreateAccountRespone; @Service("testService") public class TestService { @Autowired WmCreateAccountService.Iface wmCreateAccountService; public void getRemoteRpc() throws TException { WmCreateAccountRequest request= new WmCreateAccountRequest ( ); //request.setSource ( 10 ); request.setAccountType ( 1 ); request.setPartnerId ( 1 ); request.setPartnerType ( 1 ); request.setPartnerName ( "你好" ); request.setPoiFlag ( 1 ); WmCreateAccountRespone respone = wmCreateAccountService.getRPC ( request); System.out.println (respone); } }
就这么简单一个高性能的RPC框架就诞生了。WmCreateAccountService是thrift自动生成的,做为使用者而言不须要作任何事情,只须要在spring bean中注入xxx.Iface便可。
2. 客户端异步调用
刚刚咱们看了客户端的同步调用方式,下面咱们一块儿来看看异步的使用方式, 首先在你的xml里面配置一下引用
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:koalas="http://www.koalas.com/schema/ch" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.koalas.com/schema/ch http://www.koalas.com/schema/ch.xsd"> <koalas:client id="wmCreateAccountService2" serviceInterface="thrift.service.WmCreateAccountService" zkPath="127.0.0.1:2181" async="true"/> </beans>
和同步的区别async=true,表明异步使用,接下来就是在java里面的异步远程调用了
package thrift.service; import client.async.KoalasAsyncCallBack; import org.apache.thrift.TException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import thrift.domain.WmCreateAccountRequest; import thrift.domain.WmCreateAccountRespone; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @Service("testService") public class TestService2 { @Autowired WmCreateAccountService.AsyncIface wmCreateAccountService; public void getRemoteRpc() throws TException{ KoalasAsyncCallBack<WmCreateAccountRespone, WmCreateAccountService.AsyncClient.getRPC_call> koalasAsyncCallBack = new KoalasAsyncCallBack<> (); WmCreateAccountRequest request= new WmCreateAccountRequest ( ); request.setAccountType ( 1 ); request.setPartnerId ( 1 ); request.setPartnerType ( 1 ); request.setPartnerName ( "你好啊" ); request.setPoiFlag ( 1 ); wmCreateAccountService.getRPC ( request ,koalasAsyncCallBack); Future<WmCreateAccountRespone> future= koalasAsyncCallBack.getFuture (); try { //to get other things System.out.println (future.get ()); } catch (InterruptedException e) { e.printStackTrace (); } catch (ExecutionException e) { e.printStackTrace (); } } }
此次调用getRpc方法不会阻塞等待server同步结果了。而是能够去干一些本身的其余事情,而后在调用future.get ()来得到返回resopne,固然future.get ()支持最大等待时间的,超时以后会抛出TimeOutException,固然这仅仅是client超时而已不会影响server的执行结果。
3. 服务端实现
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:koalas="http://www.koalas.com/schema/ch" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.koalas.com/schema/ch http://www.koalas.com/schema/ch.xsd"> <koalas:server id="WmCreateAccountService" serviceInterface="thrift.service.WmCreateAccountService" serviceImpl="wmCreateAccountServiceImpl" port="8001" zkpath="127.0.0.1:2181"/> </beans>
服务端只须要指定暴露的端口,zk服务地址和服务端实现便可。
@Service public class WmCreateAccountServiceImpl implements WmCreateAccountService.Iface { @Override public WmCreateAccountRespone getRPC(WmCreateAccountRequest wmCreateAccountRequest) throws TException { WmCreateAccountRespone wmCreateAccountRespone = new WmCreateAccountRespone (); wmCreateAccountRespone.setCode ( 1 ); wmCreateAccountRespone.setMessage ( "你好" ); if(new Random ( ).nextInt ( 5 )>100){ throw new RuntimeException ( "测试错误" ); } System.out.println ( "getRPC start ...." + wmCreateAccountRequest + "------" + atomicInteger.incrementAndGet () ); return wmCreateAccountRespone; } }
只须要实现xxxx.Iface便可
3:注解配置方式
有的小伙伴会以为配置xml有点麻烦,koalas-rpc也提供了纯注解的使用方式
1. 客户端调用
xml中的配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:koalas="http://www.koalas.com/schema/ch" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.koalas.com/schema/ch http://www.koalas.com/schema/ch.xsd"> <koalas:annotation package="thrift.annotation.client.impl"/> </beans>
一个扫描标签就好了,若是你在spring bean里想经过调用rpc远程服务,那么扫描一下就好了
java中使用
@Service("testServiceSync") public class TestServiceSync { @KoalasClient(zkPath = "127.0.0.1:2181",readTimeout = 5000*1000) WmCreateAccountService.Iface wmCreateAccountService; public void getRemoteRpc() throws TException { WmCreateAccountRequest request= new WmCreateAccountRequest ( ); //request.setSource ( 10 ); request.setAccountType ( 1 ); request.setPartnerId ( 1 ); request.setPartnerType ( 1 ); request.setPartnerName ( "你好啊-我是注解实现的" ); request.setPoiFlag ( 1 ); WmCreateAccountRespone respone = wmCreateAccountService.getRPC ( request); System.out.println (respone); } }
只须要在你想远程调用的类上加一个@KoalasClient注解就能够了,远程调用就这么简单,固然异步使用方式也相似
@Service("testServiceAsync") public class TestServiceAsync { @KoalasClient(zkPath = "127.0.0.1:2181",readTimeout = 5000*1000) WmCreateAccountService.AsyncIface wmCreateAccountService; public void getRemoteRpc() throws TException{ KoalasAsyncCallBack<WmCreateAccountRespone, WmCreateAccountService.AsyncClient.getRPC_call> koalasAsyncCallBack = new KoalasAsyncCallBack<> (); WmCreateAccountRequest request= new WmCreateAccountRequest ( ); //request.setSource ( 10 ); request.setAccountType ( 1 ); request.setPartnerId ( 1 ); request.setPartnerType ( 1 ); request.setPartnerName ( "你好啊-我是注解实现的" ); request.setPoiFlag ( 1 ); wmCreateAccountService.getRPC ( request ,koalasAsyncCallBack); Future<WmCreateAccountRespone> future= koalasAsyncCallBack.getFuture (); try { System.out.println (future.get ()); } catch (InterruptedException e) { e.printStackTrace (); } catch (ExecutionException e) { e.printStackTrace (); } } }
注意和同步调用不一样的是自定义注解注入的接口是xxxx.AsyncIface,同步是xxxx.Iface。KoalasAsyncCallBack回调使用方式和上面的xml同样。有一点须要说明
<koalas:annotation package="thrift.annotation.client.impl"/>
若是package属性设置为空,那么全部的@KoalasClient都会生效,也就是说全部在spring bean中的自定义注解@KoalasClient都会自动注入。这里说另一种用法
private WmCreateAccountService.Iface wmCreateAccountService; @KoalasClient(zkPath = "127.0.0.1:2181",readTimeout = 5000*1000) public void setWmCreateAccountService(WmCreateAccountService.Iface wmCreateAccountService){ this.wmCreateAccountService = wmCreateAccountService; }
直接注入方法的方式也是能够的。
2. 服务端实现
xml中的配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:koalas="http://www.koalas.com/schema/ch" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.koalas.com/schema/ch http://www.koalas.com/schema/ch.xsd"> <koalas:annotation package="thrift.annotation.server.impl"/> </beans>
配置和client中同样只须要配置一个自定义标签便可,java中的使用方式以下:
package thrift.annotation.server.impl; import annotation.KoalasServer; import org.apache.thrift.TException; import thrift.domain.WmCreateAccountRequest; import thrift.domain.WmCreateAccountRespone; import thrift.service.WmCreateAccountService; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; @KoalasServer ( port = 8801,zkpath="127.0.0.1:2181") public class WmCreateAccountServiceNettyImpl implements WmCreateAccountService.Iface { private AtomicInteger atomicInteger = new AtomicInteger ( 0 ); @Override public WmCreateAccountRespone getRPC(WmCreateAccountRequest wmCreateAccountRequest) throws TException { WmCreateAccountRespone wmCreateAccountRespone = new WmCreateAccountRespone (); wmCreateAccountRespone.setCode ( 1 ); wmCreateAccountRespone.setMessage ( "你好啊" ); if(new Random ( ).nextInt ( 5 )>100){ try { Thread.sleep ( 5000 ); } catch (InterruptedException e) { e.printStackTrace (); } } System.out.println ( "getRPC start ...." + wmCreateAccountRequest + "------" + atomicInteger.incrementAndGet () ); return wmCreateAccountRespone; } }
这样服务实现就会主从注册到zookeeper中提供给client端使用了。值得说明的是被扫描到而且类上有@KoalasServer的类会被加载到spring上下文中,能够当成一个普通的spring bean来处理,还有一点若是你不指定package,配置成以下状况
<koalas:annotation package=""/>
这样配置会以spring的bean为基础实现,那么使用方式须要改为
package thrift.annotation.server.impl; import annotation.KoalasServer; import org.apache.thrift.TException; import thrift.domain.WmCreateAccountRequest; import thrift.domain.WmCreateAccountRespone; import thrift.service.WmCreateAccountService; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; @KoalasServer ( port = 8801,zkpath="127.0.0.1:2181") @Service public class WmCreateAccountServiceNettyImpl implements WmCreateAccountService.Iface { private AtomicInteger atomicInteger = new AtomicInteger ( 0 ); @Override public WmCreateAccountRespone getRPC(WmCreateAccountRequest wmCreateAccountRequest) throws TException { WmCreateAccountRespone wmCreateAccountRespone = new WmCreateAccountRespone (); wmCreateAccountRespone.setCode ( 1 ); wmCreateAccountRespone.setMessage ( "你好啊" ); if(new Random ( ).nextInt ( 5 )>100){ try { Thread.sleep ( 5000 ); } catch (InterruptedException e) { e.printStackTrace (); } } System.out.println ( "getRPC start ...." + wmCreateAccountRequest + "------" + atomicInteger.incrementAndGet () ); return wmCreateAccountRespone; } }
就这么简单便可。
3. 泛化调用
为何须要泛化调用? 1:有一个通用压测平台,想去压测不一样的server。那么如今就有一个问题了,不可能让压测平台服务端去依赖全部的下游服务,这样依赖会很繁杂,这时候若是说只配置serviceName,request模型和request请求json就能够进行远程调用,那么将大大的减小头疼的依赖。 2:假设php同事对java代码不熟悉,不可能让他们去依赖spring,一共一套简单的api来使用是颇有必要的。 3:上游服务不想依赖下游服务的数据模型。
对于泛化调用来讲,dubbo已经提供,soft-rpc也有提供。固然koalas-rpc也不会例外,而且支持xml,注解和java api的使用方式。下面几个例子来讲明一下使用方式。更多demo去源码中查看,做者已经写好,开箱即用。
xml使用方式
<koalas:client id="wmCreateAccountService3" serviceInterface="thrift.service.WmCreateAccountService" zkPath="127.0.0.1:2181" generic="true" readTimeout="50000000"/>
@Autowired @Qualifier("wmCreateAccountService3") GenericService.Iface wmGenericService; public void getGenericRpc() throws TException { GenericRequest request = new GenericRequest ( ); request.setMethodName ( "getRPC" ); request.setClassType ( new ArrayList<String> ( ){{ add ( "thrift.domain.WmCreateAccountRequest"); }} ); request.setRequestObj ( new ArrayList<String> ( ){{ add ( "{\"accountType\":1,\"partnerId\":1,\"partnerName\":\"你好\",\"partnerType\":1,\"poiFlag\":1,\"source\":0}"); }} ); String str = wmGenericService.invoke ( request ); System.out.println (str); }
简单说明一下,GenericService.Iface是通用服务,有三个参数,第一个是方法名称,第二个是请求体类型集合,第三个是请求体内容。直接调用便可,返回值是server端的json类型,使用json工具为阿里巴巴的Fast-json
注解使用方式
@KoalasClient(zkPath = "127.0.0.1:2181",readTimeout = 5000*1000,genericService = "thrift.service.WmCreateAccountService") GenericService.Iface genericService; public void getGenericRemoteRpc() throws TException { GenericRequest request = new GenericRequest ( ); request.setMethodName ( "getRPC" ); request.setClassType ( new ArrayList<String> ( ){{ add ( "thrift.domain.WmCreateAccountRequest"); }} ); request.setRequestObj ( new ArrayList<String> ( ){{ add ( "{\"accountType\":1,\"partnerId\":1,\"partnerName\":\"你好\",\"partnerType\":1,\"poiFlag\":1,\"setAccountType\":true,\"setPartnerId\":true,\"setPartnerName\":true,\"setPartnerType\":true,\"setPoiFlag\":true,\"setSource\":false,\"source\":0}"); }} ); String str = genericService.invoke ( request ); System.out.println (str); }
惟一区别的是注解要指定genericService,当genericService不为空时,默认开启泛化调用 固然,java api方式也是支持的。
KoalasClientProxy koalasClientProxy = new KoalasClientProxy(); koalasClientProxy.setServiceInterface ( "thrift.service.WmCreateAccountService" ); koalasClientProxy.setZkPath ("127.0.0.1:2181" ); koalasClientProxy.setGeneric ( true ); koalasClientProxy.setReadTimeout ( 50000000 ); koalasClientProxy.afterPropertiesSet (); GenericService.Iface genericService = (GenericService.Iface) koalasClientProxy.getObject (); GenericRequest request = new GenericRequest ( ); request.setMethodName ( "getRPC" ); request.setClassType ( new ArrayList<String> ( ){{ add ( "thrift.domain.WmCreateAccountRequest"); }} ); request.setRequestObj ( new ArrayList<String> ( ){{ add ( "{\"accountType\":1,\"partnerId\":1,\"partnerName\":\"你好\",\"partnerType\":1,\"poiFlag\":1,\"setAccountType\":true,\"setPartnerId\":true,\"setPartnerName\":true,\"setPartnerType\":true,\"setPoiFlag\":true,\"setSource\":false,\"source\":0}"); }} ); String str = genericService.invoke ( request ); System.out.println (str); koalasClientProxy.destroy ();
特别注意的是KoalasClientProxy对象很是很是重,必定要在服务关闭的时候执行koalasClientProxy.destroy ();方法,而且须要带应用程序中缓存该对象,千万不要每次使用都要建立,这样会极大的浪费资源,每一个服务对应一个KoalasClientProxy,同步和异步也是不一样的对象,这些使用者须要注意。
4. 原生调用支持
koalas-rpc在原生基础上封装了自定义协议和特定的传输类型,看过源码的朋友必定以为处理很是很是麻烦,可是在自定义协议的过程当中koalas-rpc也同时支持原生的thrift请求,能够在本地作测试等等。请求调用demo:
package xml.client; import org.apache.thrift.TException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import thrift.domain.WmCreateAccountRequest; import thrift.domain.WmCreateAccountRespone; import thrift.service.WmCreateAccountService; public class ThriftNative { public static final String SERVER_IP = "localhost"; public static final int SERVER_PORT = 8001; public static final int TIMEOUT = 3000000; public static void main(String[] args) throws TException { TTransport transport = new TFramedTransport (new TSocket (SERVER_IP, SERVER_PORT, TIMEOUT)); TProtocol protocol = new TBinaryProtocol (transport); WmCreateAccountService.Client client = new WmCreateAccountService.Client(protocol); transport.open(); WmCreateAccountRequest request= new WmCreateAccountRequest ( ); //request.setSource ( 10 ); request.setAccountType ( 1 ); request.setPartnerId ( 1 ); request.setPartnerType ( 1 ); request.setPartnerName ( "你好啊-我是ThriftNative实现的服务端getRemoteRpc" ); request.setPoiFlag ( 1 ); WmCreateAccountRespone respone=client.getRPC (request ); System.out.println (respone); } }
三:参数配置文档
1:客户端
参数名 | 说明 | 是否必须 |
---|---|---|
serviceInterface | thrift生成的接口类 | Y |
zkPath | zk的服务地址,集群中间逗号分隔 | Y |
serverIpPorts | 不实用zk发现直接链接服务器server,格式ip:端口#权重。多个逗号分隔 | N |
async | 是否异步 | N,默认false同步 |
generic | 是否泛化调用(xml配置中使用) | N,默认false |
genericService | 泛化调用的serviceName(注解配置中使用)使用方法参照代码中demo | N,默认false |
cat | 是否开启CAT数据大盘,须要配置CAT服务,便可查看详细调用状况) | N,默认false |
connTimeout | 链接超时 | N,默认3000ms |
readTimeout | 读取超时 | N,默认5000ms,按照服务端指定时间适当调整 |
localMockServiceImpl | 本地测试的实现 | N |
retryRequest | 是否错误重试 | N,默认true |
retryTimes | 重试次数 | N,默认3次 |
maxTotal | TCP长链接池,参照Apache Pool参数 | 100 |
maxIdle | TCP长链接池,参照Apache Pool参数 | 50 |
minIdle | TCP长链接池,参照Apache Pool参数 | 10 |
lifo | TCP长链接池,参照Apache Pool参数 | true |
fairness | TCP长链接池,参照Apache Pool参数 | false |
maxWaitMillis | TCP长链接池,参照Apache Pool参数 | 30 * 1000 |
timeBetweenEvictionRunsMillis | TCP长链接池,参照Apache Pool参数 | 3 * 60 * 1000 |
minEvictableIdleTimeMillis | TCP长链接池,参照Apache Pool参数 | 5 * 60 * 1000 |
softMinEvictableIdleTimeMillis | TCP长链接池,参照Apache Pool参数 | 10 * 60 * 1000 |
numTestsPerEvictionRun | TCP长链接池,参照Apache Pool参数 | 20 |
testOnCreate | TCP长链接池,参照Apache Pool参数 | false |
testOnBorrow | TCP长链接池,参照Apache Pool参数 | false |
testOnReturn | TCP长链接池,参照Apache Pool参数 | false |
testWhileIdle | TCP长链接池,参照Apache Pool参数 | true |
iLoadBalancer | 负载略侧,默认随机 | N |
env | 环境 | N,默认dev |
removeAbandonedOnBorrow | TCP长链接池,参照Apache Pool参数 | true |
removeAbandonedOnMaintenance | TCP长链接池,参照Apache Pool参数 | true |
removeAbandonedTimeout | TCP长链接池,参照Apache Pool参数 | 30000ms |
maxLength_ | 容许发送最大字节数 | N,10 * 1024 * 1024 |
cores | selecter核心数量 | N,默认当前cpu数量 |
asyncSelectorThreadCount | 异步请求时线程数量 | N,默认当前CPU核心数量*2 |
privateKey | 私钥 | N |
publicKey | 公钥 | N |
2:服务端
参数 | 说明 | 是否必须 |
---|---|---|
serviceImpl | 服务端实现 | Y |
serviceInterface | thrift自动生成的类 | Y |
port | 暴露的服务端口 | Y |
zkpath | 服务端的zk路径 | Y |
cat | (是否开启CAT数据大盘,须要配置CAT服务,便可查看详细调用状况) | N,默认false |
bossThreadCount | 处理链接线程 | N,当前CPU核心数 |
workThreadCount | 读取线程 | N,当前CPU核心数*2 |
koalasThreadCount | 业务线程数 | 256 |
maxLength | 最大接收字节数 | Integer.MAX_VALUE |
env | 环境 | N,dev |
weight | 权重 | N,10 |
serverType | 采用哪些服务端,能够选NETTY和THRIFT,默认NETTY | N |
workQueue | 当server超载时,能够容纳等待任务的队列长度 | 0 |
privateKey | 私钥 | N |
publicKey | 公钥 | N |
3:客户端服务端RSA双向加密
源码中utils.KoalasRsaUtil的main方法已经为你们写好生成私钥和公钥的代码,执行便可 ,下面为核心源码展现
public static String sign(byte[] data, String privateKey) throws Exception { byte[] keyBytes = Base64.decodeBase64 ( privateKey.getBytes ( "UTF-8" ) ); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec ( keyBytes ); KeyFactory keyFactory = KeyFactory.getInstance ( KEY_ALGORITHM ); PrivateKey privateK = keyFactory.generatePrivate ( pkcs8KeySpec ); Signature signature = Signature.getInstance ( SIGNATURE_ALGORITHM ); signature.initSign ( privateK ); signature.update ( data ); return new String ( Base64.encodeBase64 ( signature.sign () ), "UTF-8" ); } public static boolean verify(byte[] data, String publicKey, String sign) throws Exception { byte[] keyBytes = Base64.decodeBase64 ( publicKey.getBytes ("UTF-8") ); X509EncodedKeySpec keySpec = new X509EncodedKeySpec ( keyBytes ); KeyFactory keyFactory = KeyFactory.getInstance ( KEY_ALGORITHM ); PublicKey publicK = keyFactory.generatePublic ( keySpec ); Signature signature = Signature.getInstance ( SIGNATURE_ALGORITHM ); signature.initVerify ( publicK ); signature.update ( data ); return signature.verify ( Base64.decodeBase64 ( sign.getBytes ("UTF-8") ) );
}
执行main方法以后,会获得4个长长的字符串
下面四个字符串为koalas-rpc中客户端和服务端使用的rsa非对称秘钥,复制使用便可 MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIPQIc8/+wl5hTDT8fT4rCEA//pwSqdX8djur+UDwR/qg5iW3xBHUuxTGXRko/3SXYKJLugRmT2gV4ZggSHLpToSFYJZwATIbVD2p3oqZx4ZC5g3mZdTCScHbTb4CITFPacJCKads75Plrk8ryW7wP9dWlSmrF8f3CzReKUTjf5dAgMBAAECgYBRigXwK9cCNG8lFmc9sDriq7it1psHzApqtLSQifME6FCBqwrQCh8M3BcJ/lvH30NDRdODcaeHDNI36SjYnB5X25mMG95OEgLqPm7T8oB3DBY/BhJbAY43FbZSU3Lb+El5zknpTtH0M8DTlul1EmLbe+TJVL/x/SkpDx/HSS3GAQJBALtSSBeskQ4P+Pn5M4F2+GZJmFDxaOQHIuy/RdfckxV1aEMN425ieSrinSCXyBC8uTN0zF1NlJsfWLAUhtfSQ90CQQC0I+mEXsxWtTDT+fd3bDgiJtfOwPpyNT4HSObdq+aAqO44NL7fqD2plNZ3vBULfDbdbnTlvKJJnPUdt457WjyBAkAiM63SFMIPbT8qdSPAWbaVBo73CHz8VYk87NeVyEJawqscwyZpezVgbSv/TXdMBwlRqdu+lXGyuRB6ZeUQ9uVJAkAscjfpqyIruqUDiEdgtdjbxE22+7JPf4eAcKJVy1YiJIwyXgFCWdZtAwYvoL5oiQtYcypwjKxWEV4BKQsEsG0BAkBmlDi0wSPA2x7YjudQNWv+H51CsYDWMjOQ7AzUYABfkWVnbeYS/3uf7W56AHl3Rmdo7zUTBJFCyM/Rt28yZVLj MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAAxbccTLuu12V2Le1mI5b+0kZMiQwN/WTSv8d2y0J/wVl+yMWgjZi4c8/kAs8pACEiFQ8hUUovmoAwceKEd5h3ISSV5lEPyBt+68DzinOrSGv7bZhGm5bwkRG7MMpSgAVSJj2lWTkf63fp2e/FwHs3WM64sSlbdlUN/57YtUC6QIDAQAB MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMADFtxxMu67XZXYt7WYjlv7SRkyJDA39ZNK/x3bLQn/BWX7IxaCNmLhzz+QCzykAISIVDyFRSi+agDBx4oR3mHchJJXmUQ/IG37rwPOKc6tIa/ttmEablvCREbswylKABVImPaVZOR/rd+nZ78XAezdYzrixKVt2VQ3/nti1QLpAgMBAAECgYEApwwI/4+b+AYZzRvV967Zazyaw8jTov+MLrC4cokUDfZIBAkQ5awzFKPPYkU3AXLM4ICaiGyJVoESR8ZOitgw1wB6tbI2DhP4FD5dqJkIOdUNujo+gAda3kfeCjAgWbtUL3Zhj7Ff+xFvSDDxUYKGG4fZwge3CFwyQ2vjxhPTXGECQQDpAkS6AW17LvWAiiu2924MEicJQW/s3w+chjuQ3VaauzotAHoSMi8VjBSlINbKxpklthKB4vubfA6AtTHae3hPAkEA0vVBKk9Qz8TkraN3QcILJwHjcjqP8+51n1jimSpZeZQL4BJxStdqqMP2nUzAVnh4ncEoFZ/3QA0sSwcdPtDLRwJBAIDpMmC+HXYDWuvMhbbqWUXwXQxv2Z5xIk/0q8vPyPQ+FUeEdgTPIuGG6H0bF/qDuYL1onOdwpoZHmTy2iwIF10CQBiVNdvNVFhx1EgbtWj3SL9p6+xCwMWnMxO3kuhQVA7j3qJk48jZ43b5JwLbj8pDzaJsgNRMSM6w+klf8duBDz8CQBMIMmhU84An2nv/CPNPArCC8BN8YhY1AH685zgRQBLv5untRhfZ+hJtqjSzTJlY7JHybMzc6wt2FZXrhvuopO4= MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCD0CHPP/sJeYUw0/H0+KwhAP/6cEqnV/HY7q/lA8Ef6oOYlt8QR1LsUxl0ZKP90l2CiS7oEZk9oFeGYIEhy6U6EhWCWcAEyG1Q9qd6KmceGQuYN5mXUwknB202+AiExT2nCQimnbO+T5a5PK8lu8D/XVpUpqxfH9ws0XilE43+XQIDAQAB 上面四个字符串为koalas-rpc中客户端和服务端使用的rsa非对称秘钥,复制使用便可
获得上面的四个长长的字符串,能够由server端给client端提供。其中字符串1,字符串2分别对应client的privateKey,和publicKey,字符串3和字符串4分别对应server端的privateKey,和publicKey,提供rsa双向加密的初衷是为了将很是重要的项目保护起来,不容许其余项目随意调用,可是RSA双向加密会对性能有所影响。当RSA验证失败的时候,client会抛RsaException。RSA对称加密适合给三方系统进行调用,对称加密会影响传输性能。
实际性能压测
8C 16G mac开发本,单机10000次请求耗时截图
10w次请求,大约耗时12s,平均qps在8000左右,在集群环境下会有不错的性能表现
数据大盘展现
开启数据大盘,须要设置客户端或者服务端的cat参数为true,默认为false。 koalas2.0已经接入了cat服务,cat服务支持qps统计,可用率,tp90line,tp99line,丰富自定义监控报警等,接入效果图 丰富的可视参数,流量统计,日,周,月报表展现等。
链路跟踪
对RPC服务来讲,系统间的调用和排查异常接口,肯定耗时代码是很是重要的,只要接入了cat,koalsa-rpc自然的支持链路跟踪,一切尽在眼前!
代码下载后如何测试
做者在src/test/java和resource下面有已经写好了的丰富的xml配置和注解配置,下载后直接运行测试便可,注意测试的时候须要安装zookeeper服务,若是不想经过zk作服务发现,那么客户端能够进行直连,指定的server列表,逗号分隔,#分隔权重,格式,192.168.3.253:6666#10,192.168.3.253:6667#10 详情见参数配置列表,可是这种办法做者是不推荐的,在生产环境下没有心跳和动态上下线功能。
CAT服务按需配置,不须要数据大盘不须要配置,不会影响RPC功能,CAT接入参考:https://github.com/dianping/cat
开源协议 :
Apache License Version 2.0 see http://www.apache.org/licenses/LICENSE-2.0.html
联系做者 :
高级java QQ群:825199617 博客地址:https://www.cnblogs.com/zyl2016/