上一篇介绍了如何通过hadoop-common里面自带的RPC来实现我们的功能
https://www.imooc.com/article/275335
那么我们怎么自己来实现一个RPC功能,应用到我们的调度系统中呢?
下面我们一起使用netty来实现一个简单的RPC.
依赖选择
<netty-all.version>4.1.6.Final</netty-all.version>
<fastjson.version>1.2.29</fastjson.version>
<hessian.version>4.0.38</hessian.version>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty-all.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>${hessian.version}</version>
</dependency>
实现流程
1-实现客户端的jdk代理,简单来说是帮助我们发送socket请求到服务端,让服务端根据我们传递的参数执行真正的请求操作。
2-分别实现客户端和服务端
3-定义我们的protocol和序列化的方式,并实现对应的编解码方式
4-然后分别实现客户端所需的handler和服务端所需的handler
实现客户端的jdk代理
public class JdkProxyFactory implements ProxyFactory {
@Override
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> clz, Cluster cluster) {
return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[]{clz}, new RefererInvocationHandler<>(clz,cluster));
}
}
public class RefererInvocationHandler<T> implements InvocationHandler {
protected Class<T> clz;
private Cluster cluster;
public RefererInvocationHandler(Class<T> clz, Cluster cluster) {
this.clz = clz;
this.cluster=cluster;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("{}Send Request", Constants.LOG_PREFIX);
RequestPacket request=new RequestPacket();
request.setRequestId(RequestIdGenerator.getRequestId());
request.setArguments(args);
String methodName = method.getName();
request.setMethodName(methodName);
request.setParamtersDesc(ReflectUtil.getMethodParamDesc(method));
request.setParamtersTypes(method.getParameterTypes());
request.setInterfaceName(method.getDeclaringClass().getName());
request.setRetries(cluster.getRetries());
ResponsePacket response=cluster.call(request);
if(response==null){
throw new RuntimeException("和服务端建立连接失败!!!");
}
return response.getValue();
}
public boolean isLocalMethod(Method method) {
if (method.getDeclaringClass().equals(Object.class)) {
try {
Method interfaceMethod = clz.getDeclaredMethod(method.getName(), method.getParameterTypes());
return false;
} catch (Exception e) {
return true;
}
}
return false;
}
}
客户端和服务端(部分代码)
1-客户端
try {
final Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, NettyConstants.CONNECTTIMEOUT);
bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new Spliter());
ch.pipeline().addLast(new PacketDecoder());
ch.pipeline().addLast(new ResponseHandler(messageHandler));
ch.pipeline().addLast(new PacketEncoder());
}
});
channelFuture = bootstrap.connect(remoteAddress, port);
int timeout = NettyConstants.CONNECTTIMEOUT;
if (timeout <= 0) {
throw new RuntimeException("NettyClient init Error: timeout(" + timeout + ") <= 0 is forbid.");
}
2-服务端
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
standardThreadExecutor = StandardThreadManager.transportThreadPool();
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childOption(ChannelOption.SO_REUSEADDR, true);
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new Spliter());
ch.pipeline().addLast(new PacketDecoder());
ch.pipeline().addLast(new RequestHandler(serviceCenter, standardThreadExecutor));
ch.pipeline().addLast(new PacketEncoder());
}
});
ChannelFuture channelFuture = bootstrap.bind(port).addListener(future -> {
if (future.isSuccess()) {
log.info(" 端口[" + port + "]绑定成功!");
countDownLatch.countDown();
} else {
log.error("端口[" + port + "]绑定失败!");
}
});
protocol和序列化的方式
- 我们的传输协议包括:魔数,版本,序列化算法,指令,数据长度,数据
1-protocol
@Data
public abstract class Packet {
@JSONField(deserialize = false, serialize = false)
private Byte version=1;
@JSONField(serialize = false)
public abstract Byte getCommand();
}
实际使用中会实现RequestPacket和ResponsePacket。
2-serializer
public class JSONSerializer implements Serializer {
@Override
public byte[] serializer(Object object) {
return JSON.toJSONBytes(object);
}
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) {
return JSON.parseObject(bytes, clazz);
}
@Override
public byte getSerializerAlogrithm() {
return SerializerAlogrithm.JSON;
}
}
当然还有其他序列化方式比如Hessian......
3-codeC
public void encode(ByteBuf byteBuf, Packet packet){
byte[]bytes=Serializer.DEFAULT.serializer(packet);
byteBuf.writeInt(MAGIC_NUMBER);
byteBuf.writeByte(packet.getVersion());
byteBuf.writeByte(Serializer.DEFAULT.getSerializerAlogrithm());
byteBuf.writeByte(packet.getCommand());
byteBuf.writeInt(bytes.length);
byteBuf.writeBytes(bytes);
}
public Packet decode(ByteBuf byteBuf){
byteBuf.skipBytes(4);
byteBuf.skipBytes(1);
byte serializeAlgorithm = byteBuf.readByte();
byte command = byteBuf.readByte();
int length = byteBuf.readInt();
byte[] bytes = new byte[length];
byteBuf.readBytes(bytes);
Class<? extends Packet> requestType =getRequestType(command);
if (requestType==null){
throw new RuntimeException("没有配置参数类型");
}
Serializer serializer=getSerializer(serializeAlgorithm);
if (requestType != null && serializer != null) {
return serializer.deserialize(requestType, bytes);
}
return null;
}
客户端所需的handler和服务端所需的handler
1-客户端
@Override
protected void channelRead0(ChannelHandlerContext ctx, ResponsePacket response) throws Exception {
messageHandler.handle(response);
}
2-服务端
@Override
protected void channelRead0(ChannelHandlerContext ctx, RequestPacket request) throws Exception {
log.info("{}Receive Client Request:{}", Constants.LOG_PREFIX, request.toString());
transportThreadPool.execute(new Runnable() {
@Override
public void run() {
String methodName = request.getMethodName();
String interfaceName = request.getInterfaceName();
Object serviceBean = serviceMap.get(interfaceName);
if (serviceBean == null) {
throw new CommonException(ResultEnum.EXECUTOR_SERVICE_NOT_FOUND);
}
try {
Class<?> clazz = serviceBean.getClass();
Object[] parameters = request.getArguments();
Class<?>[] parameterTypes = request.getParamtersTypes();
FastClass serviceFastClass = FastClass.create(clazz);
FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, parameterTypes);
Object result = serviceFastMethod.invoke(serviceBean, parameters);
ResponsePacket response = new ResponsePacket();
response.setRequestId(request.getRequestId());
response.setValue(result);
ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} catch (InvocationTargetException e) {
e.printStackTrace();
log.error("处理请求信息异常:{}", e);
}
}
});
}
总结
RPC的原理这里就不多做介绍了,简单来说就是客户端通过动态代理进行调用,
发送网络传输,服务端接收到数据,根据请求中的方法,方法参数等通过反射进行真实的
调用,获取返回值,返回给客户端。
当然需要注意序列化封装等操作,还有常见的粘包等问题,这里在我们的调度服务中
都做了解决,完整代码:
https://github.com/lizu18xz/faya-job
打开App,阅读手记