通用调用

java 从零开始手写 RPC (01) 基于 socket 实现

java 从零开始手写 RPC (02)-netty4 实现客户端和服务端

java 从零开始手写 RPC (03) 如何实现客户端调用服务端?

java 从零开始手写 RPC (04) -序列化

上一篇我们介绍了,如何实现基于反射的通用服务端。

这一节我们来一起学习下如何实现通用客户端。

因为内容较多,所以拆分为 2 个部分。

基本思路

所有的方法调用,基于反射进行相关处理实现。

核心类

为了便于拓展,我们把核心类调整如下:

package com.github.houbb.rpc.client.core;

import com.github.houbb.heaven.annotation.ThreadSafe;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.rpc.client.core.context.RpcClientContext;
import com.github.houbb.rpc.client.handler.RpcClientHandler;
import com.github.houbb.rpc.common.constant.RpcConstant;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; /**
* <p> rpc 客户端 </p>
*
* <pre> Created: 2019/10/16 11:21 下午 </pre>
* <pre> Project: rpc </pre>
*
* @author houbinbin
* @since 0.0.2
*/
@ThreadSafe
public class RpcClient { private static final Log log = LogFactory.getLog(RpcClient.class); /**
* 地址信息
* @since 0.0.6
*/
private final String address; /**
* 监听端口号
* @since 0.0.6
*/
private final int port; /**
* 客户端处理 handler
* 作用:用于获取请求信息
* @since 0.0.4
*/
private final ChannelHandler channelHandler; public RpcClient(final RpcClientContext clientContext) {
this.address = clientContext.address();
this.port = clientContext.port();
this.channelHandler = clientContext.channelHandler();
} /**
* 进行连接
* @since 0.0.6
*/
public ChannelFuture connect() {
// 启动服务端
log.info("RPC 服务开始启动客户端"); EventLoopGroup workerGroup = new NioEventLoopGroup(); /**
* channel future 信息
* 作用:用于写入请求信息
* @since 0.0.6
*/
ChannelFuture channelFuture;
try {
Bootstrap bootstrap = new Bootstrap();
channelFuture = bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<Channel>(){
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline()
// 解码 bytes=>resp
.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)))
// request=>bytes
.addLast(new ObjectEncoder())
// 日志输出
.addLast(new LoggingHandler(LogLevel.INFO))
.addLast(channelHandler);
}
})
.connect(address, port)
.syncUninterruptibly();
log.info("RPC 服务启动客户端完成,监听地址 {}:{}", address, port);
} catch (Exception e) {
log.error("RPC 客户端遇到异常", e);
throw new RuntimeException(e);
}
// 不要关闭线程池!!! return channelFuture;
} }

可以灵活指定对应的服务端地址、端口信息。

ChannelHandler 作为处理参数传入。

ObjectDecoder、ObjectEncoder、LoggingHandler 都和服务端类似,是 netty 的内置实现。

RpcClientHandler

客户端的 handler 实现如下:

/*
* Copyright (c) 2019. houbinbin Inc.
* rpc All rights reserved.
*/ package com.github.houbb.rpc.client.handler; import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.rpc.client.core.RpcClient;
import com.github.houbb.rpc.client.invoke.InvokeService;
import com.github.houbb.rpc.common.rpc.domain.RpcResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; /**
* <p> 客户端处理类 </p>
*
* <pre> Created: 2019/10/16 11:30 下午 </pre>
* <pre> Project: rpc </pre>
*
* @author houbinbin
* @since 0.0.2
*/
public class RpcClientHandler extends SimpleChannelInboundHandler { private static final Log log = LogFactory.getLog(RpcClient.class); /**
* 调用服务管理类
*
* @since 0.0.6
*/
private final InvokeService invokeService; public RpcClientHandler(InvokeService invokeService) {
this.invokeService = invokeService;
} @Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
RpcResponse rpcResponse = (RpcResponse)msg;
invokeService.addResponse(rpcResponse.seqId(), rpcResponse);
log.info("[Client] response is :{}", rpcResponse);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// 每次用完要关闭,不然拿不到response,我也不知道为啥(目测得了解netty才行)
// 个人理解:如果不关闭,则永远会被阻塞。
ctx.flush();
ctx.close();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}

只有 channelRead0 做了调整,基于 InvokeService 对结果进行处理。

InvokeService

接口

package com.github.houbb.rpc.client.invoke;

import com.github.houbb.rpc.common.rpc.domain.RpcResponse;

/**
* 调用服务接口
* @author binbin.hou
* @since 0.0.6
*/
public interface InvokeService { /**
* 添加请求信息
* @param seqId 序列号
* @return this
* @since 0.0.6
*/
InvokeService addRequest(final String seqId); /**
* 放入结果
* @param seqId 唯一标识
* @param rpcResponse 响应结果
* @return this
* @since 0.0.6
*/
InvokeService addResponse(final String seqId, final RpcResponse rpcResponse); /**
* 获取标志信息对应的结果
* @param seqId 序列号
* @return 结果
* @since 0.0.6
*/
RpcResponse getResponse(final String seqId); }

主要是对入参、出参的设置,以及出参的获取。

实现

package com.github.houbb.rpc.client.invoke.impl;

import com.github.houbb.heaven.util.guava.Guavas;
import com.github.houbb.heaven.util.lang.ObjectUtil;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.rpc.client.core.RpcClient;
import com.github.houbb.rpc.client.invoke.InvokeService;
import com.github.houbb.rpc.common.exception.RpcRuntimeException;
import com.github.houbb.rpc.common.rpc.domain.RpcResponse; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; /**
* 调用服务接口
* @author binbin.hou
* @since 0.0.6
*/
public class DefaultInvokeService implements InvokeService { private static final Log LOG = LogFactory.getLog(DefaultInvokeService.class); /**
* 请求序列号集合
* (1)这里后期如果要添加超时检测,可以添加对应的超时时间。
* 可以把这里调整为 map
* @since 0.0.6
*/
private final Set<String> requestSet; /**
* 响应结果
* @since 0.0.6
*/
private final ConcurrentHashMap<String, RpcResponse> responseMap; public DefaultInvokeService() {
requestSet = Guavas.newHashSet();
responseMap = new ConcurrentHashMap<>();
} @Override
public InvokeService addRequest(String seqId) {
LOG.info("[Client] start add request for seqId: {}", seqId);
requestSet.add(seqId);
return this;
} @Override
public InvokeService addResponse(String seqId, RpcResponse rpcResponse) {
// 这里放入之前,可以添加判断。
// 如果 seqId 必须处理请求集合中,才允许放入。或者直接忽略丢弃。
LOG.info("[Client] 获取结果信息,seq: {}, rpcResponse: {}", seqId, rpcResponse);
responseMap.putIfAbsent(seqId, rpcResponse); // 通知所有等待方
LOG.info("[Client] seq 信息已经放入,通知所有等待方", seqId); synchronized (this) {
this.notifyAll();
} return this;
} @Override
public RpcResponse getResponse(String seqId) {
try {
RpcResponse rpcResponse = this.responseMap.get(seqId);
if(ObjectUtil.isNotNull(rpcResponse)) {
LOG.info("[Client] seq {} 对应结果已经获取: {}", seqId, rpcResponse);
return rpcResponse;
} // 进入等待
while (rpcResponse == null) {
LOG.info("[Client] seq {} 对应结果为空,进入等待", seqId);
// 同步等待锁
synchronized (this) {
this.wait();
} rpcResponse = this.responseMap.get(seqId);
LOG.info("[Client] seq {} 对应结果已经获取: {}", seqId, rpcResponse);
} return rpcResponse;
} catch (InterruptedException e) {
throw new RpcRuntimeException(e);
}
}
}

使用 requestSet 存储对应的请求入参。

使用 responseMap 存储对应的请求出参,在获取的时候通过同步 while 循环等待,获取结果。

此处,通过 notifyAll() 和 wait() 进行等待和唤醒。

ReferenceConfig-服务端配置

说明

我们想调用服务端,首先肯定要定义好要调用的对象。

ReferenceConfig 就是要告诉 rpc 框架,调用的服务端信息。

接口

package com.github.houbb.rpc.client.config.reference;

import com.github.houbb.rpc.common.config.component.RpcAddress;

import java.util.List;

/**
* 引用配置类
*
* 后期配置:
* (1)timeout 调用超时时间
* (2)version 服务版本处理
* (3)callType 调用方式 oneWay/sync/async
* (4)check 是否必须要求服务启动。
*
* spi:
* (1)codec 序列化方式
* (2)netty 网络通讯架构
* (3)load-balance 负载均衡
* (4)失败策略 fail-over/fail-fast
*
* filter:
* (1)路由
* (2)耗时统计 monitor 服务治理
*
* 优化思考:
* (1)对于唯一的 serviceId,其实其 interface 是固定的,是否可以省去?
* @author binbin.hou
* @since 0.0.6
* @param <T> 接口泛型
*/
public interface ReferenceConfig<T> { /**
* 设置服务标识
* @param serviceId 服务标识
* @return this
* @since 0.0.6
*/
ReferenceConfig<T> serviceId(final String serviceId); /**
* 服务唯一标识
* @since 0.0.6
*/
String serviceId(); /**
* 服务接口
* @since 0.0.6
* @return 接口信息
*/
Class<T> serviceInterface(); /**
* 设置服务接口信息
* @param serviceInterface 服务接口信息
* @return this
* @since 0.0.6
*/
ReferenceConfig<T> serviceInterface(final Class<T> serviceInterface); /**
* 设置服务地址信息
* (1)单个写法:ip:port:weight
* (2)集群写法:ip1:port1:weight1,ip2:port2:weight2
*
* 其中 weight 权重可以不写,默认为1.
*
* @param addresses 地址列表信息
* @return this
* @since 0.0.6
*/
ReferenceConfig<T> addresses(final String addresses); /**
* 获取对应的引用实现
* @return 引用代理类
* @since 0.0.6
*/
T reference(); }

实现

package com.github.houbb.rpc.client.config.reference.impl;

import com.github.houbb.heaven.constant.PunctuationConst;
import com.github.houbb.heaven.util.common.ArgUtil;
import com.github.houbb.heaven.util.guava.Guavas;
import com.github.houbb.heaven.util.lang.NumUtil;
import com.github.houbb.rpc.client.config.reference.ReferenceConfig;
import com.github.houbb.rpc.client.core.RpcClient;
import com.github.houbb.rpc.client.core.context.impl.DefaultRpcClientContext;
import com.github.houbb.rpc.client.handler.RpcClientHandler;
import com.github.houbb.rpc.client.invoke.InvokeService;
import com.github.houbb.rpc.client.invoke.impl.DefaultInvokeService;
import com.github.houbb.rpc.client.proxy.ReferenceProxy;
import com.github.houbb.rpc.client.proxy.context.ProxyContext;
import com.github.houbb.rpc.client.proxy.context.impl.DefaultProxyContext;
import com.github.houbb.rpc.common.config.component.RpcAddress;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler; import java.util.List; /**
* 引用配置类默认实现
*
* @author binbin.hou
* @since 0.0.6
* @param <T> 接口泛型
*/
public class DefaultReferenceConfig<T> implements ReferenceConfig<T> { /**
* 服务唯一标识
* @since 0.0.6
*/
private String serviceId; /**
* 服务接口
* @since 0.0.6
*/
private Class<T> serviceInterface; /**
* 服务地址信息
* (1)如果不为空,则直接根据地址获取
* (2)如果为空,则采用自动发现的方式
*
* TODO: 这里调整为 set 更加合理。
*
* 如果为 subscribe 可以自动发现,然后填充这个字段信息。
* @since 0.0.6
*/
private List<RpcAddress> rpcAddresses; /**
* 用于写入信息
* (1)client 连接 server 端的 channel future
* (2)后期进行 Load-balance 路由等操作。可以放在这里执行。
* @since 0.0.6
*/
private List<ChannelFuture> channelFutures; /**
* 客户端处理信息
* @since 0.0.6
*/
@Deprecated
private RpcClientHandler channelHandler; /**
* 调用服务管理类
* @since 0.0.6
*/
private InvokeService invokeService; public DefaultReferenceConfig() {
// 初始化信息
this.rpcAddresses = Guavas.newArrayList();
this.channelFutures = Guavas.newArrayList();
this.invokeService = new DefaultInvokeService();
} @Override
public String serviceId() {
return serviceId;
} @Override
public DefaultReferenceConfig<T> serviceId(String serviceId) {
this.serviceId = serviceId;
return this;
} @Override
public Class<T> serviceInterface() {
return serviceInterface;
} @Override
public DefaultReferenceConfig<T> serviceInterface(Class<T> serviceInterface) {
this.serviceInterface = serviceInterface;
return this;
} @Override
public ReferenceConfig<T> addresses(String addresses) {
ArgUtil.notEmpty(addresses, "addresses"); String[] addressArray = addresses.split(PunctuationConst.COMMA);
ArgUtil.notEmpty(addressArray, "addresses"); for(String address : addressArray) {
String[] addressSplits = address.split(PunctuationConst.COLON);
if(addressSplits.length < 2) {
throw new IllegalArgumentException("Address must be has ip and port, like 127.0.0.1:9527");
}
String ip = addressSplits[0];
int port = NumUtil.toIntegerThrows(addressSplits[1]);
// 包含权重信息
int weight = 1;
if(addressSplits.length >= 3) {
weight = NumUtil.toInteger(addressSplits[2], 1);
} RpcAddress rpcAddress = new RpcAddress(ip, port, weight);
this.rpcAddresses.add(rpcAddress);
} return this;
} /**
* 获取对应的引用实现
* (1)处理所有的反射代理信息-方法可以抽离,启动各自独立即可。
* (2)启动对应的长连接
* @return 引用代理类
* @since 0.0.6
*/
@Override
public T reference() {
// 1. 启动 client 端到 server 端的连接信息
// 1.1 为了提升性能,可以将所有的 client=>server 的连接都调整为一个 thread。
// 1.2 初期为了简单,直接使用同步循环的方式。
// 创建 handler
// 循环连接
for(RpcAddress rpcAddress : rpcAddresses) {
final ChannelHandler channelHandler = new RpcClientHandler(invokeService);
final DefaultRpcClientContext context = new DefaultRpcClientContext();
context.address(rpcAddress.address()).port(rpcAddress.port()).channelHandler(channelHandler);
ChannelFuture channelFuture = new RpcClient(context).connect();
// 循环同步等待
// 如果出现异常,直接中断?捕获异常继续进行??
channelFutures.add(channelFuture);
} // 2. 接口动态代理
ProxyContext<T> proxyContext = buildReferenceProxyContext();
return ReferenceProxy.newProxyInstance(proxyContext);
} /**
* 构建调用上下文
* @return 引用代理上下文
* @since 0.0.6
*/
private ProxyContext<T> buildReferenceProxyContext() {
DefaultProxyContext<T> proxyContext = new DefaultProxyContext<>();
proxyContext.serviceId(this.serviceId);
proxyContext.serviceInterface(this.serviceInterface);
proxyContext.channelFutures(this.channelFutures);
proxyContext.invokeService(this.invokeService);
return proxyContext;
} }

这里主要根据指定的服务端信息,初始化对应的代理实现。

这里还可以拓展指定权重,便于后期负载均衡拓展,本期暂时不做实现。

ReferenceProxy

说明

所有的 rpc 调用,客户端只有服务端的接口。

那么,怎么才能和调用本地方法一样调用远程方法呢?

答案就是动态代理。

实现

实现如下:

package com.github.houbb.rpc.client.proxy;

import com.github.houbb.heaven.util.lang.ObjectUtil;
import com.github.houbb.heaven.util.lang.reflect.ReflectMethodUtil;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.rpc.client.proxy.context.ProxyContext;
import com.github.houbb.rpc.common.rpc.domain.RpcResponse;
import com.github.houbb.rpc.common.rpc.domain.impl.DefaultRpcRequest;
import com.github.houbb.rpc.common.support.id.impl.Uuid;
import com.github.houbb.rpc.common.support.time.impl.DefaultSystemTime;
import io.netty.channel.Channel; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; /**
* 参考:https://blog.csdn.net/u012240455/article/details/79210250
*
* (1)方法执行并不需要一定要有实现类。
* (2)直接根据反射即可处理相关信息。
* (3)rpc 是一种强制根据接口进行编程的实现方式。
* @author binbin.hou
* @since 0.0.6
*/
public class ReferenceProxy<T> implements InvocationHandler { private static final Log LOG = LogFactory.getLog(ReferenceProxy.class); /**
* 服务标识
* @since 0.0.6
*/
private final ProxyContext<T> proxyContext; /**
* 暂时私有化该构造器
* @param proxyContext 代理上下文
* @since 0.0.6
*/
private ReferenceProxy(ProxyContext<T> proxyContext) {
this.proxyContext = proxyContext;
} /**
* 反射调用
* @param proxy 代理
* @param method 方法
* @param args 参数
* @return 结果
* @throws Throwable 异常
* @since 0.0.6
* @see Method#getGenericSignature() 通用标识,可以根据这个来优化代码。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 反射信息处理成为 rpcRequest
final String seqId = Uuid.getInstance().id();
final long createTime = DefaultSystemTime.getInstance().time();
DefaultRpcRequest rpcRequest = new DefaultRpcRequest();
rpcRequest.serviceId(proxyContext.serviceId());
rpcRequest.seqId(seqId);
rpcRequest.createTime(createTime);
rpcRequest.paramValues(args);
rpcRequest.paramTypeNames(ReflectMethodUtil.getParamTypeNames(method));
rpcRequest.methodName(method.getName()); // 调用远程
LOG.info("[Client] start call remote with request: {}", rpcRequest);
proxyContext.invokeService().addRequest(seqId); // 这里使用 load-balance 进行选择 channel 写入。
final Channel channel = getChannel();
LOG.info("[Client] start call channel id: {}", channel.id().asLongText()); // 对于信息的写入,实际上有着严格的要求。
// writeAndFlush 实际是一个异步的操作,直接使用 sync() 可以看到异常信息。
// 支持的必须是 ByteBuf
channel.writeAndFlush(rpcRequest).sync(); // 循环获取结果
// 通过 Loop+match wait/notifyAll 来获取
// 分布式根据 redis+queue+loop
LOG.info("[Client] start get resp for seqId: {}", seqId);
RpcResponse rpcResponse = proxyContext.invokeService().getResponse(seqId);
LOG.info("[Client] start get resp for seqId: {}", seqId);
Throwable error = rpcResponse.error();
if(ObjectUtil.isNotNull(error)) {
throw error;
}
return rpcResponse.result();
} /**
* 获取对应的 channel
* (1)暂时使用写死的第一个
* (2)后期这里需要调整,ChannelFuture 加上权重信息。
* @return 对应的 channel 信息。
* @since 0.0.6
*/
private Channel getChannel() {
return proxyContext.channelFutures().get(0).channel();
} /**
* 获取代理实例
* (1)接口只是为了代理。
* (2)实际调用中更加关心 的是 serviceId
* @param proxyContext 代理上下文
* @param <T> 泛型
* @return 代理实例
* @since 0.0.6
*/
@SuppressWarnings("unchecked")
public static <T> T newProxyInstance(ProxyContext<T> proxyContext) {
final Class<T> interfaceClass = proxyContext.serviceInterface();
ClassLoader classLoader = interfaceClass.getClassLoader();
Class<?>[] interfaces = new Class[]{interfaceClass};
ReferenceProxy proxy = new ReferenceProxy(proxyContext);
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
} }

客户端初始化 newProxyInstance 的就是创建的代理的过程。

客户端调用远程方法,实际上是调用 invoke 的过程。

(1)构建反射 invoke 请求信息,添加 reqId

(2)netty 远程调用服务端

(3)同步获取响应信息

测试

引入 maven

<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>rpc-client</artifactId>
<version>0.0.6</version>
</dependency>

测试代码

public static void main(String[] args) {
// 服务配置信息
ReferenceConfig<CalculatorService> config = new DefaultReferenceConfig<CalculatorService>();
config.serviceId(ServiceIdConst.CALC);
config.serviceInterface(CalculatorService.class);
config.addresses("localhost:9527"); CalculatorService calculatorService = config.reference();
CalculateRequest request = new CalculateRequest();
request.setOne(10);
request.setTwo(20); CalculateResponse response = calculatorService.sum(request);
System.out.println(response);
}

测试日志:

[DEBUG] [2021-10-05 14:16:17.534] [main] [c.g.h.l.i.c.LogFactory.setImplementation] - Logging initialized using 'class com.github.houbb.log.integration.adaptors.stdout.StdOutExImpl' adapter.
[INFO] [2021-10-05 14:16:17.625] [main] [c.g.h.r.c.c.RpcClient.connect] - RPC 服务开始启动客户端
...
[INFO] [2021-10-05 14:16:19.328] [main] [c.g.h.r.c.c.RpcClient.connect] - RPC 服务启动客户端完成,监听地址 localhost:9527
[INFO] [2021-10-05 14:16:19.346] [main] [c.g.h.r.c.p.ReferenceProxy.invoke] - [Client] start call remote with request: DefaultRpcRequest{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', createTime=1633414579339, serviceId='calc', methodName='sum', paramTypeNames=[com.github.houbb.rpc.server.facade.model.CalculateRequest], paramValues=[CalculateRequest{one=10, two=20}]}
[INFO] [2021-10-05 14:16:19.347] [main] [c.g.h.r.c.i.i.DefaultInvokeService.addRequest] - [Client] start add request for seqId: a525c5a6196545f5a5241b2cdc2ec2c2
[INFO] [2021-10-05 14:16:19.348] [main] [c.g.h.r.c.p.ReferenceProxy.invoke] - [Client] start call channel id: 00e04cfffe360988-000017bc-00000000-399b9d7e1b88839d-5ccc4a29
十月 05, 2021 2:16:19 下午 io.netty.handler.logging.LoggingHandler write
信息: [id: 0x5ccc4a29, L:/127.0.0.1:50596 - R:localhost/127.0.0.1:9527] WRITE: DefaultRpcRequest{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', createTime=1633414579339, serviceId='calc', methodName='sum', paramTypeNames=[com.github.houbb.rpc.server.facade.model.CalculateRequest], paramValues=[CalculateRequest{one=10, two=20}]}
十月 05, 2021 2:16:19 下午 io.netty.handler.logging.LoggingHandler flush
信息: [id: 0x5ccc4a29, L:/127.0.0.1:50596 - R:localhost/127.0.0.1:9527] FLUSH
[INFO] [2021-10-05 14:16:19.412] [main] [c.g.h.r.c.p.ReferenceProxy.invoke] - [Client] start get resp for seqId: a525c5a6196545f5a5241b2cdc2ec2c2
[INFO] [2021-10-05 14:16:19.413] [main] [c.g.h.r.c.i.i.DefaultInvokeService.getResponse] - [Client] seq a525c5a6196545f5a5241b2cdc2ec2c2 对应结果为空,进入等待
十月 05, 2021 2:16:19 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x5ccc4a29, L:/127.0.0.1:50596 - R:localhost/127.0.0.1:9527] READ: DefaultRpcResponse{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', error=null, result=CalculateResponse{success=true, sum=30}}
...
[INFO] [2021-10-05 14:16:19.505] [nioEventLoopGroup-2-1] [c.g.h.r.c.i.i.DefaultInvokeService.addResponse] - [Client] 获取结果信息,seq: a525c5a6196545f5a5241b2cdc2ec2c2, rpcResponse: DefaultRpcResponse{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', error=null, result=CalculateResponse{success=true, sum=30}}
[INFO] [2021-10-05 14:16:19.505] [nioEventLoopGroup-2-1] [c.g.h.r.c.i.i.DefaultInvokeService.addResponse] - [Client] seq 信息已经放入,通知所有等待方
[INFO] [2021-10-05 14:16:19.506] [nioEventLoopGroup-2-1] [c.g.h.r.c.c.RpcClient.channelRead0] - [Client] response is :DefaultRpcResponse{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', error=null, result=CalculateResponse{success=true, sum=30}}
[INFO] [2021-10-05 14:16:19.506] [main] [c.g.h.r.c.i.i.DefaultInvokeService.getResponse] - [Client] seq a525c5a6196545f5a5241b2cdc2ec2c2 对应结果已经获取: DefaultRpcResponse{seqId='a525c5a6196545f5a5241b2cdc2ec2c2', error=null, result=CalculateResponse{success=true, sum=30}}
[INFO] [2021-10-05 14:16:19.507] [main] [c.g.h.r.c.p.ReferenceProxy.invoke] - [Client] start get resp for seqId: a525c5a6196545f5a5241b2cdc2ec2c2
CalculateResponse{success=true, sum=30}

小结

现在看来有一个小问题,要求服务端必须指定 port,这有点不太合理,比如代理域名,后续需要优化。

这里的启动声明方式也比较基础,后续可以考虑和 spring 进行整合。

为了便于大家学习,以上源码已经开源:

https://github.com/houbb/rpc

希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。

我是老马,期待与你的下次重逢。

java 从零开始手写 RPC (06) reflect 反射实现通用调用之客户端的更多相关文章

  1. java 从零开始手写 RPC (05) reflect 反射实现通用调用之服务端

    通用调用 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何 ...

  2. java 从零开始手写 RPC (07)-timeout 超时处理

    <过时不候> 最漫长的莫过于等待 我们不可能永远等一个人 就像请求 永远等待响应 超时处理 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RP ...

  3. java 从零开始手写 RPC (03) 如何实现客户端调用服务端?

    说明 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 写完了客户端和服务端,那么如何实现客户端和服务端的 ...

  4. java 从零开始手写 RPC (04) -序列化

    序列化 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何实 ...

  5. java 从零开始手写 RPC (01) 基于 websocket 实现

    RPC 解决的问题 RPC 主要是为了解决的两个问题: 解决分布式系统中,服务之间的调用问题. 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑. 这一节我们来学习下如何基于 we ...

  6. 手写RPC框架指北另送贴心注释代码一套

    Angular8正式发布了,Java13再过几个月也要发布了,技术迭代这么快,框架的复杂度越来越大,但是原理是基本不变的.所以沉下心看清代码本质很重要,这次给大家带来的是手写RPC框架. 完整代码以及 ...

  7. 看了这篇你就会手写RPC框架了

    一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...

  8. 手写RPC框架(六)整合Netty

    手写RPC框架(六)整合Netty Netty简介: Netty是一个基于NIO的,提供异步,事件驱动的网络应用工具,具有高性能高可靠性等特点. 使用传统的Socket来进行网络通信,服务端每一个连接 ...

  9. 从零开始手写 dubbo rpc 框架

    rpc rpc 是基于 netty 实现的 java rpc 框架,类似于 dubbo. 主要用于个人学习,由渐入深,理解 rpc 的底层实现原理. 前言 工作至今,接触 rpc 框架已经有很长时间. ...

  10. 手写RPC框架(netty+zookeeper)

    RPC是什么?远程过程调用,过程就是业务处理.计算任务,像调用本地方法一样调用远程的过程. RMI和RPC的区别是什么?RMI是远程方法调用,是oop领域中RPC的一种实现,我们熟悉的restfull ...

随机推荐

  1. Kubernerts - 概览

    1. Kubernerts K8s,是用于自动部署.扩容和管理容器化应用程序的开源系统 1.1 特性 自动化上线与回滚 分步骤针对应用或者配置更改上线,监控应用的运行状态同时不会终止所有实例,若出现问 ...

  2. http-长连接

    1. 短链接 http1.0 -- 1个请求-响应过程会创建且1个新的连接 2. 长连接 http1.1 -- 同域下可以创建1个tcp连接,多个请求在同一个tcp上串行处理请求 http2.0 -- ...

  3. 后端开发之光!Django应用的容器化部署实践~

    在此之前,我一直用uwsgi+virtualenv+nginx方式进行应用部署,操作起来比较麻烦,而且依赖于服务器上的Python版本,服务的管理方面单纯uwsgi + pid算不上特别麻烦但总没有d ...

  4. [转帖]linux服务之tuned

    https://www.cnblogs.com/createyuan/p/5701650.html RHEL/CentOS 在 6.3 版本以后引入了一套新的系统调优工具 tuned/tuned-ad ...

  5. [转帖]Prometheus监控系统存储容量优化攻略,让你的数据安心保存!

    云原生监控领域不可撼动,Prometheus 是不是就没缺点?显然不是. 一个软件如果什么问题都想解决,就会导致什么问题都解决不好.所以Prometheus 也存在不足,广受诟病的问题就是 单机存储不 ...

  6. [转帖]深入理解mysql-第十一章 mysql查询优化-Explain 详解(中)

    一.执行计划-type属性 执行计划的一条记录就代表着MySQL对某个表的执行查询时的访问方法,其中的type列就表明了这个访问这个单表的方法具体是什么,比方说下边这个查询: mysql> EX ...

  7. [转帖]Redis命令详解:Keys

    https://jackeyzhe.github.io/2018/09/22/Redis%E5%91%BD%E4%BB%A4%E8%AF%A6%E8%A7%A3%EF%BC%9AKeys/ 介绍完Re ...

  8. [转帖]Linux性能调优之内存负载调优的一些笔记

    https://zhuanlan.zhihu.com/p/548770928 写在前面 整理一些Linux内存调优的笔记,分享给小伙伴 博文没有涉及的Demo,理论方法偏多,可以用作内存调优入门 博文 ...

  9. vue新一轮的面试题

    参考的连接: https://juejin.cn/post/6844903876231954446 1. 在vue中watch和created哪个先执行?为什么? 在wacth监控数据时,设置imme ...

  10. 替换 &开头。;结尾之间的内容。用空格代替他们

    替换 &开头.;结尾之间的内容.用空格代替他们 var regExp = /\&.*?\;/g; var str = '123&asdsa;dqwe'; str = str.r ...