netty 远程主机强迫关闭了一个现有的连接_Netty高级应用实战篇(下)之手写dubbo框架...

9537907a3c7943bbc5692b7257d47118.png

手写Dubbo框架

简介

​ Dubbo 框架本身就是一个 RPC 框架,不同的是消费者要连接的服务端 IP 与端口号不是硬编码在服务端的,而是从 zk 中读取到的。

​ 一个 Dubbo 应用中会存在很多服务提供者与消费者。每个提供者都是一个 Netty Server,其会对外暴露自己所在主机的 IP 与 Port。每个消费者都是一个 Netty Client,其会通过连接相应主机的 IP 与 Port 来获取相应的服务。

​ 服务提供者的 IP 与 Port 是如何对外暴露的呢?其会为自己所提供的服务起一个服务名称,一般为业务接口名。然后将该服务名称与对应提供者主机的 IP 与 Port 相绑定注册到zk 中。

​ 服务消费者会从服务注册中心 zk 中查找自己所需要的服务名称,一般为业务接口名,然后获取到该服务名称对应的所有提供者主机信息,并通过负载均衡方式选取一个主机进行连接,获取相应服务。

自定义新增需求

​ 当客户端通过负载均衡策略选择了某一提供者主机后,我们这里新增了一个需求:提供者主机中提供同一服务名称(接口名)的实现类有多个。这样,消费者可以指定其要调用的实现类。若消费者没有指定要调用的实现类,其会调用到注册到中第一个注册的实现类。

​ 为了实现提供者端业务接口可以有多个实现类供客户端选择,这里要求实现类名必须是一个前辍 prefix 后是业务接口名。这样,消费者在进行消费时,可以通过前辍来指定要调用的是哪个实现类。

思路定义

  • 核心工具类:springboot-dubbo-core
  • ZKConstant:主要定义连接zk时的字符串常量定义。
  • Invocation:RPC接口远程调用的信息。
  • LoadBalance:负载均衡接口定义。
  • RandomLoadBalance:负载均衡-随机接口实现。
  • GcService:业务接口定义。
  • 服务端:springboot-dubbo-clinet
  • ServiceDiscovery:服务发现具体定义。
  • ZKServiceDiscovery:服务发现具体实现。
  • RpcProxy:使用代理调用RPC远程接口。
  • RpcClientHandler:Netty的客户端业务处理器。
  • RpcClinetStartApplication:客户度启动入口。
  • 服务端:springboot-dubbo-server
  • RegistryCenter: zk注册具体定义。
  • ZKRegistryCenter:RegistryCenter的具体实现,连接ZK并注册暴露的RPC接口。
  • RpcServer:将指定的目录下的RPC接口加载到缓存中,并进行注册到zk中。
  • RpcServerHandler:主要处理RPC的连接请求。
  • RpcServerStartApplication:服务端启动入口。

项目结构图

5ee355cd0576fd9d6fdda1204f4a2811.png

e1824e6d62d1321643446a6ecb3968c8.png

194390bd64cfa5a22cfd03b3b161d0a8.png

编码实现

API包

ZKConstant

package com.gc.core;

/**
 * @description: 注册中心zk常量配置
 * @author: GC
 * @create: 2020-11-16 15:16
 **/
public class ZKConstant {

    //连接集群机器
    public final static String ZK_CLUSTER = "127.0.0.1:2181";

    //创建默认跟节点
    public final static String ZK_DUBBO_ROOT_PATH = "/gcdubbo";
}

Invocation

package com.gc.core;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

import java.io.Serializable;

/**
 * @description: 封装调用远程RPC信息
 * @author: GC
 * @create: 2020-11-16 15:19
 **/
@Data
@Builder
@AllArgsConstructor
public class Invocation implements Serializable {

    //接口名
    private String className;

    //方法名
    private String methodName;

    //参数类型列表
    private Class<?>[] paramTypes;

    //参数值列表
    private Object[] paramValues;

    //业务接口的前缀
    private String prefix;
}

LoadBalance

package com.gc.loadbalance;


import java.util.List;

public interface LoadBalance {
    /**
     *
     * @param invokerPaths  所有提供者主机列表
     * @return  负载均衡后的结果
     */
    String choose(List<String> invokerPaths);
}

RandomLoadBalance

package com.gc.loadbalance;

import org.springframework.stereotype.Component;

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

/**
 * 随机负载均衡策略
 */
@Component
public class RandomLoadBalance implements LoadBalance {
    @Override
    public String choose(List<String> invokerPaths) {
        int index = new Random().nextInt(invokerPaths.size());
        return invokerPaths.get(index);
    }
}

GcService

package com.gc.service;

/**
 * @description: 用户个人信息
 * @author: GC
 * @create: 2020-11-16 15:50
 **/
public interface GcService {

    /**
     * 获取GC的信息
     * @param name
     * @return
     */
    public String getGcInfo(String name);
}

pom

<dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <version>1.18.16</version>
     <scope>compile</scope>
</dependency>

客户端

ServiceDiscovery

package com.gc.discovery;

/**
 * @description: 服务发现规范定义
 * @author: GC
 * @create: 2020-11-16 16:14
 **/
public interface ServiceDiscovery {
    String discovery(String serviceName);
}

ZKServiceDiscovery

package com.gc.discovery;

import com.gc.core.ZKConstant;
import com.gc.loadbalance.LoadBalance;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @description: 注册具体实现
 * @author: GC
 * @create: 2020-11-16 16:14
 **/

@Component
public class ZKServiceDiscovery implements ServiceDiscovery {

    @Autowired
    private LoadBalance loadBalance;

    //ZK的工具类客户端
    private CuratorFramework client;

    //读取Server端的主机
    private List<String> servers;

    public ZKServiceDiscovery() {
        client = CuratorFrameworkFactory.builder()
                .connectString(ZKConstant.ZK_CLUSTER)//连接注册中心的地址
                .connectionTimeoutMs(10000) //连接超时上线
                .sessionTimeoutMs(4000) //会话过期时间
                //连接失败时的重试策略。最多10次,每连接一次sheel一秒
                .retryPolicy(new ExponentialBackoffRetry(1000, 10))
                .build();
        client.start();
    }

    @Override
    public String discovery(String serviceName) {
        try {
            //配置的根路径 + 服务名称的类路径
            String servicePath = ZKConstant.ZK_DUBBO_ROOT_PATH + "/" + serviceName;

            servers = client.getChildren()
                    //在该路径在注册一个子节点列表变更监听器。
                    .usingWatcher((CuratorWatcher) event -> {
                        servers = client.getChildren().forPath(servicePath);
                    }).forPath(servicePath);//获得该路径下的所有服务节点


            if(servers.size() == 0) {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return loadBalance.choose(servers);
    }
}

RpcProxy

package com.gc.client;

import com.gc.core.Invocation;
import com.gc.discovery.ServiceDiscovery;
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.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

/**
 * @description: 使用代理调用远程接口
 * @author: GC
 * @create: 2020-11-16 17:55
 **/
@Component
public class RpcProxy {

    @Autowired
    private ServiceDiscovery serviceDiscovery;

    public <T> T create(Class<?> clazz, String prefix){
        return (T)Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //如果是本地方法,则直接本地调用
                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(args);
                }
                //否则使用RPC远程调用
                return rpcInvoke(clazz, method, args, prefix);
            }
        });
    }

    /**
     * @param clazz 调用的类
     * @param method 方法名称
     * @param args 参数
     * @param prefix 前缀
     * @return Object 远程调用结果集
     */
    private Object rpcInvoke(Class<?> clazz, Method method, Object[] args, String prefix) {

        RpcClientHandler rpcClientHandler = new RpcClientHandler();
        EventLoopGroup clinetLoopGroup = new NioEventLoopGroup();
        //客户端连接服务器的地址
        String serviceAddress = null;
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(clinetLoopGroup)
                    //开启Nagle算法,它默认是关闭的
                    //该算法的目的就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
                    .option(ChannelOption.TCP_NODELAY, true)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new ObjectEncoder());
                            pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                            pipeline.addLast(rpcClientHandler);
                        }
                    });
            /**
             * 服务发现
             *  拿到类名称,从zk中读取到远程RPC接口的暴露地址。用于本次Netty的连接地址
             */
            serviceAddress = serviceDiscovery.discovery(clazz.getName());
            //如果没有拿到连接地址,那么本次连接终止
            if (serviceAddress == null) {
                return null;
            }
            String ip = serviceAddress.split(":")[0];
            String port = serviceAddress.split(":")[1];
            ChannelFuture future = bootstrap.connect(ip, Integer.parseInt(port)).sync();

            //组装调用信息,传递给服务端
            future.channel().writeAndFlush(
                    Invocation.builder()
                    .className(clazz.getName())
                    .methodName(method.getName())
                    .paramTypes(method.getParameterTypes())
                    .prefix(prefix)
                    .paramValues(args)
                    .build()
            ).sync();


            future.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            clinetLoopGroup.shutdownGracefully();
        }
        return rpcClientHandler.getResult();
    }


}

RpcClientHandler

package com.gc.client;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @description: 使用代理调用远程接口返回的结果集
 * @author: GC
 * @create: 2020-11-16 17:55
 **/
public class RpcClientHandler extends SimpleChannelInboundHandler<Object> {
    private Object result;

    public Object getResult() {
        return result;
    }
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        this.result = msg;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

RpcClinetStartApplication

package com.gc;

import com.gc.client.RpcProxy;
import com.gc.service.GcService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.Resource;

@SpringBootApplication
public class RpcClinetStartApplication implements CommandLineRunner {
    @Resource
    private RpcProxy proxy;

    public static void main(String[] args) {
        SpringApplication.run(RpcClinetStartApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        GcService infoService = proxy.create(GcService.class,  "Info");
        System.out.println(infoService.getGcInfo("阿伟"));

        GcService planInfoService = proxy.create(GcService.class,  "PlanInfo");
        System.err.println(planInfoService.getGcInfo("GC"));
    }
}

pom

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter</artifactId>
</dependency>

<!-- api依赖 -->
<dependency>
   <groupId>com.gc.core</groupId>
   <artifactId>springboot-dubbo-core</artifactId>
   <version>0.0.1-SNAPSHOT</version>
</dependency>

<!--curator依赖-->
<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-framework</artifactId>
   <version>2.12.0</version>
</dependency>
<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-recipes</artifactId>
   <version>2.12.0</version>
</dependency>

<!-- netty-all依赖 -->
<dependency>
   <groupId>io.netty</groupId>
   <artifactId>netty-all</artifactId>
   <version>4.1.36.Final</version>
</dependency>

<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <optional>true</optional>
</dependency>

服务端

RegistryCenter

package com.gc.registry;

/**
 * @description: 注册规范定义
 * @author: GC
 * @create: 2020-11-16 16:14
 **/
public interface RegistryCenter  {

    /**
     *
     * @param serviceName  注册到注册中心的服务名称,一般为业务接口名
     * @param serviceAddress  提供该服务的主机的ip:port
     */
    void register(String serviceName, String serviceAddress) throws Exception;
}

ZKRegistryCenter

package com.gc.registry;

import com.gc.core.ZKConstant;import org.apache.curator.framework.CuratorFramework;import org.apache.curator.framework.CuratorFrameworkFactory;import org.apache.curator.retry.ExponentialBackoffRetry;import org.apache.zookeeper.CreateMode;import org.springframework.stereotype.Component;

/**
 * @description: 注册规范实现
 * @author: GC
 * @create: 2020-11-16 16:15
 **/
@Component
public class ZKRegistryCenter implements  RegistryCenter {

    private CuratorFramework client;

    public ZKRegistryCenter(){
        // 创建并初始化zk的客户端
        client = CuratorFrameworkFactory.builder()
                .connectString(ZKConstant.ZK_CLUSTER)//连接到当前集群地址
                .connectionTimeoutMs(10000) //连接超时时间
                .sessionTimeoutMs(4000)     //一次会话失效时间
                //当连接失败时,重试的连接策略,每1秒连接1次,最多连接10次
                .retryPolicy(new ExponentialBackoffRetry(1000, 10)).build();
        client.start();
    }

    @Override
    public void register(String serviceName, String serviceAddress) throws Exception {
        String pathRoot = ZKConstant.ZK_DUBBO_ROOT_PATH + "/" + serviceName;

        //创建临时节点
        String hostPath = pathRoot + "/" + serviceAddress;
        if(client.checkExists().forPath(hostPath) == null){
            String host =client.create()
                    .creatingParentsIfNeeded()  //如果当前要创建的子节点还没有父节点,那么就在此时创建
                    .withMode(CreateMode.EPHEMERAL)//创建的节点类型,临时节点
                    .forPath(hostPath);//创建节点路径
            System.err.println("创建节点:"+host);
        }

    }

}

RpcServer

package com.gc.server;

import com.gc.registry.RegistryCenter;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolver;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import lombok.Data;
import org.springframework.stereotype.Component;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @description: 将暴露的RPC接口注册到解析并以临时节点注册到zk中
 * @author: GC
 * @create: 2020-11-16 16:54
 **/
@Component
@Data
public class RpcServer {
    // 服务注册表
    private Map<String, Object> registryMap = new HashMap<>();
    //获取到指定包的需要暴露的RPC接口全限定类名
    private List<String> classCache = new ArrayList<>();

    //ZK的连接,和创建节点工具类
    private RegistryCenter center;

    //当前服务端的ip:prot
    private String serviceAddress;

    // 添加上无参构造器
    public RpcServer() {
    }

    public RpcServer(RegistryCenter center, String serviceAddress) {
        this.center = center;
        this.serviceAddress = serviceAddress;
    }

    public void publish(String basePackage) throws Exception {
        // 将指定包下的提供者类名写入到classCache中进行缓存
        getProviderClass(basePackage);
        // 将提供者名与实例的对应关系写入到注册表,并完成到zk的注册
        doRegister();
    }

    //该方法和手写tomcat是一样的原理
    private void getProviderClass(String basePackage) {
        // 加载指定的包为URL
        URL resource = this.getClass().getClassLoader().getResource(basePackage.replaceAll(".", "/"));
        // 若没有指定的资源,则直接结束
        if (resource == null) {
            return;
        }
        // 将URL资源转化为File
        File dir = new File(resource.getFile());
        // 遍历指定包及其子孙包中的所有文件,查找.class文件
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                // 若当前file为目录,则递归
                getProviderClass(basePackage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                // 去年.class的扩展名,获取到简单类名
                String fileName = file.getName().replace(".class", "");
                classCache.add(basePackage + "." + fileName);
            }
        }
    }


    private void doRegister() throws Exception {
        // 若指定包下没有任何实现类,则直接结束
        if (classCache.size() == 0) {
            return;
        }

        for (String className : classCache) {
            //根据类名获取这个类对象
            Class<?> clazz = Class.forName(className);
            // 将实现类名与实现类实例写入到map
            registryMap.put(className, clazz.newInstance());

            //获取接口名称
            Class<?>[] interfaces = clazz.getInterfaces();
            if (interfaces.length == 0) {
                return;
            }
            // 将服务名称与提供者主机地址写入到zk(也就是当前主机的ip和port)。
            center.register(interfaces[0].getName(), serviceAddress);
        }
    }


    //注册完后,启动Netty。用来做RPC间的通信信息传递工作
    public void start() throws Exception {
        EventLoopGroup parentGroup = new NioEventLoopGroup();
        EventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(parentGroup, childGroup)
                    //如果出现并发,来不及处理的连接。方法队列里面。长度最大是1024(这里面的连接都是已经通过三次握手了的)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    //上方的队列,如果存在空闲监测。开启这个配置,那么达到空闲监测的时候后,这些长连接不会被清楚
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new ObjectEncoder());
                            pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                            pipeline.addLast(new RpcServerHandler(registryMap));
                        }
                    });

            String host = serviceAddress.split(":")[0];
            String port = serviceAddress.split(":")[1];

            ChannelFuture future = serverBootstrap.bind(host, Integer.valueOf(port)).sync();
            System.out.println("Doubbo服务Server启动成功");
            future.channel().closeFuture().sync();
        }finally {
            parentGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }
}

RpcServerHandler

package com.gc.server;

import com.gc.core.Invocation;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.internal.StringUtil;

import java.util.Map;

/**
 * @description: 服务端自定义业务处理器
 * @author: GC
 * @create: 2020-11-16 17:20
 **/
public class RpcServerHandler extends SimpleChannelInboundHandler<Invocation> {


    private Map<String, Object> registryMap;

    //已经像注册中心注册过的RPC接口
    public RpcServerHandler(Map<String, Object> registryMap) {
        this.registryMap = registryMap;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Invocation msg) throws Exception {
        Object result = "404";
        // 获取要访问的业务接口名
        String interfaceName = msg.getClassName();
        // 获取接口的简单类名
        String simpleInterfaceName = interfaceName.substring(interfaceName.lastIndexOf(".") + 1);
        // 获取接口所在的包名
        String basePackage = interfaceName.substring(0, interfaceName.lastIndexOf("."));
        // 获取用户要访问的提供者实现类的前辍
        String prefix = msg.getPrefix();
        // 构建客户端要访问的提供者的key
        String key = basePackage + "." + prefix + simpleInterfaceName;


        // 若没有指定前辍,则从registryMap中查找第一个指定接口的实现类
        if (StringUtil.isNullOrEmpty(prefix)) {
            // 查找第一个以接口名结尾的实现类名
            for (String registryKey : registryMap.keySet()) {
                if (registryKey.endsWith(simpleInterfaceName)) {
                    key = registryKey;
                    break;
                }
            }
        }

        // 判断注册表中是否存在指定名称(接口名)的服务
        if (registryMap.containsKey(key)) {
            // 从注册表中获取指定的提供者
            Object provider = registryMap.get(key);
            // 按照客户端要求进行本地方法调用
            result = provider.getClass()
                    .getMethod(msg.getMethodName(), msg.getParamTypes())
                    .invoke(provider, msg.getParamValues());
        }
        //将调用方法返回的值回复给Clinet
        ctx.writeAndFlush(result);
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

自定义业务逻辑

package com.gc.service;

/**
 * @description: 用户个人信息具体实现
 * @author: GC
 * @create: 2020-11-16 15:54
 **/
public class InfoGcService implements GcService {


    public String getGcInfo(String name) {
        return "GC的信息获取成功:年纪100。真实姓名:"+name;
    }

}
package com.gc.service;

/**
 * @description: GC的计划
 * @author: GC
 * @create: 2020-11-16 15:57
 **/
public class PlanInfoGcService implements GcService {

    @Override
    public String getGcInfo(String name) {
        return name+"的计划目前是学习Netty";
    }

}

RpcServerStartApplication

package com.gc;

import com.gc.registry.RegistryCenter;
import com.gc.server.RpcServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RpcServerStartApplication implements CommandLineRunner {
    @Autowired
    private RpcServer rpcServer;
    @Autowired
    private RegistryCenter center;

    public static void main(String[] args) {
        SpringApplication.run(RpcServerStartApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        //注入ZK工具类,在此时传递
        rpcServer.setCenter(center);
        //在此时设置IP:PORT 传递给Netty使用
        rpcServer.setServiceAddress("127.0.0.1:8888");
        //将该包下的接口以RPC的形式暴露出去到注册中心中
        rpcServer.publish("com.gc.service");
        rpcServer.start();
    }
}

pom

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter</artifactId>
</dependency>

<!-- api依赖 -->
<dependency>
   <groupId>com.gc.core</groupId>
   <artifactId>springboot-dubbo-core</artifactId>
   <version>0.0.1-SNAPSHOT</version>
</dependency>

<!--curator依赖-->
<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-framework</artifactId>
   <version>2.12.0</version>
</dependency>
<dependency>
   <groupId>org.apache.curator</groupId>
   <artifactId>curator-recipes</artifactId>
   <version>2.12.0</version>
</dependency>

<!-- netty-all依赖 -->
<dependency>
   <groupId>io.netty</groupId>
   <artifactId>netty-all</artifactId>
   <version>4.1.36.Final</version>
</dependency>

<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <optional>true</optional>
</dependency>

测试运行

1ae63b05cabb3b778894f3e1c69a5c80.png

a60c89f4de589245af655cb1ec833809.png

8fa2507d7c047c96e545165b01e99d72.png

总结

API包

​ 既然我们使用的是分布式的微服务架构,那么它肯定是一个大项目,被分成了很多个小项目。那么这其中,有一些公用的自定义工具类,或者其它的一些配置。就需要建立一个全局的工具类项目,让它可以以pom文件的方式引入到其它的微服务当中就可以使用了。而不是每一个服务都取重写这些工具类,这样会常用整个项目的代码的冗余。

​ 在API包里面我们主要做的事情有:

  • 远程调用的信息封装类。在注册中心中要创建的节点跟节点和连接地址。
  • 负载均衡客户端定义以及负载均衡中的随机算法实现。
  • 具体需要实现的业务接口定义。

客户端

​ 客户端又可以被称为消费者,既服务端即是提供者。当客户端需要调用服务端的接口时,需要从zk中拿到到该业务接口的调用地址,然后去进行调用。在去zk中拿信息的时候还会给当前路径注册一个Watcher监听器,来监听该节点的内容数据变更。

​ 在客户端里面我们主要做的事情有:

  • 在做服务发现的时候,读取zk节点数据的时候会注册一个监听器。读到节点数据并解析为本次RPC远程调用地址。
  • 创建一个代理对象去调用具体的方法,如果调用的方法是本地方法,那么就使用invoke( ) 进行调用,否则就使用RPC远程调用。
  • 封装调用消息,使用writeAndFlush( ) 传递给服务端,然后RpcClientHandler中的getResult( ) 获取本次调用的接口。

服务端

​ 服务端又可以成为提供者,既客户端即是消费者。当接收到客户单的远程调用后执行本地方法调用并返回结果,服务端启动的时候会扫描指定包路径下的业务类,将暴露的RPC接口注册到解析并以临时节点注册到zk中。

​ 在服务端主要做的事情有:

  • 扫描指定包路径下的类写成节点加载到zk做服务注册。
  • 在Netty的自定义业务处理器中,接受到客户端发过来的消息并解析出具体路径进行本地方法调用,然后使用writeAndFlush( ) 返回具体的结果给客户端。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值