从头开始写一个简单RPC——第1天

gitee地址

项目基于guide-rpc-framwork 拆解,完整框架请参考guide-rpc-framwork

上篇地址:day0

本篇主要内容:

  • 解决Channel 复用问题
  • 使用kryo 序列化替代JDK 自身的序列化
  • 优化远程服务实现方法
先简单介绍下最开始这个RPC的使用方式:

服务端发布服务:

public class HelloServerTest {
    public static void main(String[] args) throws Exception {
        ServiceProvider.publishService("HelloService", new HelloService());
        NettyRpcServer server = new NettyRpcServer();
        server.start();
    }
}

客户端调用服务:

public class ClientTest {
    public static void main(String[] args) {
    	// HelloService.hello 表示要调用HelloService 这个实例的hello 方法
        Object hello = ServiceConsumer.invoke("HelloService.hello", "miemie");
        System.out.println(hello);
    }
}

先基于这种最简单的实现方式,通过手动填写远程服务名,来发布和调用远程服务,后续会进行改造,模拟dubbo 实现方式,基于注解自动发布和调用服务。

解决Channel 复用问题:

在day0 获取channel 的实现方式是这样的:

private Channel getChannel(InetSocketAddress address) {
        CompletableFuture<Channel> completableFuture = new CompletableFuture<>();
        bootstrap.connect(address).addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                log.info("The client has connected [{}] successful!", address.toString());
                completableFuture.complete(future.channel());
            } else {
                throw new IllegalStateException();
            }
        });
        return completableFuture.get();
    }

这样的实现方式,每调用一次远程服务,都要创建一个channel,严重损耗消费者和生产者的资源。
基于Netty的实现,我们在整个消费者的生命周期中,对于每一个生产者,我们可以保持一个Channel 即可。

//获取Channel,先去容器中判断Channel 存不存在,不存在再建立连接
private Channel getChannel(InetSocketAddress address) {
        Channel channel = channelProvider.get(address);
        if(channel == null) {
            synchronized (this) {
                channel = channelProvider.get(address);
                if(channel == null) {
                    //do connect
                    CompletableFuture<Channel> completableFuture = new CompletableFuture<>();
                    bootstrap.connect(address).addListener((ChannelFutureListener) future -> {
                        if (future.isSuccess()) {
                            log.info("The client has connected [{}] successful!", address.toString());
                            completableFuture.complete(future.channel());
                        } else {
                            throw new IllegalStateException();
                        }
                    });
                    channel = completableFuture.get();
                    channelProvider.put(address, channel);
                }
            }
        }
        return channel;
    }

// Channel 容器
public class ChannelProvider {
    private final Map<String, Channel> channelMap = new ConcurrentHashMap<>();
    ...
}
使用kryo 序列化替代JDK 自身的序列化:

在day0 使用的是Netty 自带的编解码器,底层用的是JDK 自带的序列化方式,性能差,如下:

p.addLast(new ObjectEncoder());
p.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));

这里我们使用自定义的编解码器,底层使用Kryo 序列化方式(当然,也可以改成hessian2,参考SerializerFactory),如下:

p.addLast(new CustomObjectEncoder());
p.addLast(new CustomObjectDecoder());

对此,读者需要了解如何在Netty 中自定义帧的格式,可以查看Netty 粘包解决方案

优化远程服务实现方法

在day0 中,要实现一个远程方法,需要实现RemoteService 接口,接口如下:

public interface RemoteService {
    Object invoke(Object ... params);
}

此时一个远程服务类只能提供一个远程方法调用,十分难用,因此本次改造,直接去掉RemoteService.invoke() 方法,每个远程服务类不限制存在多少个远程方法。

/**
 * mark interface
 * 标记这是一个远程服务,无其他左右,后续结合spring 自动注入可以用注解替代
 */
public interface RemoteService {
}

而此时消费者调用远程服务时,不仅需要提供远程服务实例名,还要提供要调用的方法信息,如下:

Object hello = ServiceConsumer.invoke("HelloService.hello", "miemie");
接下来要优化的点

1、使用zookeeper 作为服务注册中心
2、客户端调用远程服务的时候进行负载均衡

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值