阿里巴巴中间件性能挑战赛(RPC篇 同步阻塞模型)
来源:互联网 发布:简单视频网站源码 编辑:程序博客网 时间:2024/06/05 02:20
赛题要求:
一个简单的RPC框架
RPC(Remote Procedure Call )——远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
框架——让编程人员便捷地使用框架所提供的功能,由于RPC的特性,聚焦于应用的分布式服务化开发,所以成为一个对开发人员无感知的接口代理,显然是RPC框架优秀的设计。
题目要求
1.要成为框架:对于框架的使用者,隐藏RPC实现。
2.网络模块可以自己编写,如果要使用IO框架,要求使用netty-4.0.23.Final。
3.支持异步调用,提供future、callback的能力。
4.能够传输基本类型、自定义业务类型、异常类型(要在客户端抛出)。
5.要处理超时场景,服务端处理时间较长时,客户端在指定时间内跳出本次调用。
6.提供RPC上下文,客户端可以透传数据给服务端。
7.提供Hook,让开发人员进行RPC层面的AOP。
注:为了降低第一题的难度,RPC框架不需要注册中心,客户端识别-DSIP的JVM参数来获取服务端IP。
衡量标准
满足所有要求。性能测试。
这个赛题现在看来其实并不难,不过当时刚参赛的时候我还是理解了好久。首先刚开始的时候其实根本不知道到底什么是RPC,感觉应该是一种无比高大上无比难实现的东东。直到我看到了梁飞的博客http://javatar.iteye.com/blog/1123915,才知道原来RPC其实并不难。通俗来说就是客户端通过一定的协议把方法名称、参数类型还有参数传给服务器,然后服务器调用对应方法,完成以后再把结果传回来给客户端,仅此而已。
首先是Consumer部分的接口定义:
package com.alibaba.middleware.race.rpc.api;import com.alibaba.middleware.race.rpc.aop.ConsumerHook;import com.alibaba.middleware.race.rpc.async.ResponseCallbackListener;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * Created by huangsheng.hs on 2015/3/26. */public class RpcConsumer implements InvocationHandler { private Class<?> interfaceClazz; public RpcConsumer() { } private void init() { //TODO } public RpcConsumer interfaceClass(Class<?> interfaceClass) { this.interfaceClazz = interfaceClass; return this; } public RpcConsumer version(String version) { //TODO return this; } public RpcConsumer clientTimeout(int clientTimeout) { //TODO return this; } public RpcConsumer hook(ConsumerHook hook) { return this; } public Object instance() { //TODO return an Proxy return Proxy.newProxyInstance(this.getClass().getClassLoader(),new Class[]{this.interfaceClazz},this); } public void asynCall(String methodName) { asynCall(methodName, null); } public <T extends ResponseCallbackListener> void asynCall(String methodName, T callbackListener) { //TODO } public void cancelAsyn(String methodName) { //TODO } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; }}主要接口分析如下
public RpcConsumer interfaceClass(Class<?> interfaceClass):定义了RPC的服务接口(也就是客户端可以调用服务器提供的哪些服务)
public Object instance():生成一个客户端代理的实例,其中用到了Proxy类的newProxyInstance方法
public asynCall(String methodName,T callbackListener):异步调用的接口,先在注册表中注册异步回调函数,然后进行异步调用
public Object invoke(Object proxy, Method method, Object[] args):RpcConsumer实现了java中的InvocationHandler接口,因此需要重写invoke方法。invoke方法是RpcConsumer最核心的方法。调用被代理的任何服务都会被转为调用invoke方法,在invoke方法中会向服务器发送Rpc请求。如果是同步调用则会阻塞等待结果,如果是异步调用则会马上返回。
(invoke方法是程序最核心的地方,也是比较难理解的地方。如果对于invoke方法不理解,可以先参考java的代理类的原理,了解Proxy类以及InvocationHandler接口的原理再往下看)
接下来是RpcConsumer的具体实现(只贴最核心的invoke方法,其余代码请看我的github)
//调用方法的协议:方法名 参数类型 参数值 上下文 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(isAsyn(method.getName())) return null; //本方法是同步方法,被异步调用则返回空 Socket s = new Socket(host,PORT); try { ObjectOutputStream output = new ObjectOutputStream(s.getOutputStream()); try { RpcRequest request = new RpcRequest(method.getName(), method.getParameterTypes(), args, RpcContext.localMap.get()); consumerHook.before(request); output.writeObject(request); ObjectInputStream input = new ObjectInputStream(s.getInputStream()); try { //使用Callable接口和FutureTask来实现计时 FutureTask<RpcResponse> task = new FutureTask<RpcResponse>(new ResultGetter(input)); new Thread(task).start(); RpcResponse response= task.get(clientTimeout, TimeUnit.MILLISECONDS); if(response.isError()) { InvocationTargetException targetEx = (InvocationTargetException)(response.getThrowable()); Throwable t = targetEx .getTargetException(); throw t; } else { Object result = response.getAppResponse(); consumerHook.after(request); return result; } } finally { input.close(); } } finally { output.close(); } } finally { s.close(); } }思路其实很简单,就是把方法名、参数类型、参数值以及其他东东包装成RpcRequest,然后用java自带的序列化方法通过socket发送到服务器端,然后再同步阻塞等待结果返回回来。当然,这个只是最简单的版本,后面我会将一下一些更加复杂的细节。不过在此之前先贴一下服务器端的代码:
接口定义:
package com.alibaba.middleware.race.rpc.api;/** * Created by huangsheng.hs on 2015/3/26. */public class RpcProvider { public RpcProvider() {} /** * init Provider */ private void init(){ //TODO } /** * set the interface which this provider want to expose as a service * @param serviceInterface */ public RpcProvider serviceInterface(Class<?> serviceInterface){ //TODO return this; } /** * set the version of the service * @param version */ public RpcProvider version(String version){ //TODO return this; } /** * set the instance which implements the service's interface * @param serviceInstance */ public RpcProvider impl(Object serviceInstance){ //TODO return this; } /** * set the timeout of the service * @param timeout */ public RpcProvider timeout(int timeout){ //TODO return this; } /** * set serialize type of this service * @param serializeType */ public RpcProvider serializeType(String serializeType){ //TODO return this; } /** * publish this service * if you want to publish your service , you need a registry server. * after all , you cannot write servers' ips in config file when you have 1 million server. * you can use ZooKeeper as your registry server to make your services found by your consumers. */ public void publish() { //TODO }}具体实现(其他方法都不是很重要,只贴publish方法):
public void publish(Object service,int port) { System.out.println("service:"+service.getClass().getName()+" port:"+port); ServerSocket ss = null;try {ss = new ServerSocket(port);while(true) { final Socket socket = ss.accept(); Thread thread = new Thread(new ServiceThread(socket,serviceInstance)); thread.start(); try{ thread.join(TIME_OUT); }catch(InterruptedException e){ e.printStackTrace(); System.out.println("thread被打断"); } if(thread.isAlive()) { thread.interrupt(); } }} catch (Exception e) {System.out.println("thread被打断");e.printStackTrace();} }服务端的思路其实也很简单,就是起一个简单的socket,不断地监听在某一个端口上,如果有新的请求到来就新起一个线程(ServiceThread)来处理请求
ServiceThread代码如下:
package com.alibaba.middleware.race.rpc.api.impl;import java.io.*;import java.lang.reflect.Method;import java.net.Socket;import java.util.Map;import java.util.Set;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;import java.util.concurrent.TimeoutException;import com.alibaba.middleware.race.rpc.context.RpcContext;import com.alibaba.middleware.race.rpc.model.RpcRequest;import com.alibaba.middleware.race.rpc.model.RpcResponse;//RpcContext需要对props实现ThreadLocal以保证线程的同步访问public class ServiceThread implements Runnable{public ServiceThread(final Socket s,final Object service){this.s = s;this.service = service;}@Overridepublic void run() {try{try{ObjectInputStream input = new ObjectInputStream(s.getInputStream()); try{RpcRequest request = (RpcRequest)input.readObject();RpcContext.localMap.set(request.getContextMap());ObjectOutputStream output = new ObjectOutputStream(s.getOutputStream());Method method = service.getClass().getMethod(request.getMethodName(), request.getParameterTypes());RpcResponse response = new RpcResponse();try{Object result = method.invoke(service, request.getArgs());response.setAppResponse(result);output.writeObject(response);}catch(Throwable t){response.setThrowable(t);output.writeObject(response);}finally{output.close();}}finally{input.close();}}finally{s.close();}}catch(Exception e){e.printStackTrace();}}final Socket s;final Object service;}
基本的原理大概就是这样,最后再讲一下一些实现的细节以及优化:
1、如何实现异步调用:由于java没有函数指针,因此可以通过在注册表中注册回调接口的方式来实现。
2、对于更加复杂的RPC框架,会使用Zookeeper来实现注册中心。
3、在程序中使用的网络io模型是同步阻塞的io模型,在比赛中,我还基于netty框架尝试了同步非阻塞的网络io模型,这个会在接下来再细讲。
4、序列化的方法使用的是java自带的序列化,这其实是十分低效的一种方式。实际上可以使用kryo、protobuf、fst等更加高效的序列库。
- 阿里巴巴中间件性能挑战赛(RPC篇 同步阻塞模型)
- 阿里巴巴中间件性能挑战赛(RPC篇 复杂版)
- 阿里巴巴中间件性能挑战赛(MOM篇)
- 2015年阿里巴巴中间件比赛rpc框架
- [笔记]2016阿里中间件性能挑战赛(一)
- [笔记]2016阿里中间件性能挑战赛(二)
- [笔记]2016阿里中间件性能挑战赛(三)
- Windows IO模型-Select模型(同步阻塞模型)
- 网络IO模型(同步异步,阻塞非阻塞)
- IO模型(同步,异步,阻塞,非阻塞)
- IO模型:同步、异步、阻塞、非阻塞
- linux c++ socket 网络编程(1)同步阻塞、非阻塞模型
- IO的五种模型(关于同步与异步,阻塞与非阻塞)
- (转)socket阻塞与非阻塞,同步与异步、I/O模型
- IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)
- IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)
- IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)
- IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇) .
- dos写helloworld程序总结 java 和 javac的使用
- SQLLite保存单词到本地数据库中
- js 为label标签和div标签赋值
- Android greenDao 数据库的使用(一)
- web.xml中load-on-startup的作用
- 阿里巴巴中间件性能挑战赛(RPC篇 同步阻塞模型)
- Binary Tree Maximum Path Sum
- 最大子序列
- 浅谈JSON中stringify 函数、toJosn函数和parse函数
- tomcat虚拟路径发布网站
- Swift学习笔记番外篇1——桥接C源文件,实现控制台输入
- Ember.js 入门指南——路由重定向
- TI NDK应用开发过程中的一点经验及改进
- Tomcat启动提示