菜鸟什么时候才能变成老鸟,欢迎留言纠错~|

Shie1d

园龄:5年9个月粉丝:6关注:0

guide-rpc-framework 讲解 流程梳理

开源项目 https://github.com/Snailclimb/guide-rpc-framework

在学习中记录下来的大致流程梳理,没有说明类名,可以全局搜索进行定位。

代码部分很多截取内容,建议先熟悉下代码,也可复制全局搜索定位,不然可能感觉有些乱。

也可以先看看了解个大概~

NettyServerMain

通过@Component 注解 拿到nettyRpcServer

NettyRpcServer nettyRpcServer = (NettyRpcServer) applicationContext.getBean("nettyRpcServer");

手动注册HelloService

HelloService helloService2 = new HelloServiceImpl2();

// 注册到zookeeper
RpcServiceConfig rpcServiceConfig = RpcServiceConfig.builder()
                .group("test2").version("version2").service(helloService2).build();

public class RpcServiceConfig {
    /**
     * service version
     */
    private String version = "";
    /**
     * when the interface has multiple implementation classes, distinguish by group
     */
    private String group = "";
    /**
     * target service
     */
    private Object service;
    public String getRpcServiceName() {
        return this.getServiceName() + this.getGroup() + this.getVersion();
    }
    // 实现的第一个接口的全限定名
    public String getServiceName() {
        return this.service.getClass().getInterfaces()[0].getCanonicalName();
    }
}
//注册
nettyRpcServer.registerService(rpcServiceConfig);

// 1 会将 service 放进一个map
serviceMap.put(rpcServiceName, rpcServiceConfig.getService());


// 2 放入zookeeper 会先获取当前服务提供方的IP地址以及端口(固定9998)组成InetSocketAddress,
new InetSocketAddress(host, NettyRpcServer.PORT)    
   
// 以注册的HelloService实现的接口名+组名+版本号 + ip port 为 zk 的节点路径,    
// 如 /my-rpc/github.javaguide.HelloServicetest1version1/20.20.20.231:9998
public static final String ZK_REGISTER_ROOT_PATH = "/my-rpc";
String servicePath = CuratorUtils.ZK_REGISTER_ROOT_PATH + "/" + rpcServiceName + inetSocketAddress.toString();


//拿到 servicePath  就会以此为path创建节点
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path);

//zk有四种节点,这里创建的持久节点
持久(persistent)节点:
(1) session断开后,数据不会丢失
(2)可以创建子节点
临时(ephemeral)节点:
(1)session断开后,数据会丢失
(2)不可以创建子节点    
持久顺序(PERSISTENT_SEQUENTIAL)节点:同持久节点
创建顺序节点时会默认设置顺序标识,即znode名称后会附加一个 顺序号 ,顺序号是一个单调递增的计数器,由父节点维护
临时顺序(EPHEMERAL_SEQUENTIAL)节点: —> ZK实现分布式锁的基础。
(1)、(2)同临时节点,(3)同持久顺序节点      临时是为了防止一个锁无法被删除,顺序:只要自己的时顺序是最小的说明拿到了锁。
// zk 的三种监听
NodeCache:只是监听某一个特定的节点
PathChildrenCache:监控一个ZNode的子节点.
TreeCache:可以监控整个树上的所有节点,类似于PathChildrenCache和NodeCache的组合

服务端启动

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
DefaultEventExecutorGroup serviceHandlerGroup = new DefaultEventExecutorGroup(
                RuntimeUtil.cpus() * 2,
                ThreadPoolFactoryUtil.createThreadFactory("service-handler-group", false)
        );


ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    // TCP默认开启了 Nagle 算法,该算法的作用是尽可能的发送大数据快,减少网络传输。TCP_NODELAY 参数的作用就是控制是否启用 Nagle 算法。
                    .childOption(ChannelOption.TCP_NODELAY, true)
                    // 是否开启 TCP 底层心跳机制
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    //表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理创建新连接较慢,可以适当调大这个参数
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    // 当客户端第一次进行请求的时候才会进行初始化
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            // 30 秒之内没有收到客户端请求的话就关闭连接
                            ChannelPipeline p = ch.pipeline();
                            // 在 Netty 中,IdleStateHandler 是一个用于检测连接空闲状态的处理器。当通道在指定时间内没有任何读、写或读写操作时,它会触发相应的事件。
                            p.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
                            p.addLast(new RpcMessageEncoder());
                            p.addLast(new RpcMessageDecoder());
                            p.addLast(serviceHandlerGroup, new NettyRpcServerHandler());
                        }
                    });

            // 绑定端口,同步等待绑定成功
            ChannelFuture f = b.bind(host, PORT).sync();
            // 等待服务端监听端口关闭
            f.channel().closeFuture().sync();

流水线

通信协议:

 *   0     1     2     3     4        5     6     7     8         9          10      11     12  13  14   15 16
 *   +-----+-----+-----+-----+--------+----+----+----+------+-----------+-------+----- --+-----+-----+-------+
 *   |   magic   code        |version | full length         | messageType| codec|compress|    RequestId       |
 *   +-----------------------+--------+---------------------+-----------+-----------+-----------+------------+
 *   |                                                                                                       |
 *   |                                         body                                                          |
 *   |                                                                                                       |
 *   |                                        ... ...                                                        |
 *   +-------------------------------------------------------------------------------------------------------+
 * 4B  magic code(魔法数)   1B version(版本)   4B full length(消息长度)    1B messageType(消息类型)
 * 1B compress(压缩类型) 1B codec(序列化类型)    4B  requestId(请求的Id)
 * body(object类型数据)

Encoder

public class RpcMessageEncoder extends MessageToByteEncoder<RpcMessage> {
    private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);

    @Override
    protected void encode(ChannelHandlerContext ctx, RpcMessage rpcMessage, ByteBuf out) {
        try {
            // 魔法数 用于识别该消息是否是该接受的
            // public static final byte[] MAGIC_NUMBER = {(byte) 'g', (byte) 'r', (byte) 'p', (byte) 'c'};
            out.writeBytes(RpcConstants.MAGIC_NUMBER);
            // 协议版本
            out.writeByte(RpcConstants.VERSION);
            // 留出4字节的消息长度字段
            out.writerIndex(out.writerIndex() + 4);
            // 消息类型 请求/心跳/
            byte messageType = rpcMessage.getMessageType();
            out.writeByte(messageType);
            // 序列化类型
            out.writeByte(rpcMessage.getCodec());
            // 压缩类型
            out.writeByte(CompressTypeEnum.GZIP.getCode());
            // requestId 用了原子自增
            out.writeInt(ATOMIC_INTEGER.getAndIncrement());
            // build full length
            // 构造消息体
            byte[] bodyBytes = null;
            // 常量16字节
            int fullLength = RpcConstants.HEAD_LENGTH;
            // if messageType is not heartbeat message,fullLength = head length + body length
            // 如果不是心跳检测
            if (messageType != RpcConstants.HEARTBEAT_REQUEST_TYPE
                    && messageType != RpcConstants.HEARTBEAT_RESPONSE_TYPE) {
                // serialize the object 序列化
                String codecName = SerializationTypeEnum.getName(rpcMessage.getCodec());
                log.info("codec name: [{}] ", codecName);
                // 加载序列化器
                Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
                        .getExtension(codecName);
                bodyBytes = serializer.serialize(rpcMessage.getData());
                // compress the bytes 压缩
                String compressName = CompressTypeEnum.getName(rpcMessage.getCompress());
                Compress compress = ExtensionLoader.getExtensionLoader(Compress.class)
                        .getExtension(compressName);
                bodyBytes = compress.compress(bodyBytes);
                // 总长度 = 头+体
                fullLength += bodyBytes.length;
            }

            if (bodyBytes != null) {
                // 写入消息
                out.writeBytes(bodyBytes);
            }
            int writeIndex = out.writerIndex();
            // 定位到消息协议长度字段 写入长度
            out.writerIndex(writeIndex - fullLength + RpcConstants.MAGIC_NUMBER.length + 1);
            out.writeInt(fullLength);
            // 还原index
            out.writerIndex(writeIndex);
        } catch (Exception e) {
            log.error("Encode request error!", e);
        }
    }
}

Decoder

LengthFieldBasedFrameDecoder 是 Netty 提供的一个解码器,用于处理基于长度字段的帧。它通过在消息中指定长度字段,来确定每个消息的边界,以解决 TCP 粘包和拆包问题。

public class RpcMessageDecoder extends LengthFieldBasedFrameDecoder {
    public RpcMessageDecoder() {
        // lengthFieldOffset: magic code is 4B, and version is 1B, and then full length. so value is 5
        // lengthFieldLength: full length is 4B. so value is 4
        // lengthAdjustment: full length include all data and read 9 bytes before, so the left length is (fullLength-9). so values is -9
        // initialBytesToStrip: we will check magic code and version manually, so do not strip any bytes. so values is 0
        this(RpcConstants.MAX_FRAME_LENGTH, 5, 4, -9, 0);
        
        /**
     * @param maxFrameLength      最大长度,如果超过则丢弃
     * @param lengthFieldOffset      长度字段在那个位置
     * @param lengthFieldLength     长度字段的长度
     * @param lengthAdjustment    由于长度字段包括了消息头和消息体,假设长度字段为N,长度字段在9字节,还剩下N-9个字节要读取。
     * @param initialBytesToStrip Number of bytes skipped.
     *                            If you need to receive all of the header+body data, this value is 0
     *                            if you only want to receive the body data, then you need to skip the number of bytes consumed by the header.
     */
    }

然后根据协议定义的每个字段几个字节,一个一个字段的读取。 然后解压缩,反序列化。

private Object decodeFrame(ByteBuf in) {
        // note: must read ByteBuf in order
        checkMagicNumber(in);
        checkVersion(in);
        int fullLength = in.readInt();
        // build RpcMessage object
        byte messageType = in.readByte();
        byte codecType = in.readByte();
        byte compressType = in.readByte();
        int requestId = in.readInt();
        RpcMessage rpcMessage = RpcMessage.builder()
                .codec(codecType)
                .requestId(requestId)
                .messageType(messageType).build();
        if (messageType == RpcConstants.HEARTBEAT_REQUEST_TYPE) {
            rpcMessage.setData(RpcConstants.PING);
            return rpcMessage;
        }
        if (messageType == RpcConstants.HEARTBEAT_RESPONSE_TYPE) {
            rpcMessage.setData(RpcConstants.PONG);
            return rpcMessage;
        }
        int bodyLength = fullLength - RpcConstants.HEAD_LENGTH;
        if (bodyLength > 0) {
            byte[] bs = new byte[bodyLength];
            in.readBytes(bs);
            // decompress the bytes
            String compressName = CompressTypeEnum.getName(compressType);
            Compress compress = ExtensionLoader.getExtensionLoader(Compress.class)
                    .getExtension(compressName);
            bs = compress.decompress(bs);
            // deserialize the object
            String codecName = SerializationTypeEnum.getName(rpcMessage.getCodec());
            log.info("codec name: [{}] ", codecName);
            Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class)
                    .getExtension(codecName);
            if (messageType == RpcConstants.REQUEST_TYPE) {
                RpcRequest tmpValue = serializer.deserialize(bs, RpcRequest.class);
                rpcMessage.setData(tmpValue);
            } else {
                RpcResponse tmpValue = serializer.deserialize(bs, RpcResponse.class);
                rpcMessage.setData(tmpValue);
            }
        }
        return rpcMessage;

    }

NettyRpcServerHandler

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    try {
        if (msg instanceof RpcMessage) {
            log.info("server receive msg: [{}] ", msg);
            byte messageType = ((RpcMessage) msg).getMessageType();
            RpcMessage rpcMessage = new RpcMessage();
            rpcMessage.setCodec(SerializationTypeEnum.HESSIAN.getCode());
            rpcMessage.setCompress(CompressTypeEnum.GZIP.getCode());
            // 心跳检测
            if (messageType == RpcConstants.HEARTBEAT_REQUEST_TYPE) {
                rpcMessage.setMessageType(RpcConstants.HEARTBEAT_RESPONSE_TYPE);
                // 返回一个pong
                rpcMessage.setData(RpcConstants.PONG);
            } else {
                // 转换类
                RpcRequest rpcRequest = (RpcRequest) ((RpcMessage) msg).getData();
                //  rpcRequest.getRpcServiceName() 通过serviceName 去map里拿到对应的类,再根据方法名和参数类型确定方法反射执行
                Object result = rpcRequestHandler.handle(rpcRequest);
                log.info(String.format("server get result: %s", result.toString()));
                //  类型为  返回
                rpcMessage.setMessageType(RpcConstants.RESPONSE_TYPE);
                if (ctx.channel().isActive() && ctx.channel().isWritable()) {
                    RpcResponse<Object> rpcResponse = RpcResponse.success(result, rpcRequest.getRequestId());
                    rpcMessage.setData(rpcResponse);
                } else {
                    RpcResponse<Object> rpcResponse = RpcResponse.fail(RpcResponseCodeEnum.FAIL);
                    rpcMessage.setData(rpcResponse);
                    log.error("not writable now, message dropped");
                }
            }
            // 如果在写入数据的过程中出现错误,Netty 会关闭连接
            ctx.writeAndFlush(rpcMessage).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
        }
    } finally {
        //Ensure that ByteBuf is released, otherwise there may be memory leaks
        ReferenceCountUtil.release(msg);
    }
}

NettyClientMain

客户端调用HelloController 的test方法

然后调用了helloService.hello 方法,而HelloService由与服务端提供

@Component
public class HelloController {

    @RpcReference(version = "version1", group = "test1")
    private HelloService helloService;

    public void test() throws InterruptedException {
        String hello = this.helloService.hello(new Hello("111", "222"));
        //如需使用 assert 断言,需要在 VM options 添加参数:-ea
        assert "Hello description is 222".equals(hello);
        Thread.sleep(12000);
        for (int i = 0; i < 10; i++) {
            System.out.println(helloService.hello(new Hello("111", "222")));
        }
    }
}

在这个bean加载的时候,就将helloService 替换为了代理对象,那么执行hello方法,是由代理对象来完成。

@Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetClass = bean.getClass();
        Field[] declaredFields = targetClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            RpcReference rpcReference = declaredField.getAnnotation(RpcReference.class);
            if (rpcReference != null) {
                RpcServiceConfig rpcServiceConfig = RpcServiceConfig.builder()
                        .group(rpcReference.group())
                        .version(rpcReference.version()).build();
                // rpcClient = NettyRpcClient     rpcServiceConfig = 注解指定的service  结构在上文已经提过
                RpcClientProxy rpcClientProxy = new RpcClientProxy(rpcClient, rpcServiceConfig);
                Object clientProxy = rpcClientProxy.getProxy(declaredField.getType());
                declaredField.setAccessible(true);
                try {
                    // 将
                    // @RpcReference(version = "version1", group = "test1")
                    //    private HelloService helloService;
                    // 替换为代理对象
                    declaredField.set(bean, clientProxy);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }

        }
        return bean;
    }

代理对象

代理对象的invoke 方法

@Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        log.info("invoked method: [{}]", method.getName());
        // 构造RpcRequest  类结构在下一个代码块
        RpcRequest rpcRequest = RpcRequest.builder().methodName(method.getName())
                .parameters(args)
                .interfaceName(method.getDeclaringClass().getName()) // 类名 github.javaguide.HelloService
                .paramTypes(method.getParameterTypes())
                .requestId(UUID.randomUUID().toString())
                // rpcServiceConfig 在上一块代码中 初始化代理对象时传入
                .group(rpcServiceConfig.getGroup())
                .version(rpcServiceConfig.getVersion())
                .build();
        RpcResponse<Object> rpcResponse = null;
        // 基于netty
        if (rpcRequestTransport instanceof NettyRpcClient) {
            // 发送request请求
            CompletableFuture<RpcResponse<Object>> completableFuture = (CompletableFuture<RpcResponse<Object>>) rpcRequestTransport.sendRpcRequest(rpcRequest);
            rpcResponse = completableFuture.get();
        }
        // 基于socket
        if (rpcRequestTransport instanceof SocketRpcClient) {
            rpcResponse = (RpcResponse<Object>) rpcRequestTransport.sendRpcRequest(rpcRequest);
        }
        this.check(rpcResponse, rpcRequest);
        return rpcResponse.getData();
    }
public class RpcRequest implements Serializable {
    private static final long serialVersionUID = 1905122041950251207L;
    private String requestId;
    private String interfaceName;
    private String methodName;
    private Object[] parameters;
    private Class<?>[] paramTypes;
    private String version;
    private String group;

    public String getRpcServiceName() {
        return this.getInterfaceName() + this.getGroup() + this.getVersion();
    }
}

sendRpcRequest

@Override
    public Object sendRpcRequest(RpcRequest rpcRequest) {
        // build return value
        CompletableFuture<RpcResponse<Object>> resultFuture = new CompletableFuture<>();
        // 发现服务
        /**  
        	1. lookupService 中使用到了负载均衡算法,我们首先需要再次回顾一下服务注册时的zk节点结构
        	   是以注册的HelloService实现的接口名+组名+版本号 + ip port为zk的路径,如/my-rpc/github.javaguide.HelloServicetest1version1/20.20.20.231:9998
        	2. 通过 rpcRequest.getRpcServiceName 得到接口名+组名+版本号 然后拿到这个节点的所有子节点 如上举例的就是20.20.20.231:9998,如果注册了更多,就会拿到多个ip + port,也就对应了多个服务提供方,这样的话就由负载均衡算法选择一个来提供服务,负载均衡算法这里就不说了。
        	
        **/
        InetSocketAddress inetSocketAddress = serviceDiscovery.lookupService(rpcRequest);
        // 拿到对应服务方的channel 如果没有的话 就会执行初始的连接操作。
        Channel channel = getChannel(inetSocketAddress);
        if (channel.isActive()) {
            // put unprocessed request
            unprocessedRequests.put(rpcRequest.getRequestId(), resultFuture);
            RpcMessage rpcMessage = RpcMessage.builder().data(rpcRequest)
                    .codec(SerializationTypeEnum.HESSIAN.getCode())
                    .compress(CompressTypeEnum.GZIP.getCode())
                    .messageType(RpcConstants.REQUEST_TYPE).build();
            // 将 rpcMessage 写入到与服务端的连接中,并触发刷新操作,确保数据立即发送到服务端
            channel.writeAndFlush(rpcMessage).addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    log.info("client send message: [{}]", rpcMessage);
                } else {
                    future.channel().close();
                    resultFuture.completeExceptionally(future.cause());
                    log.error("Send failed:", future.cause());
                }
            });
        } else {
            throw new IllegalStateException();
        }

        return resultFuture;
    }

tips:

该项目还有Extension去加载一些类,大部分在类初始化的时候会触发。举个例子。

在服务注册的时候调用了nettyRpcServer.registerService(rpcServiceConfig);

public class NettyRpcServer {

    public static final int PORT = 9998;

    // 单例获取实例 通过反射调用 ZkServiceProviderImpl 的构造器 初始化实例
    private final ServiceProvider serviceProvider = SingletonFactory.getInstance(ZkServiceProviderImpl.class);

    public void registerService(RpcServiceConfig rpcServiceConfig) {
        serviceProvider.publishService(rpcServiceConfig);
    }
}

registerService 调用了 serviceProviderpublishService方法,至于serviceProvider 用到双重校验锁来实现单例 这里就不说了。

关键在于ZkServiceProviderImpl在构造器 初始化实例时加载了服务注册类 serviceRegistry

public class ZkServiceProviderImpl implements ServiceProvider {

    /**
     * key: rpc service name(interface name + version + group)
     * value: service object
     */

    // 实现类 ZkServiceRegistryImpl
    private final ServiceRegistry serviceRegistry;

    public ZkServiceProviderImpl() {
        serviceRegistry = ExtensionLoader.getExtensionLoader(ServiceRegistry.class).getExtension(ServiceRegistryEnum.ZK.getName());
        // 实际就是 serviceRegistry = ZkServiceRegistryImpl
    }

getExtensionLoader 会返回一个 ExtensionLoader 类

public final class ExtensionLoader<T> {
	private final Class<?> type;
}

主要是指定type

其中的 type属性就是 传入的参数 ServiceRegistry.class 也就是 github.javaguide.registry.ServiceRegistry

需要注意这个在 src/main/resources/META-INF/extensions 下有同名的文件

然后 调用这个返回的ExtensionLoader.getExtension 方法,参数为常量 "zk"

你去看下那个同名文件就知道为什么了~

// name = zk  netty  socket
public T getExtension(String name) {
    if (StringUtil.isBlank(name)) {
        throw new IllegalArgumentException("Extension name should not be null or empty.");
    }
    // firstly get from cache, if not hit, create one
    // 实例缓存               Map<String, Holder<Object>> cachedInstances
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<>());
        holder = cachedInstances.get(name);// 一个空的Holder 类,holder 只有一个属性 value 用于存放实例对象
    }
    // create a singleton if no instance exists
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

private T createExtension(String name) {
    // load all extension classes of type T from file and get specific one by name
    //  getExtensionClasses() 中返回了 Map  "zk" -> "class github.javaguide.registry.zk.ZkServiceRegistryImpl"  这也是上文中提到了resource文件中的类,那个文件指定了要加载哪些类    然后存在 cachedClasses
    Class<?> clazz = getExtensionClasses().get(name);
    // clazz = github.javaguide.registry.zk.ZkServiceRegistryImpl
    if (clazz == null) {
        throw new RuntimeException("No such extension of name " + name);
    }
    T instance = (T) EXTENSION_INSTANCES.get(clazz);
    if (instance == null) {
        try {
            //  反射实例化
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            // github.javaguide.registry.zk.ZkServiceRegistryImpl -> 实例
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
    return instance;
}
private Map<String, Class<?>> getExtensionClasses() {
    // get the loaded extension class from the cache
    // zk -> "class github.javaguide.registry.zk.ZkServiceRegistryImpl"
    Map<String, Class<?>> classes = cachedClasses.get();
    // double check
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                classes = new HashMap<>();
                // load all extensions from our extensions directory
                loadDirectory(classes);
                // class 缓存
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

本文作者:Shie1d

本文链接:https://www.cnblogs.com/ganyq/p/18347876

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Shie1d  阅读(97)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起