代码目录结构

  • rpc-common存放公共类
  • rpc-interface为rpc调用方需要调用的接口
  • rpc-register提供服务的注册与发现
  • rpc-client为rpc调用方底层实现
  • rpc-server为rpc被调用方底层实现
  • rpc-sample-client就是使用自实现的rpc框架调用rpc-sample-server
  • rpc-sample-server就是rpc框架的被调用方

技术要点

1. 使用zookeeper作注册中心,把被调用方的信息注册上去





服务的注册

public void register(String data) {
if (data != null) {
byte[] bytes = data.getBytes();
try {
if (zk.exists(dataPath, null) == null) {
zk.create(dataPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
zk.create(dataPath + "/data", bytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

服务的发现

public String discover() {
String data = null;
int size = dataList.size();
// 存在新节点,使用即可
if (size > 0) {
if (size == 1) {
data = dataList.get(0);
} else {
data = dataList.get(ThreadLocalRandom.current().nextInt(size));
}
}
return data;
}

2、自定义注解

注解RpcService标记被调用方的实现类,RpcClientService标记调用方的类需要生成代理类

@Target({ ElementType.TYPE })//注解用在接口上
@Retention(RetentionPolicy.RUNTIME)//VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息
@Component
public @interface RpcClientService {
}
@Target({ ElementType.TYPE })//注解用在接口上
@Retention(RetentionPolicy.RUNTIME)//VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息
@Component
public @interface RpcService {
Class<?> value();
}

3、调用方代理类的注入

扫描包下的RpcClientService注解,并生成代理类

/**
* 用于Spring动态注入自定义接口
*
* @author shuangyueliao
*/
@Component
public class ServiceBeanDefinitionRegistry implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Set<Class<?>> typesAnnotatedWith = new Reflections("com.shuangyueliao.rpc.myinterface").getTypesAnnotatedWith(RpcClientService.class);
for (Class beanClazz : typesAnnotatedWith) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);
GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition(); //在这里,我们可以给该对象的属性注入对应的实例。
//比如mybatis,就在这里注入了dataSource和sqlSessionFactory,
// 注意,如果采用definition.getPropertyValues()方式的话,
// 类似definition.getPropertyValues().add("interfaceType", beanClazz);
// 则要求在FactoryBean(本应用中即ServiceFactory)提供setter方法,否则会注入失败
// 如果采用definition.getConstructorArgumentValues(),
// 则FactoryBean中需要提供包含该属性的构造方法,否则会注入失败
Properties properties = new Properties();
InputStream is=this.getClass().getResourceAsStream("/application.properties");
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
String registerAddress = properties.getProperty("zookeeper.url");
String dataPath = properties.getProperty("zookeeper.register.path.prefix");
ServiceDiscovery serviceDiscovery = new ServiceDiscovery(registerAddress, dataPath);
definition.getPropertyValues().addPropertyValue("serviceDiscovery", serviceDiscovery);
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz); //注意,这里的BeanClass是生成Bean实例的工厂,不是Bean本身。
// FactoryBean是一种特殊的Bean,其返回的对象不是指定类的一个实例,
// 其返回的是该工厂Bean的getObject方法所返回的对象。
definition.setBeanClass(RpcProxy.class); //这里采用的是byType方式注入,类似的还有byName等
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);
}
} @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } }

获取代理类

public class RpcProxy<T> implements FactoryBean<T> {

	private String serverAddress;

	private Class<T> interfaceType;

	private ServiceDiscovery serviceDiscovery;

	public RpcProxy(Class<T> interfaceType) {
this.interfaceType = interfaceType;
} public ServiceDiscovery getServiceDiscovery() {
return serviceDiscovery;
} public void setServiceDiscovery(ServiceDiscovery serviceDiscovery) {
this.serviceDiscovery = serviceDiscovery;
} private RpcClient rpcClient; @Override
public T getObject() throws Exception {
return (T) Proxy.newProxyInstance(interfaceType.getClassLoader(),
new Class<?>[] { interfaceType }, (proxy, method, args) -> {
//创建RpcRequest,封装被代理类的属性
RpcRequest request = new RpcRequest();
request.setRequestId(UUID.randomUUID().toString());
//拿到声明这个方法的业务接口名称
request.setClassName(method.getDeclaringClass()
.getName());
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
synchronized (this) {
if (rpcClient == null) {
//查找服务
if (serviceDiscovery != null) {
serverAddress = serviceDiscovery.discover();
}
//随机获取服务的地址
String[] array = serverAddress.split(":");
String host = array[0];
int port = Integer.parseInt(array[1]);
//创建Netty实现的RpcClient,链接服务端
rpcClient = new RpcClient(host, port);
}
}
//通过netty向服务端发送请求
RpcResponse response = rpcClient.send(request);
//返回信息
if (response.isError()) {
throw response.getError();
} else {
return response.getResult();
}
});
} @Override
public Class<?> getObjectType() {
return interfaceType;
} }

调用方底层基于netty的发送请求和接收响应

public RpcClient(String host, int port) {
this.host = host;
this.port = port;
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) {
// 向pipeline中添加编码、解码、业务处理的handler
channel.pipeline()
.addLast(new RpcEncoder(RpcRequest.class)) //OUT - 1
.addLast(new RpcDecoder(RpcResponse.class)) //IN - 1
.addLast(RpcClient.this); //IN - 2
}
}).option(ChannelOption.SO_KEEPALIVE, true);
// 链接服务器
future = bootstrap.connect(host, port).sync();
} catch (Exception e) {
e.printStackTrace();
try {
future.channel().closeFuture().sync();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
group.shutdownGracefully();
}
} /**
* 链接服务端,发送消息
*
* @param request
* @return
* @throws Exception
*/
public RpcResponse send(RpcRequest request) throws Exception {
//将request对象写入outbundle处理后发出(即RpcEncoder编码器)
// 用线程等待的方式决定是否关闭连接
// 其意义是:先在此阻塞,等待获取到服务端的返回后,被唤醒,从而关闭网络连接
Object o = new Object();
locks.put(request.getRequestId(), o);
synchronized (o) {
future.channel().writeAndFlush(request);
o.wait(10000);
}
return response;
} /**
* 读取服务端的返回结果
*/
@Override
public void channelRead0(ChannelHandlerContext ctx, RpcResponse response)
throws Exception {
this.response = response;
Object o = locks.remove(response.getRequestId());
synchronized (o) {
o.notify();
}
}

4、被调用方

获取接口与实现类的对应关系

public void setApplicationContext(ApplicationContext ctx)
throws BeansException {
Map<String, Object> serviceBeanMap = ctx
.getBeansWithAnnotation(RpcService.class);
if (MapUtils.isNotEmpty(serviceBeanMap)) {
for (Object serviceBean : serviceBeanMap.values()) {
//从业务实现类上的自定义注解中获取到value,从来获取到业务接口的全名
String interfaceName = serviceBean.getClass()
.getAnnotation(RpcService.class).value().getName();
handlerMap.put(interfaceName, serviceBean);
}
}
}

读取调用方传递过来的接口名和参数,利用反射调用相应类并返回结果给前端

public void channelRead0(final ChannelHandlerContext ctx, RpcRequest request)
throws Exception {
RpcResponse response = new RpcResponse();
response.setRequestId(request.getRequestId());
try {
//根据request来处理具体的业务调用
Object result = handle(request);
response.setResult(result);
} catch (Throwable t) {
response.setError(t);
}
//写入 outbundle(即RpcEncoder)进行下一步处理(即编码)后发送到channel中给客户端
ctx.writeAndFlush(response);
} /**
* 根据request来处理具体的业务调用
* 调用是通过反射的方式来完成
*
* @param request
* @return
* @throws Throwable
*/
private Object handle(RpcRequest request) throws Throwable {
String className = request.getClassName(); //拿到实现类对象
Object serviceBean = handlerMap.get(className); //拿到要调用的方法名、参数类型、参数值
String methodName = request.getMethodName();
Class<?>[] parameterTypes = request.getParameterTypes();
Object[] parameters = request.getParameters(); //拿到接口类
Class<?> forName = Class.forName(className); //调用实现类对象的指定方法并返回结果
Method method = forName.getMethod(methodName, parameterTypes);
return method.invoke(serviceBean, parameters);
}

5、自定义rpc框架的使用

1、被调用方maven依赖

<dependency>
<groupId>com.shuangyueliao</groupId>
<artifactId>rpc-server</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

2、调用方maven依赖

<dependency>
<groupId>com.shuangyueliao</groupId>
<artifactId>rpc-client</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

3、被调用方实现类加上注解RpcService,里面的值是被调用的接口

@RpcService(PayService.class)
public class PayServiceImpl implements PayService {
@Override
public int calculate(int a, int b) {
int result = a + b;
return result;
}
}

4、调用方建立包名com.shuangyueliao.rpc.myinterface,新建要调用的接口,并加上注解RpcClientService

@RpcClientService
public interface PayService {
int calculate(int a, int b);
}

功能演示

1、启动zookeeper,如需要修改zookeeper连接地址,请修改rpc-sample-server和rpc-sample-client的配置文件application.properties中的配置项zookeeper.url

2、运行rpc-sample-server(被调用方)RpcBootstrap的main方法启动被调用方

3、运行rpc-sample-client(调用方)的StartApp的main方法启动调用方

4、浏览器输入http://localhost:8090/order请求rpc-sample-client,rpc-sample-client会RPC调用rpc-sample-server



项目地址

基于netty手写RPC框架的更多相关文章

  1. 手写RPC框架(六)整合Netty

    手写RPC框架(六)整合Netty Netty简介: Netty是一个基于NIO的,提供异步,事件驱动的网络应用工具,具有高性能高可靠性等特点. 使用传统的Socket来进行网络通信,服务端每一个连接 ...

  2. 手写RPC框架指北另送贴心注释代码一套

    Angular8正式发布了,Java13再过几个月也要发布了,技术迭代这么快,框架的复杂度越来越大,但是原理是基本不变的.所以沉下心看清代码本质很重要,这次给大家带来的是手写RPC框架. 完整代码以及 ...

  3. 看了这篇你就会手写RPC框架了

    一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...

  4. [年薪60W分水岭]基于Netty手写Apache Dubbo(带注册中心和注解)

    阅读这篇文章之前,建议先阅读和这篇文章关联的内容. 1. 详细剖析分布式微服务架构下网络通信的底层实现原理(图解) 2. (年薪60W的技巧)工作了5年,你真的理解Netty以及为什么要用吗?(深度干 ...

  5. 手写RPC框架(netty+zookeeper)

    RPC是什么?远程过程调用,过程就是业务处理.计算任务,像调用本地方法一样调用远程的过程. RMI和RPC的区别是什么?RMI是远程方法调用,是oop领域中RPC的一种实现,我们熟悉的restfull ...

  6. 手写RPC框架

    https://www.bilibili.com/video/av23508597?from=search&seid=6870947260580707913 https://github.co ...

  7. 基于springJDBC手写ORM框架

    一.添加MySQLjar包依赖 二.结构 三.文件内容 (一).bean包 1.ColumnInfo.java 2.javaFiledInfo.java 3.TableInfo.java 4.Conf ...

  8. 手写MQ框架(四)-使用netty改造梳理

    一.背景 书接上文手写MQ框架(三)-客户端实现,前面通过web的形式实现了mq的服务端和客户端,现在计划使用netty来改造一下.前段时间学习了一下netty的使用(https://www.w3cs ...

  9. java 从零开始手写 RPC (04) -序列化

    序列化 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何实 ...

随机推荐

  1. Windows 2016 & Windows 10 中IIS安装和配置PHP的步骤

    Windows 2016 和 Windows 10 内核是相同的,我们首先需要安装 Internet Information Services (IIS),当然 Win2016 跟 Win10 安装  ...

  2. Tram POJ - 1847

    题目链接:https://vjudge.net/problem/POJ-1847 思路:想从A到B使用开关少,想清楚了就是个简单的最短路,可以把不用开开关为权值0, 要开开关为权值1,就是求A到B开开 ...

  3. 可变lambda, lambda使用mutable关键字

    关于lambda的捕获和调用 C++ primer上对可变lambda举的例子如下: size_t v1=42; auto f=[v1] () mutable{return ++v1; }; v1=0 ...

  4. tf.gather_nd()

    tf.gather_nd( params, indices, name=None, batch_dims=0) TensorFlow链接:https://tensorflow.google.cn/ap ...

  5. vue-router路由嵌套与二级路由重定向

    (1)公共路由裁减 不是每个页面都存在导航,所以不要把导航组件在根组件APP.vue里引入,哪个页面需要,在哪里引入即可. 方案: 哪个页面需要,在哪个页面引入即可 (2)嵌套路由 注意:childr ...

  6. python参数传递

    1.形式参数:在定义函数时,函数名后面括号中的参数为“形式参数”,也称形参 2.实际参数:在调用一个函数时,函数名后面括号种的参数为“实际参数”,也就是将函数的调用者提供给函数的参数称为实际参数,也称 ...

  7. selenium 加载出新的窗口

    加载出新的窗口的时候 在点击某一个按钮的时候 有些时候会加载出新的页面 此时直接定位是定位不到的 就比如一开始在 1窗口定位 后来跳转到了2窗口,需要在2窗口上定位元素,此时就要先切换到2窗口 这里引 ...

  8. ES6学习笔记 -- 尾调用优化

    什么是尾调用? 尾调用(Tail Call)是函数式编程的一个重要概念,就是指某个函数的最后一步是调用另一个函数. function f(x) { return g(x) } 如上,函数 f 的最后一 ...

  9. Margin和padding失效

    太久不写原生果然不行,Margin和padding对div有效,对span失效,原因就不解释了(元素性质,块状之类的)

  10. 梅尔倒谱系数特征(Mel-frequency cepstral coefficients,MFCC)

    引言 感知实验表明,人耳对于声音信号的感知聚焦于某一特定频率区域内,而非在整个频谱包络中. MFCC特征是应用非常广泛的语音特征. 语音的MFCC特征是基于人耳感知实验得到,将人耳当成特定的滤波器,只 ...