Rpc框架
简介
RPC (Remote Procedure Call)即远程过程调用,是分布式系统常见的一种通信方法。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。
简单的说:
- RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
- RPC会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯)。
- 客户端发起请求,服务器返回响应(类似于Http的工作方式)RPC在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。
最终解决的问题:
**让分布式或者微服务系统中不同服务之间的调用(远程调用)像本地调用一样简单!**调用者感知不到远程调用的逻辑。为此rpc需要解决三个问题(实现的关键)
Call ID映射
我们怎么告诉远程机器(注册中心)我们要调用哪个函数呢?
在本地调用中,函数体是直接通过函数指针来指定的,我们调用具体函数,编译器就自动帮我们调用它相应的函数指针。
但是在远程调用中,是无法调用函数指针的,因为两个进程的地址空间是完全不一样。所以,在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个 {函数 <–> Call ID} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,它就查一下这个表,找出相应的Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
序列化和反序列化客户端怎么把参数值传给远程的函数呢?
在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。
但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
数据网络传输:
远程调用往往是基于网络的,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。
网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端*。只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了HTTP2。Java的Netty(基于NIO通信方式作为高性能网络服务的前提)也属于这层的东西。
采用Netty实现
需要解决的问题
- 数据在服务端和客户端之间传输需要遵守的协议怎么设置(解决粘包和半包的问题)?
可以查看上一篇文章(Netty协议的设计与解析)[]
编解码器
@Slf4j
@ChannelHandler.Sharable
/**
* 必须和 LengthFieldBasedFrameDecoder 一起使用,确保接到的 ByteBuf 消息是完整的
*/
public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, Message> {
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, List<Object> outList) throws Exception {
ByteBuf out = ctx.alloc().buffer();
// 1. 4 字节的魔数
out.writeBytes(new byte[]{1, 2, 3, 4});
// 2. 1 字节的版本,
out.writeByte(1);
// 3. 1 字节的序列化方式 jdk 0 , json 1
out.writeByte(Config.getSerializerAlgorithm().ordinal());
// 4. 1 字节的指令类型
out.writeByte(msg.getMessageType());
// 5. 4 个字节
out.writeInt(msg.getSequenceId());
// 无意义,对齐填充
out.writeByte(0xff);
// 6. 获取内容的字节数组
byte[] bytes = Config.getSerializerAlgorithm().serialize(msg);
// 7. 长度
out.writeInt(bytes.length);
// 8. 写入内容
out.writeBytes(bytes);
outList.add(out);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int magicNum = in.readInt();
byte version = in.readByte();
byte serializerType = in.readByte();
byte messageType = in.readByte();
int sequenceId = in.readInt();
in.readByte();
int length = in.readInt();
byte[] bytes = new byte[length];
in.readBytes(bytes, 0, length);
Class<?> messageClass = Message.getMessageClass(messageType);
Serializer.SerializerAlgorithm serializerAlgorithm = Serializer.SerializerAlgorithm.values()[serializerType];
Object message = serializerAlgorithm.deserialize(messageClass, bytes);
// log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);
// log.debug("{}", message);
out.add(message);
}
}
- 数据在传输和接收时的解码方式怎么规定?
定义编码和解码接口,设置不同的编码和解码方式,并且实现配置文件设置
序列化和反序列化方法接口
public interface Serializer {
// 反序列化方法
<T> T deserialize(Class<T> clazz, byte[] bytes);
// 序列化方法
<T> byte[] serialize(T object);
enum SerializerAlgorithm implements Serializer{
Java{
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
Object obj = ois.readObject();
return (T) obj;
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("SerializerAlgorithm.Java 反序列化异常", e);
}
}
@Override
public <T> byte[] serialize(T object) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
return bos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("SerializerAlgorithm.Java 序列化错误", e);
}
}
},
Json{
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
Gson gson = new GsonBuilder().registerTypeAdapter(Class.class,new Serializer.ClassCodec()).create();
return gson.fromJson(new String(bytes, StandardCharsets.UTF_8), clazz);
}
@Override
public <T> byte[] serialize(T object) {
Gson gson = new GsonBuilder().registerTypeAdapter(Class.class,new Serializer.ClassCodec()).create();
return gson.toJson(object).getBytes(StandardCharsets.UTF_8);
}
}
}
// 解决gjson无法直接解析 Class类型的参数
class ClassCodec implements JsonSerializer<Class<?>>, JsonDeserializer<Class<?>> {
// 反序列化
@SneakyThrows
@Override
public Class<?> deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
String clazz = jsonElement.getAsString();
return Class.forName(clazz);
}
// 序列化
@Override
public JsonElement serialize(Class<?> aClass, Type type, JsonSerializationContext jsonSerializationContext) {
// 将 Class 变为 json
return new JsonPrimitive(aClass.getName());
}
}
// 需要从协议的字节中得到是哪种序列化算法
public static SerializerAlgorithm getByInt(int type) {
SerializerAlgorithm[] array = SerializerAlgorithm.values();
if (type < 0 || type > array.length - 1) {
throw new IllegalArgumentException("超过 SerializerAlgorithm 范围");
}
return array[type];
}
}
实现配置文件设置
public abstract class Config {
static Properties properties;
static {
try (InputStream in = Config.class.getResourceAsStream("/application.properties")) {
properties = new Properties();
properties.load(in);
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
public static int getServerPort() {
String value = properties.getProperty("server.port");
if(value == null) {
return 8080;
} else {
return Integer.parseInt(value);
}
}
public static Serializer.SerializerAlgorithm getSerializerAlgorithm() {
String value = properties.getProperty("serializer.algorithm");
if(value == null) {
return Serializer.SerializerAlgorithm.Java;
} else {
return Serializer.SerializerAlgorithm.valueOf(value);
}
}
}
在resource文件夹下创建配置文件application.properties
# 采用json编码
serializer.algorithm=Json
# 填入自己发送方法的全类名和实现类具体位置
cn.itcast.rpc.HelloService=cn.itcast.rpc.HelloServiceImpl
- 怎么将需要执行的操作告诉给服务器让它帮我们调用?
定义一个代理类,调用方法就是发送需要的参数给服务端进行调用
代理类
public static void main(String[] args) {
HelloService helloService = getProxyService(HelloService.class);
System.out.println(helloService.sayHello("张三"));
System.out.println(helloService.sayHello("李四"));
}
public static AtomicInteger sequenceIdGen = new AtomicInteger(0);
public static <T> T getProxyService(Class<T> serviceClass){
// 获取类类加载器
ClassLoader classLoader = serviceClass.getClassLoader();
// 获取类接口
Class<Object>[] interfaces = new Class[]{serviceClass};
Object o = Proxy.newProxyInstance(classLoader, interfaces, (proxy, method, args) -> {
int sequenceId = sequenceIdGen.getAndIncrement();
RpcRequestMessage requestMessage = new RpcRequestMessage(
sequenceId,
serviceClass.getName(),
method.getName(),
method.getReturnType(),
method.getParameterTypes(),
args
);
// 发送消息
getChannel().writeAndFlush(requestMessage);
DefaultPromise<Object> promise = new DefaultPromise<>(getChannel().eventLoop());
RpcResponseMessageHandler.PROMISES.put(sequenceId, promise);
// 等待请求响应
promise.await();
if (promise.isSuccess()) {
// 调用正常
return promise.getNow();
} else {
// 调用失败
throw new RuntimeException(promise.cause());
}
});
return (T) o;
}
搭建Netty框架
服务器:
@Slf4j
public class RpcServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
// 日志处理器
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
// 信息解码处理器
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
// rpc 请求消息处理器,待实现
RpcRequestMessageHandler RPC_HANDLER = new RpcRequestMessageHandler();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.group(boss, worker);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 定义接收安装协议接收
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 12, 4, 0, 0));
// 日志处理器
ch.pipeline().addLast(LOGGING_HANDLER);
// 消息编码和解码
ch.pipeline().addLast(MESSAGE_CODEC);
// rpc消息解析
ch.pipeline().addLast(RPC_HANDLER);
}
});
Channel channel = serverBootstrap.bind(8080).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
客户端:
@Slf4j
public class RpcClient {
public static void main(String[] args) {
HelloService helloService = getProxyService(HelloService.class);
System.out.println(helloService.sayHello("张三"));
System.out.println(helloService.sayHello("李四"));
}
public static AtomicInteger sequenceIdGen = new AtomicInteger(0);
public static <T> T getProxyService(Class<T> serviceClass){
// 获取类类加载器
ClassLoader classLoader = serviceClass.getClassLoader();
// 获取类接口
Class<Object>[] interfaces = new Class[]{serviceClass};
Object o = Proxy.newProxyInstance(classLoader, interfaces, (proxy, method, args) -> {
int sequenceId = sequenceIdGen.getAndIncrement();
RpcRequestMessage requestMessage = new RpcRequestMessage(
sequenceId,
serviceClass.getName(),
method.getName(),
method.getReturnType(),
method.getParameterTypes(),
args
);
// 发送消息
getChannel().writeAndFlush(requestMessage);
DefaultPromise<Object> promise = new DefaultPromise<>(getChannel().eventLoop());
RpcResponseMessageHandler.PROMISES.put(sequenceId, promise);
// 等待请求响应
promise.await();
if (promise.isSuccess()) {
// 调用正常
return promise.getNow();
} else {
// 调用失败
throw new RuntimeException(promise.cause());
}
});
return (T) o;
}
// 将channel设置成单例
public static Channel channel = null;
private static final Object LOCK = new Object();
public static Channel getChannel(){
if (channel != null){
return channel;
}else {
synchronized (LOCK){
if (channel !=null){
return channel;
}
initChannel();
return channel;
}
}
}
// 只进行初始化channel
private static void initChannel() {
NioEventLoopGroup group = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
RpcResponseMessageHandler RPC_HANDLER = new RpcResponseMessageHandler();
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(group);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProcotolFrameDecoder());
ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
ch.pipeline().addLast(RPC_HANDLER);
}
});
try {
channel = bootstrap.connect("localhost", 8080).sync().channel();
channel.closeFuture().addListener(future->{
group.shutdownGracefully();
});
} catch (Exception e) {
log.error("client error", e);
}
}
}
**Rpc请求消息:RpcRequestMessage
**
@Getter
@ToString(callSuper = true)
public class RpcRequestMessage extends Message {
/**
* 调用的接口全限定名,服务端根据它找到实现
*/
private String interfaceName;
/**
* 调用接口中的方法名
*/
private String methodName;
/**
* 方法返回类型
*/
private Class<?> returnType;
/**
* 方法参数类型数组
*/
private Class[] parameterTypes;
/**
* 方法参数值数组
*/
private Object[] parameterValue;
public RpcRequestMessage(int sequenceId, String interfaceName, String methodName, Class<?> returnType, Class[] parameterTypes, Object[] parameterValue) {
super.setSequenceId(sequenceId);
this.interfaceName = interfaceName;
this.methodName = methodName;
this.returnType = returnType;
this.parameterTypes = parameterTypes;
this.parameterValue = parameterValue;
}
@Override
public int getMessageType() {
return RPC_MESSAGE_TYPE_REQUEST;
}
}
**Rpc响应消息:RpcResponseMessage
**
@Data
@ToString(callSuper = true)
public class RpcResponseMessage extends Message {
/**
* 返回值
*/
private Object returnValue;
/**
* 异常值
*/
private Exception exceptionValue;
@Override
public int getMessageType() {
return RPC_MESSAGE_TYPE_RESPONSE;
}
}
**Rpc请求消息处理器:RpcRequestMessageHandler
**
@ChannelHandler.Sharable
@Slf4j
public class RpcRequestMessageHandler extends SimpleChannelInboundHandler<RpcRequestMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcRequestMessage msg) throws Exception {
RpcResponseMessage response = new RpcResponseMessage();
response.setSequenceId(msg.getSequenceId());
try {
HelloService service = (HelloService) ServicesFactory.getService(Class.forName(msg.getInterfaceName()));
Method method = service.getClass().getMethod(msg.getMethodName(),msg.getParameterTypes());
Object invoke = method.invoke(service, msg.getParameterValue());
response.setReturnValue(invoke);
} catch (Exception e) {
e.printStackTrace();
response.setExceptionValue(new Exception("调用方法错误!"+e.getCause().getCause()));
}
ctx.writeAndFlush(response);
}
}
**Rpc响应消息处理器:RpcResponseMessageHandler
**
@Slf4j
@ChannelHandler.Sharable
public class RpcResponseMessageHandler extends SimpleChannelInboundHandler<RpcResponseMessage> {
public static Map<Integer , Promise<Object>> PROMISES = new ConcurrentHashMap<>();
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcResponseMessage msg) throws Exception {
int sequenceId = msg.getSequenceId();
Promise<Object> promise = PROMISES.get(sequenceId);
// 获取到promise
if (promise != null){
Object returnValue = msg.getReturnValue();
Exception exceptionValue = msg.getExceptionValue();
if (msg.getExceptionValue() !=null){
promise.setFailure(exceptionValue);
}
promise.setSuccess(returnValue);
}
}
}
测试调用服务器的方法:
// 接口
public interface HelloService {
String sayHello(String name);
}
// 实现类
public class HelloServiceImpl implements HelloService{
@Override
public String sayHello(String name) {
return name + " 你好!";
}
}
服务端测试结果:
lue();
Exception exceptionValue = msg.getExceptionValue();
if (msg.getExceptionValue() !=null){
promise.setFailure(exceptionValue);
}
promise.setSuccess(returnValue);
}
}
}
**测试调用服务器的方法:**
```java
// 接口
public interface HelloService {
String sayHello(String name);
}
// 实现类
public class HelloServiceImpl implements HelloService{
@Override
public String sayHello(String name) {
return name + " 你好!";
}
}
服务端测试结果: