Dubbo 底层使用 Netty 作为网络通信框架。
网络传输问题】:相对于传统的 RPC 或者 RMI 等方式的远程服务过程调用采用了同步阻塞IO,当客户端的并发压力或者网络时延增长之后,同步阻塞 I/O 会由于频繁的等待导致 I/O 线程经常性阻塞,由于线程无法高效的工作,I/O处理能力自然会下降。
序列化性能差链接】:无法跨语言、码流长、性能差。
线程模型问题】:采用同步阻塞IO,这会导致每个 TCP 连接都占用 1个线程,由于线程资源是 JVM 虚拟机非常宝贵的资源,当 I/O 读写阻塞导致线程无法释放时,会导致性能急剧下降。

一、设计思想


模仿 Dubbo 的消费者和提供者约定接口和协议,消费者远程调用提供者,提供者返回数据,消费者打印提供者返回的数据。
【1】创建一个接口,定义抽象方法。用于消费者和提供者之间的约定。
【2】创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据。
【3】创建一个消费者,该类需要透明的调用自己不存在的方法,内部需要使用 Netty 请求提供者返回数据。

二、服务端


【1】添加 Netty Maven 依赖:

1 <dependencies>
2 <dependency>
3 <groupId>io.netty</groupId>
4 <artifactId>netty-all</artifactId>
5 <version>4.1.16.Final</version>
6 </dependency>
7 </dependencies>

【2】首先准备客户端和服务端需要的公共接口

1 public interface HelloInterface {
2 String hello(String msg);
3 }

【3】服务端实现 HelloInterface 接口:

1 public class HelloImpl implements HelloInterface {
2 @Override
3 public String hello(String msg) {
4 //返回客户端的消息
5 return msg != null ? msg + " -----> I am fine." : "I am fine.";
6 }
7 }

【4】实现 Netty Server 端代码(代码固定,通常作为公共代码使用):

 1 public class Provider {
2
3 static void startServer(String hostName, int port) {
4 //配置服务端的 NIO 线程组
5 NioEventLoopGroup bossGroup = new NioEventLoopGroup();
6 NioEventLoopGroup workerGroup = new NioEventLoopGroup();
7 try {
8 ServerBootstrap bootstrap = new ServerBootstrap();
9 bootstrap.group(bossGroup,workerGroup)
10 .channel(NioServerSocketChannel.class)
11 .childHandler(new ChannelInitializer<SocketChannel>() {
12 @Override
13 protected void initChannel(SocketChannel ch) throws Exception {
14 ChannelPipeline p = ch.pipeline();
15 //字符串的编解码器
16 p.addLast(new StringDecoder());
17 p.addLast(new StringEncoder());
18 p.addLast(new HelloHandler());
19 }
20 });
21 //绑定端口,同步等待成功
22 ChannelFuture f = bootstrap.bind(hostName, port).sync();
23 //等待服务端监听端口关闭
24 f.channel().closeFuture().sync();
25 } catch (InterruptedException e) {
26 e.printStackTrace();
27 }finally {
28 //优雅退出释放线程池资源
29 //bossGroup.shutdownGracefully();
30 //workerGroup.shutdownGracefully();
31 }
32 }
33 }

【5】服务端对应的 HelloHandler 类的实现:实现 ChannelInboundHandlerAdapter 适配器,对客户端发送的消息进行处理。这里显示判断了是否符合约定(并没有使用复杂的协议,只是一个字符串判断),然后创建一个具体实现类,并调用方法写回客户端。

 1 public class HelloHandler extends ChannelInboundHandlerAdapter {
2 @Override
3 public void channelRead(ChannelHandlerContext ctx, Object msg) {
4
5 // 如何符合约定,则调用本地方法,返回数据
6 if (msg.toString().startsWith(ClientBootstrap.providerName)) {
7 String result = new HelloImpl()
8 .hello(msg.toString().substring(msg.toString().lastIndexOf("#") + 1));
9 ctx.writeAndFlush(result);
10 }
11 }
12 }

【6】服务端启动类:先运行服务端,后运行客户端

1 public class Bootstrap {
2 public static void main(String[] args) {
3 Provider.startServer("localhost", 8088);
4 }
5 }

三、客户端


消费者有一个需要注意的地方,就是调用需要透明,也就是说,框架使用者不用关心底层的网络实现。这里我们可以使用 JDK 的动态代理链接】来实现这个目的。思路是客户端调用代理方法,返回一个实现了 HelloService 接口的代理对象,调用代理对象的方法,返回结果。当调用代理方法的时候,我们需要初始化 Netty 客户端,还需要向服务端请求数据,并返回数据。

【1】首先创建代理相关的类:该类有 2 个方法,创建代理和初始化客户端。初始化客户端逻辑: 创建一个 Netty 的客户端,并连接提供者,并设置一个自定义 handler,和一些 String 类型的编解码器。创建代理逻辑:使用 JDK 的动态代理技术,代理对象中的 invoke 方法实现如下:如果 client 没有初始化,则初始化 client,这个 client 既是 handler ,也是一个 Callback。将参数设置进 client ,使用线程池调用 client 的 call 方法并阻塞等待数据返回。

 1 public class Consumer {
2 private static ExecutorService executor = Executors
3 .newFixedThreadPool(Runtime.getRuntime().availableProcessors());
4
5 private static HelloClientHandler client;
6
7 /**
8 * 创建一个代理对象
9 */
10 public Object createProxy(final Class<?> serviceClass,
11 final String providerName) {
12 return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
13 new Class<?>[]{serviceClass}, (proxy, method, args) -> {
14 if (client == null) {
15 initClient();
16 }
17 // 设置参数
18 client.setPara(providerName + args[0]);
19 return executor.submit(client).get();
20 });
21 }
22
23 /**
24 * 初始化客户端
25 */
26 private static void initClient() {
27 client = new HelloClientHandler();
28 EventLoopGroup group = new NioEventLoopGroup();
29 try {
30 Bootstrap b = new Bootstrap();
31 b.group(group)
32 .channel(NioSocketChannel.class)
33 .option(ChannelOption.TCP_NODELAY, true)
34 .handler(new ChannelInitializer<SocketChannel>() {
35 @Override
36 public void initChannel(SocketChannel ch) throws Exception {
37 ChannelPipeline p = ch.pipeline();
38 p.addLast(new StringDecoder());
39 p.addLast(new StringEncoder());
40 p.addLast(client);
41 }
42 });
43 //发起异步连接操作
44 ChannelFuture f = b.connect("localhost", 8088).sync();
45 //等待客户端关闭连接
46 f.channel().closeFuture().sync();
47 } catch (InterruptedException e) {
48 e.printStackTrace();
49 }finally {
50 //group.shutdownGracefully();
51 }
52 }
53 }

【2】客户端 Netty 中 HelloClientHandler 类的实现:该类缓存了 ChannelHandlerContext,用于下次使用,有两个属性:返回结果和请求参数。当成功连接后,缓存 ChannelHandlerContext,当调用 call 方法的时候,将请求参数发送到服务端,等待。当服务端收到并返回数据后,调用 channelRead 方法,将返回值赋值个 result,并唤醒等待在 call 方法上的线程。此时,代理对象返回数据。

 1 public class HelloClientHandler extends ChannelInboundHandlerAdapter implements Callable {
2
3 private ChannelHandlerContext context;
4 private String result;
5 private String para;
6
7 @Override
8 public void channelActive(ChannelHandlerContext ctx) {
9 context = ctx;
10 }
11
12 /**
13 * 收到服务端数据,唤醒等待线程
14 */
15 @Override
16 public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) {
17 result = msg.toString();
18 notify();
19 }
20
21 /**
22 * 写出数据,开始等待唤醒
23 */
24 @Override
25 public synchronized Object call() throws InterruptedException {
26 context.writeAndFlush(para);
27 wait();
28 return result;
29 }
30
31 void setPara(String para) {
32 this.para = para;
33 }
34 }

四、测试


首先创建了一个代理对象,然后每隔一秒钟调用代理的 hello 方法,并打印服务端返回的结果。

 1 public class ClientBootstrap {
2 public static final String providerName = "HelloService#hello#";
3
4 public static void main(String[] args) throws InterruptedException {
5
6 Consumer consumer = new Consumer();
7 // 创建一个代理对象
8 HelloInterface service = (HelloInterface) consumer
9 .createProxy(HelloInterface.class, providerName);
10 for (; ; ) {
11 Thread.sleep(1000);
12 System.out.println(service.hello("are you ok ?"));
13 }
14 }
15 }

结果展示:

 

使用 Netty 实现简单的 RPC 框架的更多相关文章

  1. Java实现简单的RPC框架(美团面试)

    一.RPC简介 RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议.它允许像调用本地服务一样调用远程服务.它可以有不同的实现方式.如RMI(远程方法调用) ...

  2. 最简单的RPC框架实现

    通过java原生的序列化,Socket通信,动态代理和反射机制,实现一个简单的RPC框架,由三部分组成: 1.服务提供者,运行再服务端,负责提供服务接口定义和服务实现类 2.服务发布者,运行再RPC服 ...

  3. 分布式架构的基石.简单的 RPC 框架实现(JAVA)

    前言 RPC 的全称是 Remote Procedure Call,它是一种进程间通信方式.允许像调用本地服务一样调用远程服务. 学习来源:<分布式系统架构:原理与实践> - 李林锋 1. ...

  4. 学习写简单的RPC框架demo

    学习实现一个简单的RPC框架. 工程主要目录分级结构: rpc-common: 公共基础包,能力提供包 rpc-provider: 服务提供者 rpc-consumer:服务消费者 rpc-servi ...

  5. 徒手撸一个简单的RPC框架

    来源:https://juejin.im/post/5c4481a4f265da613438aec3 之前在牛逼哄哄的 RPC 框架,底层到底什么原理得知了RPC(远程过程调用)简单来说就是调用远程的 ...

  6. Java实现简单的RPC框架

    一.RPC简介 RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议.它允许像调用本地服务一样调用远程服务.它可以有不同的实现方式.如RMI(远程方法调用) ...

  7. 造个轮子之基于 Netty 实现自己的 RPC 框架

    原文地址: haifeiWu和他朋友们的博客 博客地址:www.hchstudio.cn 欢迎转载,转载请注明作者及出处,谢谢! 服务端开发都会或多或少的涉及到 RPC 的使用,当然如果止步于会用,对 ...

  8. Java 实现简单的RPC框架

    0 引言 RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议.它允许像调用本地服务一样调用远程服务.它可以有不同的实现方式.如RMI(远程方法调用).He ...

  9. netty 实现简单的rpc调用

    yls 2020/5/23 netty 实现简单rpc准备 使用netty传输java bean对象,可以使用protobuf,也可以通过json转化 客户端要将调用的接口名称,方法名称,参数列表的类 ...

  10. Java 实现简单的 RPC 框架

    RPC 简介 RPC,全称为 Remote Procedure Call,即远程过程调用,它是一个计算机通信协议.它允许像调用本地服务一样调用远程服务.它可以有不同的实现方式,而不需要了解底层网络技术 ...

随机推荐

  1. 20211306 实验一《Python程序设计》实验报告

    202111306 丁文博 第一次实验报告 课程:<Python程序设计> 班级: 2113 姓名: 丁文博 学号:20211306 实验教师:王志强 实验日期:2022年3月24日 必修 ...

  2. window安装、启动consul

    1.官网下载:https://www.consul.io/downloads.html 2.下载解压后的安装包只有一个consul.exe文件,双击可查看版本信息 3.设置环境变量,在Path下新增一 ...

  3. JS通用公共函数

    function formatTime(time) { if (typeof time !== 'number' || time < 0) { return time } var hour = ...

  4. linux查看进程信息

    top 实时查看进程信息,展示进程id,使用内存,占用cpu等信息,可以查看内容占用最多.cpu使用最多的进程,然后再根据进程id查看进程的详细信息.实时更新 ps 瞬时查看进程情况,ps -ef | ...

  5. sqlsever中使用的 select top n在mysql 语句中如何更改

    string sqlSelect = "select top(3) ROW_NUMBER() over(order by UserTime) as Num,* from " + & ...

  6. Navicat连接Mysql报错:Client does not support authentication protocol requested by server(转载)

    Navicat连接MySQL Server8.0版本时出现Client does not support authentication protocol requested  by server:解决 ...

  7. Spring Boot 中的异步调用

    通常我们开发的程序都是同步调用的,即程序按照代码的顺序一行一行的逐步往下执行,每一行代码都必须等待上一行代码执行完毕才能开始执行.而异步编程则没有这个限制,代码的调用不再是阻塞的.所以在一些情景下,通 ...

  8. P2330 繁忙的都市

    题目描述 城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造.城市C的道路是这样分布的:城市中有n个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条 ...

  9. 狂神的学习笔记demo04

    package com.company; public class demo04 { public static void main(String[] args){ int i=10;//二进制 in ...

  10. UG二次开发-内存访问违例

    在项目中修改路径参数后重算发生了内存访问违例的错误,经过调试,发现是下面这一行出的错 surfaceContourBuilder1.Commit(); 经过反复调试,发现这个东西不能随便放,不可以想当 ...