基于Netty 手写 Dubbo 框架

基于Netty 手写 Dubbo 框架

1、Dubbo是什么,解决什么样的问题?

为了解决模块拆分后,彼此远程调用的问题。

RPC -> Remote Procedure Call 远程调用,常见的RPC框架有:

阿里的:dubbo。

当当的:dubbox。

谷歌的:grpc。

SpringCloud(一站式开发)等。

2、实现方案

查看官网dubbo结构图

基于Netty 手写 Dubbo 框架_第1张图片

1、首先通过register将服务提供者的url注册到Registry注册中心中。

2、客户端Consumer从注册中心获取被调用服务端注册信息,如:接口名称,URL地址等信息。

3、将获取的url地址返回到Consumer客户端,客户端通过获取的URL地址支持invoke反射机制获取服务的实现。

3、整体项目结构信息

|-- netty-to-dubbo
	|-- netty-dubbo-api
		|-- cn.org.july.netty.dubbo.api
			|-- Iservice : 对外服务暴露接口
			|-- RpcRequest :服务请求对象Bean
	|-- netty-dubbo-common
		|-- cn.org.july.netty.dubbo.annotation
			|-- RpcAnnotation : 定义一个接口标识注解
    |-- netty-dubbo-server
    	|-- cn.org.july.netty.dubbo.registry
    		|-- IRegisterCenter :服务注册接口
    		|-- RegisterCenterImpl:服务注册实现类
    		|-- ZkConfig:ZK配置文件
    	|-- cn.org.july.netty.dubbo.rpc
    		|-- NettyRpcServer:基于netty实现的Rpc通讯服务
    		|-- RpcServerHandler:Rpc服务处理流程
    	|-- cn.org.july.netty.dubbo.service
    		|-- ServiceImpl:对外接口IService接口实现类
    |-- netty-dubbo-client
    	|-- cn.org.july.netty.dubbo.loadbalance
    		|-- LoadBalance :负载均衡实现接口
    		|-- RandomLoadBalance:负载均衡实现类随机获取服务提供者
    	|-- cn.org.july.netty.dubbo.proxy
    		|-- RpcClientProxy:netty客户端通讯组件
    		|-- RpcProxyHandler:netty与服务端通讯消息处理组件
    	|-- cn.org.july.netty.dubbo.registry
    		|-- IServiceDiscover:从注册中心获取注册的服务接口
    		|-- ServiceDiscoverImpl:接口IServiceDiscover的实现类
    		|-- ZkConfig:zk配置文件。

4、服务提供者Provider

4.1、实现Iservice接口

首先我们看下Iservice接口的内容:

package cn.org.july.netty.dubbo.api;

/**
 * @author july_whj
 */
public interface IService {
    /**
     * 计算加法
     */
    int add(int a, int b);
    /**
     * @param msg
     */
    String sayHello(String msg);
}

我们编写ServiceImpl实现以上两个接口类。

package cn.org.july.netty.dubbo.service;

import cn.org.july.netty.dubbo.annotation.RpcAnnotation;
import cn.org.july.netty.dubbo.api.IService;

/**
 * @author july_whj
 */
@RpcAnnotation(IService.class)
public class ServiceImpl implements IService {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
    @Override
    public String sayHello(String msg) {
        System.out.println("rpc say :" + msg);
        return "rpc say: " + msg;
    }
}

该类实现比较简单,不做多处理,下面分析服务注册。

4.2、服务注册到ZK

​ 首先我们定义一个接口类IRegisterCenter,里面定义一个registry方法,该方法实现服务注册。服务注册需要将服务的名称、服务的地址注册到注册中心中,我们定义接口如下:

package cn.org.july.netty.dubbo.registry;

/**
 * @author july_whj
 */
public interface IRegisterCenter {
    /**
     * 服务注册
     * @param serverName 服务名称(实现方法路径)
     * @param serviceAddress 服务地址
     */
    void registry(String serverName,String serviceAddress);
}

​ 第二,我们使用zookeerper作为服务注册中心,在netty-dubbo-server模块中引入zk的客户端操作类,pom文件如下:

<dependency>
     <groupId>org.apache.curatorgroupId>
     <artifactId>curator-recipesartifactId>
     <version>2.5.0version>
dependency>
<dependency>
     <groupId>org.apache.curatorgroupId>
     <artifactId>curator-frameworkartifactId>
     <version>2.5.0version>
dependency>

注意:这里版本使用的2.5.0,我使用的zk版,

​ 第三,实现该接口编写接口实现类RegisterCenterImpl

package cn.org.july.netty.dubbo.registry;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;

/**
 * @author july_whj
 */
public class RegisterCenterImpl implements IRegisterCenter {

    private CuratorFramework curatorFramework;
    {
        curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(ZkConfig.addr).sessionTimeoutMs(4000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 10)).build();
        curatorFramework.start();
    }
    @Override
    public void registry(String serverName, String serviceAddress) {
        String serverPath = ZkConfig.ZK_REGISTER_PATH.concat("/").concat(serverName);
        try {
            if (curatorFramework.checkExists().forPath(serverPath) == null) {
                curatorFramework.create().creatingParentsIfNeeded()
                        .withMode(CreateMode.PERSISTENT).forPath(serverPath, "0".getBytes());
            }
            String addr = serverPath.concat("/").concat(serviceAddress);
            String rsNode = curatorFramework.create().withMode(CreateMode.EPHEMERAL)
                    .forPath(addr, "0".getBytes());
            System.out.println("服务注册成功," + rsNode);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

我们分析下以上代码:

​ 定义一个CuratorFramework对象,通过代码块来实例化该对象,并通过curatorFramework.start();来连接ZKConfig中配置好的地址连接ZK。

​ 使用zk作为注册中心,我们了解下ZK的存储结构。zookeeper的命名空间的结构和文件系统很像。一个名字和文件一样使用/的路径表现,zookeeper的每个节点都是被路径唯一标识的。

基于Netty 手写 Dubbo 框架_第2张图片
​ 分析一下registry方法,首先从ZkConfig中获取要注册数据的根节点信息,并将该信息和服务名称进行拼接,判断该路径是否存在,如果不存在使用PERSISTENT方式创建该服务名称路径信息。PERSISTENT方式为持久方式,我们使用这种方式创建因为服务名称不是动态变化的,不用每次去监听它的变化。而我们服务的地址是有可能存在多个,并且有可能发生变化,我们使用EPHEMERAL方式来创建服务的实现地址。

​ 我们将ServiceImpl服务注册到zk上,我们首先获取这个服务的服务名称,和服务实现的地址,将该服务的服务名称和服务地址注册到zk上,下面看下我们的注册服务的测试类RegTest

import cn.org.july.netty.dubbo.registry.IRegisterCenter;
import cn.org.july.netty.dubbo.registry.RegisterCenterImpl;

import java.io.IOException;

public class RegTest {
    public static void main(String[] args) throws IOException {
        IRegisterCenter registerCenter = new RegisterCenterImpl();
        registerCenter.registry("cn.org.july.test", "127.0.0.1:9090");
        System.in.read();
    }
}

​ 我们将cn.org.july.test服务,和服务实现的地址127.0.0.1:9090注册到zk中。

看下服务执行效果:

基于Netty 手写 Dubbo 框架_第3张图片
服务端显示注册成功,我们看以下zk服务中有没有该数据,

最后,我们可以看到数据注册成功。

4.3、实现NettyRpcServer

​ 我们要将ServiceImpl服务发布到zk上,并通过netty监听某个端口信息。

​ 我们先看下

package cn.org.july.netty.dubbo.rpc;

import cn.org.july.netty.dubbo.annotation.RpcAnnotation;
import cn.org.july.netty.dubbo.registry.IRegisterCenter;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

import java.util.HashMap;
import java.util.Map;

/**
 * @author july_whj
 */
public class NettyRpcServer {

    private IRegisterCenter registerCenter;
    private String serviceAddress;
    private Map<String, Object> handlerMap = new HashMap<>(16);

    public NettyRpcServer(IRegisterCenter registerCenter, String serviceAddress) {
        this.registerCenter = registerCenter;
        this.serviceAddress = serviceAddress;
    }

    /**
     * 发布服务
     */
    public void publisher() {
        for (String serviceName : handlerMap.keySet()) {
            registerCenter.registry(serviceName, serviceAddress);
        }
        try {
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            //启动netty服务
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class);
            bootstrap.childHandler(new ChannelInitializer<Channel>() {
                @Override
                protected void initChannel(Channel channel) throws Exception {
                    ChannelPipeline channelPipeline = channel.pipeline();
                    channelPipeline.addLast(new ObjectDecoder(1024 * 1024, ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())));
                    channelPipeline.addLast(new ObjectEncoder());
                    channelPipeline.addLast(new RpcServerHandler(handlerMap));
                }
            }).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);
            String[] addr = serviceAddress.split(":");
            String ip = addr[0];
            int port = Integer.valueOf(addr[1]);
            ChannelFuture future = bootstrap.bind(ip, port).sync();
            System.out.println("服务启动,成功。");
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 子对象的实现
     *
     * @param services 对象实现类
     */
    public void bind(Object... services) {
        //将实现类通过注解获取实现类的名称、实现类的实现放入map集合中。
        for (Object service : services) {
            RpcAnnotation annotation = service.getClass().getAnnotation(RpcAnnotation.class);
            String serviceName = annotation.value().getName();
            handlerMap.put(serviceName, service);
        }
    }
}

分析下以上代码:

​ 通过bind方法,将服务提供者通过RpcAnnotation注解获取服务名称,并将服务名称,服务实现类放入handlerMap 中。

​ 通过publisher方法,获取handlerMap 中的服务实现,将这些服务实现通过registerCenter.registry(serviceName, serviceAddress)将这些服务注册到zk注册中心中,完成服务的注册。下面代码是netty的基础代码,创建两个工作线程池,启动netty服务,通过channelPipeline定义序列化对象和RpcServerHandler实现。这里不做过多解析。

​ 我们看下RpcServerHandler的代码实现。

package cn.org.july.netty.dubbo.rpc;

import cn.org.july.netty.dubbo.api.RpcRequest;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.nio.Buffer;
import java.util.HashMap;
import java.util.Map;

public class RpcServerHandler extends ChannelInboundHandlerAdapter {
    private Map<String, Object> handlerMap = new HashMap<>();

    public RpcServerHandler(Map<String, Object> handlerMap) {
        this.handlerMap = handlerMap;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws UnsupportedEncodingException {
        System.out.println("channelActive:" + ctx.channel().remoteAddress());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务端接收到消息:" + msg);
        RpcRequest rpcRequest = (RpcRequest) msg;
        Object result = new Object();
        if (handlerMap.containsKey(rpcRequest.getClassName())) {
            Object clazz = handlerMap.get(rpcRequest.getClassName());
            Method method = clazz.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getTypes());
            result = method.invoke(clazz, rpcRequest.getParams());
        }
        ctx.write(result);
        ctx.flush();
        ctx.close();
    }
}

​ 这里复写了channelRead方法,接收客户端传递的RpcRequest对象信息。下面判断handlerMap中是否存在客户端调用的实现类,如果存在通过反射机制获取服务端实现类,通过invoke方法调用方法实现,并将执行结果result对象通过ctx.write(result);将执行结果返回客户端。

4.4、编写服务启动类ServerTest

import cn.org.july.netty.dubbo.api.IService;
import cn.org.july.netty.dubbo.registry.IRegisterCenter;
import cn.org.july.netty.dubbo.registry.RegisterCenterImpl;
import cn.org.july.netty.dubbo.rpc.NettyRpcServer;
import cn.org.july.netty.dubbo.service.ServiceImpl;

/**
 * Created with IntelliJ IDEA.
 * User:  wanghongjie
 * Date:  2019/5/3 - 23:03
 * 

* Description: */ public class ServerTest { public static void main(String[] args) { IService service = new ServiceImpl(); IRegisterCenter registerCenter = new RegisterCenterImpl(); NettyRpcServer rpcServer = new NettyRpcServer(registerCenter, "127.0.0.1:8080"); rpcServer.bind(service); rpcServer.publisher(); } }

启动netty服务,将服务实现类service通过bind方法绑定到handlerMap中,通过publisher方法,将service、服务实现地址发布到zk,并启动netty服务,监听8080端口。

5、实现服务消费者

​ 做为服务消费者,我们首先要连接zk注册中心,获取服务实现的地址,并实时监听获取最新的地址信息。通过远程调用实现该服务。如果服务实现是多个我们需实现客户端负载,选取我们的服务地址。

5.1、负载均衡实现

​ 定义loadbalance接口.

package cn.org.july.netty.dubbo.loadbalance;

import java.util.List;

public interface LoadBalance {
    String select(List<String> repos);
}

​ 定义select选择方法。

通过RandomLoadBalance 实现loadbalance接口,从实现名称可以看到Random随机获取。

package cn.org.july.netty.dubbo.loadbalance;

import java.util.List;
import java.util.Random;

public class RandomLoadBalance implements LoadBalance {
    @Override
    public String select(List<String> repos) {
        int len = repos.size();
        if (len == 0)
            throw new RuntimeException("未发现注册的服务。");
        Random random = new Random();
        return repos.get(random.nextInt(len));
    }
}

5.2、获取注册中心服务注册信息

​ 定义IServiceDiscover接口,定义discover方法,进行服务发现。

package cn.org.july.netty.dubbo.registry;

public interface IServiceDiscover {
    String discover(String serviceName);
}

通过ServiceDiscoverImpl实现IServiceDiscover接口。

package cn.org.july.netty.dubbo.registry;

import cn.org.july.netty.dubbo.loadbalance.LoadBalance;
import cn.org.july.netty.dubbo.loadbalance.RandomLoadBalance;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.util.ArrayList;
import java.util.List;

/**
 * @author july_whj
 */
public class ServiceDiscoverImpl implements IServiceDiscover {

    List<String> repos = new ArrayList<String>();
    private CuratorFramework curatorFramework;

    public ServiceDiscoverImpl() {
        curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(ZkConfig.addr).sessionTimeoutMs(4000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 10))
                .build();
        curatorFramework.start();
    }

    @Override
    public String discover(String serviceName) {
        String path = ZkConfig.ZK_REGISTER_PATH.concat("/").concat(serviceName);
        try {
            repos = curatorFramework.getChildren().forPath(path);
        } catch (Exception e) {
            e.printStackTrace();
        }
        registerWatch(path);
        LoadBalance loadBalance = new RandomLoadBalance();
        return loadBalance.select(repos);
    }

    /**
     * 监听ZK节点内容刷新
     *
     * @param path 路径
     */
    private void registerWatch(final String path) {
        PathChildrenCache childrenCache = new PathChildrenCache(curatorFramework, path, true);
        PathChildrenCacheListener childrenCacheListener = new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
                repos = curatorFramework.getChildren().forPath(path);
            }
        };
        childrenCache.getListenable().addListener(childrenCacheListener);
        try {
            childrenCache.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

​ 和服务注册同样定义CuratorFramework对象,并通过curatorFramework.start();连接ZK。

连接成功后通过zk注册的根节点加服务名称,获取该服务的服务地址。

​ 获取的服务地址有可能不是最新的服务地址,我们需要监听zk节点的内容刷新,通过调用registerWatch方法,监听该节点的数据变化。

​ 最后,将获取到的地址集合,通过LoadBalance随机选出一个地址,实现该服务。

5.3、客户端netty实现RPC远程调用

定义客户端实现类RpcClientProxy.

package cn.org.july.netty.dubbo.proxy;

import cn.org.july.netty.dubbo.api.RpcRequest;
import cn.org.july.netty.dubbo.registry.IServiceDiscover;
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 io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Created with IntelliJ IDEA.
 * User:  wanghongjie
 * Date:  2019/5/3 - 23:08
 * 

* Description: */ public class RpcClientProxy { private IServiceDiscover serviceDiscover; public RpcClientProxy(IServiceDiscover serviceDiscover) { this.serviceDiscover = serviceDiscover; } public <T> T create(final Class<T> interfaceClass) { return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, new InvocationHandler() { //封装RpcRequest请求对象,然后通过netty发送给服务等 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { RpcRequest rpcRequest = new RpcRequest(); rpcRequest.setClassName(method.getDeclaringClass().getName()); rpcRequest.setMethodName(method.getName()); rpcRequest.setTypes(method.getParameterTypes()); rpcRequest.setParams(args); //服务发现,zk进行通讯 String serviceName = interfaceClass.getName(); //获取服务实现url地址 String serviceAddress = serviceDiscover.discover(serviceName); //解析ip和port System.out.println("服务端实现地址:" + serviceAddress); String[] arrs = serviceAddress.split(":"); String host = arrs[0]; int port = Integer.parseInt(arrs[1]); System.out.println("服务实现ip:" + host); System.out.println("服务实现port:" + port); final RpcProxyHandler rpcProxyHandler = new RpcProxyHandler(); //通过netty方式进行连接发送数据 EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline channelPipeline = socketChannel.pipeline(); channelPipeline.addLast(new ObjectDecoder(1024 * 1024, ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader()))); channelPipeline.addLast(new ObjectEncoder()); //netty实现代码 channelPipeline.addLast(rpcProxyHandler); } }); ChannelFuture future = bootstrap.connect(host, port).sync(); //将封装好的对象写入 future.channel().writeAndFlush(rpcRequest); future.channel().closeFuture().sync(); } catch (Exception e) { } finally { group.shutdownGracefully(); } return rpcProxyHandler.getResponse(); } }); } }

​ 我们看下create方法,通过动态代理newProxyInstance方法,传入待调用的接口对象,获取getClassLoader后,实现invoke方法。定义RpcRequest对象,封装请求参数。通过interfaceClass对象获取服务实现名称,调用discover方法获取服务提供者的地址信息,netty通过该信息连接服务,并将RpcRequest对象发送到服务端,服务端解析对象,获取接口请求参数等信息,执行方法,并将结果返回到客户端RpcProxyHandler对象接收返回结果。RpcProxyHandler代码实现:

package cn.org.july.netty.dubbo.proxy;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * Created with IntelliJ IDEA.
 * User:  wanghongjie
 * Date:  2019/5/3 - 23:21
 * 

* Description: */ public class RpcProxyHandler extends ChannelInboundHandlerAdapter { private Object response; public Object getResponse() { return response; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //将服务端返回的内容返回 response = msg; } }

​ 我们复写channelRead方法,获取服务端返回的结果信息msg,并将msg赋值给response,通过getResponse获取返回信息。

5.4、客户单调用测试

import cn.org.july.netty.dubbo.api.IService;
import cn.org.july.netty.dubbo.proxy.RpcClientProxy;
import cn.org.july.netty.dubbo.registry.IServiceDiscover;
import cn.org.july.netty.dubbo.registry.ServiceDiscoverImpl;

/**
 * Created with IntelliJ IDEA.
 * User:  wanghongjie
 * Date:  2019/5/3 - 23:06
 * 

* Description: */ public class ClientTest { public static void main(String[] args) { IServiceDiscover serviceDiscover = new ServiceDiscoverImpl(); RpcClientProxy rpcClientProxy = new RpcClientProxy(serviceDiscover); IService iService = rpcClientProxy.create(IService.class); System.out.println(iService.sayHello("netty-to-dubbo")); System.out.println(iService.sayHello("你好")); System.out.println(iService.sayHello("成功咯,很高兴")); System.out.println(iService.add(10, 4)); } }

我们看下执行效果。​

服务端启动:

基于Netty 手写 Dubbo 框架_第4张图片

客户单调用:

远程调用完成。
源码地址:传送门

你可能感兴趣的:(GRPC,zookeeper,微服务,netty,dubbo)