Coder Social home page Coder Social logo

snailclimb / guide-rpc-framework Goto Github PK

View Code? Open in Web Editor NEW
3.7K 50.0 2.0K 4.26 MB

A custom RPC framework implemented by Netty+Kyro+Zookeeper.(一款基于 Netty+Kyro+Zookeeper 实现的自定义 RPC 框架-附详细实现过程和相关教程。)

Home Page: https://gitee.com/SnailClimb/guide-rpc-framework

License: Other

Java 99.86% Shell 0.14%

guide-rpc-framework's Introduction

guide-rpc-framework

该 RPC 框架配套教程已经更新在我的星球,点击此链接了解详情。

中文| English

Github | Gitee

前言

虽说 RPC 的原理实际不难,但是,自己在实现的过程中自己也遇到了很多问题。guide-rpc-framework 目前只实现了 RPC 框架最基本的功能,一些可优化点都在下面提到了,有兴趣的小伙伴可以自行完善。

通过这个简易的轮子,你可以学到 RPC 的底层原理和原理以及各种 Java 编码实践的运用。

你甚至可以把 guide-rpc-framework 当做你的毕设/项目经验的选择,这是非常不错!对比其他求职者的项目经验都是各种系统,造轮子肯定是更加能赢得面试官的青睐。

如果你要将 guide-rpc-framework 当做你的毕设/项目经验的话,我希望你一定要搞懂,而不是直接复制粘贴我的**。你可以 fork 我的项目,然后进行优化。如果你觉得的优化是有价值的话,你可以提交 PR 给我,我会尽快处理。

介绍

guide-rpc-framework 是一款基于 Netty+Kyro+Zookeeper 实现的 RPC 框架。代码注释详细,结构清晰,并且集成了 Check Style 规范代码结构,非常适合阅读和学习。

由于 Guide哥自身精力和能力有限,如果大家觉得有需要改进和完善的地方的话,欢迎 fork 本项目,然后 clone 到本地,在本地修改后提交 PR 给我,我会在第一时间 Review 你的代码。

我们先从一个基本的 RPC 框架设计思路说起!

一个基本的 RPC 框架设计思路

注意 :我们这里说的 RPC 框架指的是:可以让客户端直接调用服务端方法就像调用本地方法一样简单的框架,比如我前面介绍的 Dubbo、Motan、gRPC 这些。 如果需要和 HTTP 协议打交道,解析和封装 HTTP 请求和响应。这类框架并不能算是“RPC 框架”,比如 Feign。

一个最简单的 RPC 框架使用示意图如下图所示,这也是 guide-rpc-framework 目前的架构 :

服务提供端 Server 向注册中心注册服务,服务消费者 Client 通过注册中心拿到服务相关信息,然后再通过网络请求服务提供端 Server。

作为 RPC 框架领域的佼佼者Dubbo的架构如下图所示,和我们上面画的大体也是差不多的。

一般情况下, RPC 框架不仅要提供服务发现功能,还要提供负载均衡、容错等功能,这样的 RPC 框架才算真正合格的。

简单说一下设计一个最基本的 RPC 框架的思路:

  1. 注册中心 :注册中心首先是要有的,推荐使用 Zookeeper。注册中心负责服务地址的注册与查找,相当于目录服务。服务端启动的时候将服务名称及其对应的地址(ip+port)注册到注册中心,服务消费端根据服务名称找到对应的服务地址。有了服务地址之后,服务消费端就可以通过网络请求服务端了。
  2. 网络传输 :既然要调用远程的方法就要发请求,请求中至少要包含你调用的类名、方法名以及相关参数吧!推荐基于 NIO 的 Netty 框架。
  3. 序列化 :既然涉及到网络传输就一定涉及到序列化,你不可能直接使用 JDK 自带的序列化吧!JDK 自带的序列化效率低并且有安全漏洞。 所以,你还要考虑使用哪种序列化协议,比较常用的有 hession2、kyro、protostuff。
  4. 动态代理 : 另外,动态代理也是需要的。因为 RPC 的主要目的就是让我们调用远程方法像调用本地方法一样简单,使用动态代理可以屏蔽远程方法调用的细节比如网络传输。也就是说当你调用远程方法的时候,实际会通过代理对象来传输网络请求,不然的话,怎么可能直接就调用到远程方法呢?
  5. 负载均衡 :负载均衡也是需要的。为啥?举个例子我们的系统中的某个服务的访问量特别大,我们将这个服务部署在了多台服务器上,当客户端发起请求的时候,多台服务器都可以处理这个请求。那么,如何正确选择处理该请求的服务器就很关键。假如,你就要一台服务器来处理该服务的请求,那该服务部署在多台服务器的意义就不复存在了。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题,我们从负载均衡的这四个字就能明显感受到它的意义。
  6. ......

项目基本情况和可优化点

为了循序渐进,最初的是时候,我是基于传统的 BIO 的方式 Socket 进行网络传输,然后利用 JDK 自带的序列化机制 来实现这个 RPC 框架的。后面,我对原始版本进行了优化,已完成的优化点和可以完成的优化点我都列在了下面 👇。

为什么要把可优化点列出来? 主要是想给哪些希望优化这个 RPC 框架的小伙伴一点思路。欢迎大家 fork 本仓库,然后自己进行优化。

  • 使用 Netty(基于 NIO)替代 BIO 实现网络传输;
  • 使用开源的序列化机制 Kyro(也可以用其它的)替代 JDK 自带的序列化机制;
  • 使用 Zookeeper 管理相关服务地址信息
  • Netty 重用 Channel 避免重复连接服务端
  • 使用 CompletableFuture 包装接受客户端返回结果(之前的实现是通过 AttributeMap 绑定到 Channel 上实现的) 详见:使用 CompletableFuture 优化接受服务提供端返回结果
  • 增加 Netty 心跳机制 : 保证客户端和服务端的连接不被断掉,避免重连。
  • 客户端调用远程服务的时候进行负载均衡 :调用服务的时候,从很多服务地址中根据相应的负载均衡算法选取一个服务地址。ps:目前实现了随机负载均衡算法与一致性哈希算法。
  • 处理一个接口有多个类实现的情况 :对服务分组,发布服务的时候增加一个 group 参数即可。
  • 集成 Spring 通过注解注册服务
  • 集成 Spring 通过注解进行服务消费 。参考: PR#10
  • 增加服务版本号 :建议使用两位数字版本,如:1.0,通常在接口不兼容时版本号才需要升级。为什么要增加服务版本号?为后续不兼容升级提供可能,比如服务接口增加方法,或服务模型增加字段,可向后兼容,删除方法或删除字段,将不兼容,枚举类型新增字段也不兼容,需通过变更版本号升级。
  • 对 SPI 机制的运用
  • 增加可配置比如序列化方式、注册中心的实现方式,避免硬编码 :通过 API 配置,后续集成 Spring 的话建议使用配置文件的方式进行配置
  • 客户端与服务端通信协议(数据包结构)重新设计 ,可以将原有的 RpcRequestRpcRequest 对象作为消息体,然后增加如下字段(可以参考:《Netty 入门实战小册》和 Dubbo 框架对这块的设计):
    • 魔数 : 通常是 4 个字节。这个魔数主要是为了筛选来到服务端的数据包,有了这个魔数之后,服务端首先取出前面四个字节进行比对,能够在第一时间识别出这个数据包并非是遵循自定义协议的,也就是无效数据包,为了安全考虑可以直接关闭连接以节省资源。
    • 序列化器编号 :标识序列化的方式,比如是使用 Java 自带的序列化,还是 json,kyro 等序列化方式。
    • 消息体长度 : 运行时计算出来。
    • ......
  • 编写测试为重构代码提供信心
  • 服务监控中心(类似dubbo admin)
  • 设置 gzip 压缩

项目模块概览

运行项目

导入项目

fork 项目到自己的仓库,然后克隆项目到自己的本地:git clone [email protected]:username/guide-rpc-framework.git,使用 IDEA 打开,等待项目初始化完成。

初始化 git hooks

这一步主要是为了在 commit 代码之前,跑 Check Style,保证代码格式没问题,如果有问题的话就不能提交。

以下演示的是 Mac/Linux 对应的操作,Window 用户需要手动将 config/git-hooks 目录下的pre-commit 文件拷贝到 项目下的 .git/hooks/ 目录。

执行下面这些命令:

➜  guide-rpc-framework git:(master) ✗ chmod +x ./init.sh
➜  guide-rpc-framework git:(master) ✗ ./init.sh

init.sh 这个脚本的主要作用是将 git commit 钩子拷贝到项目下的 .git/hooks/ 目录,这样你每次 commit 的时候就会执行了。

CheckStyle 插件下载和配置

IntelliJ IDEA-> Preferences->Plugins->搜索下载 CheckStyle 插件,然后按照如下方式进行配置。

CheckStyle 插件下载和配置

配置完成之后,按照如下方式使用这个插件!

插件使用方式

下载运行 zookeeper

这里使用 Docker 来下载安装。

下载:

docker pull zookeeper:3.5.8

运行:

docker run -d --name zookeeper -p 2181:2181 zookeeper:3.5.8

使用

服务提供端

实现接口:

@Slf4j
@RpcService(group = "test1", version = "version1")
public class HelloServiceImpl implements HelloService {
    static {
        System.out.println("HelloServiceImpl被创建");
    }

    @Override
    public String hello(Hello hello) {
        log.info("HelloServiceImpl收到: {}.", hello.getMessage());
        String result = "Hello description is " + hello.getDescription();
        log.info("HelloServiceImpl返回: {}.", result);
        return result;
    }
}
	
@Slf4j
public class HelloServiceImpl2 implements HelloService {

    static {
        System.out.println("HelloServiceImpl2被创建");
    }

    @Override
    public String hello(Hello hello) {
        log.info("HelloServiceImpl2收到: {}.", hello.getMessage());
        String result = "Hello description is " + hello.getDescription();
        log.info("HelloServiceImpl2返回: {}.", result);
        return result;
    }
}

发布服务(使用 Netty 进行传输):

/**
 * Server: Automatic registration service via @RpcService annotation
 *
 * @author shuang.kou
 * @createTime 2020年05月10日 07:25:00
 */
@RpcScan(basePackage = {"github.javaguide.serviceimpl"})
public class NettyServerMain {
    public static void main(String[] args) {
        // Register service via annotation
        new AnnotationConfigApplicationContext(NettyServerMain.class);
        NettyServer nettyServer = new NettyServer();
        // Register service manually
        HelloService helloService2 = new HelloServiceImpl2();
        RpcServiceProperties rpcServiceConfig = RpcServiceProperties.builder()
                .group("test2").version("version2").build();
        nettyServer.registerService(helloService2, rpcServiceConfig);
        nettyServer.start();
    }
}

服务消费端

@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")));
        }
    }
}
ClientTransport rpcRequestTransport = new SocketRpcClient();
RpcServiceProperties rpcServiceConfig = RpcServiceProperties.builder()
        .group("test2").version("version2").build();
RpcClientProxy rpcClientProxy = new RpcClientProxy(rpcRequestTransport, rpcServiceConfig);
HelloService helloService = rpcClientProxy.getProxy(HelloService.class);
String hello = helloService.hello(new Hello("111", "222"));
System.out.println(hello);

相关问题

为什么要造这个轮子?Dubbo 不香么?

写这个 RPC 框架主要是为了通过造轮子的方式来学习,检验自己对于自己所掌握的知识的运用。

实现一个简单的 RPC 框架实际是比较容易的,不过,相比于手写 AOP 和 IoC 还是要难一点点,前提是你搞懂了 RPC 的基本原理。

我之前从理论层面在我的知识星球分享过如何实现一个 RPC。不过理论层面的东西只是支撑,你看懂了理论可能只能糊弄住面试官。咱程序员这一行还是最需要动手能力,即使你是架构师级别的人物。当你动手去实践某个东西,将理论付诸实践的时候,你就会发现有很多坑等着你。

大家在实际项目上还是要尽量少造轮子,有优秀的框架之后尽量就去用,Dubbo 在各个方面做的都比较好和完善。

如果我要自己写的话,需要提前了解哪些知识

Java

  1. 动态代理机制;
  2. 序列化机制以及各种序列化框架的对比,比如 hession2、kyro、protostuff。
  3. 线程池的使用;
  4. CompletableFuture 的使用
  5. ......

Netty

  1. 使用 Netty 进行网络传输;
  2. ByteBuf 介绍
  3. Netty 粘包拆包
  4. Netty 长连接和心跳机制

Zookeeper :

  1. 基本概念;
  2. 数据结构;
  3. 如何使用 Netflix 公司开源的 zookeeper 客户端框架 Curator 进行增删改查;

教程

Guide 的星球正在更新《从零开始手把手教你实现一个简单的 RPC 框架》。扫描下方二维码关注“JavaGuide”后回复 “星球”即可。

我的公众号

guide-rpc-framework's People

Contributors

1228857713 avatar busysha avatar chokhoou avatar doddddza avatar foamvalue avatar lmgty avatar mesmerizeby avatar michaelxi3 avatar mrbigthree avatar mrzhang-badminton avatar nicknameid avatar pansonpanson avatar sakuragi1111 avatar smile2coder avatar snailclimb avatar tangj1992 avatar tangminxuan avatar tydhot avatar vinlee19 avatar xiaoguyueyue avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

guide-rpc-framework's Issues

SPI实现的一点小疑问

image
我看这个 EXTENSION_INSTANCES 只是用来实例化,那么能不能改成这样?
难道只是因为 putIfAbsent 是原子性的?在进入方法之前,不也用 synchronized 加锁了?

    private T createExtension(String name) {
        // 从加载类缓存中获取 class 对象
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw new RuntimeException("No such extension of name " + name);
        }
        T instance = null;
        try {
            instance = (T) clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            log.error(e.getMessage());
        }
        return instance;
    }

关于NettyRpcClientHandler中NettyRpcClient使用单例工厂获取的问题,单例工厂中没有ExtensionLoader创建的client。

当前master中,NettyRpcClientHandler通过单例工厂获得NettyRpcClient,然后在userEventTriggered方法中调用Client的getChannel方法。
image
image
这种方式会不会造成:第一次获取NettyRpcClient对象的时候,一定会通过单例工厂重新构建一个新的NettyRpcClient对象。
如果改成,NettyRpcClientHandler通过构造函数NettyRpcClient传递方式,是否存在问题:
image

关于加载gzip扩展的一个小错误

下载最新的代码,运行之后报错如下
image

发现rpc-framework-simple下面的MERA-INF中关于Compress的配置文件名称写错了,因为github.javaguide.compress.Compress接口是大写字母开头的,而配置文件名称是小写字母c所以导致找不到这个接口的SPI扩展类gzip

发现的一些小问题

我个人很喜欢这个项目,因为它能帮助我更好的理解rpc的细节。前段时间我看了李林峰的 《Netty权威指南》,也自己实现过简单的rpc,但是却不如你的全面和细节。在阅读源码的过程中,我发现了一下小问题,也可能是我没有get到你的思路,请指教

  1. github.javaguide.factory.SingletonFactory#getInstance,这个单例只是实现了一次校验,所以它恐怕不能正确的运行。
  2. github.javaguide.serialize.kyro.KryoSerializer#kryoThreadLocal,因为Kryo是非线程安全的,所以用了ThreadLocal为每个线程创建一个实例。由于netty的序列化操作是在线程池中运行,所以没必要每次序列化完成后都做remove操作。
  3. 我看项目中实现了以注解的方式发布服务,主要原理是用了SpringBeanPostProcessor对实例做了后置处理,但是SpringBeanPostProcessor能注册到容器原因是,你的示例项目example-server中NettyServerMain类中含有@componentscan注解,扫描的包名与rpc-framework-simple项目相同而已,这显然是不合理的。
  4. 许多资源的创建没有加锁,比如github.javaguide.registry.zk.util.CuratorUtils#getZkClient。当然,这些可以靠spring完成。

个人的一些理解,如有不足,还望指教

关于SingletonFactory的一些问题,是否写成这样会更好?

当前master的SingletonFactory代码是这样的:

public final class SingletonFactory {
    private static final Map<String, Object> OBJECT_MAP = new HashMap<>();

    private SingletonFactory() {
    }

    public static <T> T getInstance(Class<T> c) {
        String key = c.toString();
        Object instance;
        synchronized (SingletonFactory.class) {
            instance = OBJECT_MAP.get(key);
            if (instance == null) {
                try {
                    instance = c.getDeclaredConstructor().newInstance();
                    OBJECT_MAP.put(key, instance);
                } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
            }
        }
        return c.cast(instance);
    }
}

我很疑惑为什么所有单例的对象需要共享同一把锁?他们之间也互不冲突。

其次,代码中似乎没有体现出double check,目前master中代码的逻辑中,无论OBJECT_MAP中是否已经存在目标单例对象,都需要先与其他线程竞争锁,这一点是不是不太合理?

基于这些存在的问题, 是否改成下面的代码会更好?

public class SingletonFactory {
    private static final Map<String, Object> SINGLETON_MAP = new ConcurrentHashMap<>();
    private static final Map<Class<?>, Object> LOCK_MAP = new ConcurrentHashMap<>();

    private SingletonFactory() {
    }

    /**
     * Class must have no-arguments constructor.
     */
    public static <T> T getInstance(Class<T> clazz) {
        if (clazz == null) {
            throw new IllegalArgumentException();
        }
        String key = clazz.toString();
        Object instance = SINGLETON_MAP.get(key);
        if (instance == null) {
            synchronized (LOCK_MAP.computeIfAbsent(clazz, k -> new Object())) {
                instance = SINGLETON_MAP.get(key);
                if (instance == null) {
                    try {
                        instance = clazz.getDeclaredConstructor().newInstance();
                        SINGLETON_MAP.put(key, instance);
                    } catch (InstantiationException | InvocationTargetException | NoSuchMethodException
                            | IllegalAccessException e) {
                        throw new RuntimeException(e.getMessage(), e);
                    }
                }
            }
        }
        return clazz.cast(instance);
    }
}

RpcRequestHandler优化

想问下在RpcRequestHandler里对获取到的Method加个缓存会不会有性能上的提升/会不会有什么副作用?反射调用的invoke性能到底如何呢?

@RpcScan相关的一个bug

运行项目的时候发现,虽然只定义了一个RpcService服务,但是log显示rpcServiceScanner扫描到了两个Bean。
image
是NettyRpcServer这个Bean被rpcServiceScanner扫描到了。但是,NettyRpcServer我觉得应该被springBeanScanner扫描到。这里被扫描到,是因为RpcScan定义扫描的包恰巧是github.javaguide。所以建议将springBeanScanner的扫描范围由:
"github.javaguide.spring" -> "github.javaguide"

问个问题

guide 教程可以开源吗。。。我就问问。。

一致性hash中,相同入参无法路由到同一个服务

return selector.select(rpcServiceName + Arrays.stream(rpcRequest.getParameters()));

Arrays.stream() 每次调用都会返回一个新对象,即使入参相同。如下图所示:
image

客户端调用,如下图所示:
image

是否可以直接将参数按顺序拼接呢,个人的一些思考,还望指教

zookeeper版本和curator版本不匹配导致的问题

Zookeeper已启动
其它配置都和教程一致

服务端端运行状态:
[main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Opening socket connection to server 127.0.0.1/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
[main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Socket connection established, initiating session, client: /127.0.0.1:59179, server: 127.0.0.1/127.0.0.1:2181
[main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Session establishment complete on server 127.0.0.1/127.0.0.1:2181, sessionid = 0x18318f179ba0006, negotiated timeout = 40000
[main-EventThread] INFO org.apache.curator.framework.state.ConnectionStateManager - State change: RECONNECTED

客户端运行状态:
[main-SendThread(127.0.0.1:2181)] INFO org.apache.zookeeper.ClientCnxn - Session establishment complete on server 127.0.0.1/127.0.0.1:2181, sessionid = 0x18318f179ba0004, negotiated timeout = 40000
[main-EventThread] INFO org.apache.curator.framework.state.ConnectionStateManager - State change: CONNECTED
[main] ERROR github.javaguide.registry.zk.util.CuratorUtils - get children nodes for path [/my-rpc/github.javaguide.HelloServicetest1version1] fail
Exception in thread "main" github.javaguide.exception.RpcException: 没有找到指定的服务:github.javaguide.HelloServicetest1version1

关于ProtostuffSerializer 的使用问题

https://github.com/Snailclimb/guide-rpc-framework/blob/master/rpc-framework-simple/src/main/java/github/javaguide/serialize/protostuff/ProtostuffSerializer.java

/**
 * Avoid re applying buffer space every time serialization
 */
private static final LinkedBuffer BUFFER = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);

@Override
public byte[] serialize(Object obj) {
    Class<?> clazz = obj.getClass();
    Schema schema = RuntimeSchema.getSchema(clazz);
    byte[] bytes;
    try {
        bytes = ProtostuffIOUtil.toByteArray(obj, schema, BUFFER);
    } finally {
        BUFFER.clear();
    }
    return bytes;
}

@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) {
    Schema<T> schema = RuntimeSchema.getSchema(clazz);
    T obj = schema.newMessage();
    ProtostuffIOUtil.mergeFrom(bytes, obj, schema);
    return obj;
}

1,其中LinkedBuffer 为静态,在并发情况下其中一个线程执行这个方法BUFFER.clear(),其他的是否会报错
2,Schema schema 这个可以用一个ConcurrentHashMap 存储起来,性能会更好
下面是我修改的代码:

private static Map<Class<?>, Schema<?>> schemaMap = new ConcurrentHashMap<>();
private static <T>Schema<T> getSchema(Class<?> clazz) {
    if (schemaMap.containsKey(clazz)) {
        return (Schema<T>)schemaMap.get(clazz);
    }
    Schema schema = RuntimeSchema.getSchema(clazz);
    // 双重检查
    if (schema != null) {
        schemaMap.put(clazz, schema);
    }
    return schema;
}

@Override
public byte[] serialize(Object object) {
    Class<?> clazz = object.getClass();
    LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
    try {
        Schema schema = getSchema(clazz);
        return ProtostuffIOUtil.toByteArray(object, schema, buffer);
    } catch (Exception e) {
        throw new IllegalStateException(e.getMessage(), e);
    } finally {
        buffer.clear();
    }
}

@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) {
    Schema<T> schema = getSchema(clazz);
    T obj = schema.newMessage();
    ProtostuffIOUtil.mergeFrom(bytes, obj, schema);
    return obj;
}

github.javaguide.provider.impl.ZkServiceProviderImpl类中为什么有serviceMap,还需要registeredService这个set去保存接口名?

请问github.javaguide.provider.impl.ZkServiceProviderImpl类中为什么有serviceMap,还需要registeredService这个set去保存接口名?

/**
     * key: rpc service name(interface name + version + group)
     * value: service object
     */
    private final Map<String, Object> serviceMap;
    private final Set<String> registeredService;

    // 在get方法中:
    String rpcServiceName = rpcServiceConfig.getRpcServiceName();
    if (registeredService.contains(rpcServiceName)) {
        return;
    }
    registeredService.add(rpcServiceName);
    serviceMap.put(rpcServiceName, rpcServiceConfig.getService());

直接用serviceMap.containsKey(rpcServiceName)不行吗?对此有些疑惑 :-)

发现个小问题

image
我跑这个项目是中文路径下,发现读rpc.properties文件的值读不到,然后打断点发现在PropertiesFileUtil里getResource()读取中文路径直接转义了,这个地方是不是加个转码更好呢

一致性哈希负载均衡器中,可以使用服务名的哈希值作为选择服务端地址的 key 吗?

如题:一致性哈希负载均衡器中,可以使用服务名的哈希值作为选择服务端地址的 key 吗?

Dubbo 中使用调用参数进行散列作为 key,保证相同参数的请求总是发到同一提供者。

如果使用服务名称散列后的值做为 key 的话,会不会导致不同客户端对同服务的所有请求都落在一个服务器上?

请问自定义RPC协议的设计思路是如何?

codec包中的编码器解码器是根据这个自定义协议设计的,那这个自定义协议如果让我自己设计,思路应该是怎样的?字段、长度等方面的考量

指的是以下部分
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 |
| |
| ... ... |
+-------------------------------------------------------------------------------------------------------+
这个部分 这里没有对齐请谅解

zookeeper 的 watch 机制导致的问题

guide 哥我在复习 zookeeper 的时候发现它的 watcher 机制会在触发一次后就删除掉监听器,也就是说现在只能对节点的一次改变进行监听,第二次就不生效了。
目前有两种解决方案:

  1. 在回调函数里再次注册一个新的监听器
  2. 删除本地缓存的服务表,这样下一次去读取服务列表的时候就会重新去 zookeeper 拉最新数据,类似 Redis 和 MySQL 做双写一致性时的操作
PathChildrenCacheListener pathChildrenCacheListener = (curatorFramework, pathChildCacheEvent) -> {
    // List<String> serviceAddresses = curatorFramework.getChildren().forPath(servicePath);
    // SERVICE_ADDRESS_MAP.put(rpcServiceName, serviceAddresses);
    // 删除缓存以便下次从 zookeeper 拉取最新数据
    SERVICE_ADDRESS_MAP.remove(rpcServiceName);
};

关于 SingletonFactory 获取单例的问题

https://github.com/Snailclimb/guide-rpc-framework/blob/master/rpc-framework-common/src/main/java/github/javaguide/factory/SingletonFactory.java

public static <T> T getInstance(Class<T> c) {
        String key = c.toString();
        Object instance = null;
        if (instance == null) {
            synchronized (SingletonFactory.class) {
                instance = OBJECT_MAP.get(key);
                if (instance == null) {
                    try {
                        instance = c.getDeclaredConstructor().newInstance();
                        OBJECT_MAP.put(key, instance);
                    } catch (IllegalAccessException | InstantiationException e) {
                        throw new RuntimeException(e.getMessage(), e);
                    } catch (NoSuchMethodException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        return c.cast(instance);
    }

这一段感觉在模仿标准的 dcl 写法,但是

Object instance = null;
if (instance == null) {
synchronized (SingletonFactory.class) {

明显 instance 肯定为空,判断多余,且synchronized (SingletonFactory.class) { 使用 类锁,锁的力度很大,在多线程情况下并发度很低阻塞会很严重

Error creating bean with name 'helloServiceImpl' defined in file

[main] WARN org.springframework.context.annotation.AnnotationConfigApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'helloServiceImpl' defined in file [/Users/liuyangos8888/20200808/guide-rpc-framework/example-server/target/classes/github/javaguide/serviceimpl/HelloServiceImpl.class]: Initialization of bean failed; nested exception is github.javaguide.exception.RpcException: KeeperErrorCode = Unimplemented for /my-rpc/github.javaguide.HelloServicetest1version1/127.0.0.1:9998
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'helloServiceImpl' defined in file [/Users/liuyangos8888/20200808/guide-rpc-framework/example-server/target/classes/github/javaguide/serviceimpl/HelloServiceImpl.class]: Initialization of bean failed; nested exception is github.javaguide.exception.RpcException: KeeperErrorCode = Unimplemented for /my-rpc/github.javaguide.HelloServicetest1version1/127.0.0.1:9998
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:603)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:893)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.(AnnotationConfigApplicationContext.java:89)
at NettyServerMain.main(NettyServerMain.java:18)
Caused by: github.javaguide.exception.RpcException: KeeperErrorCode = Unimplemented for /my-rpc/github.javaguide.HelloServicetest1version1/127.0.0.1:9998
at github.javaguide.registry.zk.util.CuratorUtils.createPersistentNode(CuratorUtils.java:58)
at github.javaguide.registry.zk.ZkServiceRegistry.registerService(ZkServiceRegistry.java:23)
at github.javaguide.provider.ServiceProviderImpl.publishService(ServiceProviderImpl.java:73)
at github.javaguide.spring.SpringBeanPostProcessor.postProcessBeforeInitialization(SpringBeanPostProcessor.java:48)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:416)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1788)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595)
... 10 more

发现的小问题

image
这个字节数组输出流不需要关闭么?感觉应该写到try后面的括号里

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.