发送消息的情况。
这种情况常常是由DownstreamHandler主动发起,在对接收的消息处理后主动发送消息ctx.sendMessage,由内向外经过由DownstreamHandler和encode组成的过滤链,在链的尽头由NioServerSocketPipelineSink处理MessageEvent事件。
1.首先将事件放入channel中的writeBufferQueue中。
MessageEvent event = (MessageEvent) e; NioSocketChannel channel = (NioSocketChannel) event.getChannel(); boolean offered = channel.writeBufferQueue.offer(event); assert offered; channel.worker.writeFromUserCode(channel);
2.调用writeFromUserCode()方法写数据,该方法会检测当前执行线程是否是对应worker的IO线程。如果不是,则向该worker添加写任务(执行writeFromTaskLoop,无须检测执行线程)并返回,。
3.对channel.writeLock加锁,防止在写的过程中另外的cleanUpWriterBuffer操作writeBufferQueue
4.依次写各个messge event中的消息写到channel中,如果阻塞无法写入,则向worker注册该channel的可写事件,如果已把事件队列全部写完则取消在worker上channel的可写事件。
5.完成后,在workder的IO线程的通知写完成事件。
在写消息时采用了类似自旋锁方式,在向管道不能正常写入数据时直接重复16次,如还不能写再向worker注册可写事件。在大量数据需要写出时,消息数据将在channel中的queue堆积,worker监听线程疲于发消息而不会去select,直到kernel buffer填满而不能再向channel写入数据时再停止此次写数据回到监听状态。
接收消息的情况
这各情况是从worker的select阻塞退出了开始的,退出阻塞后首先执行任务队列中的任务。然后依次处理各个selectorKey。
1.如果key上有WRITE事件,表明当前channel处于可写状态,调用write0,如同发送消息的情况。
2.如果Key上有READ事件,表明有新的数据可读,通过ReceiveBufferSizePredictor根据上次消息的大小来决定预测本次消息所需的缓存大小。从channel读取数据到缓存到,并向上行流通知消息接收事件。
3.依次调用事件处理链上的decoder,handler。
读消息时使用到了ReceiveBufferSizePredictor来预测这次数据包的大小,其预测方法:
初始化预测索引表格SIZE_TABLE,其中的数据呈类指数增长。当实际数据大小比预测值SIZE_TABLE[index]大时,预测索引向右移动4格index+=4;当比预测值两次小于当前索引值左侧一格的值SIZE_TABLE[index-4]时,则预测索引向左移1格index--;当实际值在预测值索引SIZE_TABLE[index]和SIZE_TABLE[index-1]之前,预测成功,不作调整。
读消息所使用的缓存分析:
1.使用ByteBuffer.allocateDirect向OS直接申请内存空间,并不由GC来管理。
2.通过normalizeCapacity(size)来归一化缓存大小,修改为比size大的最小1024的倍数
3.每个worker都有一个SocketReceiveBufferAllocator分配直接缓存DirectBuffer,这类缓存IO时不需要把数据从OS内存复制到Java堆中,因此性能比堆缓存高,但分配、释放的代价比堆缓存高,Allocator通过尽量重复使用已分配的缓存来降低了分配直接缓存的频率。
3.1当首次分配时,直接分配directBuffer。
3.2如果上次分配的buffer比size小,则释放并重新分配缓存
3.3如果size持续16小于上次分配buffer大小的80%,则释放并重要分配缓存
3.4其它情况,则重复使用上次分配的directBuffer
ByteBuffer get(int size) { if (buf == null) { return newBuffer(size); } if (buf.capacity() < size) { return newBuffer(size); } if (buf.capacity() * percentual / 100 > size) { if (++exceedCount == maxExceedCount) { return newBuffer(size); } else { buf.clear(); } } else { exceedCount = 0; buf.clear(); } return buf; }
4.在用Direct类型ByteBuffer接收完数据后,再处理创建HeapChannelBuffer,复制directBuffer中的字节数据到HeapChannelBuffer。这个类有两个write read两个索引,在复制完数据后,更新write索引,但不需要flip(),因为这时候读索引不会变。
bb.flip(); final ChannelBuffer buffer = bufferFactory.getBuffer(readBytes); buffer.setBytes(0, bb); buffer.writerIndex(readBytes); // Update the predictor. predictor.previousReceiveBufferSize(readBytes); // Fire the event. fireMessageReceived(channel, buffer);