英语水平有限,翻译的不是太好,大家多多见谅:blush:,但是读完之后对Reactor的设计肯定是更上一层楼,还需要细细品尝,建议配合我另一篇文章食用味道更佳:Reactor模型你知道都有哪些吗?
系列文章
- 你知道都有哪些I/O模型吗?
- Java NIO三大角色Channel、Buffer、Selector
- Doug lea《Scalable IO in Java》翻译
- Reactor模型你知道都有哪些吗?
- Netty服务端创建源码流程解析
- EventLoopGroup到底是个啥?
- 未完待续..
创作不易,如果对您有帮助,麻烦辛苦下小手点个关注,有任何问题都可以私信交流哈。 祝您虎年虎虎生威。
目录
Reactor模型的由来
网络服务
Web服务、分布式对象等大多数都具有相同的基础组件:读请求、解码、处理器、编码、响应,但是每一步处理的方式和成本都不同。
经典服务设计
每一个请求进来后都会丢给对应的Handler去处理,每个Handler都启动一个线程执行。伪代码如下:
public class Server implements Runnable {
@Override
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(8080);
while (!Thread.interrupted()) {
new Thread(new Handler(serverSocket.accept())).start();
}
} catch (IOException exception) {
exception.printStackTrace();
}
}
static class Handler implements Runnable {
final Socket socket;
public Handler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
byte[] input = new byte[1024];
socket.getInputStream().read(input);
byte[] output = process(input);
socket.getOutputStream().write(output);
} catch (IOException exception) {
exception.printStackTrace();
}
}
private byte[] process(byte[] cmd) {
return null;
}
}
}
可伸缩性目标
分治处理
分治思想通常是最佳的实现以上目标的的方法:
- 将处理划分为一个个的小任务,每个任务执行一个动作而不阻塞
- 当任务被开启后则去执行它
在java.nio包中有相应的基本机制,非阻塞的read、write将不同IO事件发送给事件对应的任务去执行,其实这就是事件驱动的设计,使用的资源更少,不用针对每个请求启用一条线程,为了减少上下文切换,减少阻塞,但是任务分发可能会更慢,而且必须手动将事件和对应的处理动作绑定,响应的编程实现也会更难。
Reactor模型
基础Reactor设计(单线程版本)
示例代码
public class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel serverSocketChannel;
public Reactor(int port) throws Exception {
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
selectionKey.attach(new Acceptor());
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
//发生阻塞直到有事件
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//处理所有事件
while (iterator.hasNext()) {
//分发事件
dispatch(iterator.next());
}
selectionKeys.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void dispatch(SelectionKey selectionKey) {
Runnable r = (Runnable) selectionKey.attachment();
if (r != null) {
r.run();
}
}
class Acceptor implements Runnable{
@Override
public void run() {
try {
SocketChannel channel = serverSocketChannel.accept();
if(channel != null){
//单线程处理
new BasicHandler(channel,selector);
//多线程处理
}
}catch (Exception e){
}
}
}
}
final class BasicHandler implements Runnable {
final SocketChannel socketChannel;
final SelectionKey selectionKey;
ByteBuffer input = ByteBuffer.allocate(1024);
ByteBuffer output = ByteBuffer.allocate(1024);
static final int READING = 0, SENDING = 1;
int state = READING;
public BasicHandler(SocketChannel socketChannel, Selector selector) throws Exception {
this.socketChannel = socketChannel;
socketChannel.configureBlocking(false);
selectionKey = socketChannel.register(selector, 0);
selectionKey.attach(this);
selectionKey.interestOps(SelectionKey.OP_READ);
selector.wakeup();
}
boolean inputIsComplete() {
return true;
}
boolean outputIsComplete() {
return true;
}
void process() { }
@Override
public void run() {
try {
if(state == READING){
read();
}else if(state == SENDING){
send();
}
}catch (Exception e){
}
多线版本设计
- 增加线程以实现扩展性,主要适用于多处理器模式
- 工作线程
- Reactor应该快速的触发Handler去处理
- 将非IO的处理放到其他线程
- 多Reactor线程
worker Theads(单Reactor 多线程模型)
- 只处理IO操作来提升Reactor线程的处理性能
- 计算绑定处理比转换事件驱动的形式更简单,个人认为,如果使用workder Theads,方便了handler的实现,如果都通过事件驱动,那么handler的实现必须按照Reactor中的要求
- 对于IO的持续处理比较困难,最好是一开始就读取到所有的输入到一个缓冲区内
- 使用线程池可以协调和控制,通常需要更小的线程服务更多的设备
经过修改之后Handler就变成了这个样子:
public class HandlerWithThreadPool implements Runnable{
//线程池
static ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
static final int PROCESSING = 3;
final SocketChannel socketChannel;
final SelectionKey selectionKey;
ByteBuffer input = ByteBuffer.allocate(1024);
ByteBuffer output = ByteBuffer.allocate(1024);
static final int READING = 0, SENDING = 1;
int state = READING;
public HandlerWithThreadPool(SocketChannel socketChannel, Selector selector) throws Exception {
this.socketChannel = socketChannel;
socketChannel.configureBlocking(false);
selectionKey = socketChannel.register(selector, 0);
selectionKey.attach(this);
selectionKey.interestOps(SelectionKey.OP_READ);
selector.wakeup();
}
boolean inputIsComplete() {
return true;
}
boolean outputIsComplete() {
return true;
}
void process() { }
@Override
public void run() {
try {
if(state == READING){
read();
}else if(state == SENDING){
send();
}
}catch (Exception e){
}
}
void read() throws Exception {
socketChannel.read(input);
if (inputIsComplete()) {
state = PROCESSING;
poolExecutor.execute(new Processer());
}
}
void send() throws Exception {
socketChannel.write(output);
if (outputIsComplete()) {
selectionKey.cancel();
}
}
class Processer implements Runnable{
@Override
public void run() {
}
}
}
协调任务
使用池化的Executor
- 一个可调节的worker线程池,相当于Netty的workerGroup(EventLoopGroup)
- 核心执行方法execute(Runnable r)
- 可控制
多Reactor Theads版本
mainReactor就像是Netty中的boosGroup或者parentGroup,subReactor就像是Netty中的workerGroup或者childGroup
大家好,我是壹氿,感谢各位小伙伴点赞、收藏和评论,文章持续更新,我们下期再见! 也可以加我的个人VX交流沟通:lhj502819,一起努力冲击大厂,另外有很多学习以及面试的材料提供给大家。