从零开始写一个RPC框架
当前,互联网公司的应用基本都是基于微服务的,系统被拆分为多个分布式的服务,每个服务之间通过RPC远程调用来交互。常用的RPC框架为Dubbo、Spring Cloud的相关套件等。我们试着自己实现一套简易的RPC框架,加深自己的技术理解。技术选型为Netty、SpringBoot。完整代码在Github会持续更新,欢迎大家关注:https://github.com/CvShrimp/NettyRpc
客户端
客户端的实现参考了Mybatis中Mapper的实现,通过ClassPathBeanDefinitionScanner将咱们自己的Bean注入Spring容器中,通过@Autowired注解即可注入使用,实现类通过动态代理生成,下面是核心的ClassPathBeanDefinitionScanner实现类,通过注入Spring的FactoryBean实现,FactoryBean又会生成动态代理的实现类,这个实现类会作为Netty的客户端访问Netty服务端,以实现RPC调用。
package com.cvshrimp.client;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Set;
/**
* Created by CvShrimp on 2019/11/4.
*
* @author wkn
*/
public class ClassPathRpcScanner extends ClassPathBeanDefinitionScanner {
private Class<? extends Annotation> annotationClass;
public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
this.annotationClass = annotationClass;
}
public ClassPathRpcScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No RPC mapper was found in '"
+ Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
public void registerFilters() {
boolean acceptAllInterfaces = true;
if (this.annotationClass != null) {
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
if (acceptAllInterfaces) {
addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
}
// exclude package-info.java
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory)
throws IOException {
String className = metadataReader.getClassMetadata()
.getClassName();
return className.endsWith("package-info");
}
});
}
private void processBeanDefinitions(
Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(RpcFactoryBean.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
System.out.println(holder);
}
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
}
package com.cvshrimp.client;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.reflect.Proxy;
/**
* Created by CvShrimp on 2019/11/4.
*
* @author wkn
*/
@Slf4j
public class RpcFactoryBean<T> implements FactoryBean<T> {
private Class<T> rpcInterface;
@Autowired
private ZkClient zkClient;
public RpcFactoryBean() {}
public RpcFactoryBean(Class<T> rpcInterface) {
this.rpcInterface = rpcInterface;
}
@Override
public T getObject() throws Exception {
return getRpc();
}
@Override
public Class<?> getObjectType() {
return this.rpcInterface;
}
@Override
public boolean isSingleton() {
return true;
}
@SuppressWarnings(value = "unchecked")
private <T> T getRpc() {
ZooKeeper zooKeeper = zkClient.getInstance();
StringBuilder pathBuilder = new StringBuilder("/rpc/");
pathBuilder.append(rpcInterface.getName());
String providerAddress;
try {
providerAddress = new String(zooKeeper.getData(pathBuilder.toString(), true, new Stat()));
String[] infoArray = providerAddress.split(":");
RpcClient rpcClient = new RpcClient(infoArray[0], Integer.valueOf(infoArray[1]));
return (T) Proxy.newProxyInstance(rpcInterface.getClassLoader(), new Class[] { rpcInterface },
new RpcFactory<>(rpcInterface, rpcClient));
} catch (Exception e) {
log.error("No provider, interface={}", rpcInterface.getName());
}
return null;
}
}
服务端
服务端基于一个Spring的NettyServer的Bean来实现,该Bean扩展了ApplicationContextAware和InitializingBean。将容器中带有@RpcService自定义注解的类暴露出去,并将服务的调用地址注册到Zookeeper中,这里参考了Dubbo的实现。
package com.cvshrimp.server;
import com.cvshrimp.tool.ThreadPoolManagerUtil;
import com.google.common.collect.Maps;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
import java.util.Map;
/**
* Created by CvShrimp on 2019/11/4.
*
* @author wkn
*/
@Slf4j
@Component
public class NettyServer implements ApplicationContextAware, InitializingBean {
@Value("${export.port}")
private int port;
private Map<String, Object> serviceMap = Maps.newHashMap();
@Autowired
private ServiceRegistry serviceRegistry;
/**
* 设置ApplicationContext,先于InitializingBean的afterPropertiesSet被调用
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(RpcService.class);
serviceRegistry.registerRoot();
for(Object serviceBean : serviceBeanMap.values()) {
Class<?> clazz = serviceBean.getClass();
Class<?>[] interfaces = clazz.getInterfaces();
for(Class<?> inter : interfaces) {
String interfaceName = inter.getName();
serviceMap.put(interfaceName, serviceBean);
serviceRegistry.registerService(inter, "127.0.0.1:" + port);
// 注册服务成功
log.info("Register service successful: {}", interfaceName);
}
}
}
@Override
public void afterPropertiesSet() throws Exception {
ThreadPoolManagerUtil.getInstance().addTask(() -> {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
// 将注册的服务给NettyServerHandler
NettyServerHandler nettyServerHandler = new NettyServerHandler(serviceMap);
try {
ServerBootstrap bootstrap = new ServerBootstrap().group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ObjectDecoder(1024 * 1024,
ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())));
ch.pipeline().addLast(new ObjectEncoder());
ch.pipeline().addLast(nettyServerHandler);
}
}).option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.TCP_NODELAY, true).childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
});
}
}
完整的实现已经在Github,会持续更新,欢迎大家关注:https://github.com/CvShrimp/NettyRpc