关于rpc:这次我设计了一款TPS百万级别的分布式高性能可扩展的RPC框架

52次阅读

共计 9288 个字符,预计需要花费 24 分钟才能阅读完成。

作者:冰河

博客地址:https://binghe001.github.io

大家好,我是冰河~~

没错,这次冰河又要搞事件了,这次筹备下手的是 RPC 框架我的项目。为什么要对RPC 框架我的项目 下手呢,因为在现在分布式、微服务乃至云原生一直倒退的过程中,RPC 作为底层必不可少的通信组件,被广泛应用在分布式、微服务和云原生我的项目中。

为啥要开发 RPC 框架

事件是这样的,在开发这个 RPC 框架之前,我破费了不少工夫算是对 Dubbo 框架彻底钻研透彻了。

冰河在撸透了 Dubbo2.x 和 Dubbo3.x 的源码之后,原本想给大家写一个 Dubbo 源码解析的专栏。为此,我其实私下筹备了一个多月:画流程图、剖析源码、写测试 Demo,本人在看 Dubbo 源码时,也为 Dubbo 源码增加了十分具体的正文。这里,就蕴含 Dubbo2.x 和 Dubbo3.x 的源码。

当我就这么熬夜肝文一个多月后,忽然发现一个问题:Dubbo 通过多年一直的迭代开发,它的源码曾经十分多了,如果以文章的模式将 Dubbo 的源码八面玲珑的剖析到位,那还不晓得要写到何年何月去了。当我写文章剖析 Dubbo 的最新版本 3.x 时,可能写到专栏的中后期 Dubbo 曾经更新到 4.x、5.x,设置有可能是 6.x、7.x 了。

与其这么吃力吧咧的剖析源码,还不如从零开始带着大家一起手撸一个可能在理论生产环境应用的、分布式、高性能、可扩大的 RPC 框架。这样,大家也可能直观的感触到一个可能在理论场景应用的 RPC 框架是如何一步步开发进去的。

置信大家在学完《RPC 手撸专栏》后,本人再去看 Dubbo 源码的话,就相对来说简略多了。你说是不是这样的呢?

你能学到什么?

既然是整个专栏的开篇嘛,必定是要通知你在这个专栏中可能学习到哪些实用的技术的。这里,我就画一张图来直观的通知你在《RPC 手撸专栏》可能学到哪些技术吧。

《RPC 手撸专栏》整体框架技术全貌如图所示,退出星球后与冰河一起从零实现它,搞定它,当你紧跟冰河节奏搞定这个 RPC 框架后,你会发现:什么 Dubbo、什么 gRPC、什么 BRPC、什么 Hessian、什么 Tars、什么 Thrift、什么 motan、什么 hprose 等等等等,市面上支流的 RPC 框架,对你来说就都不叫事儿了,跟紧冰河的节奏,你能够的。

置信小伙伴们看到《RPC 手撸专栏》波及到的知识点,应该可能理解到咱们这个从零开始的《RPC 手撸专栏》还是比拟硬核的吧?

另外,咱这 RPC 我的项目反对同步调用、异步调用、回调和单向调用。

  • 同步调用
  • 异步调用
  • 回调
  • 单向调用

对,没错,咱们《RPC 手撸专栏》最终实现的 RPC 框架的定位就是尽量能够在理论环境应用。通过这个专栏的学习,让大家深刻理解到可能在理论场景应用的 RPC 框架是如何一步步开发进去的。

代码构造

我将这个 bhrpc 我的项目 的定位为可在理论场景应用的、分布式、高性能、可扩大的 RPC 框架,目前总体上曾经开发并欠缺的性能达到 60+ 个子我的项目,大家看图吧。

我的项目大量应用了对标 Dubbo 的自定义 SPI 技术实现高度可扩展性,各位小伙伴能够依据本人的须要,依照 SPI 的设计要求增加本人实现的自定义插件。

演示成果

说了那么多,咱们一起来看看这个 RPC 框架的应用成果吧,因为咱们这个 RPC 框架反对的调用形式有:原生 RPC 调用、整合 Spring(XML/ 注解)、整合 SpringBoot、整合 SpringCloud、整合 SpringCloud Alibaba,整合 Docker 和整合 K8S 七种应用形式。

这里,咱们就以 整合 Spring 注解的形式 来给大家演示下这个 RPC 框架。

RPC 外围注解阐明

为了让大家更好的理解这个 RPC 框架,我先给大家看下 RPC 框架的两个外围注解,一个是 RPC 的服务提供者注解@RpcService,一个是 RPC 的服务调用者注解@RpcReference

(1)服务提供者注解 @RpcService 的外围源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description bhrpc 服务提供者注解
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcService {

    /**
     * 接口的 Class
     */
    Class<?> interfaceClass() default void.class;

    /**
     * 接口的 ClassName
     */
    String interfaceClassName() default "";

    /**
     * 版本号
     */
    String version() default "1.0.0";

    /**
     * 服务分组,默认为空
     */
    String group() default "";

    /**
     * 提早公布,预留
     */
    int delay() default 0;

    /**
     * 是否导出 rpc 服务,预留
     */
    boolean export() default true;}

(2)服务调用者注解 @RpcReference 的外围源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description bhrpc 服务消费者
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Autowired
public @interface RpcReference {

    /**
     * 版本号
     */
    String version() default "1.0.0";

    /**
     * 注册核心类型, 目前的类型蕴含:zookeeper、nacos、etcd、consul
     */
    String registryType() default "zookeeper";

    /**
     * 注册地址
     */
    String registryAddress() default "127.0.0.1:2181";

    /**
     * 负载平衡类型,默认基于 ZK 的一致性 Hash
     */
    String loadBalanceType() default "zkconsistenthash";

    /**
     * 序列化类型,目前的类型蕴含:protostuff、kryo、json、jdk、hessian2、fst
     */
    String serializationType() default "protostuff";

    /**
     * 超时工夫,默认 5s
     */
    long timeout() default 5000;

    /**
     * 是否异步执行
     */
    boolean async() default false;

    /**
     * 是否单向调用
     */
    boolean oneway() default false;

    /**
     * 代理的类型,jdk:jdk 代理,javassist: javassist 代理, cglib: cglib 代理
     */
    String proxy() default "jdk";

    /**
     * 服务分组,默认为空
     */
    String group() default "";}

这里,我只列出了服务提供者注解 @RpcService 和服务调用者注解 @RpcReference 的局部源码,后续在 RPC 框架不断完善的过程中,大家就能够缓缓看到源码的全貌和其每个注解实现的性能。这里,我就不具体介绍了。

当然啦,在这个 RPC 框架实现的原生调用形式中,能够不必这些注解就可能实现近程调用。

成果演示

接口定义

定义两个接口,别离为 HelloService 和 HelloPersonService,源码如下所示。

  • HelloService 接口源码
public interface HelloService {String hello(String name);
    String hello(Person person);
}
  • HelloPersonService 接口源码
public interface HelloPersonService {List<Person> getTestPerson(String name,int num);
}

实现服务提供者 demo

(1)创立 HelloService 接口和 HelloPersonService 接口的实现类 HelloServiceImpl 和 HelloPersonServiceImpl,如下所示。

  • HelloServiceImpl 类源码
@RpcService(interfaceClass = HelloService.class, version = "1.0.0")
public class HelloServiceImpl implements HelloService {

    @Override
    public String hello(String name) {return "Hello!" + name;}

    @Override
    public String hello(Person person) {return "Hello!" + person.getFirstName() + " " + person.getLastName();}
}

能够看到,在 HelloServiceImpl 类上增加了 RPC 服务提供者注解@RpcService,示意将其公布为一个 RPC 服务。

  • HelloPersonServiceImpl 类源码
@RpcService(interfaceClass = HelloPersonService.class, version = "1.0.0")
public class HelloPersonServiceImpl implements HelloPersonService {
    @Override
    public List<Person> getTestPerson(String name, int num) {List<Person> persons = new ArrayList<>(num);
        for (int i = 0; i < num; ++i) {persons.add(new Person(Integer.toString(i), name));
        }
        return persons;
    }
}

能够看到,在 HelloPersonServiceImpl 类上增加了 RPC 服务提供者注解@RpcService,示意将其公布为一个 RPC 服务。

(2)创立服务提供者 demo 的配置类 ServerConfig,在 ServerConfig 类中注入 RegistryService 注册核心接口的实现类,以及 RPC 服务提供者的外围类 RpcServer,如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 基于注解的配置类
 */
@Configuration
@ComponentScan(value = {"io.binghe.rpc.demo"})
@PropertySource(value = {"classpath:rpc.properties"})
public class SpringAnnotationProviderConfig {@Value("${registry.address}")
    private String registryAddress;

    @Value("${registry.type}")
    private String registryType;

    @Value("${registry.loadbalance.type}")
    private String registryLoadbalanceType;

    @Value("${server.address}")
    private String serverAddress;

    @Value("${reflect.type}")
    private String reflectType;

    @Bean
    public RpcSpringServer rpcSpringServer(){return new RpcSpringServer(serverAddress, registryAddress, registryType, registryLoadbalanceType, reflectType);
    }
}

(3)创立服务提供者 demo 的启动类 ServerTest,如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description RPC 整合 Spring 注解,服务提供者 demo 启动类
 */
public class ServerTest {public static void main(String[] args){new AnnotationConfigApplicationContext(ServerConfig.class);
    }
}

实现服务调用者 demo

(1)创立测试服务调用者的 TestService 接口,如下所示。

public interface TestService {void printResult();
}

(2)创立 TestService 接口的实现类 TestServiceImpl,在 TestServiceImpl 类上标注 Spring 的 @Service 注解,并在 TestServiceImpl 类中通过 @RpcReference 注解注入 HelloService 接口的实现类和 HelloPersonService 接口的实现类,并实现 TestService 接口的 printResult()办法,源码如下所示。

/**
 * @author binghe
 * @version 1.0.0
 * @description 测试 RPC 服务调用者
 */
@Service
public class TestServiceImpl implements TestService {@RpcReference(version = "1.0.0", timeout = 3000, proxy = "javassist", isAsync = true)
    private HelloService helloService;
    
    @RpcReference(proxy = "cglib")
    private HelloPersonService helloPersonService;

    @Override
    public void printResult() {String result = helloService.hello("binghe");
        System.out.println(result);
        result = helloService.hello(new Person("binghe001", "binghe002"));
        System.out.println(result);
        System.out.println("=================================");
        List<Person> personList = helloPersonService.getTestPerson("binghe", 2);
        personList.stream().forEach(System.out::println);
    }
}

通过 TestServiceImpl 类的源码咱们能够看到,近程调用 HelloService 接口的办法时应用的是 javassist 动静代理,近程调用 HelloPersonService 接口时,应用的是 cglib 动静代理。

(3)创立服务调用者 demo 的配置类 ClientConfig,如下所示。

@Configuration
@ComponentScan(value = {"io.binghe.rpc.*"})
@PropertySource(value = {"classpath:rpc.properties"})
public class ClientConfig {}

(4)创立服务调用者 demo 的启动类 ClientTest,如下所示。

public class ClientTest {public static void main(String[] args){AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ClientConfig.class);
        TestService testService = context.getBean(TestService.class);
        testService.printResult();
        context.close();}
}

启动服务测试

(1)启动 Zookeeper,这里,为了演示简略,就间接在我本机启动单机 Zookeeper 好了,启动后的成果如下图所示。

(2)启动服务提供者 ServerTest 类,启动后输入的日志信息如下所示。

13:43:36,876  INFO ConnectionStateManager:228 - State change: CONNECTED
13:43:36,905  INFO RpcClient:79 - use cglib dynamic proxy...
13:43:36,942  INFO CuratorFrameworkImpl:235 - Starting
13:43:36,943  INFO ZooKeeper:868 - Initiating client connection, connectString=127.0.0.1:2181 

能够看到,服务提供者曾经将公布的服务注册到了 Zookeeper 中。

(3)登录 Zookeeper 客户端查看 Zookeeper 中注册的服务,如下所示。

  • 查看 HelloService 接口公布的服务信息
[zk: localhost:2181(CONNECTED) 5] get /binghe_rpc/io.binghe.rpc.test.client.HelloService#1.0.0/65eb0d7f-4bf7-4a0a-bafc-1b7e0e030353

{"name":"io.binghe.rpc.test.client.HelloService#1.0.0","id":"65eb0d7f-4bf7-4a0a-bafc-1b7e0e030353","address":"127.0.0.1","port":18866,"sslPort":null,"payload":{"@class":"io.binghe.rpc.center.meta.ServiceMeta","serviceName":"io.binghe.rpc.test.client.HelloService","serviceVersion":"1.0.0","serviceAddr":"127.0.0.1","servicePort":18866},"registrationTimeUTC":1656135817627,"serviceType":"DYNAMIC","uriSpec":null,"enabled":true}
  • 查看 HelloPersonService 接口公布的服务信息
[zk: localhost:2181(CONNECTED) 7] get /binghe_rpc/io.binghe.rpc.test.client.HelloPersonService#1.0.0/882a5cdb-f581-4a83-8d56-800a8f14e831

{"name":"io.binghe.rpc.test.client.HelloPersonService#1.0.0","id":"882a5cdb-f581-4a83-8d56-800a8f14e831","address":"127.0.0.1","port":18866,"sslPort":null,"payload":{"@class":"io.binghe.rpc.center.meta.ServiceMeta","serviceName":"io.binghe.rpc.test.client.HelloPersonService","serviceVersion":"1.0.0","serviceAddr":"127.0.0.1","servicePort":18866},"registrationTimeUTC":1656135817274,"serviceType":"DYNAMIC","uriSpec":null,"enabled":true}

通过 Zookeeper 客户端能够看出,HelloService 接口和 HelloPersonService 接口公布的服务都曾经被注册到 Zookeeper 了。

(4)启动服务提供者 ClientTest 类,实现 RPC 调用,输入的日志信息如下所示。

13:56:47,391  INFO ConnectionStateManager:228 - State change: CONNECTED
13:56:47,488  INFO RpcClient:76 - use javassist dynamic proxy...
13:56:47,518  INFO ConnectionStateManager:228 - State change: CONNECTED
13:56:47,545  INFO RpcClient:79 - use cglib dynamic proxy...
13:56:48,253  INFO RpcConsumer:85 - connect rpc server 127.0.0.1 on port 18866 success.
Hello! binghe
Hello! binghe001 binghe002
=================================
0 binghe
1 binghe

能够看到,在 ClientTest 类的命令行输入了近程调用的后果信息。并输入了调用 HelloService 接口的近程办法应用的是 javassist 动静代理。调用 HelloPersonService 接口的近程办法应用的是 cglib 动静代理。

咱们一起手撸的 RPC 框架其实还有很多十分弱小的性能,这里,就不一一演示了,前面咱们都会一起手撸来实现它。

一点点倡议

咱们这个专栏属于实战类型比拟强的专栏,加上咱们一起从零开始手撸的 RPC 框架会波及泛滥的知识点。正所谓纸上得来终觉浅,绝知此事要躬行。冰河心愿大家在学习这个专栏的时候勤入手,跟着专栏一起实现代码。期间要多动脑,多总结,这样才可能加深对各项知识点的了解。切忌眼高手低,学了半天却最终啥也没学会。

好了,明天的开篇文章就到这儿吧,如果文章对你有点帮忙,记得给冰河一键三连哦,欢送将文章转发给更多的小伙伴,冰河将不胜感激~~

一起登程

我会将《RPC 手撸专栏》的源码获取形式放到常识星球中,同时在微信上会创立专门的常识星球群,冰河会在常识星球上和星球群里解答球友的发问,关注 冰河技术公号 回复 星球 即可获取优惠券。

欢送大家将文章或者星球转发到群里或者朋友圈,这些内容冰河将用上班、周末、假期的工夫不断完善。通过视频 + 文章 + 常识小册 + 直播 + 作业的模式与你一起学习、晋升和提高,最终的目标就是晋升你的技术实力,让你退职场走的更远,顺便多赚些钱。

好了,明天就到这儿吧,我是冰河,咱们下期见~~

正文完
 0