一、概述
手动实现一款轻量,高效的RPC框架,基于TCP的二进制协议实现
github源码:https://github.com/wosn00/srpc
二、特征
- 基于netty的主从Reactor模型,NIO通信
- 支持同步,异步,携带回调等调用方式
- 支持spring项目下引入starter包开箱即用,整合spring,实现服务接口透明使用
- 支持非spring项目下单独使用,可不依赖spring环境
- 支持多种序列化类型,Protostuff,Kryo,Json,Jdk等
- 支持多种压缩算法,Snappy,Lz4,gzip,bzip2,Deflate,Lzo等
- 支持注册中心,自动服务注册和发现,默认实现zookpeer,也可不使用注册中心,手动指定服务端节点地址列表
- 支持多种负载均衡策略,随机,轮询,一致性hash等
- 支持服务容错,连接/调用异常情况下自动排除服务端故障节点
- 支持SPI扩展点,可扩展负载均衡策略,压缩算法,序列化类型,线程池,注册中心等
- 支持TLS双向认证加密
- 支持流量整形,请求异常重试,服务端请求去重等功能
三、设计
可能对RPC框架性能产生影响的几个因素:
- 网络IO线程模型
- 通信协议设计
- 序列化性能
- 服务调用管理方式
- 连接池的维护
3.1 RPC协议
协议上设计尽量紧凑,4位bit用于标识序列化类型,压缩类型和指令类型,方法映射上不需要像dubbo那样传输完整的类 方法 参数信息导致的无用流量增大,而是自行生成对应rpc调用方法的唯一标识字符串
3.2 同步线程模型
3.3 异步线程模型
四、使用示例
4.1、spring环境下
maven依赖
<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);
}
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;
private Integer businessQueueSize = 500;
private Integer connectionIdleTime = 180;
private Integer printConnectionNumInterval = 0;
private Boolean isPrintHearBeatPacketInfo = false;
private CompressType compressType = CompressType.SNAPPY;
private SerializeType serializeType = SerializeType.PROTOSTUFF;
private Integer sendBuf = 65535;
private Integer receiveBuf = 65535;
private Integer lowWaterLevel = 1024 * 1024;
private Integer highWaterLevel = 10 * 1024 * 1024;
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;
private Boolean useTLS = false;
private String keyPath;
private String keyPwd;
private String certPath;
private String trustCertPath;
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;
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;
private SerializeType serializeType = SerializeType.PROTOSTUFF;
private LoadBalanceRule loadBalanceRule = LoadBalanceRule.RANDOM;
private boolean excludeUnAvailableNodesEnable = true;
private Integer nodeErrorTimes = 3;
private Integer nodeHealthCheckTimeInterval = 10;
private Integer sendBuf = 65535;
private Integer receiveBuf = 65535;
private Integer lowWaterLevel = 1024 * 1024;
private Integer highWaterLevel = 10 * 1024 * 1024;
private Boolean trafficMonitorEnable = false;
private Long maxReadSpeed = 10 * 1000 * 1000L;
private Long maxWriteSpeed = 10 * 1000 * 1000L;
private Boolean useTLS = false;
private String keyPath;
private String keyPwd;
private String certPath;
private String trustCertPath;
private String clientAuth;
private Boolean enableRegistry = false;
private String registrySchema;
private List<String> registryAddress;
配置类信息: https://github.com/wosn00/srpc/blob/master/srpc-spring-boot-starter/src/main/java/com/hex/rpc/spring/starter/properties/RpcClientProperties.java 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;
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) {
SRpcServer.builder()
.serverConfig(new SRpcServerConfig())
.sourceClass(ServerTest.class)
.port(8005)
.start();
}
}
启动类上添加@SRpcScan注解,值需填写包含@SRpcRoute注解的类的包路径,缺省为启动类的上级包路径,即可自动扫描
client端使用
1.客户端启动和服务接口调用
public class ClientTest {
public static void main(String[] args1) {
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);
}
}
Client更多调用接口及参数可查看接口说明:
https://github.com/wosn00/srpc/blob/master/srpc-core/src/main/java/com/hex/srpc/core/rpc/Client.java
五、性能测试
5.1 与dubbo的性能对比测试
目前只是与dubbo进行了简单了的性能测试对比 0_0,后续有时间会进行更多的测试
测试代码:https://github.com/wosn00/THOC/blob/master/srpc-demo-provider/src/main/java/com/hex/srpc/SRpcProviderApplication.java
条件:
1.测试相同接口模拟业务处理延迟30ms后返回
2.服务端业务处理线程池均为500
3.dubbo采用默认的dubbo协议,srpc使用protostuff序列化
并发调用线程数 | srpc(TPS) | dubbo(TPS) |
---|
100 | 2810 | 2800 | 200 | 5522 | 5480 | 300 | 7834 | 7100 | 400 | 9480 | 8520 | 500 | 11380 | 9700 |
|