01-RPC框架深入剖析与设计实践(上)

00.概述

基础架构

网关:要从三个角度来理解

  1. 对系统外部访问者来说,网关起到隐藏系统内部细节,统一访问入口的作用;
  2. 站在网关自身的角度看,所有与业务无关的通用性功能,全部由网关来承载;
  3. 从系统内部的功能来看,网关主要负责路由转发和负载均衡。

立体监控平台:可怕的不是出现问题,而是出现了问题却不知道。

在互联网中,服务与服务之间的通信方式主要有两种:一种是RPC(远程过程调用),另一种是通过消息中间件(如MQ)来通信。当生产者(被调用的)服务产生的结果会影响消费者(调用的)服务的处理逻辑时,使用RPC;反之,当消费者服务对生产者服务的处理结果不关心时,使用消息中间件。

平台、基础组件、基础服务等本质上都是为了更好地服务于核心业务。

当所有数据都存放在一个数据库时,数据库本身可以保证事务分布性,可一旦数据库被分库分表存放在不同的数据库中,分布式事务的重要性就体现出来了。

传统的关系型数据库如MySQL,虽然便于使用,但其数据是存储在表中难以扩展,因此诞生基于K/V(即Key/Value键值对)的数据库。目前已有结合二者优点的NewSQL数据库如TiDB,完全兼容MySQL。Codis与TiDB的作者同为黄东旭。

无/有 0–>1
坏/好 1–>10

01.RPC实现原理深入分析

RPC(Remote Procedure Call):远程过程调用,Remote Procedure Call Protocol,它是一个计算机通信协议。它允许像调用本地方法一样调用远程服务。由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

函数是组织代码的一个单位,在相同内存空间的函数调用,其实就是一个不断压栈和出栈的过程。

RPC作用

  • 屏蔽组包解包
  • 屏蔽数据发送/接收
  • 提高开发效率
  • 业务发展的必然产物

RPC核心组成

  • 远程方法对象代理
  • 连接管理
  • 序列化/反序列化
  • 寻址与负载均衡

RPC调用方式

  • 同步调用
  • 异步调用

此处的同步调用与异步调用和IO中的同步与异步是完全不同的概念。

02.精减版RPC调用代码实现

序列化协议:远程调用涉及数据的传输,就会涉及组包和解包,需要调用方和服务方约定数据格式。

在这里插入图片描述

public class RpcProtocol implements Serializable {
	
	public static int CMD_CREATE_USER;
	private int version;
	private int cmd;
	private int magicNum;
	private int bodylen = 0;
	private byte[] body;
	public static final int HEAD_LEN = 16;
	......
}

在这里插入图片描述

该图上行为请求方法,下行为返回结果。RPC的关键在于序列化/反序列化。

  • 序列化 对象–>字节流
  • 反序列化 字节流–>对象

以int型数据的序列化/反序列化为例,展示部分核心代码:

public static byte[] intToBytes(int n) {
	byte[] buf = new byte[4];
	for (int i = 0; i < buf.length; i++) {
		buf[i] = (byte) (n >> (8 * i));
	}
	return buf;
}
public static int bytesToInt(byte[] buf, int offset) {
	return (buf[offset] && 0xff)
			| ((buf[offset + 1] << 8) && 0xff00)
			| ((buf[offset + 2] << 16) && 0xff0000)
			| ((buf[offset + 3] << 24) && 0xff000000);
}

对象序列化过程

  • 序列化请求参数到body
  • 序列化RpcProtocol
public class User implements Serializable {

    private long uid;
    private short age;
    private short sex;
    ......
}
public byte[] userInfoToByteArray(User info) {
	byte[] data = new byte[Long.BYTES + Short.BYTES + Short.BYTES];
	int index = 0;
	
	System.arraycopy(ByteConverter.longToBytes(info.getUid()), 0, data, index, Long.BYTES);
	index += Long.BYTES;
	
	System.arraycopy(ByteConverter.shortToBytes(info.getAge()), 0, data, index, Short.BYTES);
	index += Short.BYTES;
	
	System.arraycopy(ByteConverter.shortToBytes(info.getSex()), 0, data, index, Short.BYTES);
	
	return data;
} 

把header和body合并成一个完整的RPC协议包

public byte[] generateByteArray() {
	byte[] data = new byte[HEAD_LEN + bodyLen];
	int index = 0;
	
	System.arraycopy(ByteConverter.intToBytes(version), 0, data, index, Integer.BYTES);
	index += Integer.BYTES;
	
	System.arraycopy(ByteConverter.intToBytes(cmd), 0, data, index, Integer.BYTES);
	index += Integer.BYTES;
	
	System.arraycopy(ByteConverter.intToBytes(magicNum), 0, data, index, Integer.BYTES);
	index += Integer.BYTES;
	
	System.arraycopy(ByteConverter.intToBytes(bodyLen), 0, data, index, Integer.BYTES);
	index += Integer.BYTES;
	
	System.arraycopy(body, 0, data, index, body.length);
	
	return data;
}

反序列化过程

  • 反序列化RpcProtocol
  • 反序列化body
public RpcProtocol byteArrayToRpcHeader(byte[] data) {
	int index = 0;
	
    this.setVersion(ByteConverter.bytesToInt(data, index));
    index += Integer.BYTES;

    this.setCmd(ByteConverter.bytesToInt(data, index));
	index += Integer.BYTES;

	this.setMagicNum(ByteConverter.bytesToInt(data, index));
	index += Integer.BYTES;

	this.setBodyLen(ByteConverter.bytesToInt(data, index));
	index += Integer.BYTES;

	this.body = new byte[this.bodyLen];
	System.arraycopy(data, index, this.body, 0, this.bodyLen);

	return this;
}
public User byteArrayToUserInfo(byte[] data) {
	User user = new User();
	int index = 0;

	user.setUid(ByteConverter.bytesToLong(data, index));
	index += Long.BYTES;

	user.setAge(ByteConverter.bytesToShort(data, index));
	index += Short.BYTES;

	user.setSex(ByteConverter.bytesToShort(data, index));
	index += Short.BYTES;
	
	return user;
}

Provider代码实现

  • 启动服务监听端口
public class RpcServer {

    private static Logger logger = LoggerFactory.getLogger(RpcServer.class);
    private static int SERVER_LISTEN_PORT = 58885;

    public static void main(String[] args) throws Exception {
        Thread tcpServerThread = new Thread("tcpServer") {
		@Override
		public void  run() {
			TcpServer tcpServer = new TcpServer(SERVER_LISTEN_PORT);
			try {
					tcpServer.start();
                } catch (Exception e) {
                    logger.info("TcpServer start exception: " + e.getMessage());
                }
            }
        };
        tcpServerThread.start();
        tcpServerThread.join();
    }
}

Server启动

  • 设置解码器
  • 设置处理类
  • 绑定端口
public void start() throws Exception {
	try {
		ServerBootstrap serverBootstrap = new ServerBootstrap();
		serverBootstrap.group(bossGroup, workerGroup);
		serverBootstrap.channel(NioServerSocketChannel.class);
		serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);	//连接数
		serverBootstrap.localAddress(this.port);
		serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
		serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
		
			@Override
			protected void initChannel(SocketChannel socketChannel) throws Exception {
				ChannelPipeline pipeline = socketChannel.pipeline();
				pipeline.addLast(new PkgDecoder());
				pipeline.addLast(new ServerHandler());
			}
		});

		ChannelFuture channelFuture = serverBootstrap.bind().sync();
		if (channelFuture.isSuccess()) {
			logger.info("rpc server start success!");
		} else {
			logger.info("rpc server start fail!");
		}
		channelFuture.channel().closeFuture().sync();
	} catch (Exception ex) {
		logger.error("exception occurred exception=" + ex.getMessage());
	} finally {
		bossGroup.shutdownGracefully().sync();	// 释放线程池资源
		workerGroup.shutdownGracefully().sync();
	}
}

请求包完整性校验

  • 判断包头是否完整
  • 判断Body是否完整
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception
{
	if (buffer.readableBytes() < RpcProtocol.HEAD_LEN) {
		return; //未读完足够的字节流,缓存后继续读
        }

        byte[] intBuf = new byte[4];
        buffer.getBytes(buffer.readerIndex() + RpcProtocol.HEAD_LEN - 4, intBuf);
        int bodyLen = ByteConverter.bytesToIntBigEndian(intBuf);

        if (buffer.readableBytes() < RpcProtocol.HEAD_LEN + bodyLen) {
		return; //未读完足够的字节流,缓存后继续读
        }

        byte[] bytesReady = new byte[RpcProtocol.HEAD_LEN + bodyLen];
        buffer.readBytes(bytesReady);
        out.add(bytesReady);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值