RPC(Remote Process Call) 远程过程调用,是分布式服务调用的流行的解决方案,现在比较流行的RPC框架有Google的grpc,脸谱的Thrift,Hadoop的子项目Avro-RPC等。
作为RPC的菜鸟,该怎么建立起对RPC的初步认识呢,那就亲自手动的自己敲一个简单的RPC实现,对RPC的理解有很大的帮助。下面是自己模拟简单实现RPC的实现,代码简陋,适合初学(最好自己使用过RPC技术)。
建立两个项目RpcService,RpcClient
一、Rpc服务端(RpcService)
服务器端建立对外提供服务的service,rpc的发布,开启服务,对应的包结构:
com.hhh.rpc.service –服务提供
com.hhh.rpc.publish –服务发布
com.hhh.app –服务开启
1、 对外提供的服务
1.1 服务接口
package com.hhh.rpc.service;
/**
* Created by 泡泡 on 2017/3/23.
* 服务提供
*/
public interface GreetService {
String greet(String msg);
}
1.2 服务实现
package com.hhh.rpc.service.impl;
import com.hhh.rpc.service.GreetService;
/**
* Created by 泡泡 on 2017/3/23.
* 服务实现
*/
public class GreetServiceImpl implements GreetService {
@Override
public String greet(String msg) {
return "servive greeting:" + msg != null ? msg :"Hello RPC!";
}
}
2、服务发布
服务发布利用传统的阻塞Socket通信
package com.hhh.rpc.publish;
import java.io.*;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by 泡泡 on 2017/3/23.
* 服务发布
*/
public class RpcPublish {
private static final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
/**
* host:对外暴露的服务地址
* port:服务端口
**/
public static void publish(String host,int port){
ServerSocket server = null;
try{
server = new ServerSocket();
server.bind(new InetSocketAddress(host,port));
while (true){
executor.execute(new PublishTask(server.accept()));
}
}catch(Exception e){
e.printStackTrace();
}finally {
try {
if(server != null){
server.close();
}
} catch (IOException e) {
}
}
}
/**
* 利用线程池方式还处理客户端的服务调用
*
* 服务调用处理任务
*/
private static final class PublishTask implements Runnable{
private Socket socket;
@Override
public void run() {
ObjectInputStream is = null;
ObjectOutputStream os = null;
try{
is = new ObjectInputStream(socket.getInputStream());
//读取类名并获取对应的类的类
Class<?> service = Class.forName(is.readUTF());
//读取调用的方法名
String methodName = is.readUTF();
//读取参数类型
Class<?>[] paramTypes = (Class<?>[])is.readObject();
Method method = service.getMethod(methodName,paramTypes);
//读取参数列表
Object[] args = (Object[]) is.readObject();
//方法调用返回的结果
Object result = method.invoke(service.newInstance(),args);
System.out.println(service.getName()+"."+methodName+"("+paramTypes[0].toString()+" "+args[0].toString()+")");
os = new ObjectOutputStream(socket.getOutputStream());
//结果写回
os.writeObject(result);
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if(is !=null){
is.close();
}
if(os != null){
os.close();
}
if(socket != null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public PublishTask(Socket socket){
this.socket = socket;
}
}
}
3、服务开启
package com.hhh.app;
import com.hhh.rpc.publish.RpcPublish;
/**
* Created by 泡泡 on 2017/3/23.
*/
public class ServiceApp {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
RpcPublish.publish("127.0.0.1",8888);
}
},"service").start();
System.out.println("server start...");
while (true){
}
}
二、RPC消费端(RpcClient)
模拟调用RpcService提供的服务,包结构如下:
com.hhh.rpc.service –相当于远程服务对外暴露的API
com.hhh.rpc.client–本地调用远程服务发现
com.hhh.app –消费远程服务
1、远程服务的API
package com.hhh.rpc.service;
/**
* Created by 泡泡 on 2017/3/23.
* 远程服务的API
*/
public interface GreetService {
String greet(String msg);
}
2、本地调用远程服务发现
package com.hhh.rpc.client;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* Created by 泡泡 on 2017/3/23.
* 远程服务发现
* T 远程服务代理
*/
public class RpcClient<T> {
/**
*
* @param className 远程服务的实现类
* @param service 远程调用的API
* @param address 远程服务地址
* @return
*/
public T subscribe(final String className,final Class<?> service, final InetSocketAddress address){
System.out.println(service.getInterfaces().length);
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{
service},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = null;
ObjectInputStream is = null;
ObjectOutputStream os =null;
try {
socket = new Socket();
socket.connect(address);
os = new ObjectOutputStream(socket.getOutputStream());
//远程调用的实现类
os.writeUTF(className);
//调用的方法
os.writeUTF(method.getName());
//方法的参数类型列表
os.writeObject(method.getParameterTypes());
//方法参数列表
os.writeObject(args);
//返回远程调用结果
is = new ObjectInputStream(socket.getInputStream());
return is.readObject();
}catch(Exception e){
e.printStackTrace();
}finally {
if(os != null){
os.close();
}
if(is != null){
is.close();
}
if(socket != null){
socket.close();
}
}
return null;
}
});
}
}
3、远程服务调用
package com.hhh.app;
import com.hhh.rpc.client.RpcClient;
import com.hhh.rpc.service.GreetService;
import java.net.InetSocketAddress;
/**
* Created by 泡泡 on 2017/3/23.
* 远程服务调用
*/
public class ClientApp {
/**
* 服务的发现有很多种,服务的拉取在这里这里写死了
*/
private static final String SERVER_NAME = "com.hhh.rpc.service.impl.GreetServiceImpl";
public static void main(String[] args) {
RpcClient<GreetService> client = new RpcClient<>();
GreetService greet = client.subscribe(SERVER_NAME,GreetService.class,new InetSocketAddress("127.0.0.1",8888));
System.out.println(greet.greet("I'm coming! RPC"));
}
}
三、运行
先启动服务(运行ServiceApp),运行结果:
server start...
运行客户端(ClientApp),运行结果:
0
servive greeting:I'm coming! RPC
这时你会发现服务端还会打印一句话,说明远程服务确实调用了
com.hhh.rpc.service.impl.GreetServiceImpl.greet(class java.lang.String I'm coming! RPC)
好了,简单的RPC框架的实现就结束了。
总结如下:
1. 服务端的发布,通过不同的协议来处理消费服务的请求,通过反射来来实现服务的调用,本实例的使用socket通信。
2. 消费服务端需要知道服务提供的那些服务(也就是对外的API)以及服务发现
这里就不多细说RPC框架的细节,本篇只作为RPC新手的引导和认识一下RPC,更多RPC框架内容请期待下期。