为了账号安全,请及时绑定邮箱和手机立即绑定

实现一个分布式调度系统-RPC(下)

标签:
Java
上一篇介绍了如何通过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();
    }

    /**
     * tostring,equals,hashCode,finalize等接口未声明的方法不进行远程调用
     *
     * @param method
     * @return
     */
    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());
                    //in
                    ch.pipeline().addLast(new PacketDecoder());
                    //in
                    ch.pipeline().addLast(new ResponseHandler(messageHandler));
                    //out
                    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();
        //ConnectionCountHandler connectionCountHandler=new ConnectionCountHandler();//统计
        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());

                    //in
                    ch.pipeline().addLast(new PacketDecoder());

                    //ch.pipeline().addLast(connectionCountHandler);//统计连接数
                    ch.pipeline().addLast(new RequestHandler(serviceCenter, standardThreadExecutor));

                    //out
                    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
/**
 * @author dalizu on 2018/10/11.
 * @version v1.0
 * @desc 对于嵌套场景无法支持
 */
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){
        // 1. 创建 ByteBuf 对象
        // 2. 序列化 Java 对象
        byte[]bytes=Serializer.DEFAULT.serializer(packet);
        // 3. 实际编码过程  魔数,版本,序列化算法,指令,数据长度,数据
        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){
        // 跳过 magic number
        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);//调用真正的方法  Result<?>
                    ResponsePacket response = new ResponsePacket();//使用默认的响应对象返回
                    response.setRequestId(request.getRequestId());
                    response.setValue(result);
                    ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);

                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                    //TODO 异常返回
                    log.error("处理请求信息异常:{}", e);
                }
            }
        });
    }

总结

RPC的原理这里就不多做介绍了,简单来说就是客户端通过动态代理进行调用,
发送网络传输,服务端接收到数据,根据请求中的方法,方法参数等通过反射进行真实的
调用,获取返回值,返回给客户端。
当然需要注意序列化封装等操作,还有常见的粘包等问题,这里在我们的调度服务中
都做了解决,完整代码:
https://github.com/lizu18xz/faya-job
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
6395
获赞与收藏
157

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消