欢迎各位品尝SpringCloud-Starter-Drift-Client

跨语言RPC框架Thrift无需IDL而使用注解的SpringCloudStrater客户端

Posted by Naah on Saturday, Dec 01,2018 15:31:06

1 介绍

1.1 Thrift

在介绍spring-cloud-starter-drift之前,我们应该先了解什么是Thrift

Thrift是一款跨语言的RPC序列化传输框架,由Facebook开源。现已移至Apache。

Thrift如何做到跨语言沟通的呢,我们都知道RPC框架是基于Socket的长连接。

Thrift通过标准化的接口定义语言IDL来定义接口,再使用Thrift的生成工具将IDL语言翻译为各个语言的接口代码,比如什么JAVACPPPython等。

在对比了几个可以跨语言的RPC框架之后,我们决定使用Thrift来作为我们的跨语言沟通的组件,因为Thrift是所有跨语言的RPC框架中性能最好的。而且出身好,又经过市场上大量的实践,之前饿了么在架构技术整合之前就大规模的使用Thrift进行跨语言沟通。Python端的thriftpy就是饿了么进行开源的。后来升级到了thriftpy2

1.2 Drift

提到Drift,我们不得不提他的老爹Nifty.

为什么说是他的老爹呢,因为DriftNifty都是Facebook进行开源的JAVA端的Thrift客户端。

Nifty是当年Facebook基于Netty开发的JAVA语言的Thrift客户端,使JAVA语言的Thrift的性能更上一层楼。

但是为什么又会有Drift呢?因为Facebook在2018-05月停止维护了Nifty,并且开启了新的项目Drift。为JAVA平台的Thrift带来了新的体验。

Drift最大的特点就是,在原有基于Netty的基础上不再需要IDL定义的接口来生成JAVA端的代码了。它使用注解的方式,动态的生成代码,为开发者带来了良好的开发体验。

1.3 SpringCloud-Starter-Drift-Client

由于需求是使用Thrift进行口语言远程调用,在SpringCloud中使用Drift进行编写,还要进行各种配置。简直烦的一比。

于是我动手对drift针对SpringCloud进行了封装,本以为这是件很简单的事,但是经过了仔细思考之后,发现要做的还是蛮多的。比如:

  1. 我们需要通过服务发现来找到对应的服务的IP地址和端口,并进行连接。在目前这个敏感的阶段,Eureka2闭源,导致大部分公司都迁移至Consul,部分与Dubbo生态圈耦合严重的公司依然是用Zookeeper来进行服务注册,随着阿里的Nacos开源,大家都在等待Nacos的成熟完善,就可以把SpringCloud和Dubbo的注册中心进行整合了。在这种情况下,需要对服务发现层进行抽象。这层我使用了SpringCloud Discovery 组件进行服务发现。并且保证服务的健康性。

  2. 服务发现后要与服务端建立长连接,还要保证连接的可用性,这就涉及到了连接池的概念。这里采用的还是commons-pool进行维护连接池的。

  3. 建立连接后还要建立负载均衡层,这样用户就可以自定义实现负载均衡的算法,比如说轮询、权重等。当服务失败后可以快速切换服务端进行重试。

  4. 因为使用Drift客户端使用接口+注解的形式,所以我们在对Drift注解进行封装的时候,要考虑到对注解标记过的字段进行注入的时候,要注入一个代理类, 所以要进行JDK动态代理来对接口进行代理转发。

  5. 这些还是功能上的问题,当我开始写代码的时候,我才发现,Spring你真的玩透了么?感觉好多的功能自己有思路却不知道怎么实现,然后针对Spring的各种机制又进行了恶补。比如:BeanPostProcessorClassPathBeanDefinitionScanner等等。

经过了这次的组件编写,真的是感觉受益良多,对以前有些模模糊糊的概念也逐渐清晰,毕竟书读百遍不如手敲一遍,特别是对Spring的一些接口也有了清晰的认识。

这里就说这么多吧,有兴趣的小伙伴可以去看我的代码,欢迎大家来给我提建议。

2 DEMO

2.1 建立SpringCloud项目

引入maven依赖(目前只支持consul,不过可以自定义实现)。

<dependency>
    <groupId>com.naah69</groupId>
    <artifactId>spring-cloud-starter-drift-client</artifactId>
    <version>1.0.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2.2 配置组件

编写SpringCloud配置。

spring:
  cloud:
    consul:
      host: 127.0.0.1
      port: 8500
      retry:
        max-attempts: 500
      discovery:
        health-check-critical-timeout: 5m
        health-check-path: /actuator/health
        health-check-interval: 5s
        tags: dev
        instance-id: demo
        enabled: true
        prefer-ip-address: true
        health-check-tls-skip-verify: true
        query-passing: true
        register: true
  drift:
    #    开关
    enable: true
    client:
      #      接口扫描路径
      package-to-scan: com.example.demo
      #      传输模型(com.naah69.rpc.drift.client.properties.DriftTransportModel)
      transport-model: framed
      #      序列化模型(com.naah69.rpc.drift.client.properties.DriftProtocolModel)
      protocol-model: binary
      #      服务列表刷新时间
      refresh-interval: 30000
      pool:
        #        连接重试次数
        retry-times: 0
        #        连接超时时间
        connect-timeout: 2000000
        #        请求超时时间
        request-timeout: 2000
        #        最大连接数
        pool-max-total-per-key: 1
        #        最大空闲数
        pool-max-idle-per-key: 1
        #        最小空闲数
        pool-min-idle-per-key: 1
        #        最大等待时间
        pool-max-wait: 3000
        #        创建时测试可用
        test-on-create: true
        #        借出时测试可用
        test-on-borrow: true
        #        返回时测试可用
        test-on-return: true
        #        空闲连接自动被回收
        test-while-idle: true

management:
  endpoint:
    shutdown:
      enabled: true
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: "*"

2.3 开启注解

在你的Application上添加以下的注解,开启组件。

@EnableDiscoveryClient
@EnableDriftClient

2.4 编写接口

通过以下几个注解来编写接口,因为怕使用者混淆,所以我们使用ThriftClient,表示这是Thrift服务的客户端。

//服务端在注册中心的名字
@ThriftClient(serviceId = "nlu_service")
public interface NLU {

    //对应的方法的名字
    @ThriftMethod("parse")
    String parse(String scene, String version, String text);

    @ThriftMethod("status")
    String status();
}

2.5 服务注入

我们使用ThriftRefer注解来注入客户端,使用version字段我们可以进行版本号的设置,防止多版本服务在注册中心注册时调用混乱的BUG.

    @ThriftRefer(version = "1.2.1")
    private NLU nlu;

2.6 使用服务

经过以上几个步骤,我们就可以进行使用了。

    @GetMapping("/demo")
    public String demo(){
        return nlu.status();
    }