分布式技术的本质可以简单地概括为两部分—— 任务拆分 + RPC
1. 将一个任务流程拆分成几个部分,需要扛压的部分做成集群
2. RPC调用 (特别是通信)
再概括点说分布式的本质就是RPC,也不算过分。
我们手写一个RPC。
一、 RPC框架
以本次Demo代码为例, 讲解下RPC框架和流程。
Server端通过publish()发布一个可用的计算服务CalculateService,告诉别人我可以提供计算服务。
1. Client端说:“我要计算!” 它通过动态代理技术得到Server端发布的CalculateService服务的动态代理,就可以调用Server端的计算服务了,然后它说要算一个 “ 3 + 8 ” 。(因为网络只能传二进制,所以它们说话就要经过序列化和反序列化来转换)
2. Server端接收Client端发送来的计算请求信息,然后调用加法功能算出 3 + 8 = 11,将结果返回给Client端。
3. Client端接收到返回的结果11,一次RPC调用就愉快地完成了。
三、 代码
1. Server端
(1)声明计算服务接口
package rpcserver;
public interface CalculateService {
public int add(int param1, int param2);
public int minus(int param1, int parm2);
public int multi(int param1, int param2);
public int divide(int param1, int param2);
}
(2)计算服务实现,真正用来干活的部分
package rpcserver;
public class CalculateServiceImpl implements CalculateService {
@Override
public int add(int param1, int param2) {
return param1 + param2;
}
@Override
public int minus(int param1, int param2) {
return param1 - param2;
}
@Override
public int multi(int param1, int param2) {
return param1 * param2;
}
@Override
public int divide(int param1, int param2) {
if (0 == param2){
System.out.println("invalid param " + param2);
return 0;
}
return param1/param2;
}
}
(3)封装请求类 (为了方便信息发送和演示序列化)
package rpcserver;
import java.io.Serializable;
public class Request implements Serializable {
private static final long serialVersionUID = 123456L;
private String methodName;
private Class<?>[] argTypes;
private Object args;
public Request(String methodName, Object args) {
this.methodName = methodName;
this.args = args;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class<?>[] getArgeTypes() {
return argTypes;
}
public void setArgeTypes(Class<?>[] argeTypes) {
this.argTypes = argeTypes;
}
public Object getArgs() {
return args;
}
public void setArgs(Object args) {
this.args = args;
}
}
(4)Server端主框架,用来接收Client端信息和调用本地服务
package rpcserver;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ServerProcedure {
private static ExecutorService pool = Executors.newFixedThreadPool(10);
public static void publish(Object service, int port){
try {
ServerSocket serverSocket = new ServerSocket(port);
Socket socket = null;
while (true){
//不断获得socket连接
socket = serverSocket.accept();
pool.execute(new Handler(service, socket));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
pool.shutdown();
}
}
private static class Handler implements Runnable{
private Object service;
private Socket socket;
public Handler(Object service, Socket socket){
this.service = service;
this.socket = socket;
}
@Override
public void run() {
ObjectInputStream ois = null;
ObjectOutputStream oos = null;
try {
//get参数,反序列化
ois = new ObjectInputStream(socket.getInputStream());
//调用
Object result = doInvoke(ois.readObject());
//返回结果
oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(result);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
try {
ois.close();
oos.close();
if (null != socket){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private Object doInvoke(Object request){
Request req = (Request)request;
String methodName = req.getMethodName();
Class<?>[] argeTypes = req.getArgeTypes();
Object[] args = (Object[]) req.getArgs();
try {
Method method = service.getClass().getMethod(methodName, argeTypes); //利用反射取得method
Object result = method.invoke(service, args);
return result;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
}
(5)Server端main方法
package rpcserver;
public class ServerDemo {
public static void main(String[] args) {
CalculateService cal = new CalculateServiceImpl();
ServerProcedure.publish(cal, 8888);
}
}
2. Client端
(1)计算服务接口声明(其实这些应写在三方包里)
package rpcserver;
public interface CalculateService {
int add(int param1, int param2);
int minus(int param1, int parm2);
int multi(int param1, int param2);
int divide(int param1, int param2);
}
(2)请求类
package rpcserver;
import java.io.Serializable;
public class Request implements Serializable {
private static final long serialVersionUID = 123456L;
private String methodName;
private Class<?>[] argTypes;
private Object args;
public Request(String methodName, Object args) {
this.methodName = methodName;
this.args = args;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class<?>[] getArgeTypes() {
return argTypes;
}
public void setArgeTypes(Class<?>[] argeTypes) {
this.argTypes = argeTypes;
}
public Object getArgs() {
return args;
}
public void setArgs(Object args) {
this.args = args;
}
}
(3)生成动态代理和与Server端交互信息
package rpcserver;
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.Socket;
public class ClientProxy {
private static String serverHost;
private static int serverPort;
private static class Handler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
Socket socket = null;
Object result = null;
//serialize
Request request = new Request(method.getName(), args);
request.setArgeTypes(method.getParameterTypes());
try {
//get socket, send request
socket = new Socket(serverHost, serverPort);
oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(request);
//get result
ois = new ObjectInputStream(socket.getInputStream());
result = ois.readObject();
}catch (Exception e){
e.printStackTrace();
}finally {
oos.close();
ois.close();
if (null != socket){
socket.close();
}
}
return result;
}
}
public static <T> T getProxy(Class<T> classValue, String host, int port){
serverHost = host;
serverPort = port;
Object proxy = Proxy.newProxyInstance(classValue.getClassLoader(), new Class[]{classValue}, new Handler());
return (T) proxy;
}
}
(4)Client端main方法
package rpcserver;
public class ClientDemo {
public static void main(String[] args) {
CalculateService proxy = ClientProxy.getProxy(CalculateService.class, "localhost", 8888);
Object result = proxy.add(8, 3);
System.out.println("the result = " + result.toString());
}
}
调用结果
其实Client端也可以不用写动态代理,直接将方法名和参数以字符串的格式发送到Server端也能解析还原,但是需要手动封装方法名和参数,不如使用动态代理直接调用方便。