原创申明:本文由公众号【猿灯塔】原创,转载请说明出处标注

今天是猿灯塔“365天原创计划”第5天。

今天呢!灯塔君跟大家讲:

基于Netty重构RPC框架

一.CyclicBarrier方法说明

1.单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。。(最开始58 同城的站点架构用一个词概括就是“ALL IN ONE”。就像一个单机系统,所有的东西都部署在一台机器 上,包括站点、数据库、文件等等。而工程师每天的核心工作就是CURD,前端传过来一些数据,然后 业务逻辑层拼装成一些CURD访问数据库,数据库返回数据,数据拼装成页面,最终返回到浏览器。此 时,用于简化增删改查工作量的数据访问框架(ORM)是关键)

2、垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提 升效率。应用拆分为不相干的几个应用,前后端分离,此时用于加速前端页面开发的Web MVC框架是 关键

3、分布式服务服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定 的服务中心,使前端应用能更快速的响应多变的市场需求。同时将公共能力API抽取出来,作为独立的 公共服务供其他调用者消费,以实现服务的共享和重用,降低开发和运维成本。应用拆分之后会按照模 块独立部署,接口调用由本地API演进成跨进程的远程方法调用,此时RPC框架应运而生。此时,用于 提高业务复用及整合的分布式服务框架(RPC)是关键。

4、流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问 压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是 关键。面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆 分,并通过这些服务之间定义良好的接口和协议联系起来。

没有RPC框架之前,我们的服务调用是这样的:

看出接口的调用完全没有规律可循,想怎么调,就怎么调。这导致业务发展到一定阶段之后,对接口的 维护变得非常困难。于是有人提出了服务治理的概念。所有服务间不允许直接调用,而是先到注册中心 进行登记,再由注册中心统一协调和管理所有服务的状态并对外发布,调用者只需要记住服务名称,去 找注册中心获取服务即可。这样,极大地规范了服务的管理,可以提高了所有服务端可控性。整个设计思想其实在我们生活中也能 找到活生生的案例。例如:我们平时工作交流,大多都是用IM 工具,而不是面对面吼。大家只需要相 互记住运营商(也就是注册中心)提供的号码(如:腾讯QQ)即可。再比如:我们打电话,所有电话 号码有运营商分配。我们需要和某一个人通话时,只需要拨通对方的号码,运营商(注册中心,如中国 移动、中国联通、中国电信)就会帮我们将信号转接过去。

二.RPC介绍

1、RPC简介:

  • Remote Procedure Call远程过程调用RPC就是从一台机器(客户端)上通过参数传递的        方式调用另一台机器(服务器)上的一个函数或 方法(可以统称为服务)并得到返回的结果。
  • RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯) RPC 是一个请求响应模 型。
  • 客户端发起请求,服务器返回响应(类似于Http的工作方式) RPC 在使用形式上像调用本地函数 (或方法)一样去调用远程的函数(或方法)。

 2、RPC通信原理

RPC的主要作用有三方面:

1、进程间通讯

2、提供和本地方法调用一样的机制

3、屏蔽用户对远程调用的细节实现

RPC框架的好处首先就是长链接,不必每次通信都要像http一样去3次握手,减少了网络开销;其次就 是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无 感知,统一化的操作。

3、RPC通过过程

1)服务消费方(client)调用以本地调用方式调用服务;

2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;

3)client stub找到服务地址,并将消息发送到服务端;

4)server stub收到消息后进行解码;

5)server stub根据解码结果调用本地的服务;6)本地服务执行并将结果返回给server stub;7)server stub将返回结果打包成消息并发送至消费方;

8)client stub接收到消息,并进行解码;

9)服务消费方得到最终结果。

4、常用的分布式RPC框架

dubbo:国内最早开源的RPC框架,由阿里巴巴公司开发并于2011年末对外开源,仅支持java语言

4、常用的分布式RPC框架

  • dubbo:国内最早开源的RPC框架,由阿里巴巴公司开发并于2011年末对外开源,仅支持java语言
  • motan:微博内部使用的RPC框架,于2016年对外开源,仅支持java语言
  • Thrift:轻量级的跨语言RPC通信方案,支持多大25种变成语言
  • gRPC:Google于2015年对外开源的跨语言PRC框架,支持常用的C++、java、Python、Go、 Ruby、PHP等多种语言。

目前流行的RPC 服务治理框架主要有Dubbo 和Spring Cloud,

下面我们以比较经典的Dubbo 为例。

Dubbo 核心模块主要有:

Registry:注册中心(主要负责保存所有可用的服务名称和服务地址。)

Provider:服务中心(实现对外提供的所有服务的具体功能)

Consumer:消费端(调用远程服务的服务消费方)

Monitor:监控中心(统计服务的调用次数和调用时间的监控中心)

Container:服务运行容器

api:主要用来定义对外开放的功能与服务接口。protocol:主要定义自定义传输协议的内容

蓝色方框代表业务有交互,绿色方框代表只对Dubbo内部交互。蓝色虚线为初始化时调用,红色虚线为 运行时异步调用,红色实线为运行时同步调用

0、服务在容器中启动,加载,运行Provider

1、Provider在启动时,向Registry注册自己提供的服务

2、Consumer在启动时,向Registry订阅自己所需的服务

3、Registry给Consumer返回Provider的地址列表,如果Provider地址有变更(上线/下线机器), Registry将基于长连接推动变更数据给Consumer

4、Consumer从Provider地址列表中,基于软负载均衡算法,选一台进行调用,如果失败,重试另一 台调用

5、Consumer和Provider,在内存中累计调用次数和时间,定时每分钟一次将统计数据发送到Monitor

4.具体实现

1、api

package com.rpc.api;

/**

* API模块,provider和Consumer都遵循API模块的

规范 * 用来定义对外开放的功能与服务接口

*/

public interface IRpcHelloService {

String hello(String name);

}

package com.rpc.api;

public interface IRpcService {

/*增加用户*/

public String addUser();

/*删除用户*/

public String deleteUser(Integer id);

/*修改用户*/

public String updateUser(Integer id);

/*查询用户*/

public String queryUser(Integer id);

}

2、Provider:服务中心

package com.rpc.provider;

import com.rpc.api.IRpcService;

public class RpcServiceImpl implements

IRpcService {

@Override

public String addUser() {

return "增加用户";

}

@Override

public String deleteUser(Integer id) {

return "删除了编号为"+id+"的用户";

}

@Override

public String updateUser(Integer id) {

return "修改了编号为"+id+"的用户";

}

@Override

public String queryUser(Integer id) {

return "查询到了编号为"+id+"这个用户的信息";

}

}

package com.rpc.provider;

import com.rpc.api.IRpcHelloService;

/**

* 服务中心,实现对外提供的所有服务的具体功能

*/

public class RpcHelloServiceImpl implements IRpcHelloService {

public String hello(String name) {

return "Hello " + name + "!";

}

}

3、protocol传输协议内容

ackage com.rpc.protocol;

import java.io.Serializable;

/**

* 自定义传输协议内容

*/

public class InvokerProtocol implements

Serializable {

private String className;//类名

private String methodName;//函数名称

private Class<?>[] parames;//形参列表

private Object[] values;//实参列表

public String getClassName() {

return className;

}

public void setClassName(String className)

{

this.className = className;

}

public String getMethodName() {

return methodName;

}

public void setMethodName

(String methodName)

{

this.methodName = methodName;

}

public Class<?>[] getParames() {

return parames;

}

public void setParames(Class<?>[] parames)

{

this.parames = parames;

}

public Object[] getValues() {

return values;

}

public void setValues(Object[] values) {

this.values = values;

}

}

4、Registry注册中心

package com.rpc.registry;

import io.netty.bootstrap.ServerBootstrap;

import io.netty.channel.*;

import io.netty.channel.nio.NioEventLoopGroup;

import io.netty.channel.socket.SocketChannel;

importio.netty.channel.socket.nio.

NioServerSocketChannel;

import io.netty.handler.codec.

LengthFieldBasedFrameDecoder;

import io.netty.handler.codec.LengthFieldPrepender;

import io.netty.handler.codec.serialization.

ClassResolvers;

import io.netty.handler.codec.serialization.

ObjectDecoder;

import io.netty.handler.codec.serialization.

ObjectEncoder;

/**

* 注册中心主要功能就是负责将所有Provider的

服务名称和服务引用地址注册到一个容器中,

* 并对外发布。启动一个对外的服务,

并对外提供一个可访问的端口

* 主要负责保存所有可用的服务名称和服务地址

*/

public class RpcRegistry {

private int port;

public RpcRegistry(int port){

this.port = port;

}

public void start(){

EventLoopGroup bossGroup = new NioEventLoopGroup();

EventLoopGroup workerGroup = new NioEventLoopGroup();

try {

ServerBootstrap b = new ServerBootstrap();

b.group(bossGroup, workerGroup)

.channel(NioServerSocketChannel.class)

.childHandler(new ChannelInitializer<SocketChannel>() {

@Override

protected void initChannel(SocketChannel ch) throws

Exception {

ChannelPipeline pipeline = ch.pipeline();

//自定义协议解码器

/** 入参有5个,分别解释如下

(1) maxFrameLength - 发送的数据包最大长度;

(2) lengthFieldOffset - 长度域偏移量,指的是长度域位于

整个数据包字节数组中的下标;

(3) lengthFieldLength - 长度域的自己的字节数长度。

(4) lengthAdjustment – 长度域的偏移量矫正。如果长度域

的值,除了包含有效数据域的长度外,

还包含了其他域(如长度域自身)长度,那么,

就需要进行矫正。矫 正的值为:包长 - 长度域的值 –

长度域偏移 – 长度域长。

(5) initialBytesToStrip – 丢弃的起始字节数。

丢弃处于有 效数据前面的字节数量。

比如前面有4个节点的长度域,则它的值为4。

*/

pipeline.addLast(new

LengthFieldBasedFrameDecoder(Integer.

MAX_VALUE, 0, 4, 0, 4));

//自定义协议编码器

pipeline.addLast(new LengthFieldPrepender(4));

//对象参数类型编码器

pipeline.addLast("encoder",new ObjectEncoder());

//对象参数类型解码器

pipeline.addLast("decoder",new

ObjectDecoder(Integer.MAX_VALUE,

ClassResolvers.cacheDisabled(null)));

pipeline.addLast(new RegistryHandler());

}

})

.option(ChannelOption.SO_BACKLOG, 128)

.childOption(ChannelOption.SO_KEEPALIVE, true);

ChannelFuture future = b.bind(port).sync();

System.out.println("jiangym RPC Registry

start listen at " + port );

future.channel().closeFuture().sync();

} catch (Exception e) {

bossGroup.shutdownGracefully();

workerGroup.shutdownGracefully();

}

}

//主启动类

public static void main(String[] args) throws Exception {

new RpcRegistry(8888).start();

}

}

package com.rpc.registry;

import com.rpc.protocol.InvokerProtocol;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.ChannelInboundHandlerAdapter;

import java.io.File;

import java.lang.reflect.Method;

import java.net.URL;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.ConcurrentHashMap;

public class RegistryHandler extends ChannelInboundHandlerAdapter {

//保存所有可用的服务(ConcurrentHashMap

是线程安全且高效的HashMap实现)

public static ConcurrentHashMap

<String, Object>

registryMap = new ConcurrentHashMap<String, Object>();

//保存所有相关的服务类

private List<String> classNames = new ArrayList<String>();

public RegistryHandler() {

//完成递归扫描

scannerClass("com.rpc.provider");

doRegister();

}

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) throws

Exception {

Object result = new Object();

InvokerProtocol request = (InvokerProtocol) msg;

//当客户端建立连接时,需要从自定义协议中获取信息,

拿到具体的服务和实参

//使用反射调用

if (registryMap.containsKey(request.getClassName())) {

Object clazz = registryMap.get(request.getClassName());

Method method = clazz.getClass().getMethod(request.

getMethodName(),

request.getParames());

result = method.invoke(clazz,

request.getValues());

}

ctx.write(result);

ctx.flush();

ctx.close();

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)

throws Exception {

cause.printStackTrace();

ctx.close();

}

/*

* 递归扫描

*/

private void scannerClass(String packageName)

{

URL url =

this.getClass().getClassLoader().getResource

(packageName.replaceAll("\\.",

"/"));

File dir = new File(url.getFile());

for (File file : dir.listFiles()) {

//如果是一个文件夹,继续递归

if (file.isDirectory()) {

scannerClass(packageName + "." +

file.getName());

} else {

classNames.add(packageName + "." +

file.getName().replace(".class", "").trim());

}

}

}

/**

* 完成注册

*/

private void doRegister() {

if (classNames.size() == 0) {

return;

}

for (String className : classNames) {

try {

Class<?> clazz = Class.forName(className);

Class<?> i = clazz.getInterfaces()[0];

registryMap.put(i.getName(),

clazz.newInstance());

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

4、消费端

package com.rpc.consumer;

import com.rpc.api.IRpcHelloService;

import com.rpc.api.IRpcService;

public class RpcConsumer {

public static void main(String [] args){

IRpcHelloService rpcHello = RpcProxy.create(IRpcHelloService.class);

System.out.println(rpcHello.hello("Netty"));

IRpcService service = RpcProxy.create(IRpcService.class);

System.out.println(service.deleteUser(4));

System.out.println(service.updateUser(3));

System.out.println(service.queryUser(2));

System.out.println(service.addUser());

}

}

package com.rpc.consumer;

import com.rpc.protocol.InvokerProtocol;

import io.netty.bootstrap.Bootstrap;

import io.netty.channel.*;

import io.netty.channel.nio.NioEventLoopGroup;

import io.netty.channel.socket.SocketChannel;

import io.netty.channel.socket.nio.NioSocketChannel;

import io.netty.handler.codec.

LengthFieldBasedFrameDecoder;

import io.netty.handler.codec.LengthFieldPrepender;

import io.netty.handler.codec.serialization.ClassResolvers;

import io.netty.handler.codec.serialization.ObjectDecoder;

import io.netty.handler.codec.serialization.ObjectEncoder;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

public class RpcProxy {

public static <T> T create(Class<?> clazz) {

//clazz传进来本身就是interface

MethodProxy proxy = new MethodProxy(clazz);

Class<?>[] interfaces = clazz.isInterface() ?

new Class[]{clazz} :

clazz.getInterfaces();

T result = (T) Proxy.newProxyInstance(clazz.getClassLoader(),

interfaces, proxy);

return result;

}

private static class MethodProxy implements InvocationHandler {

private Class<?> clazz;

public MethodProxy(Class<?> clazz) {

this.clazz = clazz;

}

public Object invoke(Object proxy, Method

method, Object[] args) throws

Throwable {

//如果传进来是一个已实现的具体类

if (Object.class.equals(method.

getDeclaringClass())) {

try {

return method.invoke(this, args);

} catch (Throwable t) {

t.printStackTrace();

}

//如果传进来的是一个接口(核心)

} else {

return rpcInvoke(proxy, method, args);

}

return null;

}

/**

* 实现接口的核心方法

*

* @param method

* @param args

* @return

*/

public Object rpcInvoke(Object proxy, Method method, Object[] args) {

//传输协议封装

InvokerProtocol msg = new InvokerProtocol();

msg.setClassName(this.clazz.getName());

msg.setMethodName(method.getName());

msg.setValues(args);

msg.setParames(method.getParameterTypes());

final RpcProxyHandler consumerHandler = new RpcProxyHandler();

EventLoopGroup group = new NioEventLoopGroup();

try {

Bootstrap b = new Bootstrap();

b.group(group)

.channel(NioSocketChannel.class)

.option(ChannelOption.TCP_NODELAY, true)

.handler(new ChannelInitializer<

SocketChannel>() {

@Override

public void initChannel(SocketChannel ch)

throws

Exception {

ChannelPipeline pipeline = ch.pipeline();

//自定义协议解码器

/** 入参有5个,分别解释如下

maxFrameLength:框架的最大长度。

如果帧的长度大于此 值,则将抛出TooLongFrameException。

lengthFieldOffset:长度字段的偏移量:

即对应的长度字 段在整个消息数据中得位置

lengthFieldLength:长度字段的长度:

如:长度字段是int 型表示,那么这个值就是

4(long型就是8)

lengthAdjustment:要添加到长度字段值的补偿值

initialBytesToStrip:从解码帧中去除的第一个字节数

*/

pipeline.addLast("frameDecoder", new

LengthFieldBasedFrameDecoder

(Integer.MAX_VALUE, 0, 4, 0, 4));

//自定义协议编码器

pipeline.addLast("frameEncoder", new

LengthFieldPrepender(4));

//对象参数类型编码器

pipeline.addLast("encoder", new

ObjectEncoder());

//对象参数类型解码器

pipeline.addLast("decoder", new

ObjectDecoder(

Integer.MAX_VALUE,

ClassResolvers.cacheDisabled(null)));

pipeline.addLast("handler", consumerHandler);

}

});

ChannelFuture future = b.connect("localhost", 8888).sync();

future.channel().writeAndFlush(msg).sync();

future.channel().closeFuture().sync();

} catch (Exception e) {

e.printStackTrace();

} finally {

group.shutdownGracefully();

}

return consumerHandler.getResponse();

}

}

}

package com.rpc.consumer;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channe

l.

ChannelInboundHandlerAdapter;public class RpcProxyHandler extends ChannelInboundHandlerAdapter {

private Object response;

public Object getResponse() {

return response;

}

@Override

public void channelRead

(ChannelHandlerContext ctx, Object msg) throws

Exception {

response = msg;

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)

throws Exception {

System.out.println("client exception is general");

}

}

文章365天持续更新,可以微信搜索「猿灯塔」第一时间阅读,回复【资料】【面试】【简历】有我准备的一线大厂面试资料和简历模板.

这样基于Netty重构RPC框架你不可能知道的更多相关文章

  1. 基于Netty重构RPC框架

    下面的这张图,大概很多小伙伴都见到过,这是Dubbo 官网中的一张图描述了项目架构的演进过程.随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在 ...

  2. 《Java 编写基于 Netty 的 RPC 框架》

    一 简单概念 RPC: ( Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO :当阻塞 ...

  3. java编写基于netty的RPC框架

    一 简单概念 RPC:(Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO:当阻塞I/O ...

  4. 基于netty实现rpc框架-spring boot服务端

    demo地址 https://gitee.com/syher/grave-netty RPC介绍 首先了解一下RPC:远程过程调用.简单点说就是本地应用可以调用远程服务器的接口.那么通过什么方式调用远 ...

  5. 基于netty实现rpc框架-spring boot客户端

    上篇讲了RPC服务端的实现.原理就是解析netty通道数据拿到类.方法及入参等信息,然后通过java反射机制调用本地接口返回结果.没有用到很复杂的技术. 这篇我们将客户端的实现.说白了客户端的任务很简 ...

  6. DIY一些基于netty的开源框架

    几款基于netty的开源框架,有益于对netty的理解和学习! 基于netty的http server框架 https://github.com/TogetherOS/cicada 基于netty的即 ...

  7. 基于Netty打造RPC服务器设计经验谈

    自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器.Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行.园友们热 ...

  8. 实现基于netty的web框架,了解一下

    上一篇写了,基于netty实现的rpc的微框架,其中详细介绍netty的原理及组件,这篇就不过多介绍 这篇实现基于netty的web框架,你说netty强不强,文中有不对的地方,欢迎大牛指正 先普及几 ...

  9. 基于Netty的RPC简易实现

    代码地址如下:http://www.demodashi.com/demo/13448.html 可以给你提供思路 也可以让你学到Netty相关的知识 当然,这只是一种实现方式 需求 看下图,其实这个项 ...

随机推荐

  1. Android中如何使用GridView

    首先在主XML中放入Grid View控件 取好id private GridView gv1; private int[] icon = {R.drawable.cat, R.drawable.co ...

  2. 诸葛亮vs司马懿,排序算法大战谁能笑到最后?

    阵前对峙 公元234年,蜀汉丞相诸葛孔明再次北伐. 一日,与司马仲达所率魏军两军相峙,二人阵前舌战. 司马曰:"诸葛村夫,吾与汝相斗数年,斗兵斗阵斗谋略,均已疲乏.今日,何不一改陈规,斗点新 ...

  3. Redis学习笔记(十六) Sentinel(哨兵)(下)

    消失了一段时间,我又回来啦.不多说,继续把哨兵看完. 检测主观下线状态 默认情况下,Sentinel会以每秒一次的频率向所有与他创建了命令连接的实例(主从服务器以及其他Sentinel)发送PING命 ...

  4. Elasticsearch 常见错误

    一 read_only_allow_delete" : "true" 当我们在向某个索引添加一条数据的时候,可能(极少情况)会碰到下面的报错: { "error ...

  5. Apollo移植

    Apollo移植 环境 平台 ubuntu16.04 Apollo_kernel 1.0 安装步骤步骤 步骤一:安装ubuntu(官方建议使用Ubuntu 14.04.3) 步骤一和步骤二参考文档路径 ...

  6. 【分区】使用 GPT 分区表分区并格式化 (非 FreeBSD 系统)

    新购买的 Linux 云服务器,由于数据盘未做分区和格式化,无法使用. 注意: 数据盘中的数据在格式化后将全部被清空.请在格式化之前,确保数据盘中没有数据或已对重要数据进行备份.为避免服务发生异常,格 ...

  7. Golang简单入门教程——函数进阶篇

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是golang专题的第八篇,我们来聊聊golang当中的函数. 我们在之前的时候已经介绍过了函数的基本用法,知道了怎么样设计或者是定义一 ...

  8. Java 源码刨析 - String

    [String 是如何实现的?它有哪些重要的方法?] String 内部实际存储结构为 char 数组,源码如下: public final class String implements java. ...

  9. Android学习笔记使用Notication 显示通知

    实现步骤 代码实现 创建MainActivity和DetailActivity(点击通知后要跳转的Activity),两个Activity的布局文件就是添加一张全屏的背景图,老规矩,不粘贴. Main ...

  10. PN532模块连接-读卡失败原因

    第一步:点击发现NFC设备 第二步:点击读整卡:读取卡片内容. 若不成功,把UID卡移开,再放一次.再点第一步,显示发现NFC,再点第二步.反复操作,直到读取到为止.2-3次一般都会成功 . 相关软件 ...