基于Netty的RPC框架实现
RPC又称远程过程调用(Remote Procedure Call),用于解决分布式系统中服务之间的调用问题。通俗地讲,就是开发者能够像调用本地方法一样调用远程的服务。
RPC框架一般必须包含三个组件,分别是客户端、服务端以及注册中心,一次完整的RPC调用流程一般为:
服务端启动服务后,将他提供的服务列表发布到注册中心(服务注册)
客户端会向注册中心订阅相关的服务地址(服务订阅)
客户端通常会利用本地代理模块 Proxy 向服务端发起远程过程调用,Proxy 负责将调用的方法、参数等数据转化为网络字节流
客户端从服务列表中根据负载均衡策略选择一个服务地址,并将数据通过网络发送给服务端
服务端得到数据后,调用对应的服务,然后将结果通过网络返回给客户端
虽然 RPC 调用流程很容易理解,但是实现一个完整的 RPC 框架设计到很多内容,例如服务注册与发现、通信协议与序列化、负载均衡、动态代理等
项目目标
实现基于Netty/Socket/Http三种方式进行网路通信
自定义消息协议,编解码器
五种序列化算法(JDK、JSON、HESSIAN、KRYO、PROTOS ...
Netty常见问题
什么是NettyNetty是一个基于Java NIO (Non-blocking I/O)的网络通信框架,它提供了高性能、可扩展性和可靠性的网络编程解决方案,是一个广泛应用于分布式系统的网络通信库
Netty有哪些核心组件Netty由三层结构构成:
网络通信层:有三个组件:Bootstrap、ServerBootstrap、Channel
Bootstrap负责客户端启动,连接指定服务器
ServerBootstrap负责服务器启动,监听指定端口
Channel是网络通信的载体
事件调度层:有EventLoopGroup、EventLoop
EventLoopGroup本质上是一个线程池,主要是负责接受IO请求,分配线程处理请求
EventLoop是具体的一个线程
服务编排层:ChannelPipline、ChannelHandler、ChannelHandlerContext
ChannelPipline负责将多个ChannelHandler组成一个链,可以看成一个流水线
ChannelHandler是对数据进行处理,可以看作成一道道工序
ChannelHandl ...
Netty优化
扩展序列化算法序列化,反序列化主要用在消息正文的转换上
序列化时,需要将Java对象变为要传输的数据(可以是byte[],json等,最终都需要变成byte[])
反序列化时,需要将传入的正文数据还原成Java对象,便于处理
Java自带的序列化,反序列化机制,核心代码如下:
// 反序列化byte[] body = new byte[bodyLength];byteByf.readBytes(body);ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(body));Message message = (Message) in.readObject();message.setSequenceId(sequenceId);// 序列化ByteArrayOutputStream out = new ByteArrayOutputStream();new ObjectOutputStream(out).writeObject(message);byte[] bytes = out.toByteAr ...
Netty进阶篇
黏包和半包前置知识滑动窗口
TCP以一个段(segment)为单位,每发送一个段就需要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差
为了解决此问题,引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值
窗口实际就起到一个缓冲区的作用,同时也能起到流量控制的作用
图中深色的部分即要发送的数据,高亮的部分即窗口
窗口内的数据才允许被发送,当应答未到达前,窗口必须停止滑动
如果 1001~2000 这个段的数据 ack 回来了,窗口就可以向前滑动
接收方也会维护一个窗口,只有落在窗口内的数据才能允许接收
MSS限制链路层对一次能够发送的最大数据有限制,这个限制称之为 MTU(maximum transmission unit),不同的链路设备的 MTU 值也有所不同,例如
以太网的 MTU 是 1500
FDDI(光纤分布式数据接口)的 MTU 是 4352
本地回环地址的 MTU 是 65535 - 本地测试不走网卡
MSS 是最大段长度(maximum segment size),它是 MTU 刨去 tcp 头和 ...
Netty基础入门
Netty is an asynchronous event-driven network application frameworkfor rapid development of maintainable high performance protocol servers & clients.
Netty是一个异步的、基于事件驱动的网络应用框架,用于快速开发可维护、高性能的网络服务器和客户端
HelloWorld开发一个简单的服务器端和客户端
客户端向服务器端发送HelloWorld
服务器仅接收,不返回
加入依赖
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.100.Final</version></dependency>
服务器端new ServerBootstrap() .group(new NioEventLo ...
Java网络IO
java中的IO可以分为BIO(blocking io,阻塞IO)、NIO(non-blocking io,非阻塞IO)、AIO(Asynchronous IO,异步IO)
IO中的三大组件Channel与Bufferchannel有一点类似于stream,它就是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是输入,要么是输出,channel 比 stream 更为底层
graph LRchannel --> bufferbuffer --> channel
常见的Channel有
FileChannel:用于对文件进行读写操作的通道,支持随机访问和映射文件
DatagramChannel:用于基于UDP进行无连接数据发送和接收的通道
SocketChannel:用于基于TCP的客户端网络通信,支持非阻塞连接与数据传输
ServerSocketChannel用于监听TCP连接请求的服务端通道,生成SocketChannel
buffer则用来缓冲读写数据,常见的bu ...
Zookeeper基础入门
Zookeeper是一个分布式的、开源的分布式应用程序的协调服务,基于ZAB协议(ZooKeeper Atomic Broadcast)实现分布式数据一致性
Zookeeper内部的数据模型类似文件系统的树形结构(ZNode),每个节点可存储不超过1MB的数据
Zookeeper提供的主要功能包括:
服务注册与发现
配置管理
分布式锁
集群管理
Zookeeper数据模型ZooKeeper是一个树形目录服务,其数据模型和Unix的文件系统目录树很类似,拥有一个层次化结构
这里面的每一个节点都被称为ZNode,每个节点上都会保存自己的数据和节点信息
节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下(可以通过jute.maxbuffer修改单个节点数据大小限制)
节点可以分为四大类:
PERSISTENT:持久化节点,创建后永久存在(除非显式删除)
EPHEMERAL:临时节点 -e,会话结束后自动删除(用于实现服务注册与心跳检测)
PERSISTENT_SEQUENTIAL:持久化顺序节点 -s,顺序节点的名称后面会自动追加单调递增序号(如 /lock/seq-00 ...
dubbo基础入门
Dubbo是一款高性能RPC(Remote Procedure Call,远程过程调用)框架,专注于分布式服务治理,提供服务发现、负载均衡、容错等能力
Dubbo架构Dubbo的架构包含5个核心角色:
Provider:服务提供者,暴露服务接口
Consumer:服务消费者,调用远程服务
Registry(注册中心):服务注册和发现的注册中心,存储服务元数据(如 IP、端口),支持 ZooKeeper/Nacos/Redis等注册中心
Monitor(监控中心):统计服务调用次数和耗时
Config Center(配置中心):动态调整参数(如超时时间)
Dubbo快速入门采用SpringBoot+Dubbo+Zookeeper实现服务提供者和消费者之间的调用:
dubbo-provider: 服务提供者
dubbo-consumer: 服务消费者
由于提供者和消费者之间需要统一类,所以两者通常要依赖同一个公共接口,编写一个接口类:
package com.example.api;public interface GreetingService { ...
JUC线程池
利用多线程,程序可以更加合理地使用CPU多核心资源,在同一时间完成更多的工作。但是,如果程序频繁地创建线程,由于线程的创建和销毁也需要占用系统资源,因此这样会降低整个程序的性能,利用线程池可以将已创建的线程复用,利用池化技术,就像数据库连接池一样,创建很多个线程,然后反复地使用这些线程,而不对它们进行销毁。
由于线程池可以反复利用已有线程执行多线程操作,所以它一般是有容量限制的,当所有的线程都处于工作状态时,那么新的多线程请求会被阻塞,直到有一个线程空闲出来为止,这里会用到阻塞队列
使用方式线程池的构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ...
JUC并发容器与并发工具
并发容器在单线程模式下,集合类提供的容器可以说是非常方便了,比如链表、顺序表、哈希表等数据结构,但是这些容器在多线程环境下,并不能正常工作。要解决并发情况下的容器问题,可以给方法前面加个synchronzed,或者使用Vector或是Hashtable,但是它们的效率实在是太低了,完全依靠锁来解决问题。
JUC提供了专用于并发场景下的容器,比如可以代替ArrayList的CopyOnWriteArrayList:
public static void main(String[] args) throws InterruptedException { List<String> list = new CopyOnWriteArrayList<>(); //使用CopyOnWriteArrayList保证线程安全 Runnable r = () -> { for (int i = 0; i < 100; i++) list.add("aaa"); }; ...