基于Netty和Spring实现RPC
1. 需求分析
基本需求分析:
- 服务端通过注册服务开放服务代理;
- 客户端通过申请服务,经过rpc系统代理远程调用服务端开放的服务;
程序需求分析:
- 代码侵入程度低,全部通过注解和配置实现;
- 客户端实现代理之后,使用与本地服务尽量接近;
2. 相关技术
- Netty:基于netty实现客户端与服务端之间的通讯,相比http请求更高效;
- spring boot
3. 通讯协议设计
- 服务端和客户端之间通讯基于netty,通过发送和接收协议包来完成通讯;
- 协议包的传输需要先进行序列化,本程序选择的是json的序列化方式,使用fastjson;
协议包:
public class dubboProtocol<T> {
Long REQUEST_ID;
int type;
T content;
}
REQUEST_ID是必要的,用于对应响应和请求;
type用于标识是请求还是响应;
content是一个泛型变量,用于接收响应或请求的内容;
请求:
public class Request {
private String serviceName;
private String methodName;
private Object[] parameters;
private Class<?>[] parametersType;
}
serviceName:请求服务的className;
methodName:请求的方法;
parameters:请求的参数,通过一个Object数组接收;
parametersType:用于接收参数的类型,因为经过序列化和反序列化后参数类型会变成JsonObject类型,因此需要知道原来的类型进行还原;
编码:
public class JSONEncoder extends MessageToByteEncoder<dubboProtocol> {
@Override
protected void encode(ChannelHandlerContext ctx, dubboProtocol msg, ByteBuf out) throws Exception {
String json = JSONObject.toJSONString(msg);
byte[] bytes = json.getBytes(CharsetUtil.UTF_8);
out.writeInt(bytes.length);
out.writeBytes(bytes);
}
}
解码:
public class JSONDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
int len = in.readInt();
byte[] bytes = new byte[len];
in.readBytes(bytes);
String json = new String(bytes, CharsetUtil.UTF_8);
dubboProtocol msg = JSONObject.parseObject(json, dubboProtocol.class);
out.add(msg);
}
}
4. 异步模型设计
Netty的执行是异步的,需要通过一个Future对象来获取响应结果;
Future:
public class RpcFuture<T> {
private Promise<T> promise;
public RpcFuture(Promise<T> promise) {
this.promise = promise;
}
public Promise<T> getPromise() {
return promise;
}
public void setPromise(Promise<T> promise) {
this.promise = promise;
}
}
由于有可能有多个请求在等待返回结果,因此需要有一个FutureFactory对多个Future进行管理;
FutureFactory:
public class FutureFactory {
public static final AtomicLong REQUEST_ID=new AtomicLong();
public static final Map<Long, RpcFuture> REQUEST_MAP=new ConcurrentHashMap<>();
public static Long put(RpcFuture future){
long Rid = REQUEST_ID.incrementAndGet();
REQUEST_MAP.put(Rid,future);
return Rid;
}
public static RpcFuture get(Long id){
return REQUEST_MAP.get(id);
}
public static void remove(Long id){
REQUEST_MAP.remove(id);
}
}
5. 功能注解
客户端注解:
申请代理注解:
/**
* 被该注解注释的类会被rpc代理
*/
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(ClientCondition.class)
public @interface Deposit {
String value() default "";
}
客户端容器注解:
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
@Conditional(ClientCondition.class)
public @interface ClientComponent {
}
包扫描注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(AutoMapperScanImportBeanDefinitionRegistrar.class)
public @interface ProxyScan {
@AliasFor("value")
String[] basePackage() default {};
@AliasFor("basePackage")
String[] value() default {};
}
用于扫描并注入需要被代理的服务的bean,由于客户端需要被代理的服务没有本地的实现类,且interface不能被实例化,需要通过代理的方式注入bean,因此需要通过扫描包并自定义注入方式来进行初始化;
服务器注解:
注册服务注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
@Conditional(ServerCondition.class)
public @interface Register {
}
服务器容器:
@Target({
ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
@Conditional(ServerCondition.class)
public @interface ServerComponent {
}
服务器容器和客户端容器都是通过@Conditional来扩展@Component,主要是为了实现通过配置来避免不必要的依赖注入;这两个注解相关的Condition如下:
public class ClientCondition implements Condition {
private static String property;
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if(property==null){
Environment environment = context.getEnvironment();
property = environment.getProperty("rpc.job");
}
return property