基于netty实现一个简单的rpc

示例:项目结构以及运行结果日志     简单的模拟实现大佬勿喷

分为7个小步骤:

  • 1:协议
  • 2:序列化
  • 3:编解码器
  • 4:Netty客户端
  • 5:Netty服务端
  • 6:客户端代理
  • 7:远程调用测试

pom依赖:

<dependencies>
        <!--######################### 定义 spring starter 版本 #########################-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--######################### 定义 netty 版本 #########################-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
        </dependency>
        <!--######################### 定义 log4j 版本 #########################-->
        <!-- 支持log4j2的模块,注意把spring-boot-starter和spring-boot-starter-web包中的logging去掉 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
    </dependencies>

协议:(定义请求,响应)

package cn.lsr.rpc.protocol;

import java.util.Arrays;

/**
 * @Description: 请求DTO
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
public class RpcRequest {
    /**
     * 请求对象的ID
     */
    private String requestId;
    /**
     * 类名
     */
    private String className;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 参数类型
     */
    private Class<?>[] parameterTypes;
    /**
     * 入参
     */
    private Object[] parameters;
  //get set toString
 }
package cn.lsr.rpc.protocol;

/**
 * @Description: 响应DTO
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
public class RpcResponse {
    /**
     * 响应ID
     */
    private String requestId;
    /**
     * 错误信息
     */
    private String error;
    /**
     * 返回的结果
     */
    private Object result;

    //get set toString
}

序列化:

  市面上序列化协议很多,比如jdk自带的,Google的protobuf,kyro、Hessian等,只要不选择jdk自带的序列化方法,(因为其性能太差,序列化后产生的码流太大),其他方式其实都可以,这里为了方便起见,选用JSON作为序列化协议,使用fastjson作为JSON框架

定义序列化接口

package cn.lsr.rpc.serializer;

import java.io.IOException;

/**
 * @Description: 序列化接口
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
public interface Serializer {
    /**
     * java对象转换为二进制
     *
     * @param object
     * @return
     */
    byte[] serialize(Object object) throws IOException;

    /**
     * 二进制转换成java对象
     *
     * @param clazz
     * @param bytes
     * @param <T>
     * @return
     */
    <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException;
}

fastjson序列化实现

package cn.lsr.rpc.serializer;

import com.alibaba.fastjson.JSON;

import java.io.IOException;

/**
 * @Description: json方式序列化
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
public class JSONSerializer implements Serializer{
    @Override
    public byte[] serialize(Object object) throws IOException {
        return JSON.toJSONBytes(object);
    }

    @Override
    public <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException {
        return JSON.parseObject(bytes,clazz);
    }
}

编解码器

  约定好协议格式和序列化方式之后,我们还需要编解码器,编码器将请求对象转换为适合于传输的格式(一般来说是字节流),而对应的解码器是将网络字节流转换回应用程序的消息格式。

编码器

package cn.lsr.rpc.encoder;

import cn.lsr.rpc.serializer.Serializer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * @Description: 编码器
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
public class RpcEncoder extends MessageToByteEncoder {
    private Class<?> clazz;
    private Serializer serializer;

    public RpcEncoder(Class<?> clazz, Serializer serializer) {
        this.clazz = clazz;
        this.serializer = serializer;
    }
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Object msg, ByteBuf byteBuf) throws Exception {
        if (clazz != null && clazz.isInstance(msg)) {
            byte[] bytes = serializer.serialize(msg);
            byteBuf.writeInt(bytes.length);
            byteBuf.writeBytes(bytes);
        }
    }
}

解码器

package cn.lsr.rpc.encoder;

import cn.lsr.rpc.serializer.Serializer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * @Description: 解码器
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
public class RpcDecoder extends ByteToMessageDecoder {
    private Class<?> clazz;
    private Serializer serializer;

    public RpcDecoder(Class<?> clazz, Serializer serializer) {
        this.clazz = clazz;
        this.serializer = serializer;
    }
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        //因为之前编码的时候写入一个Int型,4个字节来表示长度
        if (byteBuf.readableBytes() < 4) {
            return;
        }
        //标记当前读的位置
        byteBuf.markReaderIndex();
        int dataLength = byteBuf.readInt();
        if (byteBuf.readableBytes() < dataLength) {
            byteBuf.resetReaderIndex();
            return;
        }
        byte[] data = new byte[dataLength];
        //将byteBuf中的数据读入data字节数组
        byteBuf.readBytes(data);
        Object obj = serializer.deserialize(clazz, data);
        list.add(obj);
    }
}

Netty客户端

  编写步骤:

  • 编写启动方法,指定传输使用Channel
  • 指定ChannelHandler,对网络传输中的数据进行读写处理
  • 添加编解码器
  • 添加失败重试机制
  • 添加发送请求消息的方法
package cn.lsr.rpc;

import cn.lsr.rpc.encoder.RpcDecoder;
import cn.lsr.rpc.encoder.RpcEncoder;
import cn.lsr.rpc.handler.ClientHandler;
import cn.lsr.rpc.protocol.RpcRequest;
import cn.lsr.rpc.protocol.RpcResponse;
import cn.lsr.rpc.serializer.JSONSerializer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.PreDestroy;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @Description: netty客户端
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/

public class NettyClient {
    private Logger log = LoggerFactory.getLogger(NettyClient.class);
    private EventLoopGroup eventLoopGroup;
    @Autowired
    private Channel channel;
    private ClientHandler clientHandler;
    private String host;
    private Integer port;
    private static final int MAX_RETRY = 5;
    public NettyClient(String host, Integer port) {
        this.host = host;
        this.port = port;
    }
    public void connect() {
        clientHandler = new ClientHandler();
        eventLoopGroup = new NioEventLoopGroup();
        //启动类
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventLoopGroup)
                //指定传输使用的Channel
                .channel(NioSocketChannel.class)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        //添加编码器
                        pipeline.addLast(new RpcEncoder(RpcRequest.class, new JSONSerializer()));
                        //添加解码器
                        pipeline.addLast(new RpcDecoder(RpcResponse.class, new JSONSerializer()));
                        //请求处理类
                        pipeline.addLast(clientHandler);
                    }
                });
        connect(bootstrap, host, port, MAX_RETRY);
    }

    /**
     * 失败重连机制,参考Netty入门实战掘金小册
     *
     * @param bootstrap
     * @param host
     * @param port
     * @param retry
     */
    private void connect(Bootstrap bootstrap, String host, int port, int retry) {
        ChannelFuture channelFuture = bootstrap.connect(host, port).addListener(future -> {
            if (future.isSuccess()) {
                log.info("连接服务端成功");
            } else if (retry == 0) {
                log.error("重试次数已用完,放弃连接");
            } else {
                //第几次重连:
                int order = (MAX_RETRY - retry) + 1;
                //本次重连的间隔
                int delay = 1 << order;
                log.error("{} : 连接失败,第 {} 重连....", new Date(), order);
                bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit.SECONDS);
            }
        });
        channel = channelFuture.channel();
    }

    /**
     * 发送消息
     *
     * @param request
     * @return
     */
    public RpcResponse send(final RpcRequest request) {
        try {
            channel.writeAndFlush(request).await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return clientHandler.getRpcResponse(request.getRequestId());
    }

    @PreDestroy
    public void close() {
        eventLoopGroup.shutdownGracefully();
        channel.closeFuture().syncUninterruptibly();
    }
}

我们对于数据的处理重点在于ClientHandler类上,它继承了ChannelDuplexHandler类,可以对出站和入站的数据进行处理

package cn.lsr.rpc.handler;

import cn.lsr.rpc.asyn.DefaultFuture;
import cn.lsr.rpc.protocol.RpcRequest;
import cn.lsr.rpc.protocol.RpcResponse;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description: 客户端handler
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
public class ClientHandler extends ChannelDuplexHandler {
    /**
     * 使用Map维护请求对象ID与响应结果Future的映射关系
     */
    private final Map<String, DefaultFuture> futureMap = new ConcurrentHashMap<>();
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof RpcResponse) {
            //获取响应对象
            RpcResponse response = (RpcResponse) msg;
            DefaultFuture defaultFuture =
                    futureMap.get(response.getRequestId());
            //将结果写入DefaultFuture
            defaultFuture.setResponse(response);
        }
        super.channelRead(ctx,msg);
    }
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        if (msg instanceof RpcRequest) {
            RpcRequest request = (RpcRequest) msg;
            //发送请求对象之前,先把请求ID保存下来,并构建一个与响应Future的映射关系
            futureMap.putIfAbsent(request.getRequestId(), new DefaultFuture());
        }
        super.write(ctx, msg, promise);
    }

    /**
     * 获取响应结果
     *
     * @param requsetId
     * @return
     */
    public RpcResponse getRpcResponse(String requsetId) {
        try {
            DefaultFuture future = futureMap.get(requsetId);
            return future.getRpcResponse(500000);
        } finally {
            //获取成功以后,从map中移除
            futureMap.remove(requsetId);
        }
    }
}

从上面实现可以看出,我们定义了一个Map,维护请求ID与响应结果的映射关系,目的是为了客户端用来验证服务端响应是否与请求相匹配,因为Netty的channel可能被多个线程使用,当结果返回时,你不知道是从哪个线程返回的,所以需要一个映射关系。

而我们的结果是封装在DefaultFuture中的,因为Netty是异步框架,所有的返回都是基于Future和Callback机制的,我们这里自定义Future来实现客户端"异步调用"

package cn.lsr.rpc.asyn;

import cn.lsr.rpc.protocol.RpcResponse;

/**
 * @Description: 模拟异步调用 因为Netty是异步框架,所有的返回都是基于Future和Callback机制的,我们这里自定义Future来实现客户端"异步调用"
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
public class DefaultFuture {
    private RpcResponse rpcResponse;
    //实际上用了wait和notify机制,同时使用一个boolean变量做辅助
    private volatile boolean isSucceed = false;
    private final Object object = new Object();

    public RpcResponse getRpcResponse(int timeout) {
        synchronized (object) {
            while (!isSucceed) {
                try {
                    object.wait(timeout);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return rpcResponse;
        }
    }

    public void setResponse(RpcResponse response) {
        if (isSucceed) {
            return;
        }
        synchronized (object) {
            this.rpcResponse = response;
            this.isSucceed = true;
            object.notify();
        }
    }
}

Netty服务端

  Netty服务端的实现跟客户端的实现差不多,只不过要注意的是,当对请求进行解码过后,需要通过代理的方式调用本地函数。下面是Server端代码:

package cn.lsr.rpc;

import cn.lsr.rpc.encoder.RpcDecoder;
import cn.lsr.rpc.encoder.RpcEncoder;
import cn.lsr.rpc.handler.ServerHandler;
import cn.lsr.rpc.protocol.RpcRequest;
import cn.lsr.rpc.protocol.RpcResponse;
import cn.lsr.rpc.serializer.JSONSerializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import javax.imageio.spi.ServiceRegistry;

/**
 * @Description: netty服务端
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
@Component
public class NettyServer implements InitializingBean {
    private Logger log = LoggerFactory.getLogger(NettyServer.class);
    private EventLoopGroup boss = null;
    private EventLoopGroup worker = null;
    @Autowired
    private ServerHandler serverHandler;
    @Override
    public void afterPropertiesSet() throws Exception {
        //此处使用了zookeeper做注册中心,本文不涉及,可忽略
        //ServiceRegistry registry = new ZkServiceRegistry("127.0.0.1:2181");
        //start(registry);
        start();
    }
    public void start(){
        //负责处理客户端连接的线程池
        boss = new NioEventLoopGroup();
        //负责处理读写操作的线程池
        worker = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(boss, worker)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024) //重试次数
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        //添加解码器
                        pipeline.addLast(new RpcEncoder(RpcResponse.class, new JSONSerializer()));
                        //添加编码器
                        pipeline.addLast(new RpcDecoder(RpcRequest.class, new JSONSerializer()));
                        //添加请求处理器
                        pipeline.addLast(serverHandler);

                    }
                });
        bind(serverBootstrap, 8080);
    }

    /**
     * 如果端口绑定失败,端口数+1,重新绑定
     *
     * @param serverBootstrap
     * @param port
     */
    public void bind(final ServerBootstrap serverBootstrap, int port) {
        serverBootstrap.bind(port).addListener(future -> {
            if (future.isSuccess()) {
                log.info("端口[ {} ] 绑定成功",port);
            } else {
                log.error("端口[ {} ] 绑定失败", port);
                bind(serverBootstrap, port + 1);
            }
        });
    }

    @PreDestroy
    public void destory() throws InterruptedException {
        boss.shutdownGracefully().sync();
        worker.shutdownGracefully().sync();
        log.info("关闭Netty");
    }
}

下面是处理读写操作的Handler类:

package cn.lsr.rpc.handler;

import cn.lsr.rpc.protocol.RpcRequest;
import cn.lsr.rpc.protocol.RpcResponse;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.cglib.reflect.FastClass;
import org.springframework.cglib.reflect.FastMethod;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;

/**
 * @Description: 服务handler
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
@Component
@ChannelHandler.Sharable
public class ServerHandler extends SimpleChannelInboundHandler<RpcRequest> implements ApplicationContextAware {
    private Logger log = LoggerFactory.getLogger(ServerHandler.class);
    private ApplicationContext applicationContext;
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, RpcRequest rpcRequest) throws Exception {
        RpcResponse rpcResponse = new RpcResponse();
        rpcResponse.setRequestId(rpcRequest.getRequestId());
        try {
            Object handler = handler(rpcRequest);
            log.info("获取返回结果: {} ", handler);
            rpcResponse.setResult(handler);
        } catch (Throwable throwable) {
            rpcResponse.setError(throwable.toString());
            throwable.printStackTrace();
        }
        channelHandlerContext.writeAndFlush(rpcResponse);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    /**
     * 服务端使用代理处理请求
     *
     * @param request
     * @return
     */
    private Object handler(RpcRequest request) throws ClassNotFoundException, InvocationTargetException {
        //使用Class.forName进行加载Class文件
        Class<?> clazz = Class.forName(request.getClassName());
        Object serviceBean = applicationContext.getBean(clazz);
        log.info("serviceBean: {}",serviceBean);
        Class<?> serviceClass = serviceBean.getClass();
        log.info("serverClass:{}",serviceClass);
        String methodName = request.getMethodName();

        Class<?>[] parameterTypes = request.getParameterTypes();
        Object[] parameters = request.getParameters();

        //使用CGLIB Reflect
        FastClass fastClass = FastClass.create(serviceClass);
        FastMethod fastMethod = fastClass.getMethod(methodName, parameterTypes);
        log.info("开始调用CGLIB动态代理执行服务端方法...");
        return fastMethod.invoke(serviceBean, parameters);
    }
}

客户端代理

  客户端使用Java动态代理,在代理类中实现通信细节,众所众知,Java动态代理需要实现InvocationHandler接口

package cn.lsr.rpc.provider;

import cn.lsr.rpc.NettyClient;
import cn.lsr.rpc.protocol.RpcRequest;
import cn.lsr.rpc.protocol.RpcResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.UUID;

/**
 * @Description: 客户端动态代理
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
public class RpcClientDynamicProxy<T> implements InvocationHandler {
    private Logger log = LoggerFactory.getLogger(RpcClientDynamicProxy.class);
    private Class<T> clazz;
    public RpcClientDynamicProxy(Class<T> clazz) throws Exception {
        this.clazz = clazz;
    }

    /**
     * 在invoke方法中封装请求对象,构建NettyClient对象,并开启客户端,发送请求消息
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RpcRequest request = new RpcRequest();
        String requestId = UUID.randomUUID().toString();
        String className = method.getDeclaringClass().getName();
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        request.setRequestId(requestId);
        request.setClassName(className);
        request.setMethodName(methodName);
        request.setParameterTypes(parameterTypes);
        request.setParameters(args);
        log.info("请求内容: {}",request);
        //开启Netty 客户端,直连
        NettyClient nettyClient = new NettyClient("127.0.0.1", 8080);
        log.info("开始连接服务端:{}",new Date());
        nettyClient.connect();
        RpcResponse send = nettyClient.send(request);
        log.info("请求调用返回结果:{}", send.getResult());
        return send.getResult();
    }
}

在invoke方法中封装请求对象,构建NettyClient对象,并开启客户端,发送请求消息

代理工厂类如下:

package cn.lsr.rpc.factory;

import cn.lsr.rpc.provider.RpcClientDynamicProxy;

import java.lang.reflect.Proxy;

/**
 * @Description: 代理工厂
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
public class ProxyFactory {
    public static <T> T create(Class<T> interfaceClass) throws Exception {
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class<?>[] {interfaceClass}, new RpcClientDynamicProxy<T>(interfaceClass));
    }
}

通过Proxy.newProxyInstance创建接口的代理类

远程调用测试

  因为是使用boot启动方式,NettyServer实现了InitializingBean ,启动会加载。自动启动服务,

测试接口

package cn.lsr.rpc.demo;

/**
 * @Description: 测试接口
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
public interface HelloService {
    String hello(String name);
}

接口实现

package cn.lsr.rpc.demo;

import org.springframework.stereotype.Service;

/**
 * @Description: 测试服务实现
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
@Service
public class HelloServiceImp implements HelloService {
    @Override
    public String hello(String name) {
        return "我是服务hello:"+name;
    }
}

启动类

package cn.lsr.rpc;

import cn.lsr.rpc.demo.HelloService;
import cn.lsr.rpc.factory.ProxyFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Description: 启动类
 * @Package: lsr-microservice
 * @email: [email protected]
 * @author: lishirui
 **/
@SpringBootApplication
public class RPCApplication {
    private static final Logger log = LoggerFactory.getLogger(RPCApplication.class);
    public static void main(String[] args) throws Exception {
        SpringApplication.run(RPCApplication.class, args);
        HelloService helloService = ProxyFactory.create(HelloService.class);
        log.info("响应结果“: {}",helloService.hello("rpc"));
    }
}

运行启动类,会启动服务端,并在main方法进行调用。

猜你喜欢

转载自www.cnblogs.com/hacker-lsr/p/12888090.html