手动实现一款轻量 高效的RPC框架

一、概述

手动实现一款轻量,高效的RPC框架,基于TCP的二进制协议实现

二、特征

  • 基于netty的主从Reactor模型,NIO通信

  • 支持同步,异步,携带回调等调用方式

  • 支持spring项目下引入starter包开箱即用,整合spring,实现服务接口透明使用

  • 支持非spring项目下单独使用,可不依赖spring环境

  • 支持多种序列化类型,Protostuff,Kryo,Json,Jdk等

  • 支持多种压缩算法,Snappy,Lz4,gzip,bzip2,Deflate,Lzo等

  • 支持注册中心,自动服务注册和发现,默认实现zookpeer,也可不使用注册中心,手动指定服务端节点地址列表

  • 支持多种负载均衡策略,随机,轮询,一致性hash等

  • 支持服务容错,连接/调用异常情况下自动排除服务端故障节点

  • 支持SPI扩展点,可扩展负载均衡策略,压缩算法,序列化类型,线程池,注册中心等

  • 支持TLS双向认证加密

  • 支持流量整形,请求异常重试,服务端请求去重等功能

三、设计

ab6bae14838805950d1a9b3408593d2e.png

可能对RPC框架性能产生影响的几个因素:

  • 网络IO线程模型

  • 通信协议设计

  • 序列化性能

  • 服务调用管理方式

  • 连接池的维护

3.1 RPC协议

f82d09defcf10154e684b72013a2fce3.png

协议上设计尽量紧凑,4位bit用于标识序列化类型,压缩类型和指令类型,方法映射上不需要像dubbo那样传输完整的类 方法 参数信息导致的无用流量增大,而是自行生成对应rpc调用方法的唯一标识字符串。

3.2 同步线程模型

631eba076d2837894782619c24792d23.png

3.3 异步线程模型

15d855ac6cab1eeffec406e9e8f9ddf5.png

四、使用示例

4.1、spring环境下

maven依赖

项目目前暂时还未上传到maven中央仓库,需clone项目到本地maven选择srpc(root)下install后使用。

<dependency>


    <groupId>com.hex</groupId>


    <artifactId>srpc-spring-boot-starter</artifactId>


    <version>1.1.0</version>


</dependency>

若需使用zookeeper作为注册中心则引入

<dependency>


    <groupId>com.hex</groupId>


    <artifactId>srpc-registry-zookeeper</artifactId>


    <version>1.1.0</version>


</dependency>

server端使用

1.定义服务接口

@SRpcClient(serviceName = "testService")


public interface HelloService {


    String hello(String name);


}

接口添加@SRpcClient注解,serviceName属性为rpc服务都在注册中心的服务名称,若不使用注册中心,则注解nodes属性需手动指定服务端节点集群地址,将根据负载均衡策略自动选取节点调用。

@SRpcClient(nodes = {"127.0.0.1:9955;127.0.0.1:9956;127.0.0.1:9957"})


public interface HelloService {


    String hello(String name);


}

nodes属性有两种配置方式:

  • 直接指定节点地址列表,以分号隔开的字符串,例如
    @SRpcClient(nodes = "127.0.0.1:9955;127.0.0.1:9956;127.0.0.1:9957")

  • 以$符号开头支持从yml或properties配置文件获取节点配置,需保证服务消费方的yml或properties配置文件有对应的集群地址配置,同样以分号隔开,例如
    @SRpcClient(nodes = "${srpc.helloService}")

2.服务接口实现

@SRpcRoute


public class HelloServiceImpl implements HelloService {


    @Override


    public String hello(String name) {


        return name + " Hey bro, it's a good day";


    }


}

实现类添加@SRpcRoute注解,便会自动注册为spring的单例bean,可视为等同@Comphonent使用,内部可用@Autowired等spring相关注解,也可被其他bean注入。

3.配置yml

因同时包含了rpc客户端和服务端,所以客户端和服务端都需要配置,如需个性化配置的地方在yml或properties文件按需配置即可,以srpc.server或srpc.client为前缀。所有可自由配置的选项如下

服务端默认配置:

@ConfigurationProperties(prefix = "srpc.server")


public class RpcServerProperties {


    private Integer port = 9957; //绑定端口


    private Integer businessThreads = 200; //业务处理线程池大小,0为不设置


    private Integer businessQueueSize = 500; //业务线程池队列大小


    private Integer connectionIdleTime = 180;//超过连接空闲时间(秒)未收发数据则关闭连接


    private Integer printConnectionNumInterval = 0; //打印服务端当前连接详情, 时间间隔(秒), 0为不打印


    private Boolean isPrintHearBeatPacketInfo = false; //是否打印心跳包信息


    private CompressType compressType = CompressType.SNAPPY; //压缩算法类型,无需压缩为NONE


    private SerializeType serializeType = SerializeType.PROTOSTUFF; //序列化类型,默认protostuff


    private Integer sendBuf = 65535; //tcp发送缓冲区


    private Integer receiveBuf = 65535; //tcp接收缓冲区


    private Integer lowWaterLevel = 1024 * 1024; //netty低水位


    private Integer highWaterLevel = 10 * 1024 * 1024; //netty高水位


    private boolean deDuplicateEnable = false; //是否开启去重处理


    private Integer duplicateCheckTime = 10; //请求去重缓存时长(秒)


    private Long duplicateMaxSize = 1024 * 64L; //最大缓存请求个数


    private Boolean trafficMonitorEnable = false; //是否开启流控


    private Long maxReadSpeed = 10 * 1000 * 1000L; //带宽限制,最大读取速度


    private Long maxWriteSpeed = 10 * 1000 * 1000L; //带宽限制,最大写出速度


    // ----tls加密部分配置


    private Boolean useTLS = false; //是否开启tls加密


    private String keyPath; //私钥文件路径


    private String keyPwd; //密码


    private String certPath; //证书文件路径


    private String trustCertPath; //受信任ca证书路径


    private String clientAuth; //是否要求客户端认证


    // ----注册中心配置部分


    private Boolean enableRegistry = false; //是否使用注册中心


    private String registrySchema; //注册中心模式名称


    private List<String> registryAddress; //注册中心地址

客户端默认配置:

@ConfigurationProperties(prefix = "srpc.client")


public class RpcClientProperties {


    private Integer callBackTaskThreads = 200; //回调任务处理线程池大小,0为不设置


    private Integer callBackTaskQueueSize = 500; //回调任务线程池队列大小


    private Integer connectionTimeout = 5; //连接超时时间(秒)


    private Integer requestTimeout = 10; //请求超时时间(秒)


    private Integer connectionSizePerNode = 3; //每个节点连接数


    private Integer connectionIdleTime = 180; //超过连接空闲时间(秒)未收发数据则关闭连接


    private Integer heartBeatTimeInterval = 30; //发送心跳包间隔时间(秒)


    private CompressType compressType = CompressType.SNAPPY; //压缩算法类型,无需压缩为NONE


    private SerializeType serializeType = SerializeType.PROTOSTUFF; //序列化类型,默认protostuff


    private LoadBalanceRule loadBalanceRule = LoadBalanceRule.RANDOM; //集群负载均衡策略


    private boolean excludeUnAvailableNodesEnable = true; //集群模式下是否排除不可用的节点


    private Integer nodeErrorTimes = 3; //节点连接或请求超时/异常超过设置次数则置为节点不可用


    private Integer nodeHealthCheckTimeInterval = 10; //节点健康检查周期(秒),心跳包响应成功则恢复不可用的节点


    private Integer sendBuf = 65535; //tcp发送缓冲区


    private Integer receiveBuf = 65535; //tcp接收缓冲区


    private Integer lowWaterLevel = 1024 * 1024; //netty低水位


    private Integer highWaterLevel = 10 * 1024 * 1024; //netty高水位


    private Boolean trafficMonitorEnable = false; //是否开启流量控制


    private Long maxReadSpeed = 10 * 1000 * 1000L; //带宽限制,最大读取速度


    private Long maxWriteSpeed = 10 * 1000 * 1000L; //带宽限制,最大写出速度


    // ----TLS加密部分配置


    private Boolean useTLS = false; //是否开启TLS加密


    private String keyPath; //私钥文件路径


    private String keyPwd; //密码


    private String certPath; //证书文件路径


    private String trustCertPath; //受信任ca证书路径


    private String clientAuth; //是否要求客户端认证


    // ----注册中心配置部分


    private Boolean enableRegistry = false; //是否使用注册中心


    private String registrySchema; //注册中心模式名称, 缺省为zookeeper


    private List<String> registryAddress; //注册中心地址

4.服务端启动

@SpringBootApplication


@EnableSRpc(basePackages = "com.hex.example.provider")


public class RpcTestApplication {


    public static void main(String[] args) {


        SpringApplication.run(RpcTestApplication.class, args);


    }


}

启动类上添加@EnableSRpc注解,basePackages为需要扫描的包路径,包含@SRpcClient和@SRpcRoute注解的包路径,相应的类都会被自动注册为spring的单例bean,缺省为启动类上级包路径

client端使用

1.服务接口调用

@Component


public class HelloRpcTest {


    @Autowired


    private HelloService helloService; // 上面定义的rpc服务接口


    public void rpcServerTest(String name) {


        String msg = helloService.hello(name);


        System.out.println(msg);


    }


}

上述服务端定义的带有@SRpcClient注解的rpc服务接口,使用spring的@Autowired注入即可远程调用

2.配置yml(同上)

3.客户端启动(同上)

4.2、非spring环境下

maven依赖

<dependency>


    <groupId>com.hex</groupId>


    <artifactId>srpc-core</artifactId>


    <version>1.1.0</version>


</dependency>

若需使用zookeeper作为注册中心则引入

<dependency>


    <groupId>com.hex</groupId>


    <artifactId>srpc-registry-zookeeper</artifactId>


    <version>1.1.0</version>


</dependency>

server端使用

1.定义服务接口实现

@SRpcRoute


public class HelloServiceImpl {


    @Mapping("hello")


    public String hello(String name) {


        return name + " Hey bro, it's a good day";


    }


}

2.服务端启动

@SRpcScan("com.hex.example")


public class ServerTest {


    public static void main(String[] args) {


        // 启动服务端, 需填入rpc服务端配置, 可使用默认配置, source填写有@RouteScan注解的类


        SRpcServer.builder()


                .serverConfig(new SRpcServerConfig()) //包含rpc服务端的各项默认配置,可自行修改


                .sourceClass(ServerTest.class) //有@RouteScan注解的类


                .port(8005) //rpc服务端绑定的端口,默认9957


                .start();


    }


}

启动类上添加@SRpcScan注解,值需填写包含@SRpcRoute注解的类的包路径,缺省为启动类的上级包路径,即可自动扫描

client端使用

1.客户端启动和服务接口调用

public class ClientTest {


    public static void main(String[] args1) {


        // 初始化客户端,需填入rpc客户端配置,可使用默认配置


        Client rpcClient = SRpcClient.builder()


                .config(new SRpcClientConfig())


                .start();


        Object[] args = {"Jack"};


        HostAndPort node = HostAndPort.from("127.0.0.1:8005");


        // 同步发送请求,获取响应


        String response = rpcClient.invoke("hello", String.class, args, node);


        System.out.println(response);


        // 异步发送请求,发送完成即返回,不阻塞等待响应结果


        rpcClient.invokeAsync("hello",


                rpcResponse -> System.out.println("收到响应,开始执行回调方法" + rpcResponse), args, node);


    }


}

五、性能测试

5.1 与dubbo的性能对比测试

目前只是与dubbo进行了简单了的性能测试对比 0_0,后续有时间会进行更多的测试。

58482b057697655eeb8fc7c564ec9f50.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Avro是一个轻量级的数据序列化框架,同时也提供了RPC功能。Avro提供了一个基于JSON的schema定义文件来描述数据结构,使得Avro能够支持动态的数据类型。Avro还提供了一个名为avro-rpc的模块,用于实现基于Avro的RPC。 下面我们来对avro-rpc进行性能测试。我们将使用Python 3.7作为客户端和服务端编程语言,并使用Apache Bench来进行压力测试。 首先,我们需要安装avro和avro-rpc模块: ``` pip install avro pip install avro-rpc ``` 接下来,我们编写一个简单的RPC服务端程序: ```python import avro.protocol import avro.ipc import socket PROTOCOL = avro.protocol.parse(open("test.avpr").read()) class RpcServer(object): def __init__(self, host, port): self.server = avro.ipc.HTTPServer(self.handle_request) self.server.add_listener((host, port)) def handle_request(self, request, protocol): message_name = request.message_name request_params = request.request_params print("Received request: {} {}".format(message_name, request_params)) if message_name == "ping": return "pong" elif message_name == "echo": return request_params else: raise avro.AvroRemoteException("Unknown message: {}".format(message_name)) def serve_forever(self): self.server.start() self.server.join() if __name__ == "__main__": server = RpcServer("localhost", 8080) server.serve_forever() ``` 这个RPC服务端程序会监听localhost的8080端口,并实现了两个RPC方法:ping和echo。当客户端调用ping方法时,服务端会返回字符串“pong”;当客户端调用echo方法时,服务端会返回客户端传递的参数。 接下来,我们编写一个简单的RPC客户端程序: ```python import avro.protocol import avro.ipc import socket PROTOCOL = avro.protocol.parse(open("test.avpr").read()) class RpcClient(object): def __init__(self, host, port): self.transceiver = avro.ipc.HTTPTransceiver((host, port)) self.requestor = avro.ipc.Requestor(PROTOCOL, self.transceiver) def ping(self): return self.requestor.request("ping", []) def echo(self, message): return self.requestor.request("echo", [message]) if __name__ == "__main__": client = RpcClient("localhost", 8080) print(client.ping()) print(client.echo("Hello, world!")) ``` 这个RPC客户端程序会连接到localhost的8080端口,并调用服务端实现的ping和echo方法。 接下来,我们使用Apache Bench来进行压力测试: ``` ab -n 10000 -c 10 http://localhost:8080/ ``` 这个命令会模拟10个并发连接,总共发送10000个请求。我们可以通过修改-n和-c参数来改变测试规模。 测试结果如下: ``` Server Software: Server Hostname: localhost Server Port: 8080 Document Path: / Document Length: 4 bytes Concurrency Level: 10 Time taken for tests: 7.194 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 1830000 bytes HTML transferred: 40000 bytes Requests per second: 1390.36 [#/sec] (mean) Time per request: 7.194 [ms] (mean) Time per request: 0.719 [ms] (mean, across all concurrent requests) Transfer rate: 248.18 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.3 0 14 Processing: 1 7 2.3 7 24 Waiting: 1 7 2.3 7 24 Total: 1 7 2.3 7 24 Percentage of the requests served within a certain time (ms) 50% 7 66% 8 75% 8 80% 8 90% 10 95% 12 98% 15 99% 17 100% 24 (longest request) ``` 从测试结果中可以看出,avro-rpc在处理10000个请求时,平均每个请求处理时间为7.194毫秒。每秒处理请求数为1390.36,处理速度较快,而且没有出现失败的请求。因此,我们可以认为avro-rpc一个性能良好的轻量级RPC框架

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值